| /* |
| * Copyright 2023 Rive |
| */ |
| |
| #include "rive/math/simd.hpp" |
| #include "rive/artboard.hpp" |
| #include "rive/file.hpp" |
| #include "rive/layout.hpp" |
| #include "rive/animation/linear_animation_instance.hpp" |
| #include "rive/animation/state_machine_input_instance.hpp" |
| #include "rive/animation/state_machine_instance.hpp" |
| #include "rive/pls/pls_render_context.hpp" |
| #include "rive/pls/pls_renderer.hpp" |
| #include "rive/pls/webgpu/pls_render_context_webgpu_impl.hpp" |
| #include "rive/pls/webgpu/em_js_handle.hpp" |
| |
| #include <cmath> |
| #include <iterator> |
| #include <vector> |
| #include <filesystem> |
| |
| #ifdef RIVE_WEBGPU |
| #include <webgpu/webgpu_cpp.h> |
| #include <emscripten.h> |
| #include <emscripten/html5_webgpu.h> |
| #else |
| #include <dawn/webgpu_cpp.h> |
| #include "dawn/native/DawnNative.h" |
| #include "dawn/dawn_proc.h" |
| #define EMSCRIPTEN_KEEPALIVE |
| #endif |
| |
| using namespace rive; |
| using namespace rive::pls; |
| using PixelLocalStorageType = PLSRenderContextWebGPUImpl::PixelLocalStorageType; |
| |
| static std::unique_ptr<PLSRenderContext> s_plsContext; |
| static rcp<PLSRenderTargetWebGPU> s_renderTarget; |
| static std::unique_ptr<Renderer> s_renderer; |
| |
| void riveInitPlayer(int w, |
| int h, |
| wgpu::Device gpu, |
| wgpu::Queue queue, |
| const pls::PlatformFeatures& platformFeatures, |
| PixelLocalStorageType plsType, |
| int maxVertexStorageBlocks) |
| { |
| PLSRenderContextWebGPUImpl::ContextOptions contextOptions = { |
| .plsType = plsType, |
| .disableStorageBuffers = maxVertexStorageBlocks < pls::kMaxStorageBuffers, |
| }; |
| s_plsContext = |
| PLSRenderContextWebGPUImpl::MakeContext(gpu, queue, contextOptions, platformFeatures); |
| s_renderTarget = s_plsContext->static_impl_cast<PLSRenderContextWebGPUImpl>()->makeRenderTarget( |
| wgpu::TextureFormat::BGRA8Unorm, |
| w, |
| h); |
| s_renderer = std::make_unique<PLSRenderer>(s_plsContext.get()); |
| } |
| |
| #ifdef RIVE_WEBGPU |
| |
| EmJsHandle s_deviceHandle; |
| EmJsHandle s_queueHandle; |
| EmJsHandle s_textureViewHandle; |
| |
| extern "C" |
| { |
| void EMSCRIPTEN_KEEPALIVE RiveInitialize(int deviceID, |
| int queueID, |
| int canvasWidth, |
| int canvasHeight, |
| bool invertedY, |
| int pixelLocalStorageType, |
| int maxVertexStorgeBlocks) |
| { |
| s_deviceHandle = EmJsHandle(deviceID); |
| s_queueHandle = EmJsHandle(queueID); |
| pls::PlatformFeatures platformFeatures; |
| if (invertedY) |
| { |
| platformFeatures.uninvertOnScreenY = true; |
| } |
| riveInitPlayer(canvasWidth, |
| canvasHeight, |
| wgpu::Device::Acquire(emscripten_webgpu_import_device(s_deviceHandle.get())), |
| emscripten_webgpu_import_queue(s_queueHandle.get()), |
| platformFeatures, |
| static_cast<PixelLocalStorageType>(pixelLocalStorageType), |
| maxVertexStorgeBlocks); |
| } |
| |
| intptr_t EMSCRIPTEN_KEEPALIVE RiveBeginRendering(int textureViewID, |
| int loadAction, |
| ColorInt clearColor) |
| { |
| s_textureViewHandle = EmJsHandle(textureViewID); |
| auto targetTextureView = wgpu::TextureView::Acquire( |
| emscripten_webgpu_import_texture_view(s_textureViewHandle.get())); |
| s_renderTarget->setTargetTextureView(targetTextureView); |
| |
| s_plsContext->beginFrame({ |
| .renderTargetWidth = s_renderTarget->width(), |
| .renderTargetHeight = s_renderTarget->height(), |
| .loadAction = static_cast<pls::LoadAction>(loadAction), |
| .clearColor = clearColor, |
| }); |
| |
| s_renderer->save(); |
| return reinterpret_cast<intptr_t>(s_renderer.get()); |
| } |
| |
| void EMSCRIPTEN_KEEPALIVE RiveFlushRendering() |
| { |
| s_renderer->restore(); |
| s_plsContext->flush({.renderTarget = s_renderTarget.get()}); |
| s_textureViewHandle = EmJsHandle(); |
| } |
| |
| intptr_t EMSCRIPTEN_KEEPALIVE RiveLoadFile(intptr_t wasmBytesPtr, size_t length) |
| { |
| const uint8_t* bytes = reinterpret_cast<const uint8_t*>(wasmBytesPtr); |
| std::unique_ptr<File> file = File::import({bytes, length}, s_plsContext.get()); |
| return reinterpret_cast<intptr_t>(file.release()); |
| } |
| |
| intptr_t EMSCRIPTEN_KEEPALIVE File_artboardNamed(intptr_t nativePtr, const char* name) |
| { |
| auto file = reinterpret_cast<File*>(nativePtr); |
| std::unique_ptr<ArtboardInstance> artboard = file->artboardNamed(name); |
| return reinterpret_cast<intptr_t>(artboard.release()); |
| } |
| |
| intptr_t EMSCRIPTEN_KEEPALIVE File_artboardDefault(intptr_t nativePtr) |
| { |
| auto file = reinterpret_cast<File*>(nativePtr); |
| std::unique_ptr<ArtboardInstance> artboard = file->artboardDefault(); |
| return reinterpret_cast<intptr_t>(artboard.release()); |
| } |
| |
| void EMSCRIPTEN_KEEPALIVE File_destroy(intptr_t nativePtr) |
| { |
| auto file = reinterpret_cast<File*>(nativePtr); |
| delete file; |
| } |
| |
| int EMSCRIPTEN_KEEPALIVE ArtboardInstance_width(intptr_t nativePtr) |
| { |
| auto artboard = reinterpret_cast<ArtboardInstance*>(nativePtr); |
| return artboard->bounds().width(); |
| } |
| |
| int EMSCRIPTEN_KEEPALIVE ArtboardInstance_height(intptr_t nativePtr) |
| { |
| auto artboard = reinterpret_cast<ArtboardInstance*>(nativePtr); |
| return artboard->bounds().height(); |
| } |
| |
| intptr_t EMSCRIPTEN_KEEPALIVE ArtboardInstance_stateMachineNamed(intptr_t nativePtr, |
| const char* name) |
| { |
| auto artboard = reinterpret_cast<ArtboardInstance*>(nativePtr); |
| std::unique_ptr<StateMachineInstance> stateMachine = artboard->stateMachineNamed(name); |
| return reinterpret_cast<intptr_t>(stateMachine.release()); |
| } |
| |
| intptr_t EMSCRIPTEN_KEEPALIVE ArtboardInstance_animationNamed(intptr_t nativePtr, |
| const char* name) |
| { |
| auto artboard = reinterpret_cast<ArtboardInstance*>(nativePtr); |
| std::unique_ptr<LinearAnimationInstance> animation = artboard->animationNamed(name); |
| return reinterpret_cast<intptr_t>(animation.release()); |
| } |
| |
| intptr_t EMSCRIPTEN_KEEPALIVE ArtboardInstance_defaultStateMachine(intptr_t nativePtr) |
| { |
| auto artboard = reinterpret_cast<ArtboardInstance*>(nativePtr); |
| std::unique_ptr<StateMachineInstance> stateMachine = artboard->defaultStateMachine(); |
| return reinterpret_cast<intptr_t>(stateMachine.release()); |
| } |
| |
| void EMSCRIPTEN_KEEPALIVE ArtboardInstance_align(intptr_t nativePtr, |
| intptr_t rendererNativePtr, |
| float frameLeft, |
| float frameTop, |
| float frameRight, |
| float frameBot, |
| int fitValue, |
| float alignmentX, |
| float alignmentY) |
| { |
| auto artboard = reinterpret_cast<ArtboardInstance*>(nativePtr); |
| auto renderer = reinterpret_cast<PLSRenderer*>(rendererNativePtr); |
| auto fit = static_cast<Fit>(fitValue); |
| Alignment alignment = {alignmentX, alignmentY}; |
| AABB frame = {frameLeft, frameTop, frameRight, frameBot}; |
| renderer->align(fit, alignment, frame, artboard->bounds()); |
| } |
| |
| void EMSCRIPTEN_KEEPALIVE ArtboardInstance_destroy(intptr_t nativePtr) |
| { |
| auto artboard = reinterpret_cast<ArtboardInstance*>(nativePtr); |
| delete artboard; |
| } |
| |
| void EMSCRIPTEN_KEEPALIVE StateMachineInstance_setBool(intptr_t nativePtr, |
| const char* inputName, |
| int value) |
| { |
| auto stateMachine = reinterpret_cast<StateMachineInstance*>(nativePtr); |
| if (SMIBool* input = stateMachine->getBool(inputName)) |
| { |
| input->value(static_cast<bool>(value)); |
| } |
| } |
| |
| void EMSCRIPTEN_KEEPALIVE StateMachineInstance_setNumber(intptr_t nativePtr, |
| const char* inputName, |
| float value) |
| { |
| auto stateMachine = reinterpret_cast<StateMachineInstance*>(nativePtr); |
| if (SMINumber* input = stateMachine->getNumber(inputName)) |
| { |
| input->value(value); |
| } |
| } |
| |
| void EMSCRIPTEN_KEEPALIVE StateMachineInstance_fireTrigger(intptr_t nativePtr, |
| const char* inputName) |
| { |
| auto stateMachine = reinterpret_cast<StateMachineInstance*>(nativePtr); |
| if (SMITrigger* input = stateMachine->getTrigger(inputName)) |
| { |
| input->fire(); |
| } |
| } |
| |
| void EMSCRIPTEN_KEEPALIVE StateMachineInstance_pointerDown(intptr_t nativePtr, float x, float y) |
| { |
| auto stateMachine = reinterpret_cast<StateMachineInstance*>(nativePtr); |
| stateMachine->pointerDown({x, y}); |
| } |
| |
| void EMSCRIPTEN_KEEPALIVE StateMachineInstance_pointerMove(intptr_t nativePtr, float x, float y) |
| { |
| auto stateMachine = reinterpret_cast<StateMachineInstance*>(nativePtr); |
| stateMachine->pointerMove({x, y}); |
| } |
| |
| void EMSCRIPTEN_KEEPALIVE StateMachineInstance_pointerUp(intptr_t nativePtr, float x, float y) |
| { |
| auto stateMachine = reinterpret_cast<StateMachineInstance*>(nativePtr); |
| stateMachine->pointerUp({x, y}); |
| } |
| |
| void EMSCRIPTEN_KEEPALIVE StateMachineInstance_advanceAndApply(intptr_t nativePtr, |
| double elapsed) |
| { |
| auto stateMachine = reinterpret_cast<StateMachineInstance*>(nativePtr); |
| stateMachine->advanceAndApply(elapsed); |
| } |
| |
| void EMSCRIPTEN_KEEPALIVE StateMachineInstance_draw(intptr_t nativePtr, |
| intptr_t rendererNativePtr) |
| { |
| auto stateMachine = reinterpret_cast<StateMachineInstance*>(nativePtr); |
| auto renderer = reinterpret_cast<PLSRenderer*>(rendererNativePtr); |
| stateMachine->draw(renderer); |
| } |
| |
| void EMSCRIPTEN_KEEPALIVE StateMachineInstance_destroy(intptr_t nativePtr) |
| { |
| auto stateMachine = reinterpret_cast<StateMachineInstance*>(nativePtr); |
| delete stateMachine; |
| } |
| |
| void EMSCRIPTEN_KEEPALIVE LinearAnimationInstance_advanceAndApply(intptr_t nativePtr, |
| double elapsed) |
| { |
| auto animation = reinterpret_cast<LinearAnimationInstance*>(nativePtr); |
| animation->advanceAndApply(elapsed); |
| } |
| |
| void EMSCRIPTEN_KEEPALIVE LinearAnimationInstance_draw(intptr_t nativePtr, |
| intptr_t rendererNativePtr) |
| { |
| auto animation = reinterpret_cast<LinearAnimationInstance*>(nativePtr); |
| auto renderer = reinterpret_cast<PLSRenderer*>(rendererNativePtr); |
| animation->draw(renderer); |
| } |
| |
| void EMSCRIPTEN_KEEPALIVE LinearAnimationInstance_destroy(intptr_t nativePtr) |
| { |
| auto animation = reinterpret_cast<LinearAnimationInstance*>(nativePtr); |
| delete animation; |
| } |
| |
| void EMSCRIPTEN_KEEPALIVE Renderer_save(intptr_t nativePtr) |
| { |
| auto renderer = reinterpret_cast<PLSRenderer*>(nativePtr); |
| renderer->save(); |
| } |
| |
| void EMSCRIPTEN_KEEPALIVE Renderer_restore(intptr_t nativePtr) |
| { |
| auto renderer = reinterpret_cast<PLSRenderer*>(nativePtr); |
| renderer->restore(); |
| } |
| |
| void EMSCRIPTEN_KEEPALIVE Renderer_translate(intptr_t nativePtr, float x, float y) |
| { |
| auto renderer = reinterpret_cast<PLSRenderer*>(nativePtr); |
| renderer->translate(x, y); |
| } |
| |
| void EMSCRIPTEN_KEEPALIVE Renderer_transform(intptr_t nativePtr, |
| float xx, |
| float xy, |
| float yx, |
| float yy, |
| float tx, |
| float ty) |
| { |
| auto renderer = reinterpret_cast<PLSRenderer*>(nativePtr); |
| Mat2D matrix(xx, xy, yx, yy, tx, ty); |
| renderer->transform(matrix); |
| } |
| } |
| |
| #endif |
| |
| #ifdef RIVE_DAWN |
| #include <GLFW/glfw3.h> |
| #ifndef __APPLE__ |
| #define GLFW_EXPOSE_NATIVE_WIN32 |
| #include <GLFW/glfw3native.h> |
| #endif |
| |
| #include <fstream> |
| |
| static void glfw_error_callback(int code, const char* message) |
| { |
| printf("GLFW error: %i - %s\n", code, message); |
| } |
| |
| static void print_device_error(WGPUErrorType errorType, const char* message, void*) |
| { |
| const char* errorTypeName = ""; |
| switch (errorType) |
| { |
| case WGPUErrorType_Validation: |
| errorTypeName = "Validation"; |
| break; |
| case WGPUErrorType_OutOfMemory: |
| errorTypeName = "Out of memory"; |
| break; |
| case WGPUErrorType_Unknown: |
| errorTypeName = "Unknown"; |
| break; |
| case WGPUErrorType_DeviceLost: |
| errorTypeName = "Device lost"; |
| break; |
| default: |
| RIVE_UNREACHABLE(); |
| return; |
| } |
| printf("%s error: %s\n", errorTypeName, message); |
| } |
| |
| static void device_lost_callback(WGPUDeviceLostReason reason, const char* message, void*) |
| { |
| printf("device lost: %s\n", message); |
| } |
| |
| static void device_log_callback(WGPULoggingType type, const char* message, void*) |
| { |
| printf("Device log %s\n", message); |
| } |
| |
| static GLFWwindow* s_window = nullptr; |
| static WGPUDevice s_backendDevice = {}; |
| static wgpu::SwapChain s_swapchain = {}; |
| static std::unique_ptr<dawn::native::Instance> s_instance; |
| |
| #ifdef __APPLE__ |
| extern float GetDawnWindowBackingScaleFactor(GLFWwindow*, bool retina); |
| extern std::unique_ptr<wgpu::ChainedStruct> SetupDawnWindowAndGetSurfaceDescriptor(GLFWwindow*, |
| bool retina); |
| #else |
| #define GLFW_EXPOSE_NATIVE_WIN32 |
| #include <GLFW/glfw3.h> |
| #include <GLFW/glfw3native.h> |
| |
| static std::unique_ptr<wgpu::ChainedStruct> SetupDawnWindowAndGetSurfaceDescriptor( |
| GLFWwindow* window, |
| bool retina) |
| { |
| std::unique_ptr<wgpu::SurfaceDescriptorFromWindowsHWND> desc = |
| std::make_unique<wgpu::SurfaceDescriptorFromWindowsHWND>(); |
| desc->hwnd = glfwGetWin32Window(window); |
| desc->hinstance = GetModuleHandle(nullptr); |
| return std::move(desc); |
| } |
| #endif |
| |
| #endif |
| |
| int main(int argc, const char** argv) |
| { |
| #ifdef RIVE_DAWN |
| // Cause stdout and stderr to print immediately without buffering. |
| setvbuf(stdout, NULL, _IONBF, 0); |
| setvbuf(stderr, NULL, _IONBF, 0); |
| |
| glfwSetErrorCallback(glfw_error_callback); |
| |
| if (!glfwInit()) |
| { |
| fprintf(stderr, "Failed to initialize glfw.\n"); |
| return 1; |
| } |
| |
| glfwWindowHint(GLFW_SAMPLES, 0); |
| glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); |
| glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); |
| glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_TRUE); |
| glfwWindowHint(GLFW_FOCUS_ON_SHOW, GLFW_TRUE); |
| glfwWindowHint(GLFW_FLOATING, GLFW_TRUE); |
| s_window = glfwCreateWindow(1600, 1600, "Rive Renderer", nullptr, nullptr); |
| if (!s_window) |
| { |
| glfwTerminate(); |
| fprintf(stderr, "Failed to create window.\n"); |
| return -1; |
| } |
| glfwSetWindowTitle(s_window, "Rive Renderer"); |
| |
| WGPUInstanceDescriptor instanceDescriptor{}; |
| instanceDescriptor.features.timedWaitAnyEnable = true; |
| s_instance = std::make_unique<dawn::native::Instance>(&instanceDescriptor); |
| |
| wgpu::RequestAdapterOptions adapterOptions = {}; |
| |
| // Get an adapter for the backend to use, and create the device. |
| auto adapters = s_instance->EnumerateAdapters(&adapterOptions); |
| wgpu::DawnAdapterPropertiesPowerPreference power_props{}; |
| wgpu::AdapterProperties adapterProperties{}; |
| adapterProperties.nextInChain = &power_props; |
| // Find the first adapter which satisfies the adapterType requirement. |
| auto isAdapterType = [&adapterProperties](const auto& adapter) -> bool { |
| adapter.GetProperties(&adapterProperties); |
| return adapterProperties.adapterType == wgpu::AdapterType::DiscreteGPU; |
| }; |
| auto preferredAdapter = std::find_if(adapters.begin(), adapters.end(), isAdapterType); |
| if (preferredAdapter == adapters.end()) |
| { |
| fprintf(stderr, "Failed to find an adapter! Please try another adapter type.\n"); |
| abort(); |
| } |
| |
| std::vector<const char*> enableToggleNames = { |
| "allow_unsafe_apis", |
| "turn_off_vsync", |
| // "skip_validation", |
| }; |
| std::vector<const char*> disabledToggleNames; |
| |
| WGPUDawnTogglesDescriptor toggles = { |
| .chain = |
| { |
| .next = nullptr, |
| .sType = WGPUSType_DawnTogglesDescriptor, |
| }, |
| .enabledToggleCount = enableToggleNames.size(), |
| .enabledToggles = enableToggleNames.data(), |
| .disabledToggleCount = disabledToggleNames.size(), |
| .disabledToggles = disabledToggleNames.data(), |
| }; |
| |
| std::vector<WGPUFeatureName> requiredFeatures = {}; |
| |
| WGPUDeviceDescriptor deviceDesc = { |
| .nextInChain = reinterpret_cast<WGPUChainedStruct*>(&toggles), |
| .requiredFeatureCount = requiredFeatures.size(), |
| .requiredFeatures = requiredFeatures.data(), |
| }; |
| |
| s_backendDevice = preferredAdapter->CreateDevice(&deviceDesc); |
| DawnProcTable backendProcs = dawn::native::GetProcs(); |
| dawnProcSetProcs(&backendProcs); |
| backendProcs.deviceSetUncapturedErrorCallback(s_backendDevice, print_device_error, nullptr); |
| backendProcs.deviceSetDeviceLostCallback(s_backendDevice, device_lost_callback, nullptr); |
| backendProcs.deviceSetLoggingCallback(s_backendDevice, device_log_callback, nullptr); |
| wgpu::Device device = wgpu::Device::Acquire(s_backendDevice); |
| |
| int w, h; |
| glfwGetFramebufferSize(s_window, &w, &h); |
| // Create the swapchain |
| auto surfaceChainedDesc = SetupDawnWindowAndGetSurfaceDescriptor(s_window, true); |
| WGPUSurfaceDescriptor surfaceDesc = { |
| .nextInChain = reinterpret_cast<WGPUChainedStruct*>(surfaceChainedDesc.get()), |
| }; |
| WGPUSurface surface = backendProcs.instanceCreateSurface(s_instance->Get(), &surfaceDesc); |
| |
| WGPUSwapChainDescriptor swapChainDesc = { |
| .usage = WGPUTextureUsage_RenderAttachment, |
| .format = WGPUTextureFormat_BGRA8Unorm, |
| .width = static_cast<uint32_t>(w), |
| .height = static_cast<uint32_t>(h), |
| .presentMode = WGPUPresentMode_Immediate, // No vsync. |
| }; |
| |
| WGPUSwapChain backendSwapChain = |
| backendProcs.deviceCreateSwapChain(s_backendDevice, surface, &swapChainDesc); |
| s_swapchain = wgpu::SwapChain::Acquire(backendSwapChain); |
| |
| riveInitPlayer(w, |
| h, |
| device.Get(), |
| device.GetQueue(), |
| PlatformFeatures{}, |
| PixelLocalStorageType::none, |
| 8); |
| |
| std::ifstream rivStream("../../../gold/rivs/Santa_Claus.riv", std::ios::binary); |
| std::vector<uint8_t> rivBytes(std::istreambuf_iterator<char>(rivStream), {}); |
| std::unique_ptr<File> rivFile = File::import(rivBytes, s_plsContext.get()); |
| std::unique_ptr<ArtboardInstance> artboard = rivFile->artboardDefault(); |
| std::unique_ptr<Scene> scene = artboard->defaultScene(); |
| scene->advanceAndApply(0); |
| |
| double lastTimestamp = 0; |
| |
| while (!glfwWindowShouldClose(s_window)) |
| { |
| double timestamp = glfwGetTime(); |
| s_renderTarget->setTargetTextureView(s_swapchain.GetCurrentTextureView()); |
| |
| s_plsContext->beginFrame({ |
| .renderTargetWidth = s_renderTarget->width(), |
| .renderTargetHeight = s_renderTarget->height(), |
| .clearColor = 0xff8030ff, |
| }); |
| |
| s_renderer->save(); |
| s_renderer->transform(computeAlignment(rive::Fit::contain, |
| rive::Alignment::center, |
| rive::AABB(0, 0, w, h), |
| artboard->bounds())); |
| scene->draw(s_renderer.get()); |
| s_renderer->restore(); |
| |
| s_plsContext->flush({.renderTarget = s_renderTarget.get()}); |
| s_swapchain.Present(); |
| device.Tick(); |
| glfwPollEvents(); |
| |
| if (lastTimestamp != 0) |
| { |
| scene->advanceAndApply(timestamp - lastTimestamp); |
| } |
| lastTimestamp = timestamp; |
| } |
| glfwTerminate(); |
| #endif |
| return 0; |
| } |