blob: 5a115bc3be91bf5b4d379c597310e8cf4c021712 [file]
/*
* Copyright 2026 Rive
*
* GM test for Ore's texture → view → pipeline → render path.
*
* Creates a small RGBA checkerboard texture via ore::Context::makeTexture(),
* wraps it in an ore::TextureView, builds a fullscreen-quad pipeline that
* samples the texture, renders to a RenderCanvas, and composites into the
* main framebuffer.
*
* If the image appears as a 4×4 checkerboard (red/blue squares), Ore's
* texture and render-pipeline integration is working for this backend.
*/
#include "gm.hpp"
#include "gmutils.hpp"
#include "ore_gm_helper.hpp"
#if defined(ORE_BACKEND_METAL) || defined(ORE_BACKEND_GL) || \
defined(ORE_BACKEND_VK) || defined(ORE_BACKEND_WGPU) || \
defined(ORE_BACKEND_D3D11) || defined(ORE_BACKEND_D3D12)
#include "rive/renderer/render_canvas.hpp"
#include "rive/renderer/ore/ore_buffer.hpp"
#include "rive/renderer/ore/ore_texture.hpp"
#include "rive/renderer/ore/ore_sampler.hpp"
#include "rive/renderer/ore/ore_bind_group.hpp"
#include "rive/renderer/ore/ore_shader_module.hpp"
#include "rive/renderer/ore/ore_pipeline.hpp"
#include "rive/renderer/ore/ore_render_pass.hpp"
#endif
using namespace rivegm;
using namespace rive;
using namespace rive::gpu;
#if defined(ORE_BACKEND_METAL) || defined(ORE_BACKEND_GL) || \
defined(ORE_BACKEND_VK) || defined(ORE_BACKEND_WGPU) || \
defined(ORE_BACKEND_D3D11) || defined(ORE_BACKEND_D3D12)
using namespace rive::ore;
#endif
// 4×4 RGBA8 checkerboard: red (0xFF0000FF) and blue (0x0000FFFF).
static void make4x4Checkerboard(uint8_t pixels[4 * 4 * 4])
{
for (int y = 0; y < 4; y++)
{
for (int x = 0; x < 4; x++)
{
int i = (y * 4 + x) * 4;
bool isRed = ((x + y) % 2 == 0);
pixels[i + 0] = isRed ? 255 : 0; // R
pixels[i + 1] = 0; // G
pixels[i + 2] = isRed ? 0 : 255; // B
pixels[i + 3] = 255; // A
}
}
}
#if defined(ORE_BACKEND_METAL) || defined(ORE_BACKEND_GL) || \
defined(ORE_BACKEND_VK) || defined(ORE_BACKEND_WGPU) || \
defined(ORE_BACKEND_D3D11) || defined(ORE_BACKEND_D3D12)
#define ORE_IMAGE_VIEW_ACTIVE
#endif
class OreImageViewGM : public GM
{
public:
OreImageViewGM() : GM(256, 256) {}
ColorInt clearColor() const override { return 0xff000000; }
void onDraw(rive::Renderer* originalRenderer) override
{
auto renderContext = TestingWindow::Get()->renderContext();
if (!renderContext || !m_ore.ensureContext(renderContext))
return;
#ifdef ORE_IMAGE_VIEW_ACTIVE
auto& ctx = *m_ore.oreContext;
// ── 1. Create a 4×4 test image as an ore texture.
uint8_t pixels[4 * 4 * 4];
make4x4Checkerboard(pixels);
TextureDesc imgTexDesc{};
imgTexDesc.width = 4;
imgTexDesc.height = 4;
imgTexDesc.format = TextureFormat::rgba8unorm;
imgTexDesc.type = TextureType::texture2D;
imgTexDesc.numMipmaps = 1;
imgTexDesc.sampleCount = 1;
imgTexDesc.label = "ore_image_view_checkerboard";
auto oreTex = ctx.makeTexture(imgTexDesc);
if (!oreTex)
return;
TextureDataDesc uploadDesc{};
uploadDesc.data = pixels;
uploadDesc.width = 4;
uploadDesc.height = 4;
uploadDesc.bytesPerRow = 4 * 4; // rgba8unorm = 4 bytes per pixel
uploadDesc.rowsPerImage = 4;
// ── 2. Create a view of the texture ──
TextureViewDesc tvDesc{};
tvDesc.texture = oreTex.get();
tvDesc.dimension = TextureViewDimension::texture2D;
tvDesc.baseMipLevel = 0;
tvDesc.mipCount = 1;
tvDesc.baseLayer = 0;
tvDesc.layerCount = 1;
auto texView = ctx.makeTextureView(tvDesc);
if (!texView)
return;
// ── 3. Create sampler (nearest for crisp checkerboard) ──
SamplerDesc sampDesc{};
sampDesc.minFilter = Filter::nearest;
sampDesc.magFilter = Filter::nearest;
auto sampler = ctx.makeSampler(sampDesc);
// ── 4. Load shader from the pre-compiled RSTB ──
auto shader = ore_gm::loadShader(ctx, ore_gm::kImageView);
if (!shader.vsModule)
return;
// ── 5. Create pipeline (no vertex buffers — fullscreen quad from
// vertex_index) ──
auto layout1 =
ore_gm::makeLayoutFromShader(ctx, shader.vsModule.get(), 1);
auto layout2 =
ore_gm::makeLayoutFromShader(ctx, shader.vsModule.get(), 2);
BindGroupLayout* layouts[] = {nullptr, layout1.get(), layout2.get()};
PipelineDesc pipeDesc{};
pipeDesc.vertexModule = shader.vsModule.get();
pipeDesc.fragmentModule = shader.psModule.get();
pipeDesc.vertexEntryPoint = shader.vsEntryPoint;
pipeDesc.fragmentEntryPoint = shader.fsEntryPoint;
pipeDesc.vertexBufferCount = 0;
pipeDesc.topology = PrimitiveTopology::triangleList;
pipeDesc.colorTargets[0].format = TextureFormat::rgba8unorm;
pipeDesc.colorCount = 1;
pipeDesc.depthStencil.depthCompare = CompareFunction::always;
pipeDesc.depthStencil.depthWriteEnabled = false;
pipeDesc.bindGroupLayouts = layouts;
pipeDesc.bindGroupLayoutCount = 3;
pipeDesc.label = "ore_image_view_pipeline";
auto pipeline = ctx.makePipeline(pipeDesc);
if (!pipeline)
{
fprintf(stderr,
"[ore_image_view] pipeline creation failed: %s\n",
ctx.lastError().c_str());
return;
}
// ── 6. Create bind groups for texture + sampler ──
BindGroupDesc texBGDesc{};
texBGDesc.layout = layout1.get();
BindGroupDesc::TexEntry texEntry{};
texEntry.slot = 0;
texEntry.view = texView.get();
texBGDesc.textures = &texEntry;
texBGDesc.textureCount = 1;
auto texBG = ctx.makeBindGroup(texBGDesc);
BindGroupDesc sampBGDesc{};
sampBGDesc.layout = layout2.get();
BindGroupDesc::SampEntry sampEntry{};
sampEntry.slot = 0;
sampEntry.sampler = sampler.get();
sampBGDesc.samplers = &sampEntry;
sampBGDesc.samplerCount = 1;
auto sampBG = ctx.makeBindGroup(sampBGDesc);
// ── 7. Render into a RenderCanvas ──
auto canvas = renderContext->makeRenderCanvas(256, 256);
if (!canvas)
return;
auto canvasTarget = ctx.wrapCanvasTexture(canvas.get());
if (!canvasTarget)
return;
m_ore.beginFrame();
// Upload must happen after beginFrame() so the command buffer is
// in the recording state.
oreTex->upload(uploadDesc);
ColorAttachment ca{};
ca.view = canvasTarget.get();
ca.loadOp = LoadOp::clear;
ca.storeOp = StoreOp::store;
ca.clearColor = {0, 0, 0, 1};
RenderPassDesc rpDesc{};
rpDesc.colorAttachments[0] = ca;
rpDesc.colorCount = 1;
rpDesc.label = "ore_image_view_pass";
auto pass = ctx.beginRenderPass(rpDesc);
pass.setPipeline(pipeline.get());
pass.setBindGroup(1, texBG.get());
pass.setBindGroup(2, sampBG.get());
pass.setViewport(0, 0, 256, 256);
pass.draw(6); // fullscreen quad — 2 triangles
pass.finish();
m_ore.endFrame();
ore_gm::invalidateGLStateAfterOre(renderContext);
// ── 8. Composite into main framebuffer ──
originalRenderer->save();
originalRenderer->drawImage(canvas->renderImage(),
{.filter = ImageFilter::nearest},
BlendMode::srcOver,
1);
originalRenderer->restore();
#endif // ORE_IMAGE_VIEW_ACTIVE
}
private:
ore_gm::OreGMContext m_ore;
};
GMREGISTER(ore_image_view, return new OreImageViewGM)