| /* |
| * 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)) |