v2.0.0
Loading...
Searching...
No Matches
brainrenderer.cpp
Go to the documentation of this file.
1//=============================================================================================================
36
37//=============================================================================================================
38// INCLUDES
39//=============================================================================================================
40
41#include "brainrenderer.h"
42
43#include <rhi/qrhi.h>
47
48#include <QFile>
49#include <QDebug>
50#include <map>
51
52//=============================================================================================================
53// PIMPL
54//=============================================================================================================
55
57{
58 void createResources(QRhi *rhi, QRhiRenderPassDescriptor *rp, int sampleCount);
59
60 std::unique_ptr<QRhiShaderResourceBindings> srb;
61
62 // Pipelines for each mode
63 std::map<ShaderMode, std::unique_ptr<QRhiGraphicsPipeline>> pipelines;
64 std::map<ShaderMode, std::unique_ptr<QRhiGraphicsPipeline>> pipelinesBackColor; // For Holographic back faces
65
66 std::unique_ptr<QRhiBuffer> uniformBuffer;
69
70 bool resourcesDirty = true;
71};
72
73//=============================================================================================================
74// Helpers
75//=============================================================================================================
76
77static inline QRhiViewport toViewport(const BrainRenderer::SceneData &d)
78{
79 return QRhiViewport(d.viewportX, d.viewportY, d.viewportW, d.viewportH);
80}
81
82static inline QRhiScissor toScissor(const BrainRenderer::SceneData &d)
83{
84 return QRhiScissor(d.scissorX, d.scissorY, d.scissorW, d.scissorH);
85}
86
87//=============================================================================================================
88// Uniform buffer layout constants — single source of truth for shader ↔ C++ interface
89//=============================================================================================================
90
91namespace {
92 // Uniform buffer sizing
93 constexpr int kUniformSlotCount = 8192; // Max draw calls before overflow
94 constexpr int kUniformBlockSize = 256; // Bound size per SRB dynamic slot (bytes)
95
96 // Per-object uniform byte offsets (must match .vert shader layout)
97 constexpr int kOffsetMVP = 0; // mat4 (64 bytes)
98 constexpr int kOffsetCameraPos = 64; // vec3 (12 bytes)
99 constexpr int kOffsetSelected = 76; // float
100 constexpr int kOffsetLightDir = 80; // vec3 (12 bytes)
101 constexpr int kOffsetTissueType = 92; // float
102 constexpr int kOffsetLighting = 96; // float
103 constexpr int kOffsetOverlayMode = 100; // float
104}
105
106//=============================================================================================================
107// DEFINE MEMBER METHODS
108//=============================================================================================================
109
110//=============================================================================================================
111
113 : d(std::make_unique<Impl>())
114{
115}
116
117//=============================================================================================================
118
120
121//=============================================================================================================
122
123void BrainRenderer::initialize(QRhi *rhi, QRhiRenderPassDescriptor *rp, int sampleCount)
124{
125 if (d->resourcesDirty) {
126 d->createResources(rhi, rp, sampleCount);
127 }
128}
129
130//=============================================================================================================
131
132void BrainRenderer::Impl::createResources(QRhi *rhi, QRhiRenderPassDescriptor *rp, int sampleCount)
133{
134 uniformBufferOffsetAlignment = rhi->ubufAlignment();
135
136 // Create Uniform Buffer
137 if (!uniformBuffer) {
138 // Size for 8192 slots with alignment — enough for 4 viewports × ~1000 surfaces
139 uniformBuffer.reset(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, kUniformSlotCount * uniformBufferOffsetAlignment));
140 uniformBuffer->create();
141 }
142
143 // Create SRB
144 if (!srb) {
145 srb.reset(rhi->newShaderResourceBindings());
146 srb->setBindings({
147 // Use dynamic offset for the uniform buffer.
148 // The size of one uniform block in the shader is ~104 bytes,
149 // but we use uniformBufferOffsetAlignment for the stride.
150 QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, uniformBuffer.get(),kUniformBlockSize)
151 });
152 srb->create();
153 }
154
155 // Shader Loader
156 auto getShader = [](const QString &name) {
157 QFile f(name);
158 return f.open(QIODevice::ReadOnly) ? QShader::fromSerialized(f.readAll()) : QShader();
159 };
160
161 // List of modes to initialize
162 QList<ShaderMode> modes = {Standard, Holographic, Anatomical, Dipole, XRay, ShowNormals};
163
164 for (ShaderMode mode : modes) {
165 QString vert = (mode == Holographic || mode == XRay) ? ":/holographic.vert.qsb" :
166 (mode == Anatomical) ? ":/anatomical.vert.qsb" :
167 (mode == Dipole) ? ":/dipole.vert.qsb" :
168 (mode == ShowNormals) ? ":/shownormals.vert.qsb" : ":/standard.vert.qsb";
169
170 QString frag = (mode == Holographic || mode == XRay) ? ":/holographic.frag.qsb" :
171 (mode == Anatomical) ? ":/anatomical.frag.qsb" :
172 (mode == Dipole) ? ":/dipole.frag.qsb" :
173 (mode == ShowNormals) ? ":/shownormals.frag.qsb" : ":/standard.frag.qsb";
174
175 QShader vS = getShader(vert);
176 QShader fS = getShader(frag);
177
178 if (!vS.isValid() || !fS.isValid()) {
179 qWarning() << "BrainRenderer: Could not load shaders for mode" << mode << vert << frag;
180 continue;
181 }
182
183 // Setup Pipeline
184 auto pipeline = std::unique_ptr<QRhiGraphicsPipeline>(rhi->newGraphicsPipeline());
185
186 QRhiGraphicsPipeline::TargetBlend blend;
187 if (mode == Holographic || mode == XRay) {
188 blend.enable = true;
189 blend.srcColor = QRhiGraphicsPipeline::SrcAlpha;
190 blend.dstColor = QRhiGraphicsPipeline::One;
191 blend.srcAlpha = QRhiGraphicsPipeline::SrcAlpha;
192 blend.dstAlpha = QRhiGraphicsPipeline::One;
193 } else if (mode == Dipole) {
194 blend.enable = true;
195 blend.srcColor = QRhiGraphicsPipeline::SrcAlpha;
196 blend.dstColor = QRhiGraphicsPipeline::OneMinusSrcAlpha;
197 blend.srcAlpha = QRhiGraphicsPipeline::SrcAlpha;
198 blend.dstAlpha = QRhiGraphicsPipeline::OneMinusSrcAlpha;
199 }
200
201 auto setup = [&](QRhiGraphicsPipeline* p, QRhiGraphicsPipeline::CullMode cull) {
202 p->setShaderStages({{ QRhiShaderStage::Vertex, vS }, { QRhiShaderStage::Fragment, fS }});
203
204 QRhiVertexInputLayout il;
205
206 if (mode == Dipole) {
207 il.setBindings({
208 { 6 * sizeof(float) }, // Binding 0: Vertex Data (Pos + Normal) -> stride 6 floats
209 { 21 * sizeof(float), QRhiVertexInputBinding::PerInstance } // Binding 1: Instance Data (Mat4 + Color + Selected) -> stride 21 floats
210 });
211
212 il.setAttributes({
213 // Vertex Buffer (Binding 0)
214 { 0, 0, QRhiVertexInputAttribute::Float3, 0 }, // Pos
215 { 0, 1, QRhiVertexInputAttribute::Float3, 3 * sizeof(float) }, // Normal
216
217 // Instance Buffer (Binding 1)
218 // Model Matrix (4 x vec4)
219 { 1, 2, QRhiVertexInputAttribute::Float4, 0 },
220 { 1, 3, QRhiVertexInputAttribute::Float4, 4 * sizeof(float) },
221 { 1, 4, QRhiVertexInputAttribute::Float4, 8 * sizeof(float) },
222 { 1, 5, QRhiVertexInputAttribute::Float4, 12 * sizeof(float) },
223 // Color
224 { 1, 6, QRhiVertexInputAttribute::Float4, 16 * sizeof(float) },
225 // isSelected
226 { 1, 7, QRhiVertexInputAttribute::Float, 20 * sizeof(float) }
227 });
228 } else {
229 il.setBindings({{ 32 }});
230 il.setAttributes({{ 0, 0, QRhiVertexInputAttribute::Float3, 0 },
231 { 0, 1, QRhiVertexInputAttribute::Float3, 12 },
232 { 0, 2, QRhiVertexInputAttribute::UNormByte4, 24 },
233 { 0, 3, QRhiVertexInputAttribute::UNormByte4, 28 }});
234 }
235
236 p->setVertexInputLayout(il);
237 p->setShaderResourceBindings(srb.get());
238 p->setRenderPassDescriptor(rp);
239 p->setSampleCount(sampleCount);
240 p->setCullMode(cull);
241 if (mode == Holographic) {
242 p->setTargetBlends({blend});
243 p->setDepthTest(true);
244 p->setDepthWrite(false);
245 } else if (mode == XRay) {
246 p->setTargetBlends({blend});
247 p->setDepthTest(false); // Disable Depth Test to see through head
248 p->setDepthWrite(false);
249 } else if (mode == Dipole) {
250 p->setTargetBlends({blend});
251 p->setCullMode(QRhiGraphicsPipeline::None);
252 p->setDepthTest(true);
253 p->setDepthWrite(false);
254 } else {
255 p->setDepthTest(true);
256 p->setDepthWrite(true);
257 }
258 p->setFlags(QRhiGraphicsPipeline::UsesScissor);
259 p->create();
260 };
261
262 if (mode == Holographic || mode == XRay) { // Handle XRay back-faces same as Holographic
263 auto pipelineBack = std::unique_ptr<QRhiGraphicsPipeline>(rhi->newGraphicsPipeline());
264 setup(pipelineBack.get(), QRhiGraphicsPipeline::Front);
265 pipelinesBackColor[mode] = std::move(pipelineBack);
266 setup(pipeline.get(), QRhiGraphicsPipeline::Back); // Front faces
267 } else {
268 // Culling: None (Double-sided) to be safe for FreeSurfer meshes
269 setup(pipeline.get(), QRhiGraphicsPipeline::None);
270 }
271 pipelines[mode] = std::move(pipeline);
272 }
273
274 resourcesDirty = false;
275}
276
277//=============================================================================================================
278
279void BrainRenderer::beginFrame(QRhiCommandBuffer *cb, QRhiRenderTarget *rt)
280{
281 d->currentUniformOffset = 0;
282 cb->beginPass(rt, QColor(0, 0, 0), { 1.0f, 0 });
283 const int w = rt->pixelSize().width();
284 const int h = rt->pixelSize().height();
285 cb->setViewport(QRhiViewport(0, 0, w, h));
286 cb->setScissor(QRhiScissor(0, 0, w, h));
287}
288
289//=============================================================================================================
290
292{
293 // NO-OP: packed into per-object slots for simplicity
294}
295
296//=============================================================================================================
297
298void BrainRenderer::endFrame(QRhiCommandBuffer *cb)
299{
300 cb->endPass();
301}
302
303//=============================================================================================================
304
305void BrainRenderer::renderSurface(QRhiCommandBuffer *cb, QRhi *rhi, const SceneData &data, BrainSurface *surface, ShaderMode mode)
306{
307 if (!surface || !surface->isVisible()) return;
308
309 // Check if pipeline for this mode exists
310 if (d->pipelines.find(mode) == d->pipelines.end()) return;
311
312 auto *pipeline = d->pipelines[mode].get();
313 if (!pipeline) return;
314
315 QRhiResourceUpdateBatch *u = rhi->nextResourceUpdateBatch();
316 surface->updateBuffers(rhi, u);
317
318 // Dynamic slot update
319 int offset = d->currentUniformOffset;
320 d->currentUniformOffset += d->uniformBufferOffsetAlignment;
321 if (d->currentUniformOffset >= d->uniformBuffer->size()) {
322 qWarning("BrainRenderer: uniform buffer overflow (%d / %d bytes) — too many surfaces. Some draws will be skipped.",
323 d->currentUniformOffset, (int)d->uniformBuffer->size());
324 return; // Skip this draw rather than silently corrupt earlier viewport data
325 }
326
327 u->updateDynamicBuffer(d->uniformBuffer.get(), offset + kOffsetMVP, 64, data.mvp.constData());
328 u->updateDynamicBuffer(d->uniformBuffer.get(), offset + kOffsetCameraPos, 12, &data.cameraPos);
329
330 float selected = surface->isSelected() ? 1.0f : 0.0f;
331 u->updateDynamicBuffer(d->uniformBuffer.get(), offset + kOffsetSelected, 4, &selected);
332
333 u->updateDynamicBuffer(d->uniformBuffer.get(), offset + kOffsetLightDir, 12, &data.lightDir);
334
335 // Pass tissue type for anatomical shader (offset 92, uses original _pad2 slot)
336 float tissueType = static_cast<float>(surface->tissueType());
337 u->updateDynamicBuffer(d->uniformBuffer.get(), offset + kOffsetTissueType, 4, &tissueType);
338
339 float lighting = data.lightingEnabled ? 1.0f : 0.0f;
340 u->updateDynamicBuffer(d->uniformBuffer.get(), offset + kOffsetLighting, 4, &lighting);
341
342 float overlayMode = data.overlayMode;
343 u->updateDynamicBuffer(d->uniformBuffer.get(), offset + kOffsetOverlayMode, 4, &overlayMode);
344
345 cb->resourceUpdate(u);
346
347 // Re-assert the per-pane viewport and scissor after resourceUpdate.
348 // The scissor provides a hard pixel clip that guarantees no cross-pane
349 // bleeding, regardless of Metal render-encoder restarts.
350 cb->setViewport(toViewport(data));
351 cb->setScissor(toScissor(data));
352
353 auto draw = [&](QRhiGraphicsPipeline *p) {
354 cb->setGraphicsPipeline(p);
355
356 const QRhiCommandBuffer::DynamicOffset srbOffset = { 0, uint32_t(offset) };
357 cb->setShaderResources(d->srb.get(), 1, &srbOffset);
358 const QRhiCommandBuffer::VertexInput vbuf(surface->vertexBuffer(), 0);
359 cb->setVertexInput(0, 1, &vbuf, surface->indexBuffer(), 0, QRhiCommandBuffer::IndexUInt32);
360 cb->drawIndexed(surface->indexCount());
361 };
362
363 if (mode == Holographic && d->pipelinesBackColor.count(Holographic) > 0) {
364 draw(d->pipelinesBackColor[Holographic].get());
365 }
366
367 draw(pipeline);
368}
369
370//=============================================================================================================
371
372void BrainRenderer::renderDipoles(QRhiCommandBuffer *cb, QRhi *rhi, const SceneData &data, DipoleObject *dipoles)
373{
374 if (!dipoles || !dipoles->isVisible() || dipoles->instanceCount() == 0) return;
375
376 if (d->pipelines.find(Dipole) == d->pipelines.end()) return;
377
378 auto *pipeline = d->pipelines[Dipole].get();
379 if (!pipeline) return;
380
381 QRhiResourceUpdateBatch *u = rhi->nextResourceUpdateBatch();
382 dipoles->updateBuffers(rhi, u);
383
384 // Dynamic slot update
385 int offset = d->currentUniformOffset;
386 d->currentUniformOffset += d->uniformBufferOffsetAlignment;
387 if (d->currentUniformOffset >= d->uniformBuffer->size()) d->currentUniformOffset = 0;
388
389 u->updateDynamicBuffer(d->uniformBuffer.get(), offset + kOffsetMVP, 64, data.mvp.constData());
390 u->updateDynamicBuffer(d->uniformBuffer.get(), offset + kOffsetCameraPos, 12, &data.cameraPos);
391 u->updateDynamicBuffer(d->uniformBuffer.get(), offset + kOffsetLightDir, 12, &data.lightDir);
392 float lighting = data.lightingEnabled ? 1.0f : 0.0f;
393 u->updateDynamicBuffer(d->uniformBuffer.get(), offset + kOffsetLighting, 4, &lighting);
394
395 cb->resourceUpdate(u);
396
397 // Re-assert the per-pane viewport and scissor.
398 cb->setViewport(toViewport(data));
399 cb->setScissor(toScissor(data));
400
401 cb->setGraphicsPipeline(pipeline);
402
403 const QRhiCommandBuffer::DynamicOffset srbOffset = { 0, uint32_t(offset) };
404 cb->setShaderResources(d->srb.get(), 1, &srbOffset);
405
406 const QRhiCommandBuffer::VertexInput bindings[2] = {
407 QRhiCommandBuffer::VertexInput(dipoles->vertexBuffer(), 0),
408 QRhiCommandBuffer::VertexInput(dipoles->instanceBuffer(), 0)
409 };
410
411 cb->setVertexInput(0, 2, bindings, dipoles->indexBuffer(), 0, QRhiCommandBuffer::IndexUInt32);
412
413 cb->drawIndexed(dipoles->indexCount(), dipoles->instanceCount());
414}
415
416//=============================================================================================================
417
418void BrainRenderer::renderNetwork(QRhiCommandBuffer *cb, QRhi *rhi, const SceneData &data, NetworkObject *network)
419{
420 if (!network || !network->isVisible() || !network->hasData()) return;
421
422 if (d->pipelines.find(Dipole) == d->pipelines.end()) return;
423
424 auto *pipeline = d->pipelines[Dipole].get();
425 if (!pipeline) return;
426
427 // --- Render Nodes (instanced spheres) ---
428 if (network->nodeInstanceCount() > 0) {
429 QRhiResourceUpdateBatch *uNodes = rhi->nextResourceUpdateBatch();
430 network->updateNodeBuffers(rhi, uNodes);
431
432 int offset = d->currentUniformOffset;
433 d->currentUniformOffset += d->uniformBufferOffsetAlignment;
434 if (d->currentUniformOffset >= d->uniformBuffer->size()) {
435 qWarning("BrainRenderer: uniform buffer overflow in renderNetwork (nodes)");
436 return;
437 }
438
439 uNodes->updateDynamicBuffer(d->uniformBuffer.get(), offset + kOffsetMVP, 64, data.mvp.constData());
440 uNodes->updateDynamicBuffer(d->uniformBuffer.get(), offset + kOffsetCameraPos, 12, &data.cameraPos);
441 uNodes->updateDynamicBuffer(d->uniformBuffer.get(), offset + kOffsetLightDir, 12, &data.lightDir);
442 float lighting = data.lightingEnabled ? 1.0f : 0.0f;
443 uNodes->updateDynamicBuffer(d->uniformBuffer.get(), offset + kOffsetLighting, 4, &lighting);
444
445 cb->resourceUpdate(uNodes);
446 cb->setViewport(toViewport(data));
447 cb->setScissor(toScissor(data));
448
449 cb->setGraphicsPipeline(pipeline);
450
451 const QRhiCommandBuffer::DynamicOffset srbOffset = { 0, uint32_t(offset) };
452 cb->setShaderResources(d->srb.get(), 1, &srbOffset);
453
454 const QRhiCommandBuffer::VertexInput nodeBindings[2] = {
455 QRhiCommandBuffer::VertexInput(network->nodeVertexBuffer(), 0),
456 QRhiCommandBuffer::VertexInput(network->nodeInstanceBuffer(), 0)
457 };
458
459 cb->setVertexInput(0, 2, nodeBindings, network->nodeIndexBuffer(), 0, QRhiCommandBuffer::IndexUInt32);
460 cb->drawIndexed(network->nodeIndexCount(), network->nodeInstanceCount());
461 }
462
463 // --- Render Edges (instanced cylinders) ---
464 if (network->edgeInstanceCount() > 0) {
465 QRhiResourceUpdateBatch *uEdges = rhi->nextResourceUpdateBatch();
466 network->updateEdgeBuffers(rhi, uEdges);
467
468 int offset = d->currentUniformOffset;
469 d->currentUniformOffset += d->uniformBufferOffsetAlignment;
470 if (d->currentUniformOffset >= d->uniformBuffer->size()) {
471 qWarning("BrainRenderer: uniform buffer overflow in renderNetwork (edges)");
472 return;
473 }
474
475 uEdges->updateDynamicBuffer(d->uniformBuffer.get(), offset + kOffsetMVP, 64, data.mvp.constData());
476 uEdges->updateDynamicBuffer(d->uniformBuffer.get(), offset + kOffsetCameraPos, 12, &data.cameraPos);
477 uEdges->updateDynamicBuffer(d->uniformBuffer.get(), offset + kOffsetLightDir, 12, &data.lightDir);
478 float lighting = data.lightingEnabled ? 1.0f : 0.0f;
479 uEdges->updateDynamicBuffer(d->uniformBuffer.get(), offset + kOffsetLighting, 4, &lighting);
480
481 cb->resourceUpdate(uEdges);
482 cb->setViewport(toViewport(data));
483 cb->setScissor(toScissor(data));
484
485 cb->setGraphicsPipeline(pipeline);
486
487 const QRhiCommandBuffer::DynamicOffset srbOffset = { 0, uint32_t(offset) };
488 cb->setShaderResources(d->srb.get(), 1, &srbOffset);
489
490 const QRhiCommandBuffer::VertexInput edgeBindings[2] = {
491 QRhiCommandBuffer::VertexInput(network->edgeVertexBuffer(), 0),
492 QRhiCommandBuffer::VertexInput(network->edgeInstanceBuffer(), 0)
493 };
494
495 cb->setVertexInput(0, 2, edgeBindings, network->edgeIndexBuffer(), 0, QRhiCommandBuffer::IndexUInt32);
496 cb->drawIndexed(network->edgeIndexCount(), network->edgeInstanceCount());
497 }
498}
NetworkObject class declaration.
BrainSurface class declaration.
DipoleObject class declaration.
BrainRenderer class declaration.
Renderable cortical surface mesh with per-vertex color, curvature data, and GPU buffer management.
uint32_t indexCount() const
TissueType tissueType() const
QRhiBuffer * vertexBuffer() const
bool isSelected() const
QRhiBuffer * indexBuffer() const
void updateBuffers(QRhi *rhi, QRhiResourceUpdateBatch *u)
bool isVisible() const
Renderable dipole arrow set with instanced GPU rendering for QRhi.
QRhiBuffer * instanceBuffer() const
int instanceCount() const
void updateBuffers(QRhi *rhi, QRhiResourceUpdateBatch *u)
int indexCount() const
QRhiBuffer * indexBuffer() const
QRhiBuffer * vertexBuffer() const
bool isVisible() const
Renderable network visualization for QRhi.
QRhiBuffer * nodeIndexBuffer() const
int nodeInstanceCount() const
int edgeIndexCount() const
void updateNodeBuffers(QRhi *rhi, QRhiResourceUpdateBatch *u)
QRhiBuffer * edgeIndexBuffer() const
bool isVisible() const
QRhiBuffer * nodeInstanceBuffer() const
int edgeInstanceCount() const
bool hasData() const
QRhiBuffer * edgeVertexBuffer() const
int nodeIndexCount() const
QRhiBuffer * nodeVertexBuffer() const
void updateEdgeBuffers(QRhi *rhi, QRhiResourceUpdateBatch *u)
QRhiBuffer * edgeInstanceBuffer() const
std::unique_ptr< QRhiShaderResourceBindings > srb
std::unique_ptr< QRhiBuffer > uniformBuffer
std::map< ShaderMode, std::unique_ptr< QRhiGraphicsPipeline > > pipelinesBackColor
void createResources(QRhi *rhi, QRhiRenderPassDescriptor *rp, int sampleCount)
std::map< ShaderMode, std::unique_ptr< QRhiGraphicsPipeline > > pipelines
static constexpr ShaderMode Anatomical
static constexpr ShaderMode Holographic
void renderNetwork(QRhiCommandBuffer *cb, QRhi *rhi, const SceneData &data, NetworkObject *network)
::ShaderMode ShaderMode
static constexpr ShaderMode ShowNormals
void renderSurface(QRhiCommandBuffer *cb, QRhi *rhi, const SceneData &data, BrainSurface *surface, ShaderMode mode)
void beginFrame(QRhiCommandBuffer *cb, QRhiRenderTarget *rt)
void endFrame(QRhiCommandBuffer *cb)
void updateSceneUniforms(QRhi *rhi, const SceneData &data)
static constexpr ShaderMode Dipole
void initialize(QRhi *rhi, QRhiRenderPassDescriptor *rp, int sampleCount)
static constexpr ShaderMode Standard
void renderDipoles(QRhiCommandBuffer *cb, QRhi *rhi, const SceneData &data, DipoleObject *dipoles)
static constexpr ShaderMode XRay
Aggregated GPU resources and render state for the 3-D brain visualization scene.