Put rhi resources into a scene struct

This commit is contained in:
luisangelsm
2026-01-17 22:56:46 +01:00
parent 3381754c12
commit e6b7174c62
2 changed files with 162 additions and 208 deletions

View File

@ -2,17 +2,6 @@
#include <QFile>
#include <cmath>
// Structure for per-instance data
struct InstanceData {
QMatrix4x4 modelMatrix;
float leftUpShading;
float leftDownShading;
float rightUpShading;
float rightDownShading;
float opacity;
float padding[3]; // Align to 16 bytes
};
/*** Preset Configurations ***/
// Note: The preset configurations are already defined in yacreader_flow_gl.cpp
// We just reference them here as extern to avoid duplicate symbols
@ -87,58 +76,57 @@ void YACReaderFlow3D::stopAnimationTimer()
void YACReaderFlow3D::initialize(QRhiCommandBuffer *cb)
{
if (m_rhi != rhi()) {
releaseResources();
scene.reset();
m_rhi = rhi();
}
if (!m_rhi)
return;
// Helper to get or create resource update batch
auto getResourceBatch = [this]() {
if (!scene.resourceUpdates)
scene.resourceUpdates = m_rhi->nextResourceUpdateBatch();
return scene.resourceUpdates;
};
// Initialize default texture from image
if (!defaultTexture) {
if (!scene.defaultTexture) {
QImage defaultImage(":/images/defaultCover.png");
defaultTexture = m_rhi->newTexture(QRhiTexture::BGRA8, defaultImage.size(), 1, QRhiTexture::MipMapped);
defaultTexture->create();
QRhiResourceUpdateBatch *batch = m_rhi->nextResourceUpdateBatch();
batch->uploadTexture(defaultTexture, defaultImage);
cb->resourceUpdate(batch);
scene.defaultTexture.reset(m_rhi->newTexture(QRhiTexture::BGRA8, defaultImage.size(), 1, QRhiTexture::MipMapped));
scene.defaultTexture->create();
getResourceBatch()->uploadTexture(scene.defaultTexture.get(), defaultImage);
qDebug() << "YACReaderFlow3D: Created defaultTexture" << defaultImage.size();
}
#ifdef YACREADER_LIBRARY
// Initialize mark textures
if (!markTexture) {
if (!scene.markTexture) {
QImage markImage(":/images/readRibbon.png");
if (!markImage.isNull()) {
markTexture = m_rhi->newTexture(QRhiTexture::BGRA8, markImage.size(), 1, QRhiTexture::MipMapped);
markTexture->create();
QRhiResourceUpdateBatch *batch = m_rhi->nextResourceUpdateBatch();
batch->uploadTexture(markTexture, markImage);
cb->resourceUpdate(batch);
scene.markTexture.reset(m_rhi->newTexture(QRhiTexture::BGRA8, markImage.size(), 1, QRhiTexture::MipMapped));
scene.markTexture->create();
getResourceBatch()->uploadTexture(scene.markTexture.get(), markImage);
}
}
if (!readingTexture) {
if (!scene.readingTexture) {
QImage readingImage(":/images/readingRibbon.png");
if (!readingImage.isNull()) {
readingTexture = m_rhi->newTexture(QRhiTexture::BGRA8, readingImage.size(), 1, QRhiTexture::MipMapped);
readingTexture->create();
QRhiResourceUpdateBatch *batch = m_rhi->nextResourceUpdateBatch();
batch->uploadTexture(readingTexture, readingImage);
cb->resourceUpdate(batch);
scene.readingTexture.reset(m_rhi->newTexture(QRhiTexture::BGRA8, readingImage.size(), 1, QRhiTexture::MipMapped));
scene.readingTexture->create();
getResourceBatch()->uploadTexture(scene.readingTexture.get(), readingImage);
}
}
#endif
// Create vertex buffer (quad geometry)
if (!vertexBuffer) {
if (!scene.vertexBuffer) {
// Use a triangle list (two triangles = 6 vertices) because some RHI backends
// don't support TriangleFan. Each vertex: x,y,z,u,v (5 floats).
vertexBuffer = m_rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, 6 * 5 * sizeof(float));
vertexBuffer->create();
scene.vertexBuffer.reset(m_rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, 6 * 5 * sizeof(float)));
scene.vertexBuffer->create();
// Two triangles forming a quad (triangle list):
// Tri 1: bottom-left, bottom-right, top-right
@ -155,38 +143,36 @@ void YACReaderFlow3D::initialize(QRhiCommandBuffer *cb)
-0.5f, 0.5f, 0.0f, 0.0f, 0.0f // Top-left
};
QRhiResourceUpdateBatch *batch = m_rhi->nextResourceUpdateBatch();
batch->uploadStaticBuffer(vertexBuffer, vertices);
cb->resourceUpdate(batch);
getResourceBatch()->uploadStaticBuffer(scene.vertexBuffer.get(), vertices);
}
// Initialize alignment for uniform buffers
if (alignedUniformSize == 0) {
alignedUniformSize = m_rhi->ubufAligned(sizeof(UniformData));
if (scene.alignedUniformSize == 0) {
scene.alignedUniformSize = m_rhi->ubufAligned(sizeof(UniformData));
}
// Create sampler
if (!sampler) {
if (!scene.sampler) {
// Use no mipmap sampling to avoid LOD changes with camera Z
sampler = m_rhi->newSampler(
scene.sampler.reset(m_rhi->newSampler(
QRhiSampler::Linear,
QRhiSampler::Linear,
QRhiSampler::None,
QRhiSampler::ClampToEdge,
QRhiSampler::ClampToEdge);
sampler->create();
QRhiSampler::ClampToEdge));
scene.sampler->create();
}
// Create instance buffer for per-draw instance data
if (!instanceBuffer) {
if (!scene.instanceBuffer) {
// Allocate buffer for per-instance data (model matrix + shading + opacity + flipFlag)
// mat4 (16 floats) + vec4 (4 floats) + float (1 float) + float (1 float) = 22 floats
instanceBuffer = m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::VertexBuffer, 22 * sizeof(float));
instanceBuffer->create();
scene.instanceBuffer.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::VertexBuffer, 22 * sizeof(float)));
scene.instanceBuffer->create();
}
// Setup graphics pipeline
if (!pipeline) {
if (!scene.pipeline) {
// Load shaders
QShader vertShader = getShader(QLatin1String(":/shaders/flow.vert.qsb"));
QShader fragShader = getShader(QLatin1String(":/shaders/flow.frag.qsb"));
@ -198,33 +184,31 @@ void YACReaderFlow3D::initialize(QRhiCommandBuffer *cb)
// Create default shader resource bindings for pipeline creation
// We'll create texture-specific ones on-demand in drawCover
shaderBindings = m_rhi->newShaderResourceBindings();
shaderBindings->setBindings({ QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, uniformBuffer),
QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, defaultTexture, sampler) });
shaderBindings->create();
scene.shaderBindings.reset(m_rhi->newShaderResourceBindings());
scene.shaderBindings->setBindings({ QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, scene.uniformBuffer.get()),
QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, scene.defaultTexture.get(), scene.sampler.get()) });
scene.shaderBindings->create();
// Create pipeline
pipeline = m_rhi->newGraphicsPipeline();
scene.pipeline.reset(m_rhi->newGraphicsPipeline());
// Disable alpha blending temporarily to test if blending causes darkening
// Setup alpha blending
QRhiGraphicsPipeline::TargetBlend blend;
blend.enable = true;
blend.srcColor = QRhiGraphicsPipeline::SrcAlpha;
blend.dstColor = QRhiGraphicsPipeline::OneMinusSrcAlpha;
blend.srcAlpha = QRhiGraphicsPipeline::One;
blend.dstAlpha = QRhiGraphicsPipeline::OneMinusSrcAlpha;
pipeline->setTargetBlends({ blend });
scene.pipeline->setTargetBlends({ blend });
// Enable depth test (restore depth writes for normal rendering)
pipeline->setDepthTest(true);
pipeline->setDepthWrite(true);
pipeline->setDepthOp(QRhiGraphicsPipeline::Less);
// Enable depth test
scene.pipeline->setDepthTest(true);
scene.pipeline->setDepthWrite(true);
scene.pipeline->setDepthOp(QRhiGraphicsPipeline::Less);
// Diagnostic: disable culling to avoid missing-triangle artifacts
pipeline->setCullMode(QRhiGraphicsPipeline::Back);
scene.pipeline->setCullMode(QRhiGraphicsPipeline::Back);
// Determine the MSAA sample count to use. Query the RHI for supported counts
// and clamp to at most 4 samples for safety.
// Determine the MSAA sample count to use
int requestedSamples = sampleCount();
int samplesToUse = 1;
if (requestedSamples > 1 && m_rhi) {
@ -237,20 +221,17 @@ void YACReaderFlow3D::initialize(QRhiCommandBuffer *cb)
samplesToUse = qMin(requestedSamples, qMin(4, maxSupported));
}
if (samplesToUse > 1)
pipeline->setSampleCount(samplesToUse);
// Use triangle fan topology to match OpenGL draw mode (this makes the app to crash)
// pipeline->setTopology(QRhiGraphicsPipeline::TriangleFan);
scene.pipeline->setSampleCount(samplesToUse);
// Set shaders
pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vertShader },
{ QRhiShaderStage::Fragment, fragShader } });
scene.pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vertShader },
{ QRhiShaderStage::Fragment, fragShader } });
// Setup vertex input layout
QRhiVertexInputLayout inputLayout;
inputLayout.setBindings({
{ 5 * sizeof(float) }, // Per-vertex data (position + texCoord)
{ 22 * sizeof(float), QRhiVertexInputBinding::PerInstance } // Per-instance data (+ flip flag)
{ 22 * sizeof(float), QRhiVertexInputBinding::PerInstance } // Per-instance data
});
inputLayout.setAttributes({
// Per-vertex attributes
@ -266,19 +247,24 @@ void YACReaderFlow3D::initialize(QRhiCommandBuffer *cb)
{ 1, 7, QRhiVertexInputAttribute::Float, 20 * sizeof(float) }, // opacity
{ 1, 8, QRhiVertexInputAttribute::Float, 21 * sizeof(float) } // flipFlag (1.0 = reflection)
});
pipeline->setVertexInputLayout(inputLayout);
scene.pipeline->setVertexInputLayout(inputLayout);
// Set shader resource bindings and render pass descriptor
pipeline->setShaderResourceBindings(shaderBindings);
pipeline->setRenderPassDescriptor(renderTarget()->renderPassDescriptor());
scene.pipeline->setShaderResourceBindings(scene.shaderBindings.get());
scene.pipeline->setRenderPassDescriptor(renderTarget()->renderPassDescriptor());
if (!pipeline->create()) {
if (!scene.pipeline->create()) {
qWarning() << "YACReaderFlow3D: Failed to create graphics pipeline!";
delete pipeline;
pipeline = nullptr;
scene.pipeline.reset();
}
}
// Submit any pending resource updates
if (scene.resourceUpdates) {
cb->resourceUpdate(scene.resourceUpdates);
scene.resourceUpdates = nullptr;
}
// Call populate only once per data loaded.
if (!hasBeenInitialized && lazyPopulateObjects != -1) {
populate(lazyPopulateObjects);
@ -290,43 +276,35 @@ void YACReaderFlow3D::initialize(QRhiCommandBuffer *cb)
void YACReaderFlow3D::ensureUniformBufferCapacity(int requiredSlots)
{
if (!m_rhi || alignedUniformSize == 0)
if (!m_rhi || scene.alignedUniformSize == 0)
return;
// Check if we need to resize
if (uniformBufferCapacity >= requiredSlots && uniformBuffer)
if (scene.uniformBufferCapacity >= requiredSlots && scene.uniformBuffer)
return;
// Delete old buffer if it exists
if (uniformBuffer) {
delete uniformBuffer;
uniformBuffer = nullptr;
}
// Reset uniform buffer
scene.uniformBuffer.reset();
// Create new larger buffer
// Each draw needs its own uniform slot (cover + reflection + optional mark = 3 per object)
const int totalSize = requiredSlots * alignedUniformSize;
uniformBuffer = m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, totalSize);
if (uniformBuffer->create()) {
uniformBufferCapacity = requiredSlots;
const int totalSize = requiredSlots * scene.alignedUniformSize;
scene.uniformBuffer.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, totalSize));
if (scene.uniformBuffer->create()) {
scene.uniformBufferCapacity = requiredSlots;
// Invalidate shader bindings cache since the uniform buffer changed
for (auto *srb : shaderBindingsCache) {
delete srb;
}
shaderBindingsCache.clear();
qDeleteAll(scene.shaderBindingsCache);
scene.shaderBindingsCache.clear();
// Recreate default shader bindings for pipeline
if (shaderBindings) {
delete shaderBindings;
}
shaderBindings = m_rhi->newShaderResourceBindings();
shaderBindings->setBindings({ QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, uniformBuffer),
QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, defaultTexture, sampler) });
shaderBindings->create();
scene.shaderBindings.reset(m_rhi->newShaderResourceBindings());
scene.shaderBindings->setBindings({ QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, scene.uniformBuffer.get()),
QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, scene.defaultTexture.get(), scene.sampler.get()) });
scene.shaderBindings->create();
} else {
qWarning() << "YACReaderFlow3D: Failed to create uniform buffer of size" << totalSize;
uniformBufferCapacity = 0;
scene.uniformBufferCapacity = 0;
}
}
@ -418,7 +396,7 @@ void YACReaderFlow3D::render(QRhiCommandBuffer *cb)
// Add mark draw immediately after its cover
if (showMarks && loaded[idx] && marks[idx] != Unread) {
QRhiTexture *markTex = (marks[idx] == Read) ? markTexture : readingTexture;
QRhiTexture *markTex = (marks[idx] == Read) ? scene.markTexture.get() : scene.readingTexture.get();
if (markTex) {
DrawInfo markDraw;
markDraw.imageIndex = idx;
@ -434,19 +412,16 @@ void YACReaderFlow3D::render(QRhiCommandBuffer *cb)
// Ensure uniform buffer is large enough
ensureUniformBufferCapacity(draws.size());
if (!uniformBuffer) {
if (!scene.uniformBuffer) {
qWarning() << "YACReaderFlow3D: No uniform buffer available for rendering";
return;
}
// Ensure instance buffer is large enough for all draws
const int requiredInstanceSize = draws.size() * 22 * sizeof(float);
if (!instanceBuffer || instanceBuffer->size() < requiredInstanceSize) {
if (instanceBuffer) {
delete instanceBuffer;
}
instanceBuffer = m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::VertexBuffer, requiredInstanceSize);
if (!instanceBuffer->create()) {
if (!scene.instanceBuffer || scene.instanceBuffer->size() < requiredInstanceSize) {
scene.instanceBuffer.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::VertexBuffer, requiredInstanceSize));
if (!scene.instanceBuffer->create()) {
qWarning() << "YACReaderFlow3D: Failed to create instance buffer of size" << requiredInstanceSize;
return;
}
@ -468,21 +443,21 @@ void YACReaderFlow3D::render(QRhiCommandBuffer *cb)
// Update uniform buffer with all draw data
for (int i = 0; i < draws.size(); ++i) {
int offset = i * alignedUniformSize;
batch->updateDynamicBuffer(uniformBuffer, offset, sizeof(UniformData), &draws[i].uniformData);
int offset = i * scene.alignedUniformSize;
batch->updateDynamicBuffer(scene.uniformBuffer.get(), offset, sizeof(UniformData), &draws[i].uniformData);
}
// Update instance buffer with all instance data
for (int i = 0; i < draws.size(); ++i) {
int offset = i * 22 * sizeof(float);
batch->updateDynamicBuffer(instanceBuffer, offset, 22 * sizeof(float), draws[i].instanceData);
batch->updateDynamicBuffer(scene.instanceBuffer.get(), offset, 22 * sizeof(float), draws[i].instanceData);
}
// === PHASE 2: RENDER (DURING PASS) ===
cb->beginPass(renderTarget(), clearColor, { 1.0f, 0 }, batch);
if (pipeline) {
cb->setGraphicsPipeline(pipeline);
if (scene.pipeline) {
cb->setGraphicsPipeline(scene.pipeline.get());
cb->setViewport(QRhiViewport(0, 0, outputSize.width(), outputSize.height()));
// Execute all draws
@ -575,43 +550,31 @@ void YACReaderFlow3D::prepareDrawData(const YACReader3DImageRHI &image, bool isR
void YACReaderFlow3D::executeDrawWithOffset(QRhiCommandBuffer *cb, QRhiTexture *texture,
const float *instanceData, int uniformSlot)
{
if (!texture || !instanceBuffer || !vertexBuffer)
if (!texture || !scene.instanceBuffer || !scene.vertexBuffer)
return;
// NOTE: We cannot update the instance buffer here during the render pass!
// Instead, we'll need to either:
// 1. Use the instance data from uniforms (move it to uniform buffer)
// 2. Or pre-upload all instance data before the pass
//
// For now, let's use approach #1: embed instance data in uniforms via a large instance buffer
// that we populate before the pass, similar to uniforms
//
// Actually, the simplest solution: update the instance buffer ONCE per draw using dynamic updates
// But we need to do this cleverly - we can't call resourceUpdate during pass.
//
// The solution: Create an instance buffer large enough for ALL draws, update it before pass,
// and use offsets during drawing.
Q_UNUSED(instanceData)
// Get or create shader resource bindings for this texture with dynamic offset support
QRhiShaderResourceBindings *srb = shaderBindingsCache.value(texture, nullptr);
QRhiShaderResourceBindings *srb = scene.shaderBindingsCache.value(texture, nullptr);
if (!srb) {
srb = m_rhi->newShaderResourceBindings();
srb->setBindings({ QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, uniformBuffer),
QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, texture, sampler) });
srb->setBindings({ QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, scene.uniformBuffer.get()),
QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, texture, scene.sampler.get()) });
srb->create();
shaderBindingsCache.insert(texture, srb);
scene.shaderBindingsCache.insert(texture, srb);
}
// Set shader resources with dynamic offset for uniform buffer
QRhiCommandBuffer::DynamicOffset dynOfs[] = {
{ 0, quint32(uniformSlot * alignedUniformSize) }
{ 0, quint32(uniformSlot * scene.alignedUniformSize) }
};
cb->setShaderResources(srb, 1, dynOfs);
// Bind vertex buffers with offset into instance buffer
const QRhiCommandBuffer::VertexInput vbufBindings[] = {
{ vertexBuffer, 0 },
{ instanceBuffer, quint32(uniformSlot * 22 * sizeof(float)) } // Use slot index for instance data offset
{ scene.vertexBuffer.get(), 0 },
{ scene.instanceBuffer.get(), quint32(uniformSlot * 22 * sizeof(float)) }
};
cb->setVertexInput(0, 2, vbufBindings);
@ -625,39 +588,7 @@ void YACReaderFlow3D::executeDrawWithOffset(QRhiCommandBuffer *cb, QRhiTexture *
void YACReaderFlow3D::releaseResources()
{
delete vertexBuffer;
vertexBuffer = nullptr;
delete instanceBuffer;
instanceBuffer = nullptr;
delete uniformBuffer;
uniformBuffer = nullptr;
delete sampler;
sampler = nullptr;
delete pipeline;
pipeline = nullptr;
delete shaderBindings;
shaderBindings = nullptr;
// Clean up shader bindings cache
for (auto *srb : shaderBindingsCache) {
delete srb;
}
shaderBindingsCache.clear();
delete defaultTexture;
defaultTexture = nullptr;
delete markTexture;
markTexture = nullptr;
delete readingTexture;
readingTexture = nullptr;
scene.reset();
m_rhi = nullptr;
}
@ -869,7 +800,7 @@ void YACReaderFlow3D::remove(int item)
}
images.removeAt(item);
if (texture != defaultTexture)
if (texture != scene.defaultTexture.get())
delete texture;
numObjects--;
@ -890,7 +821,7 @@ void YACReaderFlow3D::add(int item)
images[i].index++;
}
insert(s.toLocal8Bit().data(), defaultTexture, x, y, item);
insert(s.toLocal8Bit().data(), scene.defaultTexture.get(), x, y, item);
}
YACReader3DImageRHI YACReaderFlow3D::getCurrentSelected()
@ -925,7 +856,7 @@ void YACReaderFlow3D::populate(int n)
for (i = 0; i < n; i++) {
QString s = "cover";
insert(s.toLocal8Bit().data(), defaultTexture, x, y);
insert(s.toLocal8Bit().data(), scene.defaultTexture.get(), x, y);
}
loaded = QVector<bool>(n, false);
@ -939,7 +870,7 @@ void YACReaderFlow3D::reset()
loaded.clear();
for (int i = 0; i < numObjects; i++) {
if (images[i].texture != defaultTexture)
if (images[i].texture != scene.defaultTexture.get())
delete images[i].texture;
}
@ -1469,7 +1400,7 @@ YACReaderPageFlow3D::~YACReaderPageFlow3D()
// Clean up textures
for (auto &image : images) {
if (image.texture != defaultTexture) {
if (image.texture != scene.defaultTexture.get()) {
delete image.texture;
}
}

View File

@ -89,12 +89,10 @@ class YACReaderFlow3D : public QRhiWidget, public ScrollManagement
protected:
int timerId;
/*** System variables ***/
YACReader3DImageRHI dummy;
int viewRotateActive;
float stepBackup;
/*functions*/
void calcPos(YACReader3DImageRHI &image, int pos);
void calcVector(YACReader3DVector &vector, int pos);
bool animate(YACReader3DVector &currentVector, YACReader3DVector &toVector);
@ -103,34 +101,7 @@ protected:
int updateCount;
int fontSize;
// RHI resources
QRhiTexture *defaultTexture = nullptr;
QRhiTexture *markTexture = nullptr;
QRhiTexture *readingTexture = nullptr;
QRhiBuffer *vertexBuffer = nullptr;
QRhiBuffer *instanceBuffer = nullptr;
QRhiBuffer *uniformBuffer = nullptr;
int alignedUniformSize = 0; // Cached aligned uniform buffer size
int uniformBufferCapacity = 0; // Number of uniform slots allocated
QRhiSampler *sampler = nullptr;
QRhiGraphicsPipeline *pipeline = nullptr;
QRhiShaderResourceBindings *shaderBindings = nullptr;
// Cache of shader resource bindings per texture (to avoid recreating every frame)
QMap<QRhiTexture *, QRhiShaderResourceBindings *> shaderBindingsCache;
// Pending texture uploads (for async image loading)
struct PendingTextureUpload {
int index;
QImage image;
float x;
float y;
};
QVector<PendingTextureUpload> pendingTextureUploads;
// Uniform buffer data structure
// Uniform buffer data structure (must match shader layout)
struct UniformData {
QMatrix4x4 viewProjectionMatrix;
QVector3D backgroundColor;
@ -143,7 +114,65 @@ protected:
float _pad2;
};
void timerEvent(QTimerEvent *);
// Pending texture uploads (for async image loading)
struct PendingTextureUpload {
int index;
QImage image;
float x;
float y;
};
// Scene struct encapsulating all RHI resources
struct Scene {
// Textures
std::unique_ptr<QRhiTexture> defaultTexture;
std::unique_ptr<QRhiTexture> markTexture;
std::unique_ptr<QRhiTexture> readingTexture;
// Buffers
std::unique_ptr<QRhiBuffer> vertexBuffer;
std::unique_ptr<QRhiBuffer> instanceBuffer;
std::unique_ptr<QRhiBuffer> uniformBuffer;
// Pipeline and bindings
std::unique_ptr<QRhiSampler> sampler;
std::unique_ptr<QRhiGraphicsPipeline> pipeline;
std::unique_ptr<QRhiShaderResourceBindings> shaderBindings;
// Cache of shader resource bindings per texture (raw pointers, owned by this struct)
QMap<QRhiTexture *, QRhiShaderResourceBindings *> shaderBindingsCache;
// Uniform buffer sizing
int alignedUniformSize = 0;
int uniformBufferCapacity = 0;
// Pending resource updates (accumulated between frames)
QRhiResourceUpdateBatch *resourceUpdates = nullptr;
// Reset all resources (cleans up cache manually)
void reset()
{
qDeleteAll(shaderBindingsCache);
shaderBindingsCache.clear();
defaultTexture.reset();
markTexture.reset();
readingTexture.reset();
vertexBuffer.reset();
instanceBuffer.reset();
uniformBuffer.reset();
sampler.reset();
pipeline.reset();
shaderBindings.reset();
alignedUniformSize = 0;
uniformBufferCapacity = 0;
resourceUpdates = nullptr;
}
};
Scene scene;
QVector<PendingTextureUpload> pendingTextureUploads;
void timerEvent(QTimerEvent *) override;
int numObjects;
int lazyPopulateObjects;
@ -205,12 +234,6 @@ protected:
protected:
QRhi *m_rhi = nullptr;
std::unique_ptr<QRhiBuffer> m_vbuf;
std::unique_ptr<QRhiBuffer> m_ubuf;
std::unique_ptr<QRhiShaderResourceBindings> m_srb;
std::unique_ptr<QRhiGraphicsPipeline> m_pipeline;
QMatrix4x4 m_viewProjection;
float m_rotation = 0.0f;
public:
YACReaderFlow3D(QWidget *parent = nullptr, struct Preset p = pressetYACReaderFlowDownConfig);