diff --git a/Sources/Rendering/Core/VolumeMapper/index.d.ts b/Sources/Rendering/Core/VolumeMapper/index.d.ts index 0c075bdbb78..3c9b53be6ef 100755 --- a/Sources/Rendering/Core/VolumeMapper/index.d.ts +++ b/Sources/Rendering/Core/VolumeMapper/index.d.ts @@ -1,16 +1,8 @@ import vtkPiecewiseFunction from "../../../Common/DataModel/PiecewiseFunction"; -import { Bounds, Range, Vector3 } from "../../../types"; +import { Bounds, Range, Extent } from "../../../types"; import vtkAbstractMapper3D, { IAbstractMapper3DInitialValues } from "../AbstractMapper3D"; import { BlendMode, FilterMode } from "./Constants"; -/** - * Represents a 3D region of a volume. - */ -export interface VolumeRegion { - start: Vector3; - size: Vector3; -} - /** * */ @@ -302,23 +294,23 @@ export interface vtkVolumeMapper extends vtkAbstractMapper3D { setLAOKernelRadius(LAOKernelRadius: number): void; /** - * Tells the mapper to only update the specified regions. - * - * If there are zero regions, the mapper updates the entire volume texture. - * Otherwise, the mapper will only update the texture by the specified regions + * Tells the mapper to only update the specified extents. + * + * If there are zero extents, the mapper updates the entire volume texture. + * Otherwise, the mapper will only update the texture by the specified extents * during the next render call. - * + * * This array is cleared after a successful render. - * @param regions + * @param extents */ - setRegionsToUpdate(regions: VolumeRegion[]): boolean; + setUpdatedExtents(extents: Extent[]): boolean; /** - * Retrieves the regions to update. - * + * Retrieves the updated extents. + * * This array is cleared after every successful render. */ - getRegionsToUpdate(): VolumeRegion[]; + getUpdatedExtents(): Extent[]; /** * diff --git a/Sources/Rendering/Core/VolumeMapper/index.js b/Sources/Rendering/Core/VolumeMapper/index.js index 599c605bf95..17b3613840f 100644 --- a/Sources/Rendering/Core/VolumeMapper/index.js +++ b/Sources/Rendering/Core/VolumeMapper/index.js @@ -163,7 +163,7 @@ const defaultValues = (initialValues) => ({ localAmbientOcclusion: false, LAOKernelSize: 15, LAOKernelRadius: 7, - regionsToUpdate: [], + updatedExtents: [], ...initialValues, }); @@ -192,7 +192,7 @@ export function extend(publicAPI, model, initialValues = {}) { 'localAmbientOcclusion', 'LAOKernelSize', 'LAOKernelRadius', - 'regionsToUpdate', + 'updatedExtents', ]); macro.setGetArray(publicAPI, model, ['ipScalarRange'], 2); diff --git a/Sources/Rendering/OpenGL/Texture/index.d.ts b/Sources/Rendering/OpenGL/Texture/index.d.ts index f550cc53ffd..122f62ef21e 100644 --- a/Sources/Rendering/OpenGL/Texture/index.d.ts +++ b/Sources/Rendering/OpenGL/Texture/index.d.ts @@ -1,7 +1,7 @@ import { Wrap, Filter } from './Constants'; import vtkOpenGLRenderWindow from '../RenderWindow'; -import { Nullable } from '../../../types'; -import { VtkDataTypes } from '../../../Common/Core/DataArray'; +import { Extent, Nullable } from '../../../types'; +import { VtkDataTypes } from "../../../Common/Core/DataArray"; import { vtkViewNode } from '../../../Rendering/SceneGraph/ViewNode'; import { vtkObject } from '../../../interfaces'; @@ -256,25 +256,25 @@ export interface vtkOpenGLTexture extends vtkViewNode { /** * Creates a 3D texture from raw data. + * + * updatedExtents is currently incompatible with webgl1, since there's no extent scaling. + * * @param width The width of the texture. * @param height The height of the texture. * @param depth The depth of the texture. * @param numComps The number of components in the texture. * @param dataType The data type of the texture. * @param data The raw data for the texture. + * @param updatedExtents Only update the specified extents (default: []) * @returns {boolean} True if the texture was successfully created, false otherwise. */ - create3DFromRaw( - width: number, - height: number, - depth: number, - numComps: number, - dataType: VtkDataTypes, - data: any - ): boolean; + create3DFromRaw(width: number, height: number, depth: number, numComps: number, dataType: VtkDataTypes, data: any, updatedExtents?: Extent[]): boolean; /** * Creates a 3D filterable texture from raw data, with a preference for size over accuracy if necessary. + * + * updatedExtents is currently incompatible with webgl1, since there's no extent scaling. + * * @param width The width of the texture. * @param height The height of the texture. * @param depth The depth of the texture. @@ -282,34 +282,25 @@ export interface vtkOpenGLTexture extends vtkViewNode { * @param dataType The data type of the texture. * @param values The raw data for the texture. * @param preferSizeOverAccuracy Whether to prefer texture size over accuracy. + * @param updatedExtents Only update the specified extents (default: []) * @returns {boolean} True if the texture was successfully created, false otherwise. */ - create3DFilterableFromRaw( - width: number, - height: number, - depth: number, - numComps: number, - dataType: VtkDataTypes, - values: any, - preferSizeOverAccuracy: boolean - ): boolean; + create3DFilterableFromRaw(width: number, height: number, depth: number, numComps: number, dataType: VtkDataTypes, values: any, preferSizeOverAccuracy: boolean, updatedExtents?: Extent[]): boolean; /** * Creates a 3D filterable texture from a data array, with a preference for size over accuracy if necessary. + * + * updatedExtents is currently incompatible with webgl1, since there's no extent scaling. + * * @param width The width of the texture. * @param height The height of the texture. * @param depth The depth of the texture. * @param dataArray The data array to use for the texture. * @param preferSizeOverAccuracy Whether to prefer texture size over accuracy. + * @param updatedExtents Only update the specified extents (default: []) * @returns {boolean} True if the texture was successfully created, false otherwise. */ - create3DFilterableFromDataArray( - width: number, - height: number, - depth: number, - dataArray: any, - preferSizeOverAccuracy: boolean - ): boolean; + create3DFilterableFromDataArray(width: number, height: number, depth: number, dataArray: any, preferSizeOverAccuracy: boolean, updatedExtents?: Extent[]): boolean; /** * Sets the OpenGL render window in which the texture will be used. diff --git a/Sources/Rendering/OpenGL/Texture/index.js b/Sources/Rendering/OpenGL/Texture/index.js index d9e08f877ae..b0a752ebbbe 100644 --- a/Sources/Rendering/OpenGL/Texture/index.js +++ b/Sources/Rendering/OpenGL/Texture/index.js @@ -616,30 +616,57 @@ function vtkOpenGLTexture(publicAPI, model) { //---------------------------------------------------------------------------- /** - * Reads a region from the image data and writes to the given output array. + * Gets the extent's size. + * @param {Extent} extent + */ + function getExtentSize(extent) { + const [xmin, xmax, ymin, ymax, zmin, zmax] = extent; + return [xmax - xmin + 1, ymax - ymin + 1, zmax - zmin + 1]; + } + + //---------------------------------------------------------------------------- + + /** + * Gets the number of pixels in the extent. + * @param {Extent} extent + */ + function getExtentPixelCount(extent) { + const [sx, sy, sz] = getExtentSize(extent); + return sx * sy * sz; + } + + //---------------------------------------------------------------------------- + + /** + * Reads a flattened extent from the image data and writes to the given output array. * * Assumes X varies the fastest and Z varies the slowest. * * @param {*} data - * @param {*} start - * @param {*} size - * @param {*} outArray - * @param {*} outOffset + * @param {Extent} extent + * @param {TypedArray} outArray + * @param {number} outOffset * @returns */ - function readRegion(data, start, size, outArray, outOffset) { - const [sx, sy, sz] = size; + function readExtentIntoArray(data, extent, outArray, outOffset) { + const [, , ymin, ymax, zmin, zmax] = extent; + const [sx, sy] = getExtentSize(extent); const sxy = sx * sy; - const startOffset = start[2] * sxy + start[1] * sx + start[0]; let writeOffset = outOffset; - for (let zi = 0; zi < sz; zi++) { - const zOffset = startOffset + zi * sxy; - for (let yi = 0; yi < sy; yi++) { + for (let zi = zmin; zi <= zmax; zi++) { + const zOffset = zi * sxy; + for (let yi = ymin; yi <= ymax; yi++) { const readOffset = zOffset + yi * sx; - const chunk = data.subarray(readOffset, readOffset + sx); - outArray.set(chunk, writeOffset); - writeOffset += chunk.length; + // explicit alternative to data.subarray, + // due to potential perf issues on v8 + for ( + let xi = readOffset, end = readOffset + sx; + xi < end; + xi++, writeOffset++ + ) { + outArray[writeOffset] = data[xi]; + } } } } @@ -647,37 +674,28 @@ function vtkOpenGLTexture(publicAPI, model) { //---------------------------------------------------------------------------- /** - * /omputes the region's size. - * @param {VolumeRegion} region - */ - function getRegionSize(region) { - const { size } = region; - return size[0] * size[1] * size[2]; - } - - //---------------------------------------------------------------------------- - - /** - * Reads several sub-image regions into a contiguous pixel array. + * Reads several image extents into a contiguous pixel array. * * @param {*} data - * @param {VolumeRegion[]} regions + * @param {Extent[]} extent + * @param {TypedArrayConstructor} typedArrayConstructor optional typed array constructor * @returns */ - function readRegions(data, regions) { - const numPixels = regions.reduce( - (count, region) => count + getRegionSize(region), + function readExtents(data, extents, typedArrayConstructor = null) { + const constructor = typedArrayConstructor || data.constructor; + const numPixels = extents.reduce( + (count, extent) => count + getExtentPixelCount(extent), 0 ); - const regionPixels = new data.constructor(numPixels); + const extentPixels = new constructor(numPixels); let writeOffset = 0; - regions.forEach((region) => { - readRegion(data, region.start, region.size, regionPixels, writeOffset); - writeOffset += getRegionSize(region); + extents.forEach((extent) => { + readExtentIntoArray(data, extent, extentPixels, writeOffset); + writeOffset += getExtentPixelCount(extent); }); - return regionPixels; + return extentPixels; } //---------------------------------------------------------------------------- @@ -685,19 +703,19 @@ function vtkOpenGLTexture(publicAPI, model) { /** * Modifies the typed array types for webgl based on the given dataType. * - * When sub-image regions are provided, the regions are all concatenated together into one typed array in the return output. + * When sub-image extents are provided, the extents are all concatenated together into one typed array in the return output. * * @param {VtkDataTypes} dataType the VTK data type * @param {TypedArray[]} data An array of typed arrays, one for each texture * @param {boolean} depth should depth be used (default: false) - * @param {Array} volumeRegions only consider these sub-image regions (default: []) + * @param {Array} imageExtents only consider these image extents (default: []) * @returns an array of newly typed arrays, one for each texture */ function updateArrayDataType( dataType, data, depth = false, - volumeRegions = [] + imageExtents = [] ) { const pixData = []; @@ -706,7 +724,7 @@ function vtkOpenGLTexture(publicAPI, model) { pixCount *= model.depth; } - const updateRegions = !!volumeRegions.length; + const onlyUpdateExtents = !!imageExtents.length; // if the opengl data type is float // then the data array must be float @@ -716,10 +734,8 @@ function vtkOpenGLTexture(publicAPI, model) { ) { for (let idx = 0; idx < data.length; idx++) { if (data[idx]) { - if (updateRegions) { - pixData.push( - new Float32Array(readRegions(data[idx], volumeRegions)) - ); + if (onlyUpdateExtents) { + pixData.push(readExtents(data[idx], imageExtents, Float32Array)); } else { const dataArrayToCopy = data[idx].length > pixCount @@ -741,8 +757,8 @@ function vtkOpenGLTexture(publicAPI, model) { ) { for (let idx = 0; idx < data.length; idx++) { if (data[idx]) { - if (updateRegions) { - pixData.push(new Uint8Array(readRegions(data[idx], volumeRegions))); + if (onlyUpdateExtents) { + pixData.push(readExtents(data[idx], imageExtents, Uint8Array)); } else { const dataArrayToCopy = data[idx].length > pixCount @@ -770,11 +786,11 @@ function vtkOpenGLTexture(publicAPI, model) { if (halfFloat) { for (let idx = 0; idx < data.length; idx++) { if (data[idx]) { - const src = updateRegions - ? readRegions(data[idx], volumeRegions) + const src = onlyUpdateExtents + ? readExtents(data[idx], imageExtents) : data[idx]; const newArray = new Uint16Array( - updateRegions ? src.length : pixCount + onlyUpdateExtents ? src.length : pixCount ); const newArrayLen = newArray.length; for (let i = 0; i < newArrayLen; i++) { @@ -791,8 +807,8 @@ function vtkOpenGLTexture(publicAPI, model) { if (pixData.length === 0) { for (let i = 0; i < data.length; i++) { pixData.push( - updateRegions && data[i] - ? readRegions(data[i], volumeRegions) + onlyUpdateExtents && data[i] + ? readExtents(data[i], imageExtents) : data[i] ); } @@ -1477,7 +1493,7 @@ function vtkOpenGLTexture(publicAPI, model) { numComps, dataType, data, - updatedRegions = [] + updatedExtents = [] ) => { // Permit OpenGLDataType to be half float, if applicable, for 3D publicAPI.getOpenGLDataType(dataType); @@ -1501,13 +1517,13 @@ function vtkOpenGLTexture(publicAPI, model) { publicAPI.createTexture(); publicAPI.bind(); - const hasUpdatedRegions = updatedRegions.length > 0; + const hasUpdatedExtents = updatedExtents.length > 0; const paramCache = model._paramCache; // It's possible for the texture parameters to change while // streaming, so check for such a change. const rebuildEntireTexture = - !hasUpdatedRegions || + !hasUpdatedExtents || !paramCache || !paramCache.tex3dAllocated || paramCache.prevInternalFormat !== model.internalFormat || @@ -1524,9 +1540,8 @@ function vtkOpenGLTexture(publicAPI, model) { dataArray, is3DArray, // if we need to reallocate the entire texture, then process the entire volume - rebuildEntireTexture ? [] : updatedRegions + rebuildEntireTexture ? [] : updatedExtents ); - // TODO(floryst): updatedRegions is currently incompatible with webgl1, since there's no region scaling const scaledData = scaleTextureToHighestPowerOfTwo(pixData); // Source texture data from the PBO. @@ -1581,28 +1596,29 @@ function vtkOpenGLTexture(publicAPI, model) { prevWidth: model.width, prevHeight: model.height, }; - } else if (hasUpdatedRegions) { - const regionPixels = scaledData[0]; + } else if (hasUpdatedExtents) { + const extentPixels = scaledData[0]; let readOffset = 0; - for (let i = 0; i < updatedRegions.length; i++) { - const region = updatedRegions[i]; - const regionSize = getRegionSize(region); - const textureData = new regionPixels.constructor( - regionPixels.buffer, + for (let i = 0; i < updatedExtents.length; i++) { + const extent = updatedExtents[i]; + const extentSize = getExtentSize(extent); + const extentPixelCount = getExtentPixelCount(extent); + const textureData = new extentPixels.constructor( + extentPixels.buffer, readOffset, - regionSize + extentPixelCount ); readOffset += textureData.byteLength; model.context.texSubImage3D( model.target, 0, - region.start[0], - region.start[1], - region.start[2], - region.size[0], - region.size[1], - region.size[2], + extent[0], + extent[2], + extent[4], + extentSize[0], + extentSize[1], + extentSize[2], model.format, model.openGLDataType, textureData @@ -1640,7 +1656,7 @@ function vtkOpenGLTexture(publicAPI, model) { dataType, values, preferSizeOverAccuracy = false, - updatedRegions = [] + updatedExtents = [] ) => publicAPI.create3DFilterableFromDataArray( width, @@ -1652,7 +1668,7 @@ function vtkOpenGLTexture(publicAPI, model) { values, }), preferSizeOverAccuracy, - updatedRegions + updatedExtents ); //---------------------------------------------------------------------------- @@ -1663,7 +1679,7 @@ function vtkOpenGLTexture(publicAPI, model) { depth, dataArray, preferSizeOverAccuracy = false, - updatedRegions = [] + updatedExtents = [] ) => { const { numComps, dataType, data, scaleOffsets } = processDataArray( dataArray, @@ -1716,7 +1732,7 @@ function vtkOpenGLTexture(publicAPI, model) { numComps, dataType, data, - updatedRegions + updatedExtents ); } if ( @@ -1734,7 +1750,7 @@ function vtkOpenGLTexture(publicAPI, model) { numComps, dataType, data, - updatedRegions + updatedExtents ); } if ( @@ -1750,7 +1766,7 @@ function vtkOpenGLTexture(publicAPI, model) { numComps, dataType, data, - updatedRegions + updatedExtents ); } if (dataType === VtkDataTypes.UNSIGNED_CHAR) { @@ -1764,12 +1780,12 @@ function vtkOpenGLTexture(publicAPI, model) { numComps, dataType, data, - updatedRegions + updatedExtents ); } - if (updatedRegions.length) { + if (updatedExtents.length) { vtkWarningMacro( - 'Cannot perform region updates with the given data type' + 'Cannot perform extent updates with the given data type' ); } // otherwise convert to float diff --git a/Sources/Rendering/OpenGL/VolumeMapper/index.js b/Sources/Rendering/OpenGL/VolumeMapper/index.js index 71f0b2ee929..6984fba0249 100644 --- a/Sources/Rendering/OpenGL/VolumeMapper/index.js +++ b/Sources/Rendering/OpenGL/VolumeMapper/index.js @@ -1664,10 +1664,10 @@ function vtkOpenGLVolumeMapper(publicAPI, model) { // rebuild the scalarTexture if the data has changed toString = `${image.getMTime()}A${scalars.getMTime()}`; const reBuildTex = !tex?.vtkObj || tex?.hash !== toString; - const hasUpdatedRegions = !!model.renderable.getRegionsToUpdate().length; + const hasUpdatedExtents = !!model.renderable.getUpdatedExtents().length; - // reset the scalars texture if there are no updated regions - if (reBuildTex && !hasUpdatedRegions) { + // reset the scalars texture if there are no updated extents + if (reBuildTex && !hasUpdatedExtents) { // Use norm16 for scalar texture if the extension is available model.scalarTexture.setOglNorm16Ext( model.context.getExtension('EXT_texture_norm16') @@ -1677,12 +1677,12 @@ function vtkOpenGLVolumeMapper(publicAPI, model) { model.scalarTexture.resetFormatAndType(); } - if (reBuildTex || hasUpdatedRegions) { + if (reBuildTex || hasUpdatedExtents) { // Build the textures - // If hasUpdatedRegions, then the texture is partially updated - const regionsToUpdate = [...model.renderable.getRegionsToUpdate()]; + // If hasUpdatedExtents, then the texture is partially updated + const updatedExtents = [...model.renderable.getUpdatedExtents()]; // clear, as we've acknowledged the update. - model.renderable.setRegionsToUpdate([]); + model.renderable.setUpdatedExtents([]); const dims = image.getDimensions(); model.scalarTexture.create3DFilterableFromDataArray( @@ -1691,7 +1691,7 @@ function vtkOpenGLVolumeMapper(publicAPI, model) { dims[2], scalars, model.renderable.getPreferSizeOverAccuracy(), - regionsToUpdate + updatedExtents ); if (scalars) {