12#include <spdlog/spdlog.h>
13#include <QMutexLocker>
27 spdlog::debug(
"[RHIImageItemRenderer::RHIImageItemRenderer]: Created");
43 spdlog::error(
"[RHIImageItemRenderer::initialize]: No RHI context available");
47 spdlog::debug(
"[RHIImageItemRenderer::initialize]: Starting initialization...");
54 if (m_vertex_buffer && m_index_buffer && m_uniform_buffer && m_pipeline && m_sampler && m_srb) {
56 spdlog::info(
"[RHIImageItemRenderer::initialize]: Initialized successfully");
58 spdlog::error(
"[RHIImageItemRenderer::initialize]: Initialization failed - missing resources");
64 spdlog::debug(
"[RHIImageItemRenderer::synchronize]: Synchronizing...");
68 spdlog::warn(
"[RHIImageItemRenderer::synchronize]: Null item");
73 QMutexLocker lock(rhi_item->getImageMutex());
76 m_zoom = rhi_item->zoom();
77 m_pan = rhi_item->pan();
79 m_item_width =
static_cast<float>(rhi_item->width());
80 m_item_height =
static_cast<float>(rhi_item->height());
82 spdlog::debug(
"[RHIImageItemRenderer::synchronize]: zoom={}, pan=({}, {})",
83 m_zoom, m_pan.x(), m_pan.y());
86 const auto* image { rhi_item->getFullImage() };
91 const bool needs_update { rhi_item->m_texture_needs_update };
92 const bool size_mismatch { image && image->isValid() &&
93 (!m_texture || m_texture->pixelSize() != QSize(image->width(), image->height())) };
95 if (needs_update || size_mismatch)
97 if (image && image->isValid())
99 m_image_width =
static_cast<int>(image->width());
100 m_image_height =
static_cast<int>(image->height());
102 spdlog::debug(
"[RHIImageItemRenderer::synchronize]: Converting image {}x{}",
103 m_image_width, m_image_height);
105 updateTextureFromImage(*image);
106 m_texture_needs_update =
true;
109 rhi_item->m_texture_needs_update =
false;
115 spdlog::error(
"[RHIImageItemRenderer::render]: RENDER CALLED");
117 if (!m_initialized || !m_pipeline || !cb) {
118 spdlog::warn(
"[RHIImageItemRenderer::render]: Not ready (initialized={}, pipeline={})",
119 m_initialized,
static_cast<bool>(m_pipeline));
123 if (!renderTarget()) {
124 spdlog::warn(
"[RHIImageItemRenderer::render]: No render target");
129 if (m_texture_needs_update && m_texture && !m_pixel_data.empty())
131 QRhiResourceUpdateBatch* resourceUpdates = rhi()->nextResourceUpdateBatch();
134 QRhiTextureSubresourceUploadDescription subresDesc(
136 static_cast<quint32
>(m_pixel_data.size())
139 QRhiTextureUploadDescription desc(QRhiTextureUploadEntry(0, 0, subresDesc));
140 resourceUpdates->uploadTexture(m_texture.get(), desc);
141 cb->resourceUpdate(resourceUpdates);
142 m_texture_needs_update =
false;
144 spdlog::debug(
"[RHIImageItemRenderer::render]: Texture uploaded {}x{}",
145 m_pixel_data_size.width(), m_pixel_data_size.height());
154 const float logical_w { m_item_width };
155 const float logical_h { m_item_height };
157 matrix.ortho(0.0f, logical_w, logical_h, 0.0f, -1.0f, 1.0f);
159 const float display_w {
static_cast<float>(m_image_width) * m_zoom };
160 const float display_h {
static_cast<float>(m_image_height) * m_zoom };
162 const float x_pos { (logical_w - display_w) / 2.0f +
static_cast<float>(m_pan.x()) };
163 const float y_pos { (logical_h - display_h) / 2.0f +
static_cast<float>(m_pan.y()) };
165 matrix.translate(x_pos, y_pos);
166 matrix.scale(display_w, display_h);
168 spdlog::debug(
"[RHIImageItemRenderer::render]: image={}x{}, zoom={}, pan=({}, {}), logical={}x{}",
169 m_image_width, m_image_height, m_zoom, m_pan.x(), m_pan.y(),
170 logical_w, logical_h);
173 if (m_uniform_buffer)
175 QRhiResourceUpdateBatch* batch = rhi()->nextResourceUpdateBatch();
177 batch->updateDynamicBuffer(m_uniform_buffer.get(), 0, 64, matrix.constData());
178 cb->resourceUpdate(batch);
183 QSize rt_size { renderTarget()->pixelSize() };
184 cb->beginPass(renderTarget(), QColor(30, 30, 30, 255), {1.0f, 0});
187 cb->setGraphicsPipeline(m_pipeline.get());
190 cb->setViewport(QRhiViewport(0, 0, rt_size.width(), rt_size.height()));
193 cb->setShaderResources(m_srb.get());
196 QRhiCommandBuffer::VertexInput vertex_input{m_vertex_buffer.get(), 0};
197 cb->setVertexInput(0, 1, &vertex_input, m_index_buffer.get(), 0, QRhiCommandBuffer::IndexUInt16);
205void RHIImageItemRenderer::createGeometry(QRhiCommandBuffer* cb)
225 Vertex vertices[] = {
226 {0.0f, 0.0f, 0.0f, 0.0f},
227 {1.0f, 0.0f, 1.0f, 0.0f},
228 {1.0f, 1.0f, 1.0f, 1.0f},
229 {0.0f, 1.0f, 0.0f, 1.0f}
235 uint16_t indices[] = {
241 m_vertex_buffer.reset(rhi()->newBuffer(
242 QRhiBuffer::Immutable,
243 QRhiBuffer::VertexBuffer,
247 m_index_buffer.reset(rhi()->newBuffer(
248 QRhiBuffer::Immutable,
249 QRhiBuffer::IndexBuffer,
254 m_uniform_buffer.reset(rhi()->newBuffer(
256 QRhiBuffer::UniformBuffer,
260 if (!m_vertex_buffer->create()) {
261 spdlog::error(
"[RHIImageItemRenderer::createGeometry]: Failed to create vertex buffer");
265 if (!m_index_buffer->create()) {
266 spdlog::error(
"[RHIImageItemRenderer::createGeometry]: Failed to create index buffer");
270 if (!m_uniform_buffer->create()) {
271 spdlog::error(
"[RHIImageItemRenderer::createGeometry]: Failed to create uniform buffer");
277 QRhiResourceUpdateBatch* batch { rhi()->nextResourceUpdateBatch() };
279 batch->uploadStaticBuffer(m_vertex_buffer.get(), vertices);
280 batch->uploadStaticBuffer(m_index_buffer.get(), indices);
281 cb->resourceUpdate(batch);
282 spdlog::debug(
"[RHIImageItemRenderer::createGeometry]: Geometry buffers uploaded");
284 spdlog::error(
"[RHIImageItemRenderer::createGeometry]: Failed to get resource update batch");
287 spdlog::error(
"[RHIImageItemRenderer::createGeometry]: No command buffer provided");
291void RHIImageItemRenderer::createPipeline()
294 QFile vs_file(
":/shaders/glsl/image_display.vert.qsb");
295 QFile fs_file(
":/shaders/glsl/image_display.frag.qsb");
297 if (!vs_file.open(QIODevice::ReadOnly)) {
298 spdlog::error(
"[RHIImageItemRenderer::createPipeline]: Failed to open vertex shader: {}",
299 vs_file.fileName().toStdString());
303 if (!fs_file.open(QIODevice::ReadOnly)) {
304 spdlog::error(
"[RHIImageItemRenderer::createPipeline]: Failed to open fragment shader: {}",
305 fs_file.fileName().toStdString());
309 QShader vs { QShader::fromSerialized(vs_file.readAll()) };
310 QShader fs { QShader::fromSerialized(fs_file.readAll()) };
313 spdlog::error(
"[RHIImageItemRenderer::createPipeline]: Invalid vertex shader");
318 spdlog::error(
"[RHIImageItemRenderer::createPipeline]: Invalid fragment shader");
322 spdlog::debug(
"[RHIImageItemRenderer::createPipeline]: Shaders loaded successfully");
325 m_sampler.reset(rhi()->newSampler(
329 QRhiSampler::ClampToEdge,
330 QRhiSampler::ClampToEdge
333 if (!m_sampler->create()) {
334 spdlog::error(
"[RHIImageItemRenderer::createPipeline]: Failed to create sampler");
339 m_texture.reset(rhi()->newTexture(QRhiTexture::RGBA8, QSize(1, 1)));
341 if (!m_texture->create()) {
342 spdlog::error(
"[RHIImageItemRenderer::createPipeline]: Failed to create placeholder texture");
347 m_srb.reset(rhi()->newShaderResourceBindings());
349 QRhiShaderResourceBinding bindings[] = {
351 QRhiShaderResourceBinding::uniformBuffer(
353 QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage,
354 m_uniform_buffer.get()
357 QRhiShaderResourceBinding::sampledTexture(
359 QRhiShaderResourceBinding::FragmentStage,
365 m_srb->setBindings(bindings, bindings + 2);
367 if (!m_srb->create()) {
368 spdlog::error(
"[RHIImageItemRenderer::createPipeline]: Failed to create shader resource bindings");
373 m_pipeline.reset(rhi()->newGraphicsPipeline());
376 m_pipeline->setShaderStages({
377 QRhiShaderStage{QRhiShaderStage::Vertex, vs},
378 QRhiShaderStage{QRhiShaderStage::Fragment, fs}
382 QRhiVertexInputLayout input_layout;
383 input_layout.setBindings({
384 QRhiVertexInputBinding(4 *
sizeof(
float))
386 input_layout.setAttributes({
387 QRhiVertexInputAttribute(0, 0, QRhiVertexInputAttribute::Float2, 0),
388 QRhiVertexInputAttribute(0, 1, QRhiVertexInputAttribute::Float2, 2 *
sizeof(
float))
391 m_pipeline->setVertexInputLayout(input_layout);
392 m_pipeline->setShaderResourceBindings(m_srb.get());
395 if (!renderTarget()) {
396 spdlog::error(
"[RHIImageItemRenderer::createPipeline]: No render target available");
400 m_pipeline->setRenderPassDescriptor(renderTarget()->renderPassDescriptor());
403 QRhiGraphicsPipeline::TargetBlend
blend;
405 blend.srcColor = QRhiGraphicsPipeline::SrcAlpha;
406 blend.dstColor = QRhiGraphicsPipeline::OneMinusSrcAlpha;
407 blend.srcAlpha = QRhiGraphicsPipeline::One;
408 blend.dstAlpha = QRhiGraphicsPipeline::OneMinusSrcAlpha;
409 m_pipeline->setTargetBlends({
blend});
412 m_pipeline->setTopology(QRhiGraphicsPipeline::Triangles);
415 if (!m_pipeline->create()) {
416 spdlog::error(
"[RHIImageItemRenderer::createPipeline]: Failed to create graphics pipeline");
420 spdlog::debug(
"[RHIImageItemRenderer::createPipeline]: Pipeline created successfully");
423void RHIImageItemRenderer::updateTextureFromImage(
const Core::Common::ImageRegion& image)
425 const int w =
static_cast<int>(image.width());
426 const int h =
static_cast<int>(image.height());
428 spdlog::debug(
"[RHIImageItemRenderer::updateTextureFromImage]: Converting {}x{} (channels: {})",
429 w, h, image.channels());
432 m_pixel_data.resize(
static_cast<size_t>(w) *
static_cast<size_t>(h) * 4);
433 m_pixel_data_size = QSize(w, h);
436 const float* src { image.getBuffer().data() };
437 uint8_t* dst { m_pixel_data.data() };
438 const size_t total_pixels {
static_cast<size_t>(w) *
static_cast<size_t>(h) };
440 for (
size_t i = 0; i < total_pixels; ++i)
442 const size_t idx = i * 4;
445 dst[idx + 0] =
static_cast<uint8_t
>(std::clamp(src[idx + 0], 0.0f, 1.0f) * 255.0f);
446 dst[idx + 1] =
static_cast<uint8_t
>(std::clamp(src[idx + 1], 0.0f, 1.0f) * 255.0f);
447 dst[idx + 2] =
static_cast<uint8_t
>(std::clamp(src[idx + 2], 0.0f, 1.0f) * 255.0f);
448 dst[idx + 3] =
static_cast<uint8_t
>(std::clamp(src[idx + 3], 0.0f, 1.0f) * 255.0f);
452 if (!m_texture || m_texture->pixelSize() != m_pixel_data_size)
454 m_texture.reset(rhi()->newTexture(QRhiTexture::RGBA8, m_pixel_data_size));
456 if (!m_texture->create()) {
457 spdlog::error(
"[RHIImageItemRenderer::updateTextureFromImage]: Failed to create texture {}x{}",
462 spdlog::debug(
"[RHIImageItemRenderer::updateTextureFromImage]: Texture created {}x{}", w, h);
465 m_srb.reset(rhi()->newShaderResourceBindings());
467 QRhiShaderResourceBinding bindings[] = {
468 QRhiShaderResourceBinding::uniformBuffer(
470 QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage,
471 m_uniform_buffer.get()
473 QRhiShaderResourceBinding::sampledTexture(
475 QRhiShaderResourceBinding::FragmentStage,
481 m_srb->setBindings(bindings, bindings + 2);
483 if (!m_srb->create()) {
484 spdlog::error(
"[RHIImageItemRenderer::updateTextureFromImage]: Failed to recreate SRB");
Modern QQuickItem that renders an image via RHI (Vulkan/Metal/DX12/OpenGL) using QQuickRhiItem.
void render(QRhiCommandBuffer *cb) override
Performs the actual GPU rendering.
void synchronize(QQuickRhiItem *item) override
Synchronizes data from the GUI thread to the render thread.
void initialize(QRhiCommandBuffer *cb) override
Initializes all GPU resources when the RHI context is ready.
RHIImageItemRenderer(RHIImageItem *item)
Constructs the renderer with a reference to its parent item.
~RHIImageItemRenderer() override
Destructor releasing all GPU resources.
Defines the ImageRegion structure for representing pixel buffers in the CaptureMoment Core library.
@ blend
Blend clipped areas with surrounding unclipped data. Natural-looking highlights.
Namespace containing Qt-specific UI components for CaptureMoment.
Modern RHI-based image display (Qt6 - Vulkan/Metal/DX12 compatible) using QQuickRhiItem.
RHI renderer implementation for hardware-accelerated image display.