Skip to content

Commit 0f13c25

Browse files
committed
feat(OpenGL/Texture): update sub-image extents
vtkVolumeMapper, vtkImageCPRMapper, and vtkImageResliceMapper all now support updating sub-image extents, reducing the GPU upload overhead when part of the image has updated.
1 parent 307ce44 commit 0f13c25

File tree

13 files changed

+527
-73
lines changed

13 files changed

+527
-73
lines changed

Sources/Rendering/Core/ImageCPRMapper/index.d.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { mat3, mat4, quat, vec3 } from 'gl-matrix';
2-
import { Nullable } from '../../../types';
2+
import { Extent, Nullable } from '../../../types';
33
import { vtkOutputPort } from '../../../interfaces';
44
import vtkAbstractMapper3D, {
55
IAbstractMapper3DInitialValues,
@@ -307,6 +307,25 @@ export interface vtkImageCPRMapper
307307
* @param imageData
308308
*/
309309
setImageConnection(imageData: vtkOutputPort): void;
310+
311+
/**
312+
* Tells the mapper to only update the specified extents.
313+
*
314+
* If there are zero extents, the mapper updates the entire volume texture.
315+
* Otherwise, the mapper will only update the texture by the specified extents
316+
* during the next render call.
317+
*
318+
* This array is cleared after a successful render.
319+
* @param extents
320+
*/
321+
setUpdatedExtents(extents: Extent[]): boolean;
322+
323+
/**
324+
* Retrieves the updated extents.
325+
*
326+
* This array is cleared after every successful render.
327+
*/
328+
getUpdatedExtents(): Extent[];
310329
}
311330

312331
/**

Sources/Rendering/Core/ImageCPRMapper/index.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ function vtkImageCPRMapper(publicAPI, model) {
331331
// Object factory
332332
// ----------------------------------------------------------------------------
333333

334-
const DEFAULT_VALUES = {
334+
const defaultValues = (initialValues) => ({
335335
width: 10,
336336
uniformOrientation: [0, 0, 0, 1],
337337
useUniformOrientation: false,
@@ -344,12 +344,14 @@ const DEFAULT_VALUES = {
344344
projectionSlabThickness: 1,
345345
projectionSlabNumberOfSamples: 1,
346346
projectionMode: ProjectionMode.MAX,
347-
};
347+
updatedExtents: [],
348+
...initialValues,
349+
});
348350

349351
// ----------------------------------------------------------------------------
350352

351353
export function extend(publicAPI, model, initialValues = {}) {
352-
Object.assign(model, DEFAULT_VALUES, initialValues);
354+
Object.assign(model, defaultValues(initialValues));
353355

354356
// Inheritance
355357
vtkAbstractImageMapper.extend(publicAPI, model, initialValues);
@@ -374,6 +376,7 @@ export function extend(publicAPI, model, initialValues = {}) {
374376
'projectionSlabThickness',
375377
'projectionSlabNumberOfSamples',
376378
'projectionMode',
379+
'updatedExtents',
377380
]);
378381
CoincidentTopologyHelper.implementCoincidentTopologyMethods(publicAPI, model);
379382

Sources/Rendering/Core/ImageResliceMapper/index.d.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import vtkAbstractImageMapper, {
22
IAbstractImageMapperInitialValues,
33
} from '../AbstractImageMapper';
4-
import vtkImageData from '../../../Common/DataModel/ImageData';
54
import vtkPlane from '../../../Common/DataModel/Plane';
65
import vtkPolyData from '../../../Common/DataModel/PolyData';
7-
import { Bounds, Nullable, Vector3 } from '../../../types';
6+
import { Bounds, Extent } from '../../../types';
87
import { SlabTypes } from './Constants';
98
import CoincidentTopologyHelper, {
109
StaticCoincidentTopologyMethods,
@@ -116,6 +115,25 @@ export interface vtkImageResliceMapper
116115
* @param {vtkPolyData} slicePolyData The polydata to slice the volume with. Default: null
117116
*/
118117
setSlicePolyData(slicePolyData: vtkPolyData): boolean;
118+
119+
/**
120+
* Tells the mapper to only update the specified extents.
121+
*
122+
* If there are zero extents, the mapper updates the entire volume texture.
123+
* Otherwise, the mapper will only update the texture by the specified extents
124+
* during the next render call.
125+
*
126+
* This array is cleared after a successful render.
127+
* @param extents
128+
*/
129+
setUpdatedExtents(extents: Extent[]): boolean;
130+
131+
/**
132+
* Retrieves the updated extents.
133+
*
134+
* This array is cleared after every successful render.
135+
*/
136+
getUpdatedExtents(): Extent[];
119137
}
120138

121139
/**

Sources/Rendering/Core/ImageResliceMapper/index.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,20 @@ function vtkImageResliceMapper(publicAPI, model) {
3939
// Object factory
4040
// ----------------------------------------------------------------------------
4141

42-
const DEFAULT_VALUES = {
42+
const defaultValues = (initialValues) => ({
4343
slabThickness: 0.0,
4444
slabTrapezoidIntegration: 0,
4545
slabType: SlabTypes.MEAN,
4646
slicePlane: null,
4747
slicePolyData: null,
48-
};
48+
updatedExtents: [],
49+
...initialValues,
50+
});
4951

5052
// ----------------------------------------------------------------------------
5153

5254
export function extend(publicAPI, model, initialValues = {}) {
53-
Object.assign(model, DEFAULT_VALUES, initialValues);
55+
Object.assign(model, defaultValues(initialValues));
5456

5557
// Build VTK API
5658
vtkAbstractImageMapper.extend(publicAPI, model, initialValues);
@@ -61,6 +63,7 @@ export function extend(publicAPI, model, initialValues = {}) {
6163
'slabType',
6264
'slicePlane',
6365
'slicePolyData',
66+
'updatedExtents',
6467
]);
6568
CoincidentTopologyHelper.implementCoincidentTopologyMethods(publicAPI, model);
6669

Sources/Rendering/Core/VolumeMapper/index.d.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import vtkPiecewiseFunction from '../../../Common/DataModel/PiecewiseFunction';
2-
import { Bounds, Range } from '../../../types';
2+
import { Bounds, Range, Extent } from '../../../types';
33
import vtkAbstractMapper3D, {
44
IAbstractMapper3DInitialValues,
55
} from '../AbstractMapper3D';
@@ -281,6 +281,39 @@ export interface vtkVolumeMapper extends vtkAbstractMapper3D {
281281
*/
282282
setLAOKernelRadius(LAOKernelRadius: number): void;
283283

284+
/**
285+
* Set kernel size for local ambient occlusion. It specifies the number of rays that are randomly sampled in the hemisphere.
286+
* Value is clipped between 1 and 32.
287+
* @param LAOKernelSize
288+
*/
289+
setLAOKernelSize(LAOKernelSize: number): void;
290+
291+
/**
292+
* Set kernel radius for local ambient occlusion. It specifies the number of samples that are considered on each random ray.
293+
* Value must be greater than or equal to 1.
294+
* @param LAOKernelRadius
295+
*/
296+
setLAOKernelRadius(LAOKernelRadius: number): void;
297+
298+
/**
299+
* Tells the mapper to only update the specified extents.
300+
*
301+
* If there are zero extents, the mapper updates the entire volume texture.
302+
* Otherwise, the mapper will only update the texture by the specified extents
303+
* during the next render call.
304+
*
305+
* This array is cleared after a successful render.
306+
* @param extents
307+
*/
308+
setUpdatedExtents(extents: Extent[]): boolean;
309+
310+
/**
311+
* Retrieves the updated extents.
312+
*
313+
* This array is cleared after every successful render.
314+
*/
315+
getUpdatedExtents(): Extent[];
316+
284317
/**
285318
*
286319
*/

Sources/Rendering/Core/VolumeMapper/index.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ function vtkVolumeMapper(publicAPI, model) {
141141
// ----------------------------------------------------------------------------
142142

143143
// TODO: what values to use for averageIPScalarRange to get GLSL to use max / min values like [-Math.inf, Math.inf]?
144-
const DEFAULT_VALUES = {
144+
const defaultValues = (initialValues) => ({
145145
bounds: [1, -1, 1, -1, 1, -1],
146146
sampleDistance: 1.0,
147147
imageSampleDistance: 1.0,
@@ -163,12 +163,14 @@ const DEFAULT_VALUES = {
163163
localAmbientOcclusion: false,
164164
LAOKernelSize: 15,
165165
LAOKernelRadius: 7,
166-
};
166+
updatedExtents: [],
167+
...initialValues,
168+
});
167169

168170
// ----------------------------------------------------------------------------
169171

170172
export function extend(publicAPI, model, initialValues = {}) {
171-
Object.assign(model, DEFAULT_VALUES, initialValues);
173+
Object.assign(model, defaultValues(initialValues));
172174

173175
vtkAbstractMapper3D.extend(publicAPI, model, initialValues);
174176

@@ -190,6 +192,7 @@ export function extend(publicAPI, model, initialValues = {}) {
190192
'localAmbientOcclusion',
191193
'LAOKernelSize',
192194
'LAOKernelRadius',
195+
'updatedExtents',
193196
]);
194197

195198
macro.setGetArray(publicAPI, model, ['ipScalarRange'], 2);

Sources/Rendering/OpenGL/ImageCPRMapper/index.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,9 @@ function vtkOpenGLImageCPRMapper(publicAPI, model) {
199199
const reBuildTex =
200200
!cachedScalarsEntry?.oglObject?.getHandle() ||
201201
cachedScalarsEntry?.hash !== volumeTextureHash;
202+
const updatedExtents = model.renderable.getUpdatedExtents();
203+
const hasUpdatedExtents = !!updatedExtents.length;
204+
202205
if (reBuildTex) {
203206
model.volumeTexture = vtkOpenGLTexture.newInstance();
204207
model.volumeTexture.setOpenGLRenderWindow(model._openGLRenderWindow);
@@ -236,6 +239,22 @@ function vtkOpenGLImageCPRMapper(publicAPI, model) {
236239
model.volumeTexture = cachedScalarsEntry.oglObject;
237240
}
238241

242+
if (hasUpdatedExtents) {
243+
// If hasUpdatedExtents, then the texture is partially updated.
244+
// clear the array to acknowledge the update.
245+
model.renderable.setUpdatedExtents([]);
246+
247+
const dims = image.getDimensions();
248+
model.volumeTexture.create3DFilterableFromDataArray(
249+
dims[0],
250+
dims[1],
251+
dims[2],
252+
scalars,
253+
false,
254+
updatedExtents
255+
);
256+
}
257+
239258
// Rebuild the color texture if needed
240259
const numComp = scalars.getNumberOfComponents();
241260
const ppty = actor.getProperty();

Sources/Rendering/OpenGL/ImageResliceMapper/index.js

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -232,21 +232,28 @@ function vtkOpenGLImageResliceMapper(publicAPI, model) {
232232

233233
const tex = model._openGLRenderWindow.getGraphicsResourceForObject(scalars);
234234
const reBuildTex = !tex?.oglObject?.getHandle() || tex?.hash !== toString;
235-
if (reBuildTex) {
235+
const updatedExtents = model.renderable.getUpdatedExtents();
236+
const hasUpdatedExtents = !!updatedExtents.length;
237+
238+
if (reBuildTex && !hasUpdatedExtents) {
236239
model.openGLTexture = vtkOpenGLTexture.newInstance();
237240
model.openGLTexture.setOpenGLRenderWindow(model._openGLRenderWindow);
238241
// Build the image scalar texture
239-
const dims = image.getDimensions();
240242
// Use norm16 for the 3D texture if the extension is available
241243
model.openGLTexture.setOglNorm16Ext(
242244
model.context.getExtension('EXT_texture_norm16')
243245
);
244246
model.openGLTexture.resetFormatAndType();
247+
248+
// Build the image scalar texture
249+
const dims = image.getDimensions();
245250
model.openGLTexture.create3DFilterableFromDataArray(
246251
dims[0],
247252
dims[1],
248253
dims[2],
249-
scalars
254+
scalars,
255+
false,
256+
updatedExtents
250257
);
251258
model._openGLRenderWindow.setGraphicsResourceForObject(
252259
scalars,
@@ -268,6 +275,22 @@ function vtkOpenGLImageResliceMapper(publicAPI, model) {
268275
model.openGLTexture = tex.oglObject;
269276
}
270277

278+
if (hasUpdatedExtents) {
279+
// If hasUpdatedExtents, then the texture is partially updated.
280+
// clear the array to acknowledge the update.
281+
model.renderable.setUpdatedExtents([]);
282+
283+
const dims = image.getDimensions();
284+
model.openGLTexture.create3DFilterableFromDataArray(
285+
dims[0],
286+
dims[1],
287+
dims[2],
288+
scalars,
289+
false,
290+
updatedExtents
291+
);
292+
}
293+
271294
const ppty = actor.getProperty();
272295
const iComps = ppty.getIndependentComponents();
273296
const numIComps = iComps ? numComp : 1;

Sources/Rendering/OpenGL/Texture/index.d.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Wrap, Filter } from './Constants';
22
import vtkOpenGLRenderWindow from '../RenderWindow';
3-
import { Nullable } from '../../../types';
3+
import { Extent, Nullable } from '../../../types';
44
import { VtkDataTypes } from '../../../Common/Core/DataArray';
55
import { vtkViewNode } from '../../../Rendering/SceneGraph/ViewNode';
66
import { vtkObject, vtkRange } from '../../../interfaces';
@@ -275,12 +275,16 @@ export interface vtkOpenGLTexture extends vtkViewNode {
275275

276276
/**
277277
* Creates a 3D texture from raw data.
278+
*
279+
* updatedExtents is currently incompatible with webgl1, since there's no extent scaling.
280+
*
278281
* @param width The width of the texture.
279282
* @param height The height of the texture.
280283
* @param depth The depth of the texture.
281284
* @param numComps The number of components in the texture.
282285
* @param dataType The data type of the texture.
283286
* @param data The raw data for the texture.
287+
* @param updatedExtents Only update the specified extents (default: [])
284288
* @returns {boolean} True if the texture was successfully created, false otherwise.
285289
*/
286290
create3DFromRaw(
@@ -289,11 +293,15 @@ export interface vtkOpenGLTexture extends vtkViewNode {
289293
depth: number,
290294
numComps: number,
291295
dataType: VtkDataTypes,
292-
data: any
296+
data: any,
297+
updatedExtents?: Extent[]
293298
): boolean;
294299

295300
/**
296301
* Creates a 3D filterable texture from raw data, with a preference for size over accuracy if necessary.
302+
*
303+
* updatedExtents is currently incompatible with webgl1, since there's no extent scaling.
304+
*
297305
* @param width The width of the texture.
298306
* @param height The height of the texture.
299307
* @param depth The depth of the texture.
@@ -302,6 +310,7 @@ export interface vtkOpenGLTexture extends vtkViewNode {
302310
* @param values The raw data for the texture.
303311
* @param preferSizeOverAccuracy Whether to prefer texture size over accuracy.
304312
* @param [ranges] The precomputed ranges of the data (optional). Provided to
313+
* @param updatedExtents Only update the specified extents (default: [])
305314
* prevent computation of the data ranges.
306315
* @returns {boolean} True if the texture was successfully created, false
307316
* otherwise.
@@ -314,24 +323,30 @@ export interface vtkOpenGLTexture extends vtkViewNode {
314323
dataType: VtkDataTypes,
315324
values: any,
316325
preferSizeOverAccuracy: boolean,
317-
ranges?: vtkRange[]
326+
ranges?: vtkRange[],
327+
updatedExtents?: Extent[]
318328
): boolean;
319329

320330
/**
321331
* Creates a 3D filterable texture from a data array, with a preference for size over accuracy if necessary.
332+
*
333+
* updatedExtents is currently incompatible with webgl1, since there's no extent scaling.
334+
*
322335
* @param width The width of the texture.
323336
* @param height The height of the texture.
324337
* @param depth The depth of the texture.
325338
* @param dataArray The data array to use for the texture.
326339
* @param preferSizeOverAccuracy Whether to prefer texture size over accuracy.
340+
* @param updatedExtents Only update the specified extents (default: [])
327341
* @returns {boolean} True if the texture was successfully created, false otherwise.
328342
*/
329343
create3DFilterableFromDataArray(
330344
width: number,
331345
height: number,
332346
depth: number,
333347
dataArray: any,
334-
preferSizeOverAccuracy: boolean
348+
preferSizeOverAccuracy: boolean,
349+
updatedExtents?: Extent[]
335350
): boolean;
336351

337352
/**

0 commit comments

Comments
 (0)