| /* | |
| * Copyright 2026 Rive | |
| * | |
| * Renders into every mip level of one texture through a per level view, with | |
| * colour and depth attached and no explicit viewport, then shows each level | |
| * in its own quadrant. A pass sized from the texture instead of the attached | |
| * level draws the triangle scaled and shifted in the smaller quadrants. Level | |
| * one is then loaded back and gets a second, flipped triangle. Each quadrant | |
| * picks its level with a sampler lod clamp, since GL cannot sample a mip view. | |
| */ | |
| #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_texture.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" | |
| #endif | |
| using namespace rivegm; | |
| using namespace rive; | |
| using namespace rive::gpu; | |
| #if ORE_GM_HAS_BACKEND | |
| using namespace rive::ore; | |
| #endif | |
| class OreMipRenderTargetGM : public GM | |
| { | |
| public: | |
| OreMipRenderTargetGM() : 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; | |
| #if ORE_GM_HAS_BACKEND | |
| auto& ctx = *renderContext->getOreContext(); | |
| constexpr uint32_t kSize = 64; | |
| constexpr uint32_t kLevels = 4; | |
| TextureDesc colorDesc{}; | |
| colorDesc.width = kSize; | |
| colorDesc.height = kSize; | |
| colorDesc.format = TextureFormat::rgba8unorm; | |
| colorDesc.renderTarget = true; | |
| colorDesc.numMipmaps = kLevels; | |
| colorDesc.label = "ore_mip_render_target_color"; | |
| auto colorTex = ctx.makeTexture(colorDesc); | |
| TextureDesc depthDesc = colorDesc; | |
| depthDesc.format = TextureFormat::depth32float; | |
| depthDesc.label = "ore_mip_render_target_depth"; | |
| auto depthTex = ctx.makeTexture(depthDesc); | |
| if (!colorTex || !depthTex) | |
| return; | |
| rcp<TextureView> colorViews[kLevels]; | |
| rcp<TextureView> depthViews[kLevels]; | |
| for (uint32_t i = 0; i < kLevels; ++i) | |
| { | |
| TextureViewDesc vd{}; | |
| vd.texture = colorTex.get(); | |
| vd.dimension = TextureViewDimension::texture2D; | |
| vd.baseMipLevel = i; | |
| vd.mipCount = 1; | |
| vd.baseLayer = 0; | |
| vd.layerCount = 1; | |
| colorViews[i] = ctx.makeTextureView(vd); | |
| vd.texture = depthTex.get(); | |
| depthViews[i] = ctx.makeTextureView(vd); | |
| if (!colorViews[i] || !depthViews[i]) | |
| return; | |
| } | |
| BufferDesc bd{}; | |
| bd.usage = BufferUsage::vertex; | |
| bd.size = sizeof(ore_gm::kTriVertices); | |
| bd.data = ore_gm::kTriVertices; | |
| bd.label = "ore_mip_render_target_vb"; | |
| auto vb = ctx.makeBuffer(bd); | |
| ore_gm::TriVertex flipped[3]; | |
| for (int i = 0; i < 3; ++i) | |
| { | |
| flipped[i] = ore_gm::kTriVertices[i]; | |
| flipped[i].y = -flipped[i].y; | |
| } | |
| bd.data = flipped; | |
| bd.label = "ore_mip_render_target_vb_flipped"; | |
| auto vbFlipped = ctx.makeBuffer(bd); | |
| if (!vb || !vbFlipped) | |
| return; | |
| auto triShader = ore_gm::loadShader(ctx, ore_gm::kTriangle); | |
| if (!triShader.vsModule) | |
| return; | |
| ore_gm::TrianglePipeline tri( | |
| triShader, | |
| TextureFormat::rgba8unorm, | |
| "ore_mip_render_target_tri"); | |
| tri.desc.depthStencil.format = TextureFormat::depth32float; | |
| tri.desc.depthStencil.depthCompare = CompareFunction::always; | |
| tri.desc.depthStencil.depthWriteEnabled = true; | |
| auto triPipeline = ctx.makePipeline(tri.desc); | |
| if (!triPipeline) | |
| { | |
| fprintf( | |
| stderr, | |
| "[ore_mip_render_target] tri pipeline failed: %s\n", | |
| ctx.lastError().c_str()); | |
| return; | |
| } | |
| auto canvas = renderContext->makeRenderCanvas(256, 256); | |
| if (!canvas) | |
| return; | |
| auto canvasTarget = ctx.wrapCanvasTexture(canvas.get()); | |
| if (!canvasTarget) | |
| return; | |
| 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 = canvasTarget->texture()->format(); | |
| imgPd.colorCount = 1; | |
| imgPd.depthStencil.depthCompare = CompareFunction::always; | |
| imgPd.depthStencil.depthWriteEnabled = false; | |
| imgPd.bindGroupLayouts = layouts; | |
| imgPd.bindGroupLayoutCount = 3; | |
| imgPd.label = "ore_mip_render_target_img"; | |
| auto imgPipeline = ctx.makePipeline(imgPd); | |
| if (!imgPipeline) | |
| { | |
| fprintf( | |
| stderr, | |
| "[ore_mip_render_target] img pipeline failed: %s\n", | |
| ctx.lastError().c_str()); | |
| return; | |
| } | |
| TextureViewDesc chainDesc{}; | |
| chainDesc.texture = colorTex.get(); | |
| chainDesc.dimension = TextureViewDimension::texture2D; | |
| chainDesc.baseMipLevel = 0; | |
| chainDesc.mipCount = kLevels; | |
| chainDesc.baseLayer = 0; | |
| chainDesc.layerCount = 1; | |
| auto chainView = ctx.makeTextureView(chainDesc); | |
| if (!chainView) | |
| return; | |
| BindGroupDesc texBGDesc{}; | |
| texBGDesc.layout = layout1.get(); | |
| BindGroupDesc::TexEntry texEntry{}; | |
| texEntry.slot = 0; | |
| texEntry.view = chainView.get(); | |
| texBGDesc.textures = &texEntry; | |
| texBGDesc.textureCount = 1; | |
| auto texBG = ctx.makeBindGroup(texBGDesc); | |
| rcp<Sampler> samplers[kLevels]; | |
| rcp<BindGroup> sampBGs[kLevels]; | |
| for (uint32_t i = 0; i < kLevels; ++i) | |
| { | |
| SamplerDesc sampDesc{}; | |
| sampDesc.minFilter = Filter::nearest; | |
| sampDesc.magFilter = Filter::nearest; | |
| sampDesc.mipmapFilter = Filter::nearest; | |
| sampDesc.minLod = static_cast<float>(i); | |
| sampDesc.maxLod = static_cast<float>(i); | |
| samplers[i] = ctx.makeSampler(sampDesc); | |
| BindGroupDesc sampBGDesc{}; | |
| sampBGDesc.layout = layout2.get(); | |
| BindGroupDesc::SampEntry sampEntry{}; | |
| sampEntry.slot = 0; | |
| sampEntry.sampler = samplers[i].get(); | |
| sampBGDesc.samplers = &sampEntry; | |
| sampBGDesc.samplerCount = 1; | |
| sampBGs[i] = ctx.makeBindGroup(sampBGDesc); | |
| } | |
| m_ore.beginFrame(renderContext); | |
| static constexpr float kClears[kLevels][4] = { | |
| {0.10f, 0.10f, 0.15f, 1.0f}, | |
| {0.35f, 0.10f, 0.10f, 1.0f}, | |
| {0.10f, 0.35f, 0.10f, 1.0f}, | |
| {0.10f, 0.10f, 0.35f, 1.0f}, | |
| }; | |
| for (uint32_t i = 0; i < kLevels; ++i) | |
| { | |
| RenderPassDesc rp{}; | |
| rp.colorAttachments[0].view = colorViews[i].get(); | |
| rp.colorAttachments[0].loadOp = LoadOp::clear; | |
| rp.colorAttachments[0].storeOp = StoreOp::store; | |
| rp.colorAttachments[0].clearColor = | |
| {kClears[i][0], kClears[i][1], kClears[i][2], kClears[i][3]}; | |
| rp.colorCount = 1; | |
| rp.depthStencil.view = depthViews[i].get(); | |
| rp.depthStencil.depthLoadOp = LoadOp::clear; | |
| rp.depthStencil.depthStoreOp = StoreOp::store; | |
| rp.label = "ore_mip_render_target_level"; | |
| auto pass = ctx.beginRenderPass(rp); | |
| pass->setPipeline(triPipeline.get()); | |
| pass->setVertexBuffer(0, vb.get()); | |
| pass->draw(3); | |
| pass->finish(); | |
| } | |
| { | |
| RenderPassDesc rp{}; | |
| rp.colorAttachments[0].view = colorViews[1].get(); | |
| rp.colorAttachments[0].loadOp = LoadOp::load; | |
| rp.colorAttachments[0].storeOp = StoreOp::store; | |
| rp.colorCount = 1; | |
| rp.depthStencil.view = depthViews[1].get(); | |
| rp.depthStencil.depthLoadOp = LoadOp::load; | |
| rp.depthStencil.depthStoreOp = StoreOp::store; | |
| rp.label = "ore_mip_render_target_reload"; | |
| auto pass = ctx.beginRenderPass(rp); | |
| pass->setPipeline(triPipeline.get()); | |
| pass->setVertexBuffer(0, vbFlipped.get()); | |
| pass->draw(3); | |
| pass->finish(); | |
| } | |
| RenderPassDesc rpShow{}; | |
| rpShow.colorAttachments[0].view = canvasTarget.get(); | |
| rpShow.colorAttachments[0].loadOp = LoadOp::clear; | |
| rpShow.colorAttachments[0].storeOp = StoreOp::store; | |
| rpShow.colorAttachments[0].clearColor = {0, 0, 0, 1}; | |
| rpShow.colorCount = 1; | |
| rpShow.label = "ore_mip_render_target_show"; | |
| auto show = ctx.beginRenderPass(rpShow); | |
| show->setPipeline(imgPipeline.get()); | |
| show->setBindGroup(1, texBG.get()); | |
| for (uint32_t i = 0; i < kLevels; ++i) | |
| { | |
| show->setViewport((i % 2) * 128.0f, (i / 2) * 128.0f, 128, 128); | |
| show->setBindGroup(2, sampBGs[i].get()); | |
| show->draw(6); | |
| } | |
| show->finish(); | |
| m_ore.endFrame(renderContext); | |
| ore_gm::invalidateGLStateAfterOre(renderContext); | |
| originalRenderer->save(); | |
| originalRenderer->drawImage( | |
| canvas->renderImage(), | |
| {.filter = ImageFilter::nearest}, | |
| BlendMode::srcOver, | |
| 1); | |
| originalRenderer->restore(); | |
| #endif | |
| } | |
| private: | |
| ore_gm::OreGMContext m_ore; | |
| }; | |
| GMREGISTER(ore_mip_render_target, return new OreMipRenderTargetGM) |