Skip to content

Commit

Permalink
chore(WebGPU): add a UBO class and use it
Browse files Browse the repository at this point in the history
Add a Uniform Buffer Object class for WebGPU to manage UBOs
and make it easier for people to extend mappers etc by adding
additional fields to the UBOs used in a pipeline.

The UBO class handles memory alignment issues but currently
only supports basic types such as vec/mat not arrays or
structs as members.
  • Loading branch information
martinken committed Apr 13, 2021
1 parent 54451ff commit 4c9bcee
Show file tree
Hide file tree
Showing 10 changed files with 509 additions and 217 deletions.
2 changes: 0 additions & 2 deletions Sources/Rendering/OpenGL/ShaderCache/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,8 +239,6 @@ export function extend(publicAPI, model, initialValues = {}) {

// Object methods
vtkShaderCache(publicAPI, model);

return Object.freeze(publicAPI);
}

// ----------------------------------------------------------------------------
Expand Down
35 changes: 3 additions & 32 deletions Sources/Rendering/WebGPU/BufferManager/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as macro from 'vtk.js/Sources/macro';
import * as vtkMath from 'vtk.js/Sources/Common/Core/Math';
import vtkWebGPUBuffer from 'vtk.js/Sources/Rendering/WebGPU/Buffer';
import vtkWebGPUTypes from 'vtk.js/Sources/Rendering/WebGPU/Types';
import vtkProperty from 'vtk.js/Sources/Rendering/Core/Property';
import Constants from './Constants';

Expand Down Expand Up @@ -362,36 +363,6 @@ function generateNormals(cellArray, primType, representation, inArray) {
return packedVBO;
}

function getStrideFromFormat(format) {
if (!format) return 0;
let numComp = 1;
if (format.substring(format.length - 2) === 'x4') numComp = 4;
if (format.substring(format.length - 2) === 'x3') numComp = 3;
if (format.substring(format.length - 2) === 'x2') numComp = 2;

let typeSize = 4;
if (numComp > 1) {
if (format.substring(format.length - 3, format.length - 1) === '8x') {
typeSize = 1;
}
if (format.substring(format.length - 4, format.length - 1) === '16x') {
typeSize = 2;
}
} else {
if (format.substring(format.length - 1) === '8') typeSize = 1;
if (format.substring(format.length - 2) === '16') typeSize = 2;
}
return numComp * typeSize;
}

function getArrayTypeFromFormat(format) {
if (!format) return null;
if (format.substring(0, 7) === 'float32') return 'Float32Array';
if (format.substring(0, 6) === 'snorm8') return 'Int8Array';
if (format.substring(0, 6) === 'unorm8') return 'Uint8Array';
return '';
}

// ----------------------------------------------------------------------------
// vtkWebGPUBufferManager methods
// ----------------------------------------------------------------------------
Expand Down Expand Up @@ -426,8 +397,8 @@ function vtkWebGPUBufferManager(publicAPI, model) {
const buffer = vtkWebGPUBuffer.newInstance();
buffer.setDevice(model.device);

const stride = getStrideFromFormat(req.format);
const arrayType = getArrayTypeFromFormat(req.format);
const stride = vtkWebGPUTypes.getByteStrideFromBufferFormat(req.format);
const arrayType = vtkWebGPUTypes.getNativeTypeFromBufferFormat(req.format);
let gpuUsage = null;

// handle uniform buffers
Expand Down
5 changes: 4 additions & 1 deletion Sources/Rendering/WebGPU/Device/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,4 +187,7 @@ export function extend(publicAPI, model, initialValues = {}) {
export const newInstance = macro.newInstance(extend, 'vtkWebGPUDevice');

// ----------------------------------------------------------------------------
export default { newInstance, extend };
export default {
newInstance,
extend,
};
136 changes: 50 additions & 86 deletions Sources/Rendering/WebGPU/PolyDataMapper/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import vtkWebGPUPipeline from 'vtk.js/Sources/Rendering/WebGPU/Pipeline';
import vtkWebGPUSampler from 'vtk.js/Sources/Rendering/WebGPU/Sampler';
import vtkWebGPUShaderCache from 'vtk.js/Sources/Rendering/WebGPU/ShaderCache';
import vtkWebGPUShaderDescription from 'vtk.js/Sources/Rendering/WebGPU/ShaderDescription';
import vtkWebGPUUniformBuffer from 'vtk.js/Sources/Rendering/WebGPU/UniformBuffer';
import vtkWebGPUVertexInput from 'vtk.js/Sources/Rendering/WebGPU/VertexInput';
import vtkViewNode from 'vtk.js/Sources/Rendering/SceneGraph/ViewNode';

Expand Down Expand Up @@ -103,28 +104,6 @@ fn main() -> void
}
`;

const vtkWebGPUPolyDataUBOCode = `
[[block]] struct mapperVals
{
[[offset(0)]] MCVCMatrix: mat4x4<f32>;
[[offset(64)]] normalMatrix: mat4x4<f32>;
[[offset(128)]] AmbientColor: vec4<f32>;
[[offset(144)]] DiffuseColor: vec4<f32>;
[[offset(160)]] SpecularColor: vec4<f32>;
[[offset(176)]] AmbientIntensity: f32;
[[offset(180)]] DiffuseIntensity: f32;
[[offset(184)]] SpecularIntensity: f32;
[[offset(188)]] Opacity: f32;
[[offset(192)]] Metallic: f32;
[[offset(196)]] Roughness: f32;
[[offset(200)]] EmissiveFactor: f32;
[[offset(204)]] SpecularPower: f32;
};
[[binding(0), group(1)]] var<uniform> mapperUBO : mapperVals;
`;

const vtkWebGPUPolyDataMapperUBOSize = 208 / 4;

// ----------------------------------------------------------------------------
// vtkWebGPUPolyDataMapper methods
// ----------------------------------------------------------------------------
Expand Down Expand Up @@ -158,76 +137,44 @@ function vtkWebGPUPolyDataMapper(publicAPI, model) {
};

publicAPI.updateUBO = () => {
let needSend = false;

// make sure the data is up to date
const actor = model.WebGPUActor.getRenderable();
const ppty = actor.getProperty();
const utime = model.UBOUpdateTime.getMTime();
const utime = model.UBO.getSendTime();
if (
publicAPI.getMTime() > utime ||
ppty.getMTime() > utime ||
model.renderable.getMTime() > utime
) {
let aColor = ppty.getAmbientColorByReference();
model.UBOData[32] = aColor[0];
model.UBOData[33] = aColor[1];
model.UBOData[34] = aColor[2];
model.UBOData[35] = 1.0;
model.UBO.setValue('AmbientIntensity', ppty.getAmbient());
model.UBO.setArray('AmbientColor', [
aColor[0],
aColor[1],
aColor[2],
1.0,
]);
model.UBO.setValue('DiffuseIntensity', ppty.getDiffuse());
aColor = ppty.getDiffuseColorByReference();
model.UBOData[36] = aColor[0];
model.UBOData[37] = aColor[1];
model.UBOData[38] = aColor[2];
model.UBOData[39] = 1.0;
model.UBO.setArray('DiffuseColor', [
aColor[0],
aColor[1],
aColor[2],
1.0,
]);
model.UBO.setValue('SpecularIntensity', ppty.getSpecular());
model.UBO.setValue('SpecularPower', ppty.getSpecularPower());
aColor = ppty.getSpecularColorByReference();
model.UBOData[40] = aColor[0];
model.UBOData[41] = aColor[1];
model.UBOData[42] = aColor[2];
model.UBOData[43] = 1.0;
model.UBOData[44] = ppty.getAmbient();
model.UBOData[45] = ppty.getDiffuse();
model.UBOData[46] = ppty.getSpecular();
model.UBOData[47] = ppty.getOpacity();
model.UBOData[51] = ppty.getSpecularPower();

model.UBOUpdateTime.modified();
needSend = true;
}
model.UBO.setArray('SpecularColor', [
aColor[0],
aColor[1],
aColor[2],
1.0,
]);
model.UBO.setValue('Opacity', ppty.getOpacity());

// make sure the buffer is created
if (!model.UBO) {
const req = {
address: model.UBOData,
time: 0,
usage: BufferUsage.UniformArray,
};
const device = model.WebGPURenderWindow.getDevice();
model.UBO = device.getBufferManager().getBuffer(req);
model.UBOBindGroup = device.getHandle().createBindGroup({
layout: device.getMapperBindGroupLayout(),
entries: [
{
binding: 0,
resource: {
buffer: model.UBO.getHandle(),
},
},
],
});
needSend = false;
}

// send data down if needed
if (needSend) {
model.WebGPURenderWindow.getDevice()
.getHandle()
.queue.writeBuffer(
model.UBO.getHandle(),
0,
model.UBOData.buffer,
model.UBOData.byteOffset,
model.UBOData.byteLength
);
model.UBO.sendIfNeeded(device, device.getMapperBindGroupLayout());
}
};

Expand All @@ -254,7 +201,7 @@ function vtkWebGPUPolyDataMapper(publicAPI, model) {
model.WebGPURenderer.getUBOCode(),
]).result;
code = vtkWebGPUShaderCache.substitute(code, '//VTK::Mapper::UBO', [
vtkWebGPUPolyDataUBOCode,
model.UBO.getShaderCode(),
]).result;
code = vtkWebGPUShaderCache.substitute(code, '//VTK::VertexInput', [
vertexInput.getShaderCode(),
Expand All @@ -270,7 +217,7 @@ function vtkWebGPUPolyDataMapper(publicAPI, model) {
model.WebGPURenderer.getUBOCode(),
]).result;
code = vtkWebGPUShaderCache.substitute(code, '//VTK::Mapper::UBO', [
vtkWebGPUPolyDataUBOCode,
model.UBO.getShaderCode(),
]).result;

const fDesc = vtkWebGPUShaderDescription.newInstance({
Expand Down Expand Up @@ -795,9 +742,26 @@ export function extend(publicAPI, model, initialValues = {}) {

model.tmpMat3 = mat3.identity(new Float64Array(9));
model.tmpMat4 = mat4.identity(new Float64Array(16));
model.UBOData = new Float32Array(vtkWebGPUPolyDataMapperUBOSize);
model.UBOUpdateTime = {};
macro.obj(model.UBOUpdateTime);

model.UBO = vtkWebGPUUniformBuffer.newInstance();
model.UBO.setBinding(0);
model.UBO.setGroup(1);
model.UBO.setName('mapperUBO');
model.UBO.addEntry('MCVCMatrix', 'mat4x4<f32>');
model.UBO.addEntry('AmbientColor', 'vec4<f32>');
model.UBO.addEntry('DiffuseColor', 'vec4<f32>');
model.UBO.addEntry('AmbientIntensity', 'f32');
model.UBO.addEntry('DiffuseIntensity', 'f32');
model.UBO.addEntry('SpecularColor', 'vec4<f32>');
model.UBO.addEntry('SpecularIntensity', 'f32');
model.UBO.addEntry('Opacity', 'f32');
model.UBO.addEntry('SpecularPower', 'f32');

// [[offset(0)]] MCVCMatrix: mat4x4<f32>;
// [[offset(64)]] normalMatrix: mat4x4<f32>;
// [[offset(192)]] Metallic: f32;
// [[offset(196)]] Roughness: f32;
// [[offset(200)]] EmissiveFactor: f32;

model.samplers = [];
model.textures = [];
Expand All @@ -813,7 +777,7 @@ export function extend(publicAPI, model, initialValues = {}) {
const renderEncoder = model.WebGPURenderer.getRenderEncoder();

// bind the mapper UBO
renderEncoder.setBindGroup(1, model.UBOBindGroup);
renderEncoder.setBindGroup(1, model.UBO.getBindGroup());

// bind any textures and samplers
for (let t = 0; t < model.textures.length; t++) {
Expand All @@ -834,7 +798,7 @@ export function extend(publicAPI, model, initialValues = {}) {
}

// Build VTK API
macro.setGet(publicAPI, model, ['context', 'renderEncoder']);
macro.setGet(publicAPI, model, ['context', 'renderEncoder', 'UBO']);

model.VBOBuildTime = {};
macro.obj(model.VBOBuildTime, { mtime: 0 });
Expand Down
6 changes: 3 additions & 3 deletions Sources/Rendering/WebGPU/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ ToDo
- PBR lighting to replace the simple model currently coded
- image display
- volume rendering
- transparency
- actor matrix support
- actor matrix support with auto shift
- sphere/stick mappers
- post render operations/framebuffers
- hardware selector
- eventually switch to using IBOs and flat interpolation
- cropping planes for polydata mapper

Developer Notes
============
Expand Down
Loading

0 comments on commit 4c9bcee

Please sign in to comment.