@kitware/vtk.js
Version:
Visualization Toolkit for the Web
366 lines (290 loc) • 11.7 kB
JavaScript
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 };