import macro from 'vtk.js/Sources/macros'; import HalfFloat from 'vtk.js/Sources/Common/Core/HalfFloat'; import vtkWebGPUTextureView from 'vtk.js/Sources/Rendering/WebGPU/TextureView'; import vtkWebGPUTypes from 'vtk.js/Sources/Rendering/WebGPU/Types'; import vtkTexture from 'vtk.js/Sources/Rendering/Core/Texture';
function vtkWebGPUTexture(publicAPI, model) { model.classHierarchy.push('vtkWebGPUTexture');
publicAPI.create = (device, options) => { model.device = device; model.width = options.width; model.height = options.height; model.depth = options.depth ? options.depth : 1; const dimension = model.depth === 1 ? '2d' : '3d'; model.format = options.format ? options.format : 'rgba8unorm'; model.mipLevel = options.mipLevel ? options.mipLevel : 0; model.usage = options.usage ? options.usage : GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST; model.handle = model.device.getHandle().createTexture({ size: [model.width, model.height, model.depth], format: model.format, usage: model.usage, label: model.label, dimension, mipLevelCount: model.mipLevel + 1, }); };
publicAPI.assignFromHandle = (device, handle, options) => { model.device = device; model.handle = handle; model.width = options.width; model.height = options.height; model.depth = options.depth ? options.depth : 1; model.format = options.format ? options.format : 'rgba8unorm'; model.usage = options.usage ? options.usage : GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST; };
publicAPI.writeImageData = (req) => { let nativeArray = []; const _copyImageToTexture = (source) => { model.device.getHandle().queue.copyExternalImageToTexture( { source, flipY: req.flip, }, { texture: model.handle, premultipliedAlpha: true, mipLevel: 0, origin: { x: 0, y: 0, z: 0 }, }, [source.width, source.height, model.depth] );
if (publicAPI.getDimensionality() !== 3 && model.mipLevel > 0) { vtkTexture.generateMipmaps( model.device.getHandle(), model.handle, model.mipLevel + 1 ); }
model.ready = true; };
if (req.canvas) { _copyImageToTexture(req.canvas); return; }
if (req.imageBitmap) { req.width = req.imageBitmap.width; req.height = req.imageBitmap.height; req.depth = 1; req.format = 'rgba8unorm'; req.flip = true; _copyImageToTexture(req.imageBitmap); return; }
if (req.jsImageData) { req.width = req.jsImageData.width; req.height = req.jsImageData.height; req.depth = 1; req.format = 'rgba8unorm'; req.flip = true; _copyImageToTexture(req.jsImageData); return; }
if (req.image) { req.width = req.image.width; req.height = req.image.height; req.depth = 1; req.format = 'rgba8unorm'; req.flip = true; _copyImageToTexture(req.image); return; }
const tDetails = vtkWebGPUTypes.getDetailsFromTextureFormat(model.format); let bufferBytesPerRow = model.width * tDetails.stride;
const alignTextureData = (arr, height, depth) => { const halfFloat = tDetails.elementSize === 2 && tDetails.sampleType === 'float';
const bytesPerElement = arr.BYTES_PER_ELEMENT; const inWidthInBytes = (arr.length / (height * depth)) * bytesPerElement;
if (!halfFloat && inWidthInBytes % 256 === 0) { return [arr, inWidthInBytes]; }
const inWidth = inWidthInBytes / bytesPerElement; const outBytesPerElement = tDetails.elementSize; const outWidthInBytes = 256 * Math.floor((inWidth * outBytesPerElement + 255) / 256); const outWidth = outWidthInBytes / outBytesPerElement;
const outArray = macro.newTypedArray( halfFloat ? 'Uint16Array' : arr.constructor.name, outWidth * height * depth );
const totalRows = height * depth; if (halfFloat) { for (let v = 0; v < totalRows; v++) { const inOffset = v * inWidth; const outOffset = v * outWidth; for (let i = 0; i < inWidth; i++) { outArray[outOffset + i] = HalfFloat.toHalf(arr[inOffset + i]); } } } else if (outWidth === inWidth) { outArray.set(arr); } else { for (let v = 0; v < totalRows; v++) { outArray.set( arr.subarray(v * inWidth, (v + 1) * inWidth), v * outWidth ); } }
return [outArray, outWidthInBytes]; };
if (req.nativeArray) { nativeArray = req.nativeArray; }
const is3D = publicAPI.getDimensionality() === 3; const alignedTextureData = alignTextureData( nativeArray, model.height, is3D ? model.depth : 1 ); bufferBytesPerRow = alignedTextureData[1]; const data = alignedTextureData[0];
model.device.getHandle().queue.writeTexture( { texture: model.handle, mipLevel: 0, origin: { x: 0, y: 0, z: 0 }, }, data, { offset: 0, bytesPerRow: bufferBytesPerRow, rowsPerImage: model.height, }, { width: model.width, height: model.height, depthOrArrayLayers: is3D ? model.depth : 1, } );
if (!is3D && model.mipLevel > 0) { vtkTexture.generateMipmaps( model.device.getHandle(), model.handle, model.mipLevel + 1 ); } model.ready = true; };
publicAPI.getScale = () => { const tDetails = vtkWebGPUTypes.getDetailsFromTextureFormat(model.format); const halfFloat = tDetails.elementSize === 2 && tDetails.sampleType === 'float'; return halfFloat ? 1.0 : 255.0; };
publicAPI.getNumberOfComponents = () => { const tDetails = vtkWebGPUTypes.getDetailsFromTextureFormat(model.format); return tDetails.numComponents; };
publicAPI.getDimensionality = () => { let dims = 0; if (model.width > 1) dims++; if (model.height > 1) dims++; if (model.depth > 1) dims++; return dims; };
publicAPI.resizeToMatch = (tex) => { if ( tex.getWidth() !== model.width || tex.getHeight() !== model.height || tex.getDepth() !== model.depth ) { model.width = tex.getWidth(); model.height = tex.getHeight(); model.depth = tex.getDepth(); model.handle = model.device.getHandle().createTexture({ size: [model.width, model.height, model.depth], format: model.format, usage: model.usage, label: model.label, }); } };
publicAPI.resize = (width, height, depth = 1) => { if ( width !== model.width || height !== model.height || depth !== model.depth ) { model.width = width; model.height = height; model.depth = depth; model.handle = model.device.getHandle().createTexture({ size: [model.width, model.height, model.depth], format: model.format, usage: model.usage, label: model.label, }); } };
publicAPI.createView = (label, options = {}) => { if (!options.dimension) { options.dimension = model.depth === 1 ? '2d' : '3d'; } const view = vtkWebGPUTextureView.newInstance({ label }); view.create(publicAPI, options); return view; }; }
const DEFAULT_VALUES = { device: null, handle: null, buffer: null, ready: false, label: null, };
export function extend(publicAPI, model, initialValues = {}) { Object.assign(model, DEFAULT_VALUES, initialValues);
macro.obj(publicAPI, model);
macro.get(publicAPI, model, [ 'handle', 'ready', 'width', 'height', 'depth', 'format', 'usage', ]); macro.setGet(publicAPI, model, ['device', 'label']);
vtkWebGPUTexture(publicAPI, model); }
export const newInstance = macro.newInstance(extend);
export default { newInstance, extend };
|