blob: 68f8b5817e131f5beacc1876c083b03d4ae934ef [file] [log] [blame]
/*
* Copyright 2024 Rive
*/
#include "testing_window.hpp"
#if defined(__APPLE__) && !defined(TESTING) && !defined(RIVE_UNREAL)
#include "rive/renderer/metal/render_context_metal_impl.h"
#include "rive/renderer/rive_renderer.hpp"
#import <Metal/Metal.h>
#import <QuartzCore/CAMetalLayer.h>
namespace rive::gpu
{
class TestingWindowMetalTexture : public TestingWindow
{
public:
TestingWindowMetalTexture()
{
RenderContextMetalImpl::ContextOptions metalOptions;
// Turn on synchronous shader compilations to ensure deterministic
// rendering and to make sure we test every unique shader.
metalOptions.synchronousShaderCompilations = true;
m_renderContext =
RenderContextMetalImpl::MakeContext(m_gpu, metalOptions);
printf("==== MTLDevice: %s ====\n", m_gpu.name.UTF8String);
}
rive::Factory* factory() override { return m_renderContext.get(); }
rive::gpu::RenderContext* renderContext() const override
{
return m_renderContext.get();
}
// rive::gpu::RenderTarget* renderTarget() const override { return
// m_renderTarget.get(); }
std::unique_ptr<rive::Renderer> beginFrame(
const FrameOptions& options) override
{
rive::gpu::RenderContext::FrameDescriptor frameDescriptor = {
.renderTargetWidth = m_width,
.renderTargetHeight = m_height,
.loadAction = options.doClear
? rive::gpu::LoadAction::clear
: rive::gpu::LoadAction::preserveRenderTarget,
.clearColor = options.clearColor,
.wireframe = options.wireframe,
.clockwiseFillOverride = options.clockwiseFillOverride,
};
m_renderContext->beginFrame(frameDescriptor);
m_flushCommandBuffer = [m_queue commandBuffer];
return std::make_unique<RiveRenderer>(m_renderContext.get());
}
void flushPLSContext() final
{
if (!m_renderTarget || m_renderTarget->height() != m_height ||
m_renderTarget->width() != m_width)
{
m_renderTarget =
m_renderContext->static_impl_cast<RenderContextMetalImpl>()
->makeRenderTarget(
MTLPixelFormatBGRA8Unorm, m_width, m_height);
MTLTextureDescriptor* desc = [[MTLTextureDescriptor alloc] init];
desc.pixelFormat = MTLPixelFormatBGRA8Unorm;
desc.width = m_width;
desc.height = m_height;
desc.usage = MTLTextureUsageRenderTarget;
desc.textureType = MTLTextureType2D;
desc.mipmapLevelCount = 1;
desc.storageMode = MTLStorageModePrivate;
m_renderTarget->setTargetTexture(
[m_gpu newTextureWithDescriptor:desc]);
m_pixelReadBuff =
[m_gpu newBufferWithLength:m_height * m_width * 4
options:MTLResourceStorageModeShared];
}
m_renderContext->flush(
{.renderTarget = m_renderTarget.get(),
.externalCommandBuffer = (__bridge void*)m_flushCommandBuffer});
}
void endFrame(std::vector<uint8_t>* pixelData) override
{
flushPLSContext();
if (pixelData)
{
id<MTLBlitCommandEncoder> blitEncoder =
[m_flushCommandBuffer blitCommandEncoder];
// Blit the framebuffer into m_pixelReadBuff.
[blitEncoder copyFromTexture:m_renderTarget->targetTexture()
sourceSlice:0
sourceLevel:0
sourceOrigin:MTLOriginMake(0, 0, 0)
sourceSize:MTLSizeMake(m_width, m_height, 1)
toBuffer:m_pixelReadBuff
destinationOffset:0
destinationBytesPerRow:m_width * 4
destinationBytesPerImage:m_height * m_width * 4];
[blitEncoder endEncoding];
}
[m_flushCommandBuffer commit];
if (pixelData)
{
[m_flushCommandBuffer waitUntilCompleted];
// Copy the image data from m_pixelReadBuff to pixelData.
pixelData->resize(m_height * m_width * 4);
assert(m_pixelReadBuff.length == pixelData->size());
const uint8_t* contents =
reinterpret_cast<const uint8_t*>(m_pixelReadBuff.contents);
const size_t rowBytes = m_width * 4;
for (size_t y = 0; y < m_height; ++y)
{
// Flip Y.
const uint8_t* src =
&contents[(m_height - y - 1) * m_width * 4];
uint8_t* dst = &(*pixelData)[y * m_width * 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];
}
}
}
m_flushCommandBuffer = nil;
}
private:
id<MTLDevice> m_gpu = MTLCreateSystemDefaultDevice();
id<MTLCommandQueue> m_queue = [m_gpu newCommandQueue];
std::unique_ptr<RenderContext> m_renderContext;
rcp<RenderTargetMetal> m_renderTarget;
id<MTLBuffer> m_pixelReadBuff;
id<MTLCommandBuffer> m_flushCommandBuffer;
};
}; // namespace rive::gpu
TestingWindow* TestingWindow::MakeMetalTexture()
{
return new rive::gpu::TestingWindowMetalTexture();
}
#endif