UNPKG

@kitware/vtk.js

Version:

Visualization Toolkit for the Web

291 lines (269 loc) 10.4 kB
import { m as macro } from '../../macros2.js'; import HalfFloat from '../../Common/Core/HalfFloat.js'; import vtkWebGPUTextureView from './TextureView.js'; import vtkWebGPUTypes from './Types.js'; import vtkTexture from '../Core/Texture.js'; // ---------------------------------------------------------------------------- // Global methods // ---------------------------------------------------------------------------- // ---------------------------------------------------------------------------- // vtkWebGPUTexture methods // ---------------------------------------------------------------------------- function vtkWebGPUTexture(publicAPI, model) { // Set our className 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; /* eslint-disable no-undef */ /* eslint-disable no-bitwise */ model.usage = options.usage ? options.usage : GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST; /* eslint-enable no-undef */ /* eslint-enable no-bitwise */ model.handle = model.device.getHandle().createTexture({ size: [model.width, model.height, model.depth], format: model.format, // 'rgba8unorm', 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'; /* eslint-disable no-undef */ /* eslint-disable no-bitwise */ model.usage = options.usage ? options.usage : GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST; /* eslint-enable no-undef */ /* eslint-enable no-bitwise */ }; 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]); // Generate mipmaps on GPU if needed 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; /** * Align texture data to ensure bytesPerRow is a multiple of 256. * This is necessary for WebGPU texture uploads, especially for half-float formats. * It also handles half-float conversion if the texture format requires it. * @param {*} arr - The input array containing texture data. * @param {*} height - The height of the texture. * @param {*} depth - The depth of the texture (1 for 2D textures). * @returns */ const alignTextureData = (arr, height, depth) => { // bytesPerRow must be a multiple of 256 so we might need to rebuild // the data here before passing to the buffer. e.g. if it is unorm8x4 then // we need to have width be a multiple of 64 // Check if the texture is half float const halfFloat = tDetails.elementSize === 2 && tDetails.sampleType === 'float'; const bytesPerElement = arr.BYTES_PER_ELEMENT; const inWidthInBytes = arr.length / (height * depth) * bytesPerElement; // No changes needed if not half float and already aligned if (!halfFloat && inWidthInBytes % 256 === 0) { return [arr, inWidthInBytes]; } // Calculate dimensions for the new buffer const inWidth = inWidthInBytes / bytesPerElement; const outBytesPerElement = tDetails.elementSize; const outWidthInBytes = 256 * Math.floor((inWidth * outBytesPerElement + 255) / 256); const outWidth = outWidthInBytes / outBytesPerElement; // Create the output array const outArray = macro.newTypedArray(halfFloat ? 'Uint16Array' : arr.constructor.name, outWidth * height * depth); // Copy and convert data when needed 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) { // If the output width is the same as input, just copy 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; }; // when data is pulled out of this texture what scale must be applied to // get back to the original source data. For formats such as r8unorm we // have to multiply by 255.0, for formats such as r16float it is 1.0 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 = function (width, height) { let depth = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 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 = function (label) { let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; // if options is missing values try to add them in if (!options.dimension) { options.dimension = model.depth === 1 ? '2d' : '3d'; } const view = vtkWebGPUTextureView.newInstance({ label }); view.create(publicAPI, options); return view; }; } // ---------------------------------------------------------------------------- // Object factory // ---------------------------------------------------------------------------- const DEFAULT_VALUES = { device: null, handle: null, buffer: null, ready: false, label: null }; // ---------------------------------------------------------------------------- function extend(publicAPI, model) { let initialValues = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; Object.assign(model, DEFAULT_VALUES, initialValues); // Object methods macro.obj(publicAPI, model); macro.get(publicAPI, model, ['handle', 'ready', 'width', 'height', 'depth', 'format', 'usage']); macro.setGet(publicAPI, model, ['device', 'label']); vtkWebGPUTexture(publicAPI, model); } // ---------------------------------------------------------------------------- const newInstance = macro.newInstance(extend); // ---------------------------------------------------------------------------- var vtkWebGPUTexture$1 = { newInstance, extend }; export { vtkWebGPUTexture$1 as default, extend, newInstance };