UNPKG

@kitware/vtk.js

Version:

Visualization Toolkit for the Web

366 lines (290 loc) 11.7 kB
import _toConsumableArray from '@babel/runtime/helpers/toConsumableArray'; import macro from '../../macros.js'; import vtkWebGPUBufferManager from './BufferManager.js'; import vtkWebGPUTypes from './Types.js'; var BufferUsage = vtkWebGPUBufferManager.BufferUsage; var vtkErrorMacro = macro.vtkErrorMacro; // ---------------------------------------------------------------------------- // vtkWebGPUUniformBuffer methods // ---------------------------------------------------------------------------- function vtkWebGPUUniformBuffer(publicAPI, model) { // Set our className model.classHierarchy.push('vtkWebGPUUniformBuffer'); publicAPI.addEntry = function (name, type) { if (model._bufferEntryNames.has(name)) { vtkErrorMacro("entry named ".concat(name, " already exists")); return; } model.sortDirty = true; model._bufferEntryNames.set(name, model.bufferEntries.length); model.bufferEntries.push({ name: name, type: type, sizeInBytes: vtkWebGPUTypes.getByteStrideFromShaderFormat(type), offset: -1, nativeType: vtkWebGPUTypes.getNativeTypeFromShaderFormat(type), packed: false }); }; // UBOs have layout rules in terms of how memory is aligned so we // have to be careful how we order the entries. For example a vec4<f32> // must be aligned on a 16 byte offset, etc. See // https://gpuweb.github.io/gpuweb/wgsl/#memory-layouts // for more details. Right now you can create a situation that would fail // in the future we could add dummy spacer entries where needed to // handle alignment issues publicAPI.sortBufferEntries = function () { if (!model.sortDirty) { return; } var currOffset = 0; var newEntries = []; // compute the max alignment, this is required as WebGPU defines a UBO to have // a size that is a multiple of the maxAlignment var maxAlignment = 4; for (var i = 0; i < model.bufferEntries.length; i++) { var entry = model.bufferEntries[i]; if (entry.sizeInBytes % 16 === 0) { maxAlignment = Math.max(16, maxAlignment); } if (entry.sizeInBytes % 8 === 0) { maxAlignment = Math.max(8, maxAlignment); } } // pack anything whose size is a multiple of 16 bytes first // this includes a couple types that don't require 16 byte alignment // such as mat2x2<f32> but that is OK for (var _i = 0; _i < model.bufferEntries.length; _i++) { var _entry = model.bufferEntries[_i]; if (_entry.packed === false && _entry.sizeInBytes % 16 === 0) { _entry.packed = true; _entry.offset = currOffset; newEntries.push(_entry); currOffset += _entry.sizeInBytes; } } // now it gets tough, we have the following common types (f32, i32, u32) // - vec2<f32> 8 byte size, 8 byte alignment // - vec3<f32> 12 byte size, 16 byte alignment // - f32 4 byte size, 4 byte alignment // try adding 12 byte, 4 byte pairs for (var _i2 = 0; _i2 < model.bufferEntries.length; _i2++) { var _entry2 = model.bufferEntries[_i2]; if (_entry2.packed === false && _entry2.sizeInBytes === 12) { for (var i2 = 0; i2 < model.bufferEntries.length; i2++) { var entry2 = model.bufferEntries[i2]; if (entry2.packed === false && entry2.sizeInBytes === 4) { _entry2.packed = true; _entry2.offset = currOffset; newEntries.push(_entry2); currOffset += _entry2.sizeInBytes; entry2.packed = true; entry2.offset = currOffset; newEntries.push(entry2); currOffset += entry2.sizeInBytes; break; } } } } // try adding 8 byte, 8 byte pairs for (var _i3 = 0; _i3 < model.bufferEntries.length; _i3++) { var _entry3 = model.bufferEntries[_i3]; if (!_entry3.packed && _entry3.sizeInBytes % 8 === 0) { for (var _i4 = _i3 + 1; _i4 < model.bufferEntries.length; _i4++) { var _entry4 = model.bufferEntries[_i4]; if (!_entry4.packed && _entry4.sizeInBytes % 8 === 0) { _entry3.packed = true; _entry3.offset = currOffset; newEntries.push(_entry3); currOffset += _entry3.sizeInBytes; _entry4.packed = true; _entry4.offset = currOffset; newEntries.push(_entry4); currOffset += _entry4.sizeInBytes; break; } } } } // try adding 8 byte, 4 byte 4 byte triplets for (var _i5 = 0; _i5 < model.bufferEntries.length; _i5++) { var _entry5 = model.bufferEntries[_i5]; if (!_entry5.packed && _entry5.sizeInBytes % 8 === 0) { var found = false; for (var _i6 = 0; !found && _i6 < model.bufferEntries.length; _i6++) { var _entry6 = model.bufferEntries[_i6]; if (!_entry6.packed && _entry6.sizeInBytes === 4) { for (var i3 = _i6 + 1; i3 < model.bufferEntries.length; i3++) { var entry3 = model.bufferEntries[i3]; if (!entry3.packed && entry3.sizeInBytes === 4) { _entry5.packed = true; _entry5.offset = currOffset; newEntries.push(_entry5); currOffset += _entry5.sizeInBytes; _entry6.packed = true; _entry6.offset = currOffset; newEntries.push(_entry6); currOffset += _entry6.sizeInBytes; entry3.packed = true; entry3.offset = currOffset; newEntries.push(entry3); currOffset += entry3.sizeInBytes; found = true; break; } } } } } } // Add anything remaining that is larger than 4 bytes and hope we get lucky. // Likely if there is more than one item added here it will result // in a failed UBO for (var _i7 = 0; _i7 < model.bufferEntries.length; _i7++) { var _entry7 = model.bufferEntries[_i7]; if (!_entry7.packed && _entry7.sizeInBytes > 4) { _entry7.packed = true; _entry7.offset = currOffset; newEntries.push(_entry7); currOffset += _entry7.sizeInBytes; } } // finally add remaining 4 byte items for (var _i8 = 0; _i8 < model.bufferEntries.length; _i8++) { var _entry8 = model.bufferEntries[_i8]; if (!_entry8.packed) { _entry8.packed = true; _entry8.offset = currOffset; newEntries.push(_entry8); currOffset += _entry8.sizeInBytes; } } // update entries and entryNames model.bufferEntries = newEntries; model._bufferEntryNames.clear(); for (var _i9 = 0; _i9 < model.bufferEntries.length; _i9++) { model._bufferEntryNames.set(model.bufferEntries[_i9].name, _i9); } model.sizeInBytes = currOffset; model.sizeInBytes = maxAlignment * Math.ceil(model.sizeInBytes / maxAlignment); model.sortDirty = false; }; publicAPI.sendIfNeeded = function (device) { if (!model.UBO) { var req = { nativeArray: model.Float32Array, usage: BufferUsage.UniformArray, label: model.label }; model.UBO = device.getBufferManager().getBuffer(req); model.bindGroupTime.modified(); model.sendDirty = false; } // send data down if needed if (model.sendDirty) { device.getHandle().queue.writeBuffer(model.UBO.getHandle(), 0, model.arrayBuffer, 0, model.sizeInBytes); model.sendDirty = false; } // always updated as mappers depend on this time // it is more of a sentIfNeededTime model.sendTime.modified(); }; publicAPI.createView = function (type) { if (type in model === false) { if (!model.arrayBuffer) { model.arrayBuffer = new ArrayBuffer(model.sizeInBytes); } model[type] = macro.newTypedArray(type, model.arrayBuffer); } }; publicAPI.setValue = function (name, val) { publicAPI.sortBufferEntries(); var idx = model._bufferEntryNames.get(name); if (idx === undefined) { vtkErrorMacro("entry named ".concat(name, " not found in UBO")); return; } var entry = model.bufferEntries[idx]; publicAPI.createView(entry.nativeType); var view = model[entry.nativeType]; if (entry.lastValue !== val) { view[entry.offset / view.BYTES_PER_ELEMENT] = val; model.sendDirty = true; } entry.lastValue = val; }; publicAPI.setArray = function (name, arr) { publicAPI.sortBufferEntries(); var idx = model._bufferEntryNames.get(name); if (idx === undefined) { vtkErrorMacro("entry named ".concat(name, " not found in UBO")); return; } var entry = model.bufferEntries[idx]; publicAPI.createView(entry.nativeType); var view = model[entry.nativeType]; var changed = false; for (var i = 0; i < arr.length; i++) { if (!entry.lastValue || entry.lastValue[i] !== arr[i]) { view[entry.offset / view.BYTES_PER_ELEMENT + i] = arr[i]; changed = true; } } if (changed) { model.sendDirty = true; entry.lastValue = _toConsumableArray(arr); } }; publicAPI.getBindGroupEntry = function () { var foo = { resource: { buffer: model.UBO.getHandle() } }; return foo; }; publicAPI.getSendTime = function () { return model.sendTime.getMTime(); }; publicAPI.getShaderCode = function (binding, group) { // sort the entries publicAPI.sortBufferEntries(); var lines = ["struct ".concat(model.label, "Struct\n{")]; for (var i = 0; i < model.bufferEntries.length; i++) { var entry = model.bufferEntries[i]; lines.push(" ".concat(entry.name, ": ").concat(entry.type, ",")); } lines.push("};\n@binding(".concat(binding, ") @group(").concat(group, ") var<uniform> ").concat(model.label, ": ").concat(model.label, "Struct;")); return lines.join('\n'); }; } // ---------------------------------------------------------------------------- // Object factory // ---------------------------------------------------------------------------- var DEFAULT_VALUES = { bufferEntries: null, bufferEntryNames: null, sizeInBytes: 0, label: null, bindGroupLayoutEntry: null, bindGroupEntry: null }; // ---------------------------------------------------------------------------- function extend(publicAPI, model) { var initialValues = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; Object.assign(model, DEFAULT_VALUES, initialValues); // Build VTK API macro.obj(publicAPI, model); // Internal objects model._bufferEntryNames = new Map(); model.bufferEntries = []; // default UBO desc model.bindGroupLayoutEntry = model.bindGroupLayoutEntry || { buffer: { type: 'uniform' } }; model.sendTime = {}; macro.obj(model.sendTime, { mtime: 0 }); model.bindGroupTime = {}; macro.obj(model.bindGroupTime, { mtime: 0 }); model.sendDirty = true; model.sortDirty = true; macro.get(publicAPI, model, ['binding', 'bindGroupTime']); macro.setGet(publicAPI, model, ['bindGroupLayoutEntry', 'device', 'label', 'sizeInBytes']); // Object methods vtkWebGPUUniformBuffer(publicAPI, model); } // ---------------------------------------------------------------------------- var newInstance = macro.newInstance(extend, 'vtkWebGPUUniformBuffer'); // ---------------------------------------------------------------------------- var vtkWebGPUUniformBuffer$1 = { newInstance: newInstance, extend: extend }; export { vtkWebGPUUniformBuffer$1 as default, extend, newInstance };