blob: 83b281108d1ae08fc5e119986a31a5f4df7208f9 [file]
/*
* Copyright 2026 Rive
*/
// Allocates past one descriptor pool generation to force a rollover then
// drops most of the bind groups. Validation layer catches lifecycle bugs
// at vkDestroyDevice. Renders the witness output for golden compare.
#include "gm.hpp"
#include "gmutils.hpp"
#include "ore_gm_helper.hpp"
#include <vector>
#if defined(ORE_BACKEND_METAL) || defined(ORE_BACKEND_GL) || \
defined(ORE_BACKEND_VK) || defined(ORE_BACKEND_WGPU) || \
defined(ORE_BACKEND_D3D11) || defined(ORE_BACKEND_D3D12)
#include "rive/renderer/render_canvas.hpp"
#include "rive/renderer/ore/ore_buffer.hpp"
#include "rive/renderer/ore/ore_bind_group.hpp"
#include "rive/renderer/ore/ore_shader_module.hpp"
#include "rive/renderer/ore/ore_pipeline.hpp"
#include "rive/renderer/ore/ore_render_pass.hpp"
#if defined(ORE_BACKEND_VK)
#include "rive/renderer/ore/ore_context_vulkan.hpp"
#endif
using namespace rive::ore;
#define ORE_POOL_ROLLOVER_ACTIVE
#endif
using namespace rivegm;
using namespace rive;
using namespace rive::gpu;
class OrePoolRolloverGM : public GM
{
public:
OrePoolRolloverGM() : GM(128, 128) {}
ColorInt clearColor() const override { return 0xff000000; }
void onDraw(rive::Renderer* originalRenderer) override
{
#ifdef ORE_POOL_ROLLOVER_ACTIVE
auto renderContext = TestingWindow::Get()->renderContext();
if (!renderContext || !m_ore.ensureContext(renderContext))
return;
auto& ctx = *renderContext->getOreContext();
struct Uniforms
{
float r, g, b, a;
};
static const Uniforms kLow = {0.3f, 0.0f, 0.0f, 0.0f};
static const Uniforms kHigh = {0.0f, 0.6f, 0.0f, 0.0f};
BufferDesc bd{};
bd.usage = BufferUsage::uniform;
bd.size = sizeof(Uniforms);
bd.data = &kLow;
bd.label = "rollover_ubo_low";
auto lowBuf = ctx.makeBuffer(bd);
bd.data = &kHigh;
bd.label = "rollover_ubo_high";
auto highBuf = ctx.makeBuffer(bd);
auto shader = ore_gm::loadShader(ctx, ore_gm::kBindingWitness);
if (!shader.vsModule)
return;
auto layout0 =
ore_gm::makeLayoutFromShader(ctx, shader.vsModule.get(), 0);
BindGroupDesc::UBOEntry uboEntries[2]{};
uboEntries[0].slot = 0;
uboEntries[0].buffer = lowBuf.get();
uboEntries[0].offset = 0;
uboEntries[0].size = sizeof(Uniforms);
uboEntries[1].slot = 7;
uboEntries[1].buffer = highBuf.get();
uboEntries[1].offset = 0;
uboEntries[1].size = sizeof(Uniforms);
BindGroupDesc bgDesc{};
bgDesc.layout = layout0.get();
bgDesc.ubos = uboEntries;
bgDesc.uboCount = 2;
bgDesc.label = "rollover_bg";
// One past the per-generation cap forces a rollover and trips
// the retry path in makeBindGroup.
#if defined(ORE_BACKEND_VK)
constexpr int kCount =
DescriptorPoolGeneration::kMaxSetsPerGeneration + 1;
#else
constexpr int kCount = 257;
#endif
std::vector<rcp<BindGroup>> bgs;
bgs.reserve(kCount);
for (int i = 0; i < kCount; ++i)
{
auto bg = ctx.makeBindGroup(bgDesc);
if (!bg)
return;
bgs.push_back(std::move(bg));
}
// Keep an early bind group alive so its generation can't die yet,
// then drop everything else. Draw with a post-rollover bind group
// to prove sets are valid after the generation roll.
auto firstGenBg = std::move(bgs.front());
auto finalBg = std::move(bgs.back());
bgs.clear();
(void)firstGenBg;
BindGroupLayout* layouts[] = {layout0.get()};
PipelineDesc pipeDesc{};
pipeDesc.vertexModule = shader.vsModule.get();
pipeDesc.fragmentModule = shader.psModule.get();
pipeDesc.vertexEntryPoint = shader.vsEntryPoint;
pipeDesc.fragmentEntryPoint = shader.fsEntryPoint;
pipeDesc.vertexBufferCount = 0;
pipeDesc.topology = PrimitiveTopology::triangleList;
pipeDesc.colorTargets[0].format = TextureFormat::rgba8unorm;
pipeDesc.colorCount = 1;
pipeDesc.depthStencil.depthCompare = CompareFunction::always;
pipeDesc.depthStencil.depthWriteEnabled = false;
pipeDesc.bindGroupLayouts = layouts;
pipeDesc.bindGroupLayoutCount = 1;
pipeDesc.label = "ore_pool_rollover_pipeline";
auto pipeline = ctx.makePipeline(pipeDesc);
if (!pipeline)
return;
auto canvas = renderContext->makeRenderCanvas(128, 128);
if (!canvas)
return;
auto canvasTarget = ctx.wrapCanvasTexture(canvas.get());
if (!canvasTarget)
return;
m_ore.beginFrame(renderContext);
ColorAttachment ca{};
ca.view = canvasTarget.get();
ca.loadOp = LoadOp::clear;
ca.storeOp = StoreOp::store;
ca.clearColor = {0, 0, 0, 1};
RenderPassDesc rpDesc{};
rpDesc.colorAttachments[0] = ca;
rpDesc.colorCount = 1;
rpDesc.label = "ore_pool_rollover_pass";
auto pass = ctx.beginRenderPass(rpDesc);
pass->setPipeline(pipeline.get());
pass->setBindGroup(0, finalBg.get());
pass->draw(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:
#ifdef ORE_POOL_ROLLOVER_ACTIVE
ore_gm::OreGMContext m_ore;
#endif
};
GMREGISTER(ore_pool_rollover, return new OrePoolRolloverGM)