blob: da4a9bfd5804e46c98002664d1bcfc03e292816a [file]
/*
* Copyright 2026 Rive
*
* A pass begun inside another. Canvas B's pass opens first, the pass that
* draws the triangle into canvas A begins and finishes inside it, then B
* samples A. The nested pass has to run ahead of B for B to see this frame's
* triangle, so every variant must match drawing A then B in order.
*/
#include "gm.hpp"
#include "gmutils.hpp"
#include "ore_gm_helper.hpp"
#if ORE_GM_HAS_BACKEND
#include "rive/renderer/render_canvas.hpp"
#include "rive/renderer/ore/ore_buffer.hpp"
#include "rive/renderer/ore/ore_sampler.hpp"
#include "rive/renderer/ore/ore_bind_group.hpp"
#include "rive/renderer/ore/ore_pipeline.hpp"
#include "rive/renderer/ore/ore_render_pass.hpp"
#include "rive/renderer/ore/cmd/ore_command_buffer.hpp"
#include "rive/renderer/ore/cmd/ore_deferred_render_pass.hpp"
#include "rive/renderer/ore/cmd/ore_render_pass_recording.hpp"
#include "rive/renderer/ore/cmd/ore_replay.hpp"
#endif
using namespace rivegm;
using namespace rive;
using namespace rive::gpu;
#if ORE_GM_HAS_BACKEND
using namespace rive::ore;
// Disambiguates from rive::cmd.
#endif
enum class NestMode
{
kImmediate, // A then B, the reference
kInline, // nested through the script entry point on the live context
kRecorded, // nested into one recording, then replayed
};
class OreNestedPassGM : public GM
{
public:
OreNestedPassGM(NestMode mode) : GM(256, 256), m_mode(mode) {}
ColorInt clearColor() const override { return 0xff000000; }
void onDraw(rive::Renderer* originalRenderer) override
{
auto renderContext = TestingWindow::Get()->renderContext();
if (!renderContext || !m_ore.ensureContext(renderContext))
return;
#if ORE_GM_HAS_BACKEND
auto& ctx = *renderContext->getOreContext();
// Canvas A is the producer and B the consumer.
auto canvasA = renderContext->makeRenderCanvas(256, 256);
auto canvasB = renderContext->makeRenderCanvas(256, 256);
if (!canvasA || !canvasB)
return;
auto targetA = ctx.wrapCanvasTexture(canvasA.get());
auto targetB = ctx.wrapCanvasTexture(canvasB.get());
if (!targetA || !targetB)
return;
BufferDesc bd{};
bd.usage = BufferUsage::vertex;
bd.size = sizeof(ore_gm::kTriVertices);
bd.data = ore_gm::kTriVertices;
bd.label = "ore_nested_pass_vb";
auto vb = ctx.makeBuffer(bd);
if (!vb)
return;
auto triShader = ore_gm::loadShader(ctx, ore_gm::kTriangle);
if (!triShader.vsModule)
return;
ore_gm::TrianglePipeline tri(triShader,
targetA->texture()->format(),
"ore_nested_pass_tri");
auto triPipeline = ctx.makePipeline(tri.desc);
if (!triPipeline)
{
fprintf(stderr,
"[ore_nested_pass] tri pipeline failed: %s\n",
ctx.lastError().c_str());
return;
}
SamplerDesc sampDesc{};
sampDesc.minFilter = Filter::nearest;
sampDesc.magFilter = Filter::nearest;
auto sampler = ctx.makeSampler(sampDesc);
auto imgShader = ore_gm::loadShader(ctx, ore_gm::kImageView);
if (!imgShader.vsModule)
return;
auto layout1 =
ore_gm::makeLayoutFromShader(ctx, imgShader.vsModule.get(), 1);
auto layout2 =
ore_gm::makeLayoutFromShader(ctx, imgShader.vsModule.get(), 2);
BindGroupLayout* layouts[] = {nullptr, layout1.get(), layout2.get()};
PipelineDesc imgPd{};
imgPd.vertexModule = imgShader.vsModule.get();
imgPd.fragmentModule = imgShader.psModule.get();
imgPd.vertexEntryPoint = imgShader.vsEntryPoint;
imgPd.fragmentEntryPoint = imgShader.fsEntryPoint;
imgPd.vertexBufferCount = 0;
imgPd.topology = PrimitiveTopology::triangleList;
imgPd.colorTargets[0].format = targetB->texture()->format();
imgPd.colorCount = 1;
imgPd.depthStencil.depthCompare = CompareFunction::always;
imgPd.depthStencil.depthWriteEnabled = false;
imgPd.bindGroupLayouts = layouts;
imgPd.bindGroupLayoutCount = 3;
imgPd.label = "ore_nested_pass_img";
auto imgPipeline = ctx.makePipeline(imgPd);
if (!imgPipeline)
{
fprintf(stderr,
"[ore_nested_pass] img pipeline failed: %s\n",
ctx.lastError().c_str());
return;
}
BindGroupDesc texBGDesc{};
texBGDesc.layout = layout1.get();
BindGroupDesc::TexEntry texEntry{};
texEntry.slot = 0;
texEntry.view = targetA.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);
ColorAttachment caA{};
caA.view = targetA.get();
caA.loadOp = LoadOp::clear;
caA.storeOp = StoreOp::store;
caA.clearColor = {0.1f, 0.1f, 0.15f, 1.0f};
RenderPassDesc rpA{};
rpA.colorAttachments[0] = caA;
rpA.colorCount = 1;
rpA.label = "ore_nested_pass_passA";
ColorAttachment caB{};
caB.view = targetB.get();
caB.loadOp = LoadOp::clear;
caB.storeOp = StoreOp::store;
caB.clearColor = {0, 0, 0, 1};
RenderPassDesc rpB{};
rpB.colorAttachments[0] = caB;
rpB.colorCount = 1;
rpB.label = "ore_nested_pass_passB";
auto drawA = [&](RenderPass& pass) {
pass.setPipeline(triPipeline.get());
pass.setVertexBuffer(0, vb.get());
pass.setViewport(0, 0, 256, 256);
pass.draw(3);
pass.finish();
};
auto sampleA = [&](RenderPass& pass) {
pass.setBindGroup(1, texBG.get());
pass.setBindGroup(2, sampBG.get());
pass.draw(6);
pass.finish();
};
m_ore.beginFrame(renderContext);
if (m_mode == NestMode::kImmediate)
{
drawA(*ctx.beginRenderPass(rpA));
auto pB = ctx.beginRenderPass(rpB);
pB->setPipeline(imgPipeline.get());
pB->setViewport(0, 0, 256, 256);
sampleA(*pB);
}
else if (m_mode == NestMode::kInline)
{
auto pB = ore::cmd::beginRecordedRenderPass(ctx, rpB);
pB->setPipeline(imgPipeline.get());
pB->setViewport(0, 0, 256, 256);
drawA(*ore::cmd::beginRecordedRenderPass(ctx, rpA));
sampleA(*pB);
}
else
{
ore::cmd::OreCommandBuffer cmdBuf;
{
ore::cmd::RenderPassRecording pB(&ctx, &cmdBuf, rpB);
pB.setPipeline(imgPipeline.get());
pB.setViewport(0, 0, 256, 256);
ore::cmd::RenderPassRecording pA(&ctx, &cmdBuf, rpA);
drawA(pA);
sampleA(pB);
}
ore::cmd::replayCommandBuffer(ctx, cmdBuf);
}
m_ore.endFrame(renderContext);
ore_gm::invalidateGLStateAfterOre(renderContext);
originalRenderer->save();
originalRenderer->drawImage(canvasB->renderImage(),
{.filter = ImageFilter::nearest},
BlendMode::srcOver,
1);
originalRenderer->restore();
#endif
}
private:
NestMode m_mode;
ore_gm::OreGMContext m_ore;
};
GMREGISTER(ore_nested_pass_immediate,
return new OreNestedPassGM(NestMode::kImmediate))
GMREGISTER(ore_nested_pass, return new OreNestedPassGM(NestMode::kInline))
GMREGISTER(ore_nested_pass_recorded,
return new OreNestedPassGM(NestMode::kRecorded))