Skip to content

Commit 41369e8

Browse files
ENH: Improve rendering updates
- Update pipelines on render window updates - Automatically request render on pipeline reset - Add utility function to take screenshots from RW to avoid unwanted behavior with existing VTK vtkWindowToImageFilter - Add blockers to request render only once for large pipeline updates - Add tests for camera clipping range updates
1 parent 17548f6 commit 41369e8

15 files changed

+462
-96
lines changed

LayerDM/MRMLDM/vtkMRMLLayerDMCameraSynchronizer.cxx

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,15 @@
1616
// STL includes
1717
#include <array>
1818

19+
/// \brief Abstract class for the camera strategies.
20+
/// Implements only the reset camera clipping range logic for the layer cameras.
21+
/// Other methods are expected to be implemented by deriving classes.
1922
class CameraSynchronizeStrategy
2023
{
2124
public:
22-
explicit CameraSynchronizeStrategy(const vtkSmartPointer<vtkCamera>& camera)
25+
explicit CameraSynchronizeStrategy(const vtkSmartPointer<vtkCamera>& camera, std::function<void()> invokeModifiedEvent)
2326
: m_camera(camera)
27+
, m_invokeModifiedEvent{ std::move(invokeModifiedEvent) }
2428
{
2529
}
2630
virtual ~CameraSynchronizeStrategy() = default;
@@ -29,26 +33,25 @@ class CameraSynchronizeStrategy
2933
protected:
3034
vtkSmartPointer<vtkCamera> m_camera;
3135
vtkNew<vtkMRMLLayerDMObjectEventObserver> m_eventObserver;
36+
std::function<void()> m_invokeModifiedEvent;
3237
};
3338

39+
/// Default camera synchronization consists in updating the camera when the first renderer active camera is updated.
3440
class DefaultCameraSynchronizeStrategy : public CameraSynchronizeStrategy
3541
{
3642
public:
37-
explicit DefaultCameraSynchronizeStrategy(const vtkSmartPointer<vtkCamera>& camera, vtkRenderer* renderer)
38-
: CameraSynchronizeStrategy(camera)
43+
explicit DefaultCameraSynchronizeStrategy(const vtkSmartPointer<vtkCamera>& camera, vtkRenderer* renderer, std::function<void()> invokeModifiedEvent)
44+
: CameraSynchronizeStrategy(camera, std::move(invokeModifiedEvent))
3945
, m_renderer(renderer)
4046
{
4147
this->m_eventObserver->SetUpdateCallback(
4248
[this](vtkObject* object)
4349
{
44-
if (object == this->m_observedCamera)
45-
{
46-
this->UpdateCamera();
47-
}
4850
if (object == this->m_renderer)
4951
{
5052
this->ObserveActiveCamera();
5153
}
54+
this->UpdateCamera();
5255
});
5356

5457
this->m_eventObserver->UpdateObserver(nullptr, this->m_renderer, vtkCommand::ActiveCameraEvent);
@@ -57,11 +60,17 @@ class DefaultCameraSynchronizeStrategy : public CameraSynchronizeStrategy
5760

5861
void UpdateCamera() override
5962
{
60-
if (this->m_observedCamera)
63+
if (!this->m_observedCamera)
6164
{
62-
this->m_camera->DeepCopy(this->m_observedCamera);
63-
this->m_camera->Modified();
65+
return;
6466
}
67+
68+
// Update camera and preserve clipping range
69+
double clippingRange[2];
70+
this->m_camera->GetClippingRange(clippingRange);
71+
this->m_camera->DeepCopy(this->m_observedCamera);
72+
this->m_camera->SetClippingRange(clippingRange);
73+
this->m_invokeModifiedEvent();
6574
}
6675

6776
private:
@@ -76,18 +85,26 @@ class DefaultCameraSynchronizeStrategy : public CameraSynchronizeStrategy
7685

7786
this->m_eventObserver->UpdateObserver(this->m_observedCamera, camera);
7887
this->m_observedCamera = camera;
79-
this->UpdateCamera();
8088
}
8189

8290
vtkWeakPointer<vtkRenderer> m_renderer;
8391
vtkWeakPointer<vtkCamera> m_observedCamera;
8492
};
8593

94+
/// Synchronizes the default camera to the current slice node view configuration.
95+
/// The Slice renderer 0 camera is not configured nor modified during camera changes.
96+
/// All of its actors are set to render in 2D.
97+
///
98+
/// To simplify adding new pipelines, the sync adjusts the default camera to render in parallel projection in the correct
99+
/// orientation with respect to the current node configuration.
100+
///
101+
/// Clipping range is configured to show all actors attached to the default camera.
102+
/// If clipping is required, then it should be done inside the specific pipeline.
86103
class SliceViewCameraSynchronizeStrategy : public CameraSynchronizeStrategy
87104
{
88105
public:
89-
explicit SliceViewCameraSynchronizeStrategy(const vtkSmartPointer<vtkCamera>& camera, vtkMRMLSliceNode* sliceNode)
90-
: CameraSynchronizeStrategy(camera)
106+
explicit SliceViewCameraSynchronizeStrategy(const vtkSmartPointer<vtkCamera>& camera, vtkMRMLSliceNode* sliceNode, std::function<void()> invokeModifiedEvent)
107+
: CameraSynchronizeStrategy(camera, std::move(invokeModifiedEvent))
91108
, m_sliceNode{ sliceNode }
92109
{
93110
this->m_eventObserver->SetUpdateCallback(
@@ -142,6 +159,7 @@ class SliceViewCameraSynchronizeStrategy : public CameraSynchronizeStrategy
142159
vtkMath::Cross(vRight.data(), vUp.data(), normal.data());
143160
double position[3] = { viewCenterRAS[0] + normal[0] * d, viewCenterRAS[1] + normal[1] * d, viewCenterRAS[2] + normal[2] * d };
144161
this->m_camera->SetPosition(position);
162+
this->m_invokeModifiedEvent();
145163
}
146164

147165
private:
@@ -199,13 +217,29 @@ void vtkMRMLLayerDMCameraSynchronizer::UpdateStrategy()
199217
return;
200218
}
201219

220+
const auto invokeModifiedEvent = [this]
221+
{
222+
if (this->m_isBlocked)
223+
{
224+
return;
225+
}
226+
this->Modified();
227+
};
228+
202229
if (auto sliceNode = vtkMRMLSliceNode::SafeDownCast(this->m_viewNode))
203230
{
204-
this->m_syncStrategy = std::make_unique<SliceViewCameraSynchronizeStrategy>(this->m_defaultCamera, sliceNode);
231+
this->m_syncStrategy = std::make_unique<SliceViewCameraSynchronizeStrategy>(this->m_defaultCamera, sliceNode, invokeModifiedEvent);
205232
}
206233
else
207234
{
208-
this->m_syncStrategy = std::make_unique<DefaultCameraSynchronizeStrategy>(this->m_defaultCamera, this->m_renderer);
235+
this->m_syncStrategy = std::make_unique<DefaultCameraSynchronizeStrategy>(this->m_defaultCamera, this->m_renderer, invokeModifiedEvent);
209236
}
210237
this->m_syncStrategy->UpdateCamera();
211238
}
239+
240+
bool vtkMRMLLayerDMCameraSynchronizer::BlockModified(bool isBlocked)
241+
{
242+
bool wasBlocked = this->m_isBlocked;
243+
m_isBlocked = isBlocked;
244+
return wasBlocked;
245+
}

LayerDM/MRMLDM/vtkMRMLLayerDMCameraSynchronizer.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ class VTK_SLICER_LAYERDM_MODULE_MRMLDISPLAYABLEMANAGER_EXPORT vtkMRMLLayerDMCame
3838
/// The renderer active camera will be monitored for change when applicable (for instance 3D views).
3939
void SetRenderer(vtkRenderer* renderer);
4040

41+
/// Blocks modified invocation. Doesn't block updates on the underlying camera changes.
42+
/// @return previous blocked state.
43+
bool BlockModified(bool isBlocked);
44+
4145
protected:
4246
vtkMRMLLayerDMCameraSynchronizer();
4347
~vtkMRMLLayerDMCameraSynchronizer() override;
@@ -50,4 +54,5 @@ class VTK_SLICER_LAYERDM_MODULE_MRMLDISPLAYABLEMANAGER_EXPORT vtkMRMLLayerDMCame
5054
vtkWeakPointer<vtkRenderer> m_renderer;
5155
vtkWeakPointer<vtkMRMLAbstractViewNode> m_viewNode;
5256
std::unique_ptr<CameraSynchronizeStrategy> m_syncStrategy;
57+
bool m_isBlocked{ false };
5358
};

LayerDM/MRMLDM/vtkMRMLLayerDMLayerManager.cxx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ void vtkMRMLLayerDMLayerManager::RemovePipeline(vtkMRMLLayerDMPipelineI* pipelin
7777
void vtkMRMLLayerDMLayerManager::ResetCameraClippingRange() const
7878
{
7979
// Reset first renderer clipping range
80-
if (auto defaultRenderer = this->GetDefaultRenderer())
80+
if (const auto defaultRenderer = this->GetDefaultRenderer())
8181
{
8282
defaultRenderer->ResetCameraClippingRange();
8383
}
@@ -107,6 +107,7 @@ void vtkMRMLLayerDMLayerManager::SetDefaultCamera(const vtkSmartPointer<vtkCamer
107107
{
108108
return;
109109
}
110+
110111
this->m_defaultCamera = camera;
111112
this->UpdateLayers();
112113
}
@@ -191,8 +192,7 @@ std::uintptr_t vtkMRMLLayerDMLayerManager::GetCameraId(vtkCamera* camera)
191192

192193
vtkCamera* vtkMRMLLayerDMLayerManager::GetCameraForLayer(const LayerKey& key, const std::set<vtkWeakPointer<vtkMRMLLayerDMPipelineI>>& pipelines) const
193194
{
194-
auto cameraId = std::get<1>(key);
195-
if (cameraId == 0)
195+
if (const auto cameraId = std::get<1>(key); cameraId == 0)
196196
{
197197
return this->m_defaultCamera;
198198
}
@@ -298,7 +298,7 @@ void vtkMRMLLayerDMLayerManager::ResetRenderersCameraClippingRange(const std::se
298298
{
299299
continue;
300300
}
301-
renderer->ResetCameraClippingRange(bounds.data());
301+
renderer->ResetCameraClippingRange(const_cast<double*>(bounds.data()));
302302
}
303303
}
304304

@@ -349,19 +349,19 @@ void vtkMRMLLayerDMLayerManager::UpdateLayers()
349349
this->RemoveOutdatedPipelines();
350350
this->RemoveOutdatedLayers();
351351
this->AddMissingLayers();
352-
this->UpdateRenderWindowLayerOrdering();
352+
this->UpdateRendererLayerOrdering();
353353
this->UpdateRendererCamera();
354354
this->SynchronizePipelineRenderers();
355+
this->UpdateRenderWindowNumberOfLayers();
355356
}
356357

357-
void vtkMRMLLayerDMLayerManager::UpdateRenderWindowLayerOrdering() const
358+
void vtkMRMLLayerDMLayerManager::UpdateRendererLayerOrdering() const
358359
{
359360
// Managed layers are always ordered from layer 1 to the number of managed renderers
360361
for (int iRenderer = 0; iRenderer < this->GetNumberOfRenderers(); iRenderer++)
361362
{
362363
this->m_renderers[iRenderer]->SetLayer(iRenderer + 1);
363364
}
364-
this->UpdateRenderWindowNumberOfLayers();
365365
}
366366

367367
void vtkMRMLLayerDMLayerManager::UpdateRendererCamera()

LayerDM/MRMLDM/vtkMRMLLayerDMLayerManager.h

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
// STL includes
1313
#include <array>
14-
#include <cstdint>
1514
#include <map>
1615
#include <set>
1716

@@ -39,14 +38,31 @@ class VTK_SLICER_LAYERDM_MODULE_MRMLDISPLAYABLEMANAGER_EXPORT vtkMRMLLayerDMLaye
3938
static vtkMRMLLayerDMLayerManager* New();
4039
vtkTypeMacro(vtkMRMLLayerDMLayerManager, vtkObject);
4140

41+
/// Adds the pipeline to the layers.
42+
/// May change an update of the layer ordering.
43+
/// Will trigger the SetRenderer call on the pipeline when it's added to its layer.
4244
void AddPipeline(vtkMRMLLayerDMPipelineI* pipeline);
45+
4346
static LayerKey GetPipelineLayerKey(vtkMRMLLayerDMPipelineI* pipeline);
47+
4448
int GetNumberOfDistinctLayers() const;
4549
int GetNumberOfManagedLayers() const;
50+
51+
/// Returns the current number of managed renderers in the render window.
4652
int GetNumberOfRenderers() const;
53+
54+
/// Removes the pipeline from the layers.
55+
/// May change the layer ordering if pipeline was the last one of its current renderer.
4756
void RemovePipeline(vtkMRMLLayerDMPipelineI* pipeline);
57+
58+
/// Iterates over the renderers and resets their clipping range to visible bounds
4859
void ResetCameraClippingRange() const;
60+
61+
/// Changes the render window managed by the layer manager.
62+
/// Will trigger a removal of all managed layers and creation of new layers if the render window is not null.
4963
void SetRenderWindow(vtkRenderWindow* renderWindow);
64+
65+
/// If the default camera has changed, update the layers with ne new camera
5066
void SetDefaultCamera(const vtkSmartPointer<vtkCamera>& camera);
5167

5268
protected:
@@ -73,7 +89,7 @@ class VTK_SLICER_LAYERDM_MODULE_MRMLDISPLAYABLEMANAGER_EXPORT vtkMRMLLayerDMLaye
7389
void SynchronizePipelineRenderers();
7490
void UpdateRenderWindowNumberOfLayers() const;
7591
void UpdateLayers();
76-
void UpdateRenderWindowLayerOrdering() const;
92+
void UpdateRendererLayerOrdering() const;
7793
void UpdateRendererCamera();
7894

7995
// Map of pipeline layers ordered by ascending <layer value, camera synchronization mode>

LayerDM/MRMLDM/vtkMRMLLayerDMPipelineFactory.cxx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,10 @@ vtkMRMLLayerDMPipelineI* vtkMRMLLayerDMPipelineFactory::GetLastPipeline() const
9898
}
9999

100100
vtkMRMLLayerDMPipelineFactory::vtkMRMLLayerDMPipelineFactory()
101-
: m_lastView(nullptr)
101+
: m_obs(vtkSmartPointer<vtkMRMLLayerDMObjectEventObserver>::New())
102+
, m_lastView(nullptr)
102103
, m_lastNode(nullptr)
103104
, m_lastPipeline(nullptr)
104-
, m_obs(vtkSmartPointer<vtkMRMLLayerDMObjectEventObserver>::New())
105105
{
106106
m_obs->SetUpdateCallback([this](vtkObject* node) { this->SortPipelineCreators(); });
107107
}

LayerDM/MRMLDM/vtkMRMLLayerDMPipelineFactory.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <vtkCommand.h>
77
#include <vtkObject.h>
88
#include <vtkSmartPointer.h>
9+
#include <vtkWeakPointer.h>
910

1011
// STL includes
1112
#include <functional>
@@ -83,8 +84,8 @@ class VTK_SLICER_LAYERDM_MODULE_MRMLDISPLAYABLEMANAGER_EXPORT vtkMRMLLayerDMPipe
8384
void SortPipelineCreators();
8485

8586
std::vector<vtkSmartPointer<vtkMRMLLayerDMPipelineCreatorI>> m_pipelineCreators;
86-
vtkMRMLAbstractViewNode* m_lastView;
87-
vtkMRMLNode* m_lastNode;
88-
vtkMRMLLayerDMPipelineI* m_lastPipeline;
8987
vtkSmartPointer<vtkMRMLLayerDMObjectEventObserver> m_obs;
88+
vtkWeakPointer<vtkMRMLAbstractViewNode> m_lastView;
89+
vtkWeakPointer<vtkMRMLNode> m_lastNode;
90+
vtkWeakPointer<vtkMRMLLayerDMPipelineI> m_lastPipeline;
9091
};

LayerDM/MRMLDM/vtkMRMLLayerDMPipelineI.cxx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,11 @@ void vtkMRMLLayerDMPipelineI::ResetDisplay()
3131
return;
3232
}
3333

34+
// Make sure to avoid looping reset display during processing
35+
this->BlockResetDisplay(true);
3436
this->UpdatePipeline();
37+
this->RequestRender();
38+
this->BlockResetDisplay(false);
3539
}
3640

3741
void vtkMRMLLayerDMPipelineI::SetViewNode(vtkMRMLAbstractViewNode* viewNode)

LayerDM/MRMLDM/vtkMRMLLayerDMPipelineI.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,9 +156,11 @@ class VTK_SLICER_LAYERDM_MODULE_MRMLDISPLAYABLEMANAGER_EXPORT vtkMRMLLayerDMPipe
156156

157157
/// Request rendering and camera clipping reset.
158158
/// Calls are delegated to \sa vtkMRMLLayerDMPipelineManager::RequestRender.
159+
///
160+
/// \sa ResetDisplay
159161
void RequestRender() const;
160162

161-
/// Resets the pipeline display.
163+
/// Resets the pipeline display and request a new render \sa RequestRender.
162164
/// Delegates actual work to \sa UpdatePipeline.
163165
/// Called the first time after pipeline initialization.
164166
void ResetDisplay();

0 commit comments

Comments
 (0)