blob: 39f7315b69e7c2d6adf585b44e7d06ca9d97c802 [file] [log] [blame]
#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"
#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);
printf("==== MTLDevice: %s ====\n", m_gpu.name.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
{
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);
}
void flushPLSContext(RenderTarget* offscreenRenderTarget) final
{
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);
}
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
{
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);
}
private:
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);
}