Skip to content

Commit 8adb3de

Browse files
feat(multi-render-order): add support for multi render order
- Add support for multi render order - Add support for dynamic render order
1 parent 5ac19f1 commit 8adb3de

14 files changed

Lines changed: 392 additions & 83 deletions

‎Docs/extension_architecture.md‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,11 @@ use this strategy to render on top of other elements.
9090
In the extension, the renderers are not manipulated directly by developers in their pipelines but automatically managed
9191
by the LayerManager class.
9292

93-
From a developer standpoint, only the `GetRenderOrder` value needs to be returned. This value is a static value read
94-
when new pipelines are added / removed from the pipeline manager.
93+
From a developer standpoint, only the `GetRenderOrder` (or `GetRenderOrders` when designing DM with multi layers) value
94+
needs to be returned. This value is read when new pipelines are added / removed from the pipeline manager.
95+
96+
If the grouping configuration (render order or custom cameras) changes dynamically, the pipeline should emit the
97+
`RenderGroupingModified` event.
9598

9699
Pipelines with the same GetRenderOrder and the same GetCustomCamera will be grouped in the same renderer layer. If the
97100
value is set to 0, the pipelines will be set to the default renderer layer.

‎Docs/getting_started.md‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,18 @@ class MyPipeline(vtkMRMLLayerDMScriptedPipeline):
9191

9292
## Rendering on top of other actors
9393

94-
Pipelines can define their rendering order through the `GetRenderOrder` method.
94+
Pipelines can define their rendering order through the `GetRenderOrder` or `GetRenderOrders` method(s).
9595

9696
* `virtual unsigned int GetRenderOrder() const`: Return render order priority (default = 0).
97+
* `virtual std::vector<unsigned int> GetRenderOrders() const`: Return render order priorities for each renderer layer
98+
the pipeline wants to use. This method is recommended when the pipelines wants to manage both background / foreground
99+
representations. The pipeline can return an arbitrary long list of RenderOrders (default = {GetRenderOrder()}).
97100

98101
If the value 0 is used, then the objects will be rendered in the default view renderer along builtin 3D Slicer objects.
99102

103+
If the grouping configuration (render order or custom cameras) changes dynamically, the pipeline should emit the
104+
`RenderGroupingModified` event.
105+
100106
```python
101107
class MyPipeline(vtkMRMLLayerDMScriptedPipeline):
102108
def GetRenderOrder(self) -> int:

‎LayerDM/MRMLDM/Python/vtkMRMLLayerDMScriptedPipeline.py‎

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,11 @@ def CanProcessInteractionEvent(self, eventData: vtkMRMLInteractionEventData) ->
4545

4646
return False, sys.float_info.max
4747

48-
def GetCustomCamera(self) -> vtkCamera | None:
48+
def GetCustomCamera(self, renderOrder: int) -> vtkCamera | None:
4949
"""
50-
Custom pipeline camera.
51-
If the returned value is not None, then the pipeline (or dedicated logic) is expected to handle its own camera.
50+
Custom pipeline camera for the input renderOrder.
51+
If the returned value is not None, then the pipeline (or dedicated logic) is expected to handle its own camera
52+
for the given input render order.
5253
Otherwise, the pipeline will be moved in a renderer with a default camera synchronized on its view default camera.
5354
5455
:return: None by default
@@ -74,6 +75,16 @@ def GetRenderOrder(self) -> int:
7475
"""
7576
return 0
7677

78+
def GetRenderOrders(self) -> list[int]:
79+
"""
80+
Arbitrary render orders number where the pipeline wants to be displayed.
81+
Allows the pipeline to be displayed in more than one renderer (for instance in the background / foreground).
82+
Return larger values to be rendered on top of pipelines with lower render orders.
83+
84+
:return: default = [self.GetRenderOrder()]
85+
"""
86+
return [self.GetRenderOrder()]
87+
7788
def GetWidgetState(self) -> int:
7889
"""
7990
Current widget state of the pipeline.
@@ -98,7 +109,7 @@ def OnDefaultCameraModified(self, camera: vtkCamera) -> None:
98109
"""
99110
pass
100111

101-
def OnReferenceToDisplayNodeAdded(self, fromNode: vtkMRMLNode | None, role:str) -> None:
112+
def OnReferenceToDisplayNodeAdded(self, fromNode: vtkMRMLNode | None, role: str) -> None:
102113
"""
103114
Triggered when a reference to the display node is added
104115
default behavior: does nothing.
@@ -109,7 +120,7 @@ def OnReferenceToDisplayNodeAdded(self, fromNode: vtkMRMLNode | None, role:str)
109120
"""
110121
pass
111122

112-
def OnReferenceToDisplayNodeRemoved(self, fromNode: vtkMRMLNode | None, role:str) -> None:
123+
def OnReferenceToDisplayNodeRemoved(self, fromNode: vtkMRMLNode | None, role: str) -> None:
113124
"""
114125
Triggered when a reference to the display node is removed
115126
default behavior: does nothing.

‎LayerDM/MRMLDM/vtkMRMLLayerDMInteractionLogic.cxx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ std::tuple<double, int> vtkMRMLLayerDMInteractionLogic::PrioritizeCanProcessPipe
7676
int widgetState = std::max(this->MinWidgetState(), pipeline->GetWidgetState());
7777
minDistance = std::min(minDistance, pipelineDistance);
7878
maxState = std::max(widgetState, maxState);
79-
priority[pipeline] = std::make_tuple(widgetState, pipeline->GetRenderOrder(), -pipelineDistance);
79+
priority[pipeline] = std::make_tuple(widgetState, pipeline->GetMaxRenderOrder(), -pipelineDistance);
8080
}
8181
}
8282
// Sort can process by layer order and inverted square distance (larger layer number first and closest to interaction)

‎LayerDM/MRMLDM/vtkMRMLLayerDMLayerManager.cxx‎

Lines changed: 53 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "vtkMRMLLayerDMLayerManager.h"
22

33
// Layer DM includes
4+
#include "vtkMRMLLayerDMObjectEventObserver.h"
45
#include "vtkMRMLLayerDMPipelineI.h"
56

67
// VTK includes
@@ -13,29 +14,35 @@
1314

1415
vtkStandardNewMacro(vtkMRMLLayerDMLayerManager);
1516

16-
void vtkMRMLLayerDMLayerManager::AddPipeline(vtkMRMLLayerDMPipelineI* pipeline)
17+
bool vtkMRMLLayerDMLayerManager::AddPipelineLayers(vtkMRMLLayerDMPipelineI* pipeline)
1718
{
1819
if (!pipeline)
1920
{
20-
return;
21+
return false;
2122
}
2223

23-
auto key = this->GetPipelineLayerKey(pipeline);
24-
if (!this->ContainsLayerKey(key))
24+
for (const auto order : pipeline->GetRenderOrders())
2525
{
26-
this->m_pipelineLayers[key] = {};
26+
auto key = std::make_tuple(order, GetCameraId(pipeline->GetCustomCamera(order)));
27+
if (!this->ContainsLayerKey(key))
28+
{
29+
this->m_pipelineLayers[key] = {};
30+
}
31+
this->m_pipelineLayers[key].emplace(pipeline);
2732
}
28-
this->m_pipelineLayers[key].emplace(pipeline);
29-
this->UpdateLayers();
33+
return true;
3034
}
3135

32-
vtkMRMLLayerDMLayerManager::LayerKey vtkMRMLLayerDMLayerManager::GetPipelineLayerKey(vtkMRMLLayerDMPipelineI* pipeline)
36+
void vtkMRMLLayerDMLayerManager::AddPipeline(vtkMRMLLayerDMPipelineI* pipeline)
3337
{
3438
if (!pipeline)
3539
{
36-
return {};
40+
return;
3741
}
38-
return { pipeline->GetRenderOrder(), vtkMRMLLayerDMLayerManager::GetCameraId(pipeline->GetCustomCamera()) };
42+
43+
this->m_obs->UpdateObserver(nullptr, pipeline, vtkMRMLLayerDMPipelineI::RenderGroupingModified);
44+
this->AddPipelineLayers(pipeline);
45+
this->UpdateLayers();
3946
}
4047

4148
int vtkMRMLLayerDMLayerManager::GetNumberOfDistinctLayers() const
@@ -53,24 +60,24 @@ int vtkMRMLLayerDMLayerManager::GetNumberOfRenderers() const
5360
return static_cast<int>(this->m_renderers.size());
5461
}
5562

56-
void vtkMRMLLayerDMLayerManager::RemovePipeline(vtkMRMLLayerDMPipelineI* pipeline)
63+
void vtkMRMLLayerDMLayerManager::RemovePipelineLayers(vtkMRMLLayerDMPipelineI* pipeline)
5764
{
58-
if (!pipeline)
65+
for (auto& [key, pipelines] : m_pipelineLayers)
5966
{
60-
return;
67+
pipelines.erase(pipeline);
6168
}
69+
}
6270

63-
auto key = this->GetPipelineLayerKey(pipeline);
64-
if (!this->ContainsLayerKey(key))
71+
void vtkMRMLLayerDMLayerManager::RemovePipeline(vtkMRMLLayerDMPipelineI* pipeline)
72+
{
73+
if (!pipeline)
6574
{
6675
return;
6776
}
6877

69-
// Remove pipeline from its renderer
78+
this->m_obs->UpdateObserver(pipeline, nullptr);
7079
this->RemovePipelineRenderer(pipeline);
71-
72-
// Update the other pipeline layers if needed
73-
this->m_pipelineLayers[key].erase(pipeline);
80+
this->RemovePipelineLayers(pipeline);
7481
this->UpdateLayers();
7582
}
7683

@@ -114,7 +121,18 @@ void vtkMRMLLayerDMLayerManager::SetDefaultCamera(const vtkSmartPointer<vtkCamer
114121

115122
vtkMRMLLayerDMLayerManager::vtkMRMLLayerDMLayerManager()
116123
: m_emptyPipeline(vtkSmartPointer<vtkMRMLLayerDMPipelineI>::New())
124+
, m_obs(vtkSmartPointer<vtkMRMLLayerDMObjectEventObserver>::New())
117125
{
126+
this->m_obs->SetUpdateCallback(
127+
[this](vtkObject* obj)
128+
{
129+
if (auto pipeline = vtkMRMLLayerDMPipelineI::SafeDownCast(obj))
130+
{
131+
this->RemovePipelineLayers(pipeline);
132+
this->AddPipelineLayers(pipeline);
133+
this->UpdateLayers();
134+
}
135+
});
118136
this->AddPipeline(this->m_emptyPipeline);
119137
}
120138

@@ -201,7 +219,7 @@ vtkCamera* vtkMRMLLayerDMLayerManager::GetCameraForLayer(const LayerKey& key, co
201219
{
202220
if (pipeline)
203221
{
204-
return pipeline->GetCustomCamera();
222+
return pipeline->GetCustomCamera(std::get<0>(key));
205223
}
206224
}
207225

@@ -248,7 +266,7 @@ void vtkMRMLLayerDMLayerManager::RemovePipelineRenderer(vtkMRMLLayerDMPipelineI*
248266
{
249267
if (pipeline)
250268
{
251-
pipeline->SetRenderer(nullptr);
269+
pipeline->SetRenderers({}, {});
252270
}
253271
}
254272

@@ -304,17 +322,27 @@ void vtkMRMLLayerDMLayerManager::ResetRenderersCameraClippingRange(const std::se
304322

305323
void vtkMRMLLayerDMLayerManager::SynchronizePipelineRenderers()
306324
{
307-
for (const auto& pair : m_pipelineLayers)
325+
std::map<vtkMRMLLayerDMPipelineI*, std::vector<vtkRenderer*>> pipelineRenderers;
326+
std::map<vtkMRMLLayerDMPipelineI*, std::vector<unsigned int>> pipelineOrders;
327+
328+
for (const auto& [key, pipelines] : m_pipelineLayers)
308329
{
309-
auto renderer = this->GetRendererMatchingKey(pair.first);
310-
for (const auto& pipeline : pair.second)
330+
auto renderer = this->GetRendererMatchingKey(key);
331+
auto order = std::get<0>(key);
332+
for (const auto& pipeline : pipelines)
311333
{
312334
if (pipeline)
313335
{
314-
pipeline->SetRenderer(renderer);
336+
pipelineRenderers[pipeline.GetPointer()].push_back(renderer);
337+
pipelineOrders[pipeline.GetPointer()].push_back(order);
315338
}
316339
}
317340
}
341+
342+
for (auto& [pipeline, renderers] : pipelineRenderers)
343+
{
344+
pipeline->SetRenderers(renderers, pipelineOrders[pipeline]);
345+
}
318346
}
319347

320348
void vtkMRMLLayerDMLayerManager::UpdateRenderWindowNumberOfLayers() const

‎LayerDM/MRMLDM/vtkMRMLLayerDMLayerManager.h‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class vtkMRMLLayerDMPipelineI;
2020
class vtkRenderWindow;
2121
class vtkRenderer;
2222
class vtkCamera;
23+
class vtkMRMLLayerDMObjectEventObserver;
2324

2425
/// \brief Responsible for adding and removing renderer layers to a vtkRenderWindow depending on the
2526
/// display pipeline preferred render order value.
@@ -45,8 +46,6 @@ class VTK_SLICER_LAYERDM_MODULE_MRMLDISPLAYABLEMANAGER_EXPORT vtkMRMLLayerDMLaye
4546
/// Will trigger the SetRenderer call on the pipeline when it's added to its layer.
4647
void AddPipeline(vtkMRMLLayerDMPipelineI* pipeline);
4748

48-
static LayerKey GetPipelineLayerKey(vtkMRMLLayerDMPipelineI* pipeline);
49-
5049
int GetNumberOfDistinctLayers() const;
5150
int GetNumberOfManagedLayers() const;
5251

@@ -94,9 +93,15 @@ class VTK_SLICER_LAYERDM_MODULE_MRMLDISPLAYABLEMANAGER_EXPORT vtkMRMLLayerDMLaye
9493
void UpdateRendererLayerOrdering() const;
9594
void UpdateRendererCamera();
9695

96+
bool AddPipelineLayers(vtkMRMLLayerDMPipelineI* pipeline);
97+
void RemovePipelineLayers(vtkMRMLLayerDMPipelineI* pipeline);
98+
9799
// Map of pipeline layers ordered by ascending <layer value, camera synchronization mode>
98100
std::map<LayerKey, std::set<vtkWeakPointer<vtkMRMLLayerDMPipelineI>>> m_pipelineLayers;
99101

102+
/// Pipeline observer listening for \sa vtkMRMLLayerDMPipelineI::RenderGroupingModified events.
103+
vtkSmartPointer<vtkMRMLLayerDMObjectEventObserver> m_obs;
104+
100105
// Placeholder empty pipeline with target layer = 0 and camera sync to layer 0 for default renderer
101106
vtkSmartPointer<vtkMRMLLayerDMPipelineI> m_emptyPipeline;
102107

0 commit comments

Comments
 (0)