| #include "fiddle_context.hpp" | |
| #ifndef RIVE_DAWN | |
| std::unique_ptr<FiddleContext> FiddleContext::MakeDawnPLS(FiddleContextOptions options) | |
| { | |
| return nullptr; | |
| } | |
| #else | |
| #include "dawn/native/DawnNative.h" | |
| #include "dawn/dawn_proc.h" | |
| #include "rive/pls/pls_factory.hpp" | |
| #include "rive/pls/pls_renderer.hpp" | |
| #include "rive/pls/webgpu/pls_render_context_webgpu_impl.hpp" | |
| #include <array> | |
| #include <thread> | |
| using namespace rive; | |
| using namespace rive::pls; | |
| 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); | |
| } | |
| #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 float GetDawnWindowBackingScaleFactor(GLFWwindow*, bool retina) { return 1; } | |
| 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 | |
| class FiddleContextDawnPLS : public FiddleContext | |
| { | |
| public: | |
| FiddleContextDawnPLS(FiddleContextOptions options) : m_options(options) | |
| { | |
| WGPUInstanceDescriptor instanceDescriptor{}; | |
| instanceDescriptor.features.timedWaitAnyEnable = true; | |
| m_instance = std::make_unique<dawn::native::Instance>(&instanceDescriptor); | |
| wgpu::RequestAdapterOptions adapterOptions = { | |
| .powerPreference = wgpu::PowerPreference::HighPerformance, | |
| }; | |
| // Get an adapter for the backend to use, and create the device. | |
| auto adapters = m_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"); | |
| return; | |
| } | |
| 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 = { | |
| // WGPUFeatureName_IndirectFirstInstance, | |
| // WGPUFeatureName_ShaderF16, | |
| // WGPUFeatureName_BGRA8UnormStorage, | |
| // WGPUFeatureName_Float32Filterable, | |
| // WGPUFeatureName_DawnInternalUsages, | |
| // WGPUFeatureName_DawnMultiPlanarFormats, | |
| // WGPUFeatureName_DawnNative, | |
| // WGPUFeatureName_ImplicitDeviceSynchronization, | |
| WGPUFeatureName_SurfaceCapabilities, | |
| // WGPUFeatureName_TransientAttachments, | |
| // WGPUFeatureName_DualSourceBlending, | |
| // WGPUFeatureName_Norm16TextureFormats, | |
| // WGPUFeatureName_HostMappedPointer, | |
| // WGPUFeatureName_ChromiumExperimentalReadWriteStorageTexture, | |
| }; | |
| WGPUDeviceDescriptor deviceDesc = { | |
| .nextInChain = reinterpret_cast<WGPUChainedStruct*>(&toggles), | |
| .requiredFeatureCount = requiredFeatures.size(), | |
| .requiredFeatures = requiredFeatures.data(), | |
| }; | |
| m_backendDevice = preferredAdapter->CreateDevice(&deviceDesc); | |
| DawnProcTable backendProcs = dawn::native::GetProcs(); | |
| dawnProcSetProcs(&backendProcs); | |
| backendProcs.deviceSetUncapturedErrorCallback(m_backendDevice, print_device_error, nullptr); | |
| backendProcs.deviceSetDeviceLostCallback(m_backendDevice, device_lost_callback, nullptr); | |
| backendProcs.deviceSetLoggingCallback(m_backendDevice, device_log_callback, nullptr); | |
| m_device = wgpu::Device::Acquire(m_backendDevice); | |
| m_queue = m_device.GetQueue(); | |
| m_plsContext = | |
| PLSRenderContextWebGPUImpl::MakeContext(m_device, | |
| m_queue, | |
| PLSRenderContextWebGPUImpl::ContextOptions()); | |
| } | |
| float dpiScale(GLFWwindow* window) const override | |
| { | |
| return GetDawnWindowBackingScaleFactor(window, m_options.retinaDisplay); | |
| } | |
| Factory* factory() override { return m_plsContext.get(); } | |
| rive::pls::PLSRenderContext* plsContextOrNull() override { return m_plsContext.get(); } | |
| void onSizeChanged(GLFWwindow* window, int width, int height, uint32_t sampleCount) override | |
| { | |
| DawnProcTable backendProcs = dawn::native::GetProcs(); | |
| // Create the swapchain | |
| auto surfaceChainedDesc = | |
| SetupDawnWindowAndGetSurfaceDescriptor(window, m_options.retinaDisplay); | |
| WGPUSurfaceDescriptor surfaceDesc = { | |
| .nextInChain = reinterpret_cast<WGPUChainedStruct*>(surfaceChainedDesc.get()), | |
| }; | |
| WGPUSurface surface = backendProcs.instanceCreateSurface(m_instance->Get(), &surfaceDesc); | |
| WGPUSwapChainDescriptor swapChainDesc = { | |
| .usage = WGPUTextureUsage_RenderAttachment, | |
| .format = WGPUTextureFormat_BGRA8Unorm, | |
| .width = static_cast<uint32_t>(width), | |
| .height = static_cast<uint32_t>(height), | |
| .presentMode = WGPUPresentMode_Immediate, // No vsync. | |
| }; | |
| if (m_options.enableReadPixels) | |
| { | |
| swapChainDesc.usage |= WGPUTextureUsage_CopySrc; | |
| } | |
| WGPUSwapChain backendSwapChain = | |
| backendProcs.deviceCreateSwapChain(m_backendDevice, surface, &swapChainDesc); | |
| m_swapchain = wgpu::SwapChain::Acquire(backendSwapChain); | |
| m_renderTarget = m_plsContext->static_impl_cast<PLSRenderContextWebGPUImpl>() | |
| ->makeRenderTarget(wgpu::TextureFormat::BGRA8Unorm, width, height); | |
| m_pixelReadBuff = {}; | |
| } | |
| void toggleZoomWindow() override {} | |
| std::unique_ptr<Renderer> makeRenderer(int width, int height) override | |
| { | |
| return std::make_unique<PLSRenderer>(m_plsContext.get()); | |
| } | |
| void begin(PLSRenderContext::FrameDescriptor&& frameDescriptor) override | |
| { | |
| assert(m_swapchain.GetCurrentTexture().GetWidth() == m_renderTarget->width()); | |
| assert(m_swapchain.GetCurrentTexture().GetHeight() == m_renderTarget->height()); | |
| m_renderTarget->setTargetTextureView(m_swapchain.GetCurrentTextureView()); | |
| frameDescriptor.renderTarget = m_renderTarget; | |
| m_plsContext->beginFrame(std::move(frameDescriptor)); | |
| } | |
| void end(GLFWwindow* window, std::vector<uint8_t>* pixelData) final | |
| { | |
| m_plsContext->flush(); | |
| if (pixelData != nullptr) | |
| { | |
| // Read back pixels from the framebuffer! | |
| uint32_t w = m_renderTarget->width(); | |
| uint32_t h = m_renderTarget->height(); | |
| uint32_t rowBytesInReadBuff = math::round_up_to_multiple_of<256>(w * 4); | |
| // Create a buffer to receive the pixels. | |
| if (!m_pixelReadBuff) | |
| { | |
| wgpu::BufferDescriptor buffDesc{ | |
| .usage = wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst, | |
| .size = h * rowBytesInReadBuff, | |
| }; | |
| m_pixelReadBuff = m_device.CreateBuffer(&buffDesc); | |
| } | |
| assert(m_pixelReadBuff.GetSize() == h * rowBytesInReadBuff); | |
| // Blit the framebuffer into m_pixelReadBuff. | |
| wgpu::CommandEncoder readEncoder = m_device.CreateCommandEncoder(); | |
| wgpu::ImageCopyTexture srcTexture = { | |
| .texture = m_swapchain.GetCurrentTexture(), | |
| .origin = {0, 0, 0}, | |
| }; | |
| wgpu::ImageCopyBuffer dstBuffer = { | |
| .layout = | |
| { | |
| .offset = 0, | |
| .bytesPerRow = rowBytesInReadBuff, | |
| }, | |
| .buffer = m_pixelReadBuff, | |
| }; | |
| wgpu::Extent3D copySize = { | |
| .width = w, | |
| .height = h, | |
| }; | |
| readEncoder.CopyTextureToBuffer(&srcTexture, &dstBuffer, ©Size); | |
| wgpu::CommandBuffer commands = readEncoder.Finish(); | |
| m_queue.Submit(1, &commands); | |
| // Request a mapping of m_pixelReadBuff and wait for it to complete. | |
| bool mapped = false; | |
| m_pixelReadBuff.MapAsync( | |
| wgpu::MapMode::Read, | |
| 0, | |
| h * rowBytesInReadBuff, | |
| [](WGPUBufferMapAsyncStatus status, void* mapped) { | |
| if (status != WGPUBufferMapAsyncStatus_Success) | |
| { | |
| fprintf(stderr, "failed to map m_pixelReadBuff\n"); | |
| exit(-1); | |
| } | |
| *reinterpret_cast<bool*>(mapped) = true; | |
| }, | |
| &mapped); | |
| while (!mapped) | |
| { | |
| // Spin until the GPU is finished with m_pixelReadBuff and we can read it. | |
| std::this_thread::sleep_for(std::chrono::milliseconds(1)); | |
| tick(); | |
| } | |
| // Copy the image data from m_pixelReadBuff to pixelData. | |
| pixelData->resize(h * w * 4); | |
| const uint8_t* pixelReadBuffData = | |
| reinterpret_cast<const uint8_t*>(m_pixelReadBuff.GetConstMappedRange()); | |
| for (size_t y = 0; y < h; ++y) | |
| { | |
| // Flip Y. | |
| const uint8_t* src = &pixelReadBuffData[(h - y - 1) * rowBytesInReadBuff]; | |
| size_t row = y * w * 4; | |
| for (size_t x = 0; x < w * 4; x += 4) | |
| { | |
| // BGBRA -> RGBA. | |
| (*pixelData)[row + x + 0] = src[x + 2]; | |
| (*pixelData)[row + x + 1] = src[x + 1]; | |
| (*pixelData)[row + x + 2] = src[x + 0]; | |
| (*pixelData)[row + x + 3] = src[x + 3]; | |
| } | |
| } | |
| m_pixelReadBuff.Unmap(); | |
| } | |
| m_swapchain.Present(); | |
| } | |
| void tick() override { m_device.Tick(); } | |
| private: | |
| const FiddleContextOptions m_options; | |
| WGPUDevice m_backendDevice = {}; | |
| wgpu::Device m_device = {}; | |
| wgpu::Queue m_queue = {}; | |
| wgpu::SwapChain m_swapchain = {}; | |
| std::unique_ptr<dawn::native::Instance> m_instance; | |
| std::unique_ptr<PLSRenderContext> m_plsContext; | |
| rcp<PLSRenderTargetWebGPU> m_renderTarget; | |
| wgpu::Buffer m_pixelReadBuff; | |
| }; | |
| std::unique_ptr<FiddleContext> FiddleContext::MakeDawnPLS(FiddleContextOptions options) | |
| { | |
| return std::make_unique<FiddleContextDawnPLS>(options); | |
| } | |
| #endif |