Ensure resources dynamically

Content and settings can vary at any moment and we need to be prepared for always have a consistent context
This commit is contained in:
luisangelsm
2026-01-18 12:36:34 +01:00
parent 48aaf36fab
commit c4c59ab96a
2 changed files with 140 additions and 101 deletions

View File

@ -95,17 +95,18 @@ void YACReaderFlow3D::stopAnimationTimer()
void YACReaderFlow3D::initialize(QRhiCommandBuffer *cb) void YACReaderFlow3D::initialize(QRhiCommandBuffer *cb)
{ {
if (m_rhi != rhi()) { auto newRhi = rhi();
if (m_rhi != newRhi) {
scene.reset(); scene.reset();
m_rhi = rhi(); m_rhi = newRhi;
} }
if (!m_rhi) if (m_rhi == nullptr)
return; return;
// Helper to get or create resource update batch // Helper to get or create resource update batch
auto getResourceBatch = [this]() { auto getResourceBatch = [this]() {
if (!scene.resourceUpdates) if (scene.resourceUpdates == nullptr)
scene.resourceUpdates = m_rhi->nextResourceUpdateBatch(); scene.resourceUpdates = m_rhi->nextResourceUpdateBatch();
return scene.resourceUpdates; return scene.resourceUpdates;
}; };
@ -191,8 +192,93 @@ void YACReaderFlow3D::initialize(QRhiCommandBuffer *cb)
scene.instanceBuffer->create(); scene.instanceBuffer->create();
} }
// Setup graphics pipeline // Determine how many items we'll have (either already populated or pending lazy population)
if (!scene.pipeline) { const int itemCount = (lazyPopulateObjects != -1) ? lazyPopulateObjects : numObjects;
// Create uniform buffer sized for the actual content
// Each item needs up to 3 draw slots: cover + reflection + mark
// If no items yet, we'll create the buffer later in ensureUniformBufferCapacity()
if (!scene.uniformBuffer && itemCount > 0) {
const int requiredSlots = itemCount * 3;
const int totalSize = requiredSlots * scene.alignedUniformSize;
scene.uniformBuffer.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, totalSize));
if (scene.uniformBuffer->create()) {
scene.uniformBufferCapacity = requiredSlots;
} else {
qWarning() << "YACReaderFlow3D: Failed to create uniform buffer for" << itemCount << "items";
scene.uniformBuffer.reset();
scene.uniformBufferCapacity = 0;
}
}
// Create shader bindings for pipeline (requires uniform buffer)
if (!scene.shaderBindings && scene.uniformBuffer) {
scene.shaderBindings.reset(m_rhi->newShaderResourceBindings());
scene.shaderBindings->setBindings({ QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, scene.uniformBuffer.get(), sizeof(UniformData)),
QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, scene.defaultTexture.get(), scene.sampler.get()) });
scene.shaderBindings->create();
}
// Create pipeline if we have all prerequisites
ensurePipeline();
// 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);
lazyPopulateObjects = -1;
}
hasBeenInitialized = true;
}
void YACReaderFlow3D::ensureUniformBufferCapacity(int requiredSlots)
{
if (!m_rhi || scene.alignedUniformSize == 0)
return;
// Check if we need to resize
if (scene.uniformBufferCapacity >= requiredSlots && scene.uniformBuffer)
return;
// 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 * 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
qDeleteAll(scene.shaderBindingsCache);
scene.shaderBindingsCache.clear();
// Recreate default shader bindings for pipeline (using dynamic offset)
scene.shaderBindings.reset(m_rhi->newShaderResourceBindings());
scene.shaderBindings->setBindings({ QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, scene.uniformBuffer.get(), sizeof(UniformData)),
QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, scene.defaultTexture.get(), scene.sampler.get()) });
scene.shaderBindings->create();
// If pipeline doesn't exist yet (content added after initial empty initialization),
// we need to create it now. This is handled by ensurePipeline().
} else {
qWarning() << "YACReaderFlow3D: Failed to create uniform buffer of size" << totalSize;
scene.uniformBufferCapacity = 0;
}
}
void YACReaderFlow3D::ensurePipeline()
{
if (scene.pipeline || !m_rhi || !scene.uniformBuffer || !scene.shaderBindings)
return;
// Load shaders // Load shaders
QShader vertShader = getShader(QLatin1String(":/shaders/flow.vert.qsb")); QShader vertShader = getShader(QLatin1String(":/shaders/flow.vert.qsb"));
QShader fragShader = getShader(QLatin1String(":/shaders/flow.frag.qsb")); QShader fragShader = getShader(QLatin1String(":/shaders/flow.frag.qsb"));
@ -202,13 +288,6 @@ void YACReaderFlow3D::initialize(QRhiCommandBuffer *cb)
return; return;
} }
// Create default shader resource bindings for pipeline creation
// We'll create texture-specific ones on-demand in drawCover
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 // Create pipeline
scene.pipeline.reset(m_rhi->newGraphicsPipeline()); scene.pipeline.reset(m_rhi->newGraphicsPipeline());
@ -234,7 +313,7 @@ void YACReaderFlow3D::initialize(QRhiCommandBuffer *cb)
if (requestedSamples > 1 && m_rhi) { if (requestedSamples > 1 && m_rhi) {
QVector<int> supported = m_rhi->supportedSampleCounts(); QVector<int> supported = m_rhi->supportedSampleCounts();
int maxSupported = 1; int maxSupported = 1;
for (int s : supported) { for (int s : std::as_const(supported)) {
if (s > maxSupported) if (s > maxSupported)
maxSupported = s; maxSupported = s;
} }
@ -279,55 +358,6 @@ void YACReaderFlow3D::initialize(QRhiCommandBuffer *cb)
} }
} }
// 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);
lazyPopulateObjects = -1;
}
hasBeenInitialized = true;
}
void YACReaderFlow3D::ensureUniformBufferCapacity(int requiredSlots)
{
if (!m_rhi || scene.alignedUniformSize == 0)
return;
// Check if we need to resize
if (scene.uniformBufferCapacity >= requiredSlots && scene.uniformBuffer)
return;
// 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 * 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
qDeleteAll(scene.shaderBindingsCache);
scene.shaderBindingsCache.clear();
// Recreate default shader bindings for pipeline
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;
scene.uniformBufferCapacity = 0;
}
}
void YACReaderFlow3D::render(QRhiCommandBuffer *cb) void YACReaderFlow3D::render(QRhiCommandBuffer *cb)
{ {
if (!m_rhi || numObjects == 0) if (!m_rhi || numObjects == 0)
@ -437,8 +467,16 @@ void YACReaderFlow3D::render(QRhiCommandBuffer *cb)
return; return;
} }
// Ensure pipeline exists (may not exist if content was added after empty initialization)
ensurePipeline();
if (!scene.pipeline) {
qWarning() << "YACReaderFlow3D: No pipeline available for rendering";
return;
}
// Ensure instance buffer is large enough for all draws // Ensure instance buffer is large enough for all draws
const int requiredInstanceSize = draws.size() * 22 * sizeof(float); auto requiredInstanceSize = static_cast<quint32>(draws.size() * 22 * sizeof(float));
if (!scene.instanceBuffer || scene.instanceBuffer->size() < requiredInstanceSize) { if (!scene.instanceBuffer || scene.instanceBuffer->size() < requiredInstanceSize) {
scene.instanceBuffer.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::VertexBuffer, requiredInstanceSize)); scene.instanceBuffer.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::VertexBuffer, requiredInstanceSize));
if (!scene.instanceBuffer->create()) { if (!scene.instanceBuffer->create()) {
@ -453,7 +491,7 @@ void YACReaderFlow3D::render(QRhiCommandBuffer *cb)
// Process pending texture uploads // Process pending texture uploads
if (!pendingTextureUploads.isEmpty()) { if (!pendingTextureUploads.isEmpty()) {
for (const auto &upload : pendingTextureUploads) { for (const auto &upload : std::as_const(pendingTextureUploads)) {
if (upload.index >= 0 && upload.index < images.size() && images[upload.index].texture) { if (upload.index >= 0 && upload.index < images.size() && images[upload.index].texture) {
batch->uploadTexture(images[upload.index].texture, upload.image); batch->uploadTexture(images[upload.index].texture, upload.image);
} }
@ -476,7 +514,6 @@ void YACReaderFlow3D::render(QRhiCommandBuffer *cb)
// === PHASE 2: RENDER (DURING PASS) === // === PHASE 2: RENDER (DURING PASS) ===
cb->beginPass(renderTarget(), clearColor, { 1.0f, 0 }, batch); cb->beginPass(renderTarget(), clearColor, { 1.0f, 0 }, batch);
if (scene.pipeline) {
cb->setGraphicsPipeline(scene.pipeline.get()); cb->setGraphicsPipeline(scene.pipeline.get());
cb->setViewport(QRhiViewport(0, 0, outputSize.width(), outputSize.height())); cb->setViewport(QRhiViewport(0, 0, outputSize.width(), outputSize.height()));
@ -485,7 +522,6 @@ void YACReaderFlow3D::render(QRhiCommandBuffer *cb)
const DrawInfo &draw = draws[i]; const DrawInfo &draw = draws[i];
executeDrawWithOffset(cb, draw.texture, draw.instanceData, i); executeDrawWithOffset(cb, draw.texture, draw.instanceData, i);
} }
}
cb->endPass(); cb->endPass();
} }
@ -579,7 +615,7 @@ void YACReaderFlow3D::executeDrawWithOffset(QRhiCommandBuffer *cb, QRhiTexture *
QRhiShaderResourceBindings *srb = scene.shaderBindingsCache.value(texture, nullptr); QRhiShaderResourceBindings *srb = scene.shaderBindingsCache.value(texture, nullptr);
if (!srb) { if (!srb) {
srb = m_rhi->newShaderResourceBindings(); srb = m_rhi->newShaderResourceBindings();
srb->setBindings({ QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, scene.uniformBuffer.get()), srb->setBindings({ QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, scene.uniformBuffer.get(), sizeof(UniformData)),
QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, texture, scene.sampler.get()) }); QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, texture, scene.sampler.get()) });
srb->create(); srb->create();
scene.shaderBindingsCache.insert(texture, srb); scene.shaderBindingsCache.insert(texture, srb);
@ -887,6 +923,7 @@ void YACReaderFlow3D::populate(int n)
} }
loaded = QVector<bool>(n, false); loaded = QVector<bool>(n, false);
marks = QVector<YACReaderComicReadStatus>(n, Unread);
} }
void YACReaderFlow3D::reset() void YACReaderFlow3D::reset()
@ -895,6 +932,7 @@ void YACReaderFlow3D::reset()
currentSelected = 0; currentSelected = 0;
loaded.clear(); loaded.clear();
marks.clear();
// Clean up image textures and remove their entries from shader bindings cache // Clean up image textures and remove their entries from shader bindings cache
for (int i = 0; i < numObjects; i++) { for (int i = 0; i < numObjects; i++) {

View File

@ -228,6 +228,7 @@ protected:
void updateUniformBuffer(QRhiCommandBuffer *cb, const UniformData &data); void updateUniformBuffer(QRhiCommandBuffer *cb, const UniformData &data);
void prepareMarkInstanceData(const YACReader3DImageRHI &image, QVector<float> &data); void prepareMarkInstanceData(const YACReader3DImageRHI &image, QVector<float> &data);
void ensureUniformBufferCapacity(int requiredSlots); void ensureUniformBufferCapacity(int requiredSlots);
void ensurePipeline();
void prepareDrawData(const YACReader3DImageRHI &image, bool isReflection, bool isMark, void prepareDrawData(const YACReader3DImageRHI &image, bool isReflection, bool isMark,
const QMatrix4x4 &viewProjectionMatrix, float *outInstanceData, const QMatrix4x4 &viewProjectionMatrix, float *outInstanceData,
UniformData &outUniformData); UniformData &outUniformData);