diff --git a/Sources/Rendering/WebGPU/ForwardPass/index.js b/Sources/Rendering/WebGPU/ForwardPass/index.js index e16486e67bd..e8b3c5ceb9e 100644 --- a/Sources/Rendering/WebGPU/ForwardPass/index.js +++ b/Sources/Rendering/WebGPU/ForwardPass/index.js @@ -1,8 +1,36 @@ import macro from 'vtk.js/Sources/macros'; +import vtkWebGPUFullScreenQuad from 'vtk.js/Sources/Rendering/WebGPU/FullScreenQuad'; import vtkWebGPUOpaquePass from 'vtk.js/Sources/Rendering/WebGPU/OpaquePass'; import vtkWebGPUOrderIndepenentTranslucentPass from 'vtk.js/Sources/Rendering/WebGPU/OrderIndependentTranslucentPass'; +import vtkWebGPURenderEncoder from 'vtk.js/Sources/Rendering/WebGPU/RenderEncoder'; import vtkWebGPUVolumePass from 'vtk.js/Sources/Rendering/WebGPU/VolumePass'; import vtkRenderPass from 'vtk.js/Sources/Rendering/SceneGraph/RenderPass'; +import vtkWebGPUSampler from 'vtk.js/Sources/Rendering/WebGPU/Sampler'; +import vtkWebGPUTextureView from 'vtk.js/Sources/Rendering/WebGPU/TextureView'; + +const finalBlitFragTemplate = ` +//VTK::Mapper::Dec + +//VTK::TCoord::Dec + +//VTK::RenderEncoder::Dec + +//VTK::IOStructs::Dec + +@fragment +fn main( +//VTK::IOStructs::Input +) +//VTK::IOStructs::Output +{ + var output: fragmentOutput; + + var computedColor: vec4 = clamp(textureSampleLevel(opaquePassColorTexture, finalPassSampler, input.tcoordVS, 0),vec4(0.0),vec4(1.0)); + + //VTK::RenderEncoder::Impl + return output; +} +`; // ---------------------------------------------------------------------------- @@ -83,26 +111,80 @@ function vtkForwardPass(publicAPI, model) { model.volumePass.setVolumes(model.volumes); model.volumePass.traverse(renNode, viewNode); } + + // blit the result into the swap chain + publicAPI.finalPass(viewNode, renNode); } } } + }; - // blit the result into the swap chain - const sctex = viewNode.getCurrentTexture(); - const optex = model.opaquePass.getColorTexture(); - const cmdEnc = viewNode.getCommandEncoder(); - cmdEnc.copyTextureToTexture( - { - texture: optex.getHandle(), - }, - { - texture: sctex, + publicAPI.finalPass = (viewNode, renNode) => { + if (!model._finalBlitEncoder) { + publicAPI.createFinalBlitEncoder(viewNode); + } + model._finalBlitOutputTextureView.createFromTextureHandle( + viewNode.getCurrentTexture(), + { depth: 1, format: viewNode.getPresentationFormat() } + ); + + model._finalBlitEncoder.attachTextureViews(); + model._finalBlitEncoder.begin(viewNode.getCommandEncoder()); + renNode.scissorAndViewport(model._finalBlitEncoder); + model._fullScreenQuad.prepareAndDraw(model._finalBlitEncoder); + model._finalBlitEncoder.end(); + }; + + publicAPI.createFinalBlitEncoder = (viewNode) => { + model._finalBlitEncoder = vtkWebGPURenderEncoder.newInstance({ + label: 'forwardPassBlit', + }); + model._finalBlitEncoder.setDescription({ + colorAttachments: [ + { + view: null, + loadOp: 'load', + storeOp: 'store', + }, + ], + }); + model._finalBlitEncoder.setPipelineHash('fpf'); + model._finalBlitEncoder.setPipelineSettings({ + primitive: { cullMode: 'none' }, + fragment: { + targets: [ + { + format: viewNode.getPresentationFormat(), + blend: { + color: { + srcFactor: 'src-alpha', + dstFactor: 'one-minus-src-alpha', + }, + alpha: { srcfactor: 'one', dstFactor: 'one-minus-src-alpha' }, + }, + }, + ], }, - { - width: optex.getWidth(), - height: optex.getHeight(), - depthOrArrayLayers: 1, - } + }); + model._fsqSampler = vtkWebGPUSampler.newInstance({ + label: 'finalPassSampler', + }); + model._fsqSampler.create(viewNode.getDevice(), { + minFilter: 'linear', + magFilter: 'linear', + }); + model._fullScreenQuad = vtkWebGPUFullScreenQuad.newInstance(); + model._fullScreenQuad.setDevice(viewNode.getDevice()); + model._fullScreenQuad.setPipelineHash('fpfsq'); + model._fullScreenQuad.setTextureViews([ + model.opaquePass.getColorTextureView(), + ]); + model._fullScreenQuad.setAdditionalBindables([model._fsqSampler]); + model._fullScreenQuad.setFragmentShaderTemplate(finalBlitFragTemplate); + model._finalBlitOutputTextureView = vtkWebGPUTextureView.newInstance(); + model._finalBlitEncoder.setColorTextureView( + 0, + model._finalBlitOutputTextureView ); }; diff --git a/Sources/Rendering/WebGPU/OpaquePass/index.js b/Sources/Rendering/WebGPU/OpaquePass/index.js index 6e1fa104d46..53a82144276 100644 --- a/Sources/Rendering/WebGPU/OpaquePass/index.js +++ b/Sources/Rendering/WebGPU/OpaquePass/index.js @@ -31,7 +31,7 @@ function vtkWebGPUOpaquePass(publicAPI, model) { model.colorTexture.create(device, { width: viewNode.getCanvas().width, height: viewNode.getCanvas().height, - format: 'bgra8unorm', + format: 'rgba16float', /* eslint-disable no-undef */ /* eslint-disable no-bitwise */ usage: diff --git a/Sources/Rendering/WebGPU/OrderIndependentTranslucentPass/index.js b/Sources/Rendering/WebGPU/OrderIndependentTranslucentPass/index.js index b825b5c8953..4178d9a6ee2 100644 --- a/Sources/Rendering/WebGPU/OrderIndependentTranslucentPass/index.js +++ b/Sources/Rendering/WebGPU/OrderIndependentTranslucentPass/index.js @@ -239,7 +239,7 @@ function vtkWebGPUOrderIndependentTranslucentPass(publicAPI, model) { fragment: { targets: [ { - format: 'bgra8unorm', + format: 'rgba16float', blend: { color: { srcFactor: 'src-alpha', diff --git a/Sources/Rendering/WebGPU/RenderEncoder/index.js b/Sources/Rendering/WebGPU/RenderEncoder/index.js index 7aa8c4d1490..b3e1d8e9335 100644 --- a/Sources/Rendering/WebGPU/RenderEncoder/index.js +++ b/Sources/Rendering/WebGPU/RenderEncoder/index.js @@ -59,8 +59,8 @@ function vtkWebGPURenderEncoder(publicAPI, model) { console.trace(); } else { for (let i = 0; i < model.colorTextureViews.length; i++) { - const fmt = model.colorTextureViews[i].getTexture().getFormat(); - if (fmt !== pd.fragment.targets[i].format) { + const fmt = model.colorTextureViews[i].getTexture()?.getFormat(); + if (fmt && fmt !== pd.fragment.targets[i].format) { console.log( `mismatched attachments for attachment ${i} on pipeline ${pd.fragment.targets[i].format} while encoder has ${fmt}` ); @@ -74,8 +74,8 @@ function vtkWebGPURenderEncoder(publicAPI, model) { console.log('mismatched depth attachments'); console.trace(); } else if (model.depthTextureView) { - const dfmt = model.depthTextureView.getTexture().getFormat(); - if (dfmt !== pd.depthStencil.format) { + const dfmt = model.depthTextureView.getTexture()?.getFormat(); + if (dfmt && dfmt !== pd.depthStencil.format) { console.log( `mismatched depth attachments on pipeline ${pd.depthStencil.format} while encoder has ${dfmt}` ); @@ -211,7 +211,7 @@ export function extend(publicAPI, model, initialValues = {}) { fragment: { targets: [ { - format: 'bgra8unorm', + format: 'rgba16float', blend: { color: { srcFactor: 'src-alpha', diff --git a/Sources/Rendering/WebGPU/RenderWindow/index.js b/Sources/Rendering/WebGPU/RenderWindow/index.js index 97224805820..91b1b8543a2 100644 --- a/Sources/Rendering/WebGPU/RenderWindow/index.js +++ b/Sources/Rendering/WebGPU/RenderWindow/index.js @@ -7,7 +7,7 @@ import vtkWebGPUHardwareSelector from 'vtk.js/Sources/Rendering/WebGPU/HardwareS import vtkWebGPUViewNodeFactory from 'vtk.js/Sources/Rendering/WebGPU/ViewNodeFactory'; import vtkRenderPass from 'vtk.js/Sources/Rendering/SceneGraph/RenderPass'; import vtkRenderWindowViewNode from 'vtk.js/Sources/Rendering/SceneGraph/RenderWindowViewNode'; -// import { VtkDataTypes } from 'vtk.js/Sources/Common/Core/DataArray/Constants'; +import HalfFloat from 'vtk.js/Sources/Common/Core/HalfFloat'; const { vtkErrorMacro } = macro; // const IS_CHROME = navigator.userAgent.indexOf('Chrome') !== -1; @@ -68,7 +68,7 @@ function vtkWebGPURenderWindow(publicAPI, model) { publicAPI.recreateSwapChain = () => { if (model.context) { model.context.unconfigure(); - const presentationFormat = navigator.gpu.getPreferredCanvasFormat( + model.presentationFormat = navigator.gpu.getPreferredCanvasFormat( model.adapter ); @@ -76,7 +76,7 @@ function vtkWebGPURenderWindow(publicAPI, model) { /* eslint-disable no-bitwise */ model.context.configure({ device: model.device.getHandle(), - format: presentationFormat, + format: model.presentationFormat, alphaMode: 'premultiplied', usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_DST, width: model.size[0], @@ -478,9 +478,9 @@ function vtkWebGPURenderWindow(publicAPI, model) { height: texture.getHeight(), }; - // must be a multiple of 256 bytes, so 64 texels with rgba8 - result.colorBufferWidth = 64 * Math.floor((result.width + 63) / 64); - result.colorBufferSizeInBytes = result.colorBufferWidth * result.height * 4; + // must be a multiple of 256 bytes, so 32 texels with rgba16 + result.colorBufferWidth = 32 * Math.floor((result.width + 31) / 32); + result.colorBufferSizeInBytes = result.colorBufferWidth * result.height * 8; const colorBuffer = vtkWebGPUBuffer.newInstance(); colorBuffer.setDevice(device); /* eslint-disable no-bitwise */ @@ -499,7 +499,7 @@ function vtkWebGPURenderWindow(publicAPI, model) { }, { buffer: colorBuffer.getHandle(), - bytesPerRow: 4 * result.colorBufferWidth, + bytesPerRow: 8 * result.colorBufferWidth, rowsPerImage: result.height, }, { @@ -515,9 +515,7 @@ function vtkWebGPURenderWindow(publicAPI, model) { await cLoad; /* eslint-enable no-undef */ - result.colorValues = new Uint8ClampedArray( - colorBuffer.getMappedRange().slice() - ); + result.colorValues = new Uint16Array(colorBuffer.getMappedRange().slice()); colorBuffer.unmap(); // repack the array const tmparray = new Uint8ClampedArray(result.height * result.width * 4); @@ -525,10 +523,14 @@ function vtkWebGPURenderWindow(publicAPI, model) { for (let x = 0; x < result.width; x++) { const doffset = (y * result.width + x) * 4; const soffset = (y * result.colorBufferWidth + x) * 4; - tmparray[doffset] = result.colorValues[soffset + 2]; - tmparray[doffset + 1] = result.colorValues[soffset + 1]; - tmparray[doffset + 2] = result.colorValues[soffset]; - tmparray[doffset + 3] = result.colorValues[soffset + 3]; + tmparray[doffset] = + 255.0 * HalfFloat.fromHalf(result.colorValues[soffset]); + tmparray[doffset + 1] = + 255.0 * HalfFloat.fromHalf(result.colorValues[soffset + 1]); + tmparray[doffset + 2] = + 255.0 * HalfFloat.fromHalf(result.colorValues[soffset + 2]); + tmparray[doffset + 3] = + 255.0 * HalfFloat.fromHalf(result.colorValues[soffset + 3]); } } result.colorValues = tmparray; @@ -564,6 +566,7 @@ const DEFAULT_VALUES = { useBackgroundImage: false, nextPropID: 1, xrSupported: false, + presentationFormat: null, }; // ---------------------------------------------------------------------------- @@ -607,6 +610,7 @@ export function extend(publicAPI, model, initialValues = {}) { macro.get(publicAPI, model, [ 'commandEncoder', 'device', + 'presentationFormat', 'useBackgroundImage', 'xrSupported', ]); diff --git a/Sources/Rendering/WebGPU/TextureView/index.js b/Sources/Rendering/WebGPU/TextureView/index.js index 1cf18f0ea8d..251eec8428f 100644 --- a/Sources/Rendering/WebGPU/TextureView/index.js +++ b/Sources/Rendering/WebGPU/TextureView/index.js @@ -26,6 +26,19 @@ function vtkWebGPUTextureView(publicAPI, model) { model.bindGroupLayoutEntry.texture.sampleType = tDetails.sampleType; }; + publicAPI.createFromTextureHandle = (textureHandle, options) => { + model.texture = null; + model.options = options; + model.options.dimension = model.options.dimension || '2d'; + model.options.label = model.label; + model.textureHandle = textureHandle; + model.handle = model.textureHandle.createView(model.options); + model.bindGroupLayoutEntry.texture.viewDimension = model.options.dimension; + const tDetails = vtkWebGPUTypes.getDetailsFromTextureFormat(options.format); + model.bindGroupLayoutEntry.texture.sampleType = tDetails.sampleType; + model.bindGroupTime.modified(); + }; + publicAPI.getBindGroupEntry = () => { const foo = { resource: publicAPI.getHandle(), @@ -57,7 +70,7 @@ function vtkWebGPUTextureView(publicAPI, model) { publicAPI.getBindGroupTime = () => { // check if the handle changed - if (model.texture.getHandle() !== model.textureHandle) { + if (model.texture && model.texture.getHandle() !== model.textureHandle) { model.textureHandle = model.texture.getHandle(); model.handle = model.textureHandle.createView(model.options); model.bindGroupTime.modified(); @@ -67,7 +80,7 @@ function vtkWebGPUTextureView(publicAPI, model) { // if the texture has changed then get a new view publicAPI.getHandle = () => { - if (model.texture.getHandle() !== model.textureHandle) { + if (model.texture && model.texture.getHandle() !== model.textureHandle) { model.textureHandle = model.texture.getHandle(); model.handle = model.textureHandle.createView(model.options); model.bindGroupTime.modified(); diff --git a/Sources/Rendering/WebGPU/VolumePass/index.js b/Sources/Rendering/WebGPU/VolumePass/index.js index d84b0008625..dd16aa39f9d 100644 --- a/Sources/Rendering/WebGPU/VolumePass/index.js +++ b/Sources/Rendering/WebGPU/VolumePass/index.js @@ -629,7 +629,7 @@ function vtkWebGPUVolumePass(publicAPI, model) { fragment: { targets: [ { - format: 'bgra8unorm', + format: 'rgba16float', blend: { color: { srcFactor: 'one',