| /* | |
| * Copyright 2025 Rive | |
| */ | |
| // GM test for Ore depth testing. Renders three overlapping colored quads at | |
| // different Z depths with depth testing enabled (lessEqual). The front quad | |
| // should correctly occlude the ones behind it. Verifies: depth texture | |
| // creation, depth attachment in render pass, pipeline depth compare/write, | |
| // and correct depth ordering. | |
| #include "gm.hpp" | |
| #include "gmutils.hpp" | |
| #include "ore_gm_helper.hpp" | |
| #if defined(ORE_BACKEND_METAL) || defined(ORE_BACKEND_D3D11) || \ | |
| defined(ORE_BACKEND_D3D12) || defined(ORE_BACKEND_GL) || \ | |
| defined(ORE_BACKEND_WGPU) || defined(ORE_BACKEND_VK) || \ | |
| defined(ORE_BACKEND_RHI) | |
| #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_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_D3D11) || \ | |
| defined(ORE_BACKEND_D3D12) || defined(ORE_BACKEND_GL) || \ | |
| defined(ORE_BACKEND_WGPU) || defined(ORE_BACKEND_VK) || \ | |
| defined(ORE_BACKEND_RHI) | |
| using namespace rive::ore; | |
| #endif | |
| #if defined(ORE_BACKEND_METAL) || defined(ORE_BACKEND_D3D11) || \ | |
| defined(ORE_BACKEND_D3D12) || defined(ORE_BACKEND_GL) || \ | |
| defined(ORE_BACKEND_WGPU) || defined(ORE_BACKEND_VK) || \ | |
| defined(ORE_BACKEND_RHI) | |
| // Interleaved position (float3) + color (float4) per vertex. | |
| struct DepthVertex | |
| { | |
| float x, y, z; | |
| float r, g, b, a; | |
| }; | |
| // Three overlapping quads (each as 2 triangles = 6 vertices). | |
| // Arranged so they overlap in the center. Without depth test, draw order | |
| // determines visibility. With depth test (lessEqual), the nearest quad wins. | |
| // | |
| // Quad 1 (red): z=0.2 (nearest), centered slightly left-up. | |
| // Quad 2 (green): z=0.5 (middle), centered. | |
| // Quad 3 (blue): z=0.8 (farthest), centered slightly right-down. | |
| static const DepthVertex kDepthVertices[] = { | |
| // Quad 1 — red, z=0.2 (nearest), offset left-up | |
| {-0.6f, 0.7f, 0.2f, 1, 0, 0, 1}, | |
| {0.3f, 0.7f, 0.2f, 1, 0, 0, 1}, | |
| {-0.6f, -0.2f, 0.2f, 1, 0, 0, 1}, | |
| {0.3f, 0.7f, 0.2f, 1, 0, 0, 1}, | |
| {0.3f, -0.2f, 0.2f, 1, 0, 0, 1}, | |
| {-0.6f, -0.2f, 0.2f, 1, 0, 0, 1}, | |
| // Quad 2 — green, z=0.5 (middle), centered | |
| {-0.4f, 0.4f, 0.5f, 0, 1, 0, 1}, | |
| {0.5f, 0.4f, 0.5f, 0, 1, 0, 1}, | |
| {-0.4f, -0.5f, 0.5f, 0, 1, 0, 1}, | |
| {0.5f, 0.4f, 0.5f, 0, 1, 0, 1}, | |
| {0.5f, -0.5f, 0.5f, 0, 1, 0, 1}, | |
| {-0.4f, -0.5f, 0.5f, 0, 1, 0, 1}, | |
| // Quad 3 — blue, z=0.8 (farthest), offset right-down | |
| {-0.2f, 0.1f, 0.8f, 0, 0, 1, 1}, | |
| {0.7f, 0.1f, 0.8f, 0, 0, 1, 1}, | |
| {-0.2f, -0.8f, 0.8f, 0, 0, 1, 1}, | |
| {0.7f, 0.1f, 0.8f, 0, 0, 1, 1}, | |
| {0.7f, -0.8f, 0.8f, 0, 0, 1, 1}, | |
| {-0.2f, -0.8f, 0.8f, 0, 0, 1, 1}, | |
| }; | |
| #endif // ORE_BACKEND_METAL || ORE_BACKEND_D3D11 || ORE_BACKEND_GL || | |
| // ORE_BACKEND_VK | |
| class OreDepthGM : public GM | |
| { | |
| public: | |
| OreDepthGM() : 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 defined(ORE_BACKEND_METAL) || defined(ORE_BACKEND_D3D11) || \ | |
| defined(ORE_BACKEND_D3D12) || defined(ORE_BACKEND_GL) || \ | |
| defined(ORE_BACKEND_WGPU) || defined(ORE_BACKEND_VK) || \ | |
| defined(ORE_BACKEND_RHI) | |
| auto& ctx = *renderContext->getOreContext(); | |
| constexpr uint32_t kSize = 256; | |
| // Create a RenderCanvas to render into. | |
| auto canvas = renderContext->makeRenderCanvas(kSize, kSize); | |
| if (!canvas) | |
| return; | |
| auto colorTarget = ctx.wrapCanvasTexture(canvas.get()); | |
| if (!colorTarget) | |
| return; | |
| // Create depth texture. | |
| TextureDesc depthTexDesc{}; | |
| depthTexDesc.width = kSize; | |
| depthTexDesc.height = kSize; | |
| depthTexDesc.format = TextureFormat::depth32float; | |
| depthTexDesc.renderTarget = true; | |
| depthTexDesc.numMipmaps = 1; | |
| depthTexDesc.label = "depth_target"; | |
| auto depthTex = ctx.makeTexture(depthTexDesc); | |
| TextureViewDesc depthViewDesc{}; | |
| depthViewDesc.texture = depthTex.get(); | |
| depthViewDesc.mipCount = 1; | |
| depthViewDesc.layerCount = 1; | |
| auto depthView = ctx.makeTextureView(depthViewDesc); | |
| // Create vertex buffer. | |
| BufferDesc bufDesc{}; | |
| bufDesc.usage = BufferUsage::vertex; | |
| bufDesc.size = sizeof(kDepthVertices); | |
| bufDesc.data = kDepthVertices; | |
| bufDesc.label = "ore_depth_vbo"; | |
| auto vbo = ctx.makeBuffer(bufDesc); | |
| // Load shader from the pre-compiled RSTB. | |
| auto shader = ore_gm::loadShader(ctx, ore_gm::kDepth); | |
| if (!shader.vsModule) | |
| return; | |
| // Vertex layout: float3 position + float4 color, interleaved. | |
| VertexAttribute attrs[] = { | |
| {offsetof(DepthVertex, x), 0, VertexFormat::float3}, | |
| {offsetof(DepthVertex, r), 1, VertexFormat::float4}, | |
| }; | |
| VertexBufferLayout vertexLayout{}; | |
| vertexLayout.stride = sizeof(DepthVertex); | |
| vertexLayout.stepMode = VertexStepMode::vertex; | |
| vertexLayout.attributes = attrs; | |
| vertexLayout.attributeCount = 2; | |
| // Pipeline with depth testing enabled. | |
| PipelineDesc pipeDesc{}; | |
| pipeDesc.vertexModule = shader.vsModule.get(); | |
| pipeDesc.fragmentModule = shader.psModule.get(); | |
| pipeDesc.vertexEntryPoint = shader.vsEntryPoint; | |
| pipeDesc.fragmentEntryPoint = shader.fsEntryPoint; | |
| pipeDesc.vertexBuffers = &vertexLayout; | |
| pipeDesc.vertexBufferCount = 1; | |
| pipeDesc.topology = PrimitiveTopology::triangleList; | |
| pipeDesc.colorTargets[0].format = TextureFormat::rgba8unorm; | |
| pipeDesc.colorCount = 1; | |
| pipeDesc.depthStencil.format = TextureFormat::depth32float; | |
| pipeDesc.depthStencil.depthCompare = CompareFunction::lessEqual; | |
| pipeDesc.depthStencil.depthWriteEnabled = true; | |
| pipeDesc.label = "ore_depth_pipeline"; | |
| auto pipeline = ctx.makePipeline(pipeDesc); | |
| // Render all three quads in draw order (red, green, blue). | |
| // Blue is drawn last but is farthest (z=0.8), so it should be | |
| // occluded in overlapping areas by red (z=0.2) and green (z=0.5). | |
| m_ore.beginFrame(renderContext); | |
| RenderPassDesc rpDesc{}; | |
| rpDesc.colorAttachments[0].view = colorTarget.get(); | |
| rpDesc.colorAttachments[0].loadOp = LoadOp::clear; | |
| rpDesc.colorAttachments[0].storeOp = StoreOp::store; | |
| rpDesc.colorAttachments[0].clearColor = {0.1f, 0.1f, 0.1f, 1.0f}; | |
| rpDesc.colorCount = 1; | |
| rpDesc.depthStencil.view = depthView.get(); | |
| rpDesc.depthStencil.depthLoadOp = LoadOp::clear; | |
| rpDesc.depthStencil.depthStoreOp = StoreOp::store; | |
| rpDesc.depthStencil.depthClearValue = 1.0f; | |
| rpDesc.label = "ore_depth_pass"; | |
| auto pass = ctx.beginRenderPass(rpDesc); | |
| pass->setPipeline(pipeline.get()); | |
| pass->setVertexBuffer(0, vbo.get()); | |
| pass->setViewport(0, 0, kSize, kSize); | |
| // Draw all 18 vertices (3 quads x 6 vertices each). | |
| pass->draw(18); | |
| pass->finish(); | |
| m_ore.endFrame(renderContext); | |
| ore_gm::invalidateGLStateAfterOre(renderContext); | |
| // Composite the canvas into the main framebuffer via the original | |
| // renderer. No frame break needed — Ore rendered to a separate canvas | |
| // texture, not the main render target. | |
| originalRenderer->save(); | |
| originalRenderer->drawImage(canvas->renderImage(), | |
| {.filter = ImageFilter::nearest}, | |
| BlendMode::srcOver, | |
| 1); | |
| originalRenderer->restore(); | |
| #endif | |
| } | |
| private: | |
| ore_gm::OreGMContext m_ore; | |
| }; | |
| GMREGISTER(ore_depth, return new OreDepthGM()) |