| /* | |
| * Copyright 2026 Rive | |
| */ | |
| // Binds the vertex buffer and the index buffer at nonzero offsets. Both | |
| // buffers open with junk the offsets skip: two off screen vertices and four | |
| // zero indices, which would draw a degenerate triangle if either offset were | |
| // dropped. Expected: the ore_triangle image. | |
| #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_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; | |
| namespace | |
| { | |
| constexpr uint32_t kJunkVertexCount = 2; | |
| constexpr uint32_t kJunkIndexCount = 4; | |
| const ore_gm::TriVertex kVertices[kJunkVertexCount + 3] = { | |
| {-2.0f, -2.0f, 1.0f, 1.0f, 1.0f, 1.0f}, | |
| {-2.0f, -2.0f, 1.0f, 1.0f, 1.0f, 1.0f}, | |
| {0.0f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f}, | |
| {-0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f}, | |
| {0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 1.0f}, | |
| }; | |
| // Seven u16s, so neither the buffer nor the two index updates are whole words. | |
| const uint16_t kIndices[kJunkIndexCount + 3] = {0, 0, 0, 0, 0, 0, 0}; | |
| const uint16_t kSecondIndex = 1; | |
| const uint16_t kThirdIndex = 2; | |
| } // namespace | |
| #endif | |
| class OreBufferOffsetsGM : public GM | |
| { | |
| public: | |
| OreBufferOffsetsGM() : 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(); | |
| auto backend = TestingWindow::backend(); | |
| // WebGPU must refuse both a size whose whole word padding wraps and a | |
| // whole word size past the device's limit. Others would allocate them. | |
| if (backend == TestingWindow::Backend::wgpu || | |
| backend == TestingWindow::Backend::dawn) | |
| { | |
| for (uint32_t size : {UINT32_MAX, UINT32_MAX & ~3u}) | |
| { | |
| BufferDesc hugeDesc{}; | |
| hugeDesc.usage = BufferUsage::vertex; | |
| hugeDesc.size = size; | |
| if (ctx.makeBuffer(hugeDesc) != nullptr) | |
| { | |
| return; | |
| } | |
| } | |
| ctx.clearLastError(); | |
| } | |
| auto canvas = renderContext->makeRenderCanvas(256, 256); | |
| if (!canvas) | |
| return; | |
| auto colorTarget = ctx.wrapCanvasTexture(canvas.get()); | |
| if (!colorTarget) | |
| return; | |
| auto shader = ore_gm::loadShader(ctx, ore_gm::kTriangle); | |
| if (!shader.vsModule) | |
| return; | |
| BufferDesc vboDesc{}; | |
| vboDesc.usage = BufferUsage::vertex; | |
| vboDesc.size = sizeof(kVertices); | |
| vboDesc.data = kVertices; | |
| vboDesc.label = "ore_buffer_offsets_vbo"; | |
| auto vbo = ctx.makeBuffer(vboDesc); | |
| BufferDesc iboDesc{}; | |
| iboDesc.usage = BufferUsage::index; | |
| iboDesc.size = sizeof(kIndices); | |
| iboDesc.data = kIndices; | |
| iboDesc.label = "ore_buffer_offsets_ibo"; | |
| auto ibo = ctx.makeBuffer(iboDesc); | |
| ore_gm::TrianglePipeline pipe( | |
| shader, | |
| TextureFormat::rgba8unorm, | |
| "ore_buffer_offsets_pipeline"); | |
| auto pipeline = ctx.makePipeline(pipe.desc); | |
| m_ore.beginFrame(renderContext); | |
| ibo->update( | |
| &kSecondIndex, | |
| sizeof(kSecondIndex), | |
| (kJunkIndexCount + 1) * sizeof(uint16_t)); | |
| ibo->update( | |
| &kThirdIndex, | |
| sizeof(kThirdIndex), | |
| (kJunkIndexCount + 2) * sizeof(uint16_t)); | |
| ColorAttachment ca{}; | |
| ca.view = colorTarget.get(); | |
| ca.loadOp = LoadOp::clear; | |
| ca.storeOp = StoreOp::store; | |
| ca.clearColor = {0.1f, 0.1f, 0.1f, 1.0f}; | |
| RenderPassDesc rpDesc{}; | |
| rpDesc.colorAttachments[0] = ca; | |
| rpDesc.colorCount = 1; | |
| rpDesc.label = "ore_buffer_offsets_pass"; | |
| auto pass = ctx.beginRenderPass(rpDesc); | |
| pass->setPipeline(pipeline.get()); | |
| pass->setVertexBuffer( | |
| 0, | |
| vbo.get(), | |
| kJunkVertexCount * sizeof(ore_gm::TriVertex)); | |
| pass->setIndexBuffer( | |
| ibo.get(), | |
| IndexFormat::uint16, | |
| kJunkIndexCount * sizeof(uint16_t)); | |
| pass->setViewport(0, 0, 256, 256); | |
| pass->drawIndexed(3); | |
| pass->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_buffer_offsets, return new OreBufferOffsetsGM()) |