| #include "fiddle_context.hpp" | |
| #include "rive/renderer/rive_renderer.hpp" | |
| #include "rive/renderer/gl/render_context_gl_impl.hpp" | |
| #include "rive/renderer/gl/render_target_gl.hpp" | |
| #include "rive/renderer/metal/render_context_metal_impl.h" | |
| #include "rive/profiler/profiler_macros.h" | |
| #import <Metal/Metal.h> | |
| #import <QuartzCore/CAMetalLayer.h> | |
| #define GLFW_INCLUDE_NONE | |
| #define GLFW_EXPOSE_NATIVE_COCOA | |
| #include <GLFW/glfw3.h> | |
| #include <GLFW/glfw3native.h> | |
| using namespace rive; | |
| using namespace rive::gpu; | |
| class FiddleContextMetalPLS : public FiddleContext | |
| { | |
| public: | |
| FiddleContextMetalPLS(FiddleContextOptions fiddleOptions) | |
| : m_fiddleOptions(fiddleOptions) | |
| { | |
| RenderContextMetalImpl::ContextOptions metalOptions; | |
| metalOptions.shaderCompilationMode = m_fiddleOptions.shaderCompilationMode; | |
| if (m_fiddleOptions.disableRasterOrdering) | |
| { | |
| // Turn on synchronous shader compilations to ensure deterministic | |
| // rendering and to make sure we test every unique shader. | |
| metalOptions.disableFramebufferReads = true; | |
| } | |
| m_renderContext = RenderContextMetalImpl::MakeContext(m_gpu, metalOptions); | |
| // Ore commits on this queue so its passes order ahead of our flush. | |
| m_renderContext->static_impl_cast<RenderContextMetalImpl>() | |
| ->setCommandQueue(m_queue); | |
| printf( | |
| "==== MTLDevice: %s (%s) ====\n", | |
| m_gpu.name.UTF8String, | |
| // MTLDebugDevice when API validation is on, the real driver | |
| // class when it isn't. | |
| NSStringFromClass([m_gpu class]).UTF8String); | |
| } | |
| float dpiScale(GLFWwindow* window) const override | |
| { | |
| NSWindow* nsWindow = glfwGetCocoaWindow(window); | |
| return m_fiddleOptions.retinaDisplay ? nsWindow.backingScaleFactor : 1; | |
| } | |
| Factory* factory() override { return m_renderContext.get(); } | |
| rive::gpu::RenderContext* renderContextOrNull() override | |
| { | |
| return m_renderContext.get(); | |
| } | |
| rive::gpu::RenderTarget* renderTargetOrNull() override | |
| { | |
| return m_renderTarget.get(); | |
| } | |
| void onSizeChanged( | |
| GLFWwindow* window, int width, int height, uint32_t sampleCount) override | |
| { | |
| @autoreleasepool | |
| { | |
| NSWindow* nsWindow = glfwGetCocoaWindow(window); | |
| NSView* view = [nsWindow contentView]; | |
| view.wantsLayer = YES; | |
| m_swapchain = [CAMetalLayer layer]; | |
| m_swapchain.device = m_gpu; | |
| m_swapchain.opaque = YES; | |
| m_swapchain.framebufferOnly = !m_fiddleOptions.enableReadPixels; | |
| m_swapchain.pixelFormat = MTLPixelFormatBGRA8Unorm; | |
| m_swapchain.contentsScale = dpiScale(window); | |
| m_swapchain.displaySyncEnabled = NO; | |
| view.layer = m_swapchain; | |
| m_swapchain.drawableSize = CGSizeMake(width, height); | |
| auto renderContextImpl = m_renderContext | |
| ->static_impl_cast<RenderContextMetalImpl>(); | |
| m_renderTarget = renderContextImpl->makeRenderTarget( | |
| MTLPixelFormatBGRA8Unorm, width, height); | |
| m_pixelReadBuff = nil; | |
| } | |
| } | |
| void toggleZoomWindow() override {} | |
| std::unique_ptr<Renderer> makeRenderer(int width, int height) override | |
| { | |
| return std::make_unique<RiveRenderer>(m_renderContext.get()); | |
| } | |
| void begin(const RenderContext::FrameDescriptor& frameDescriptor) override | |
| { | |
| m_renderContext->beginFrame(frameDescriptor); | |
| // Ore replays into the target before the first flush. | |
| acquireDrawable(); | |
| } | |
| void flushPLSContext(RenderTarget* offscreenRenderTarget) final | |
| { | |
| @autoreleasepool | |
| { | |
| acquireDrawable(); | |
| id<MTLCommandBuffer> flushCommandBuffer = [m_queue commandBuffer]; | |
| m_renderContext->flush({ | |
| .renderTarget = offscreenRenderTarget != nullptr | |
| ? offscreenRenderTarget | |
| : m_renderTarget.get(), | |
| .externalCommandBuffer = (__bridge void*)flushCommandBuffer, | |
| }); | |
| [flushCommandBuffer commit]; | |
| } | |
| } | |
| void end(GLFWwindow* window, std::vector<uint8_t>* pixelData) final | |
| { | |
| @autoreleasepool | |
| { | |
| flushPLSContext(nullptr); | |
| if (pixelData != nil) | |
| { | |
| // Read back pixels from the framebuffer! | |
| size_t w = m_renderTarget->width(); | |
| size_t h = m_renderTarget->height(); | |
| // Create a buffer to receive the pixels. | |
| if (m_pixelReadBuff == nil) | |
| { | |
| m_pixelReadBuff = [m_gpu | |
| newBufferWithLength:h * w * 4 | |
| options:MTLResourceStorageModeShared]; | |
| } | |
| assert(m_pixelReadBuff.length == h * w * 4); | |
| id<MTLCommandBuffer> commandBuffer = [m_queue commandBuffer]; | |
| id<MTLBlitCommandEncoder> | |
| blitEncoder = [commandBuffer blitCommandEncoder]; | |
| // Blit the framebuffer into m_pixelReadBuff. | |
| [blitEncoder copyFromTexture:m_renderTarget->targetTexture() | |
| sourceSlice:0 | |
| sourceLevel:0 | |
| sourceOrigin:MTLOriginMake(0, 0, 0) | |
| sourceSize:MTLSizeMake(w, h, 1) | |
| toBuffer:m_pixelReadBuff | |
| destinationOffset:0 | |
| destinationBytesPerRow:w * 4 | |
| destinationBytesPerImage:h * w * 4]; | |
| [blitEncoder endEncoding]; | |
| [commandBuffer commit]; | |
| [commandBuffer waitUntilCompleted]; | |
| // Copy the image data from m_pixelReadBuff to pixelData. | |
| pixelData->resize(h * w * 4); | |
| const uint8_t* contents = reinterpret_cast<const uint8_t*>( | |
| m_pixelReadBuff.contents); | |
| const size_t rowBytes = w * 4; | |
| for (size_t y = 0; y < h; ++y) | |
| { | |
| // Flip Y. | |
| const uint8_t* src = &contents[(h - y - 1) * w * 4]; | |
| uint8_t* dst = &(*pixelData)[y * w * 4]; | |
| for (size_t x = 0; x < rowBytes; x += 4) | |
| { | |
| // BGBRA -> RGBA. | |
| dst[x + 0] = src[x + 2]; | |
| dst[x + 1] = src[x + 1]; | |
| dst[x + 2] = src[x + 0]; | |
| dst[x + 3] = src[x + 3]; | |
| } | |
| } | |
| } | |
| id<MTLCommandBuffer> presentCommandBuffer = [m_queue commandBuffer]; | |
| [presentCommandBuffer presentDrawable:m_currentFrameSurface]; | |
| [presentCommandBuffer commit]; | |
| m_currentFrameSurface = nil; | |
| m_renderTarget->setTargetTexture(nil); | |
| RIVE_PROF_ENDFRAME() | |
| } | |
| } | |
| private: | |
| void acquireDrawable() | |
| { | |
| if (m_currentFrameSurface == nil) | |
| { | |
| m_currentFrameSurface = [m_swapchain nextDrawable]; | |
| assert(m_currentFrameSurface.texture.width == m_renderTarget->width()); | |
| assert(m_currentFrameSurface.texture.height == m_renderTarget->height()); | |
| m_renderTarget->setTargetTexture(m_currentFrameSurface.texture); | |
| } | |
| } | |
| const FiddleContextOptions m_fiddleOptions; | |
| id<MTLDevice> m_gpu = MTLCreateSystemDefaultDevice(); | |
| id<MTLCommandQueue> m_queue = [m_gpu newCommandQueue]; | |
| std::unique_ptr<RenderContext> m_renderContext; | |
| CAMetalLayer* m_swapchain; | |
| rcp<RenderTargetMetal> m_renderTarget; | |
| id<MTLBuffer> m_pixelReadBuff; | |
| id<CAMetalDrawable> m_currentFrameSurface = nil; | |
| }; | |
| std::unique_ptr<FiddleContext> FiddleContext::MakeMetalPLS( | |
| FiddleContextOptions fiddleOptions) | |
| { | |
| return std::make_unique<FiddleContextMetalPLS>(fiddleOptions); | |
| } |