MCP 3D Printer Server

by DMontgomery40
Verified
import DataMap from '../../common/DataMap.js'; import { GPUTextureViewDimension, GPUIndexFormat, GPUFilterMode, GPUPrimitiveTopology, GPULoadOp, GPUStoreOp } from './WebGPUConstants.js'; /** * A WebGPU backend utility module used by {@link WebGPUTextureUtils}. * * @private */ class WebGPUTexturePassUtils extends DataMap { /** * Constructs a new utility object. * * @param {GPUDevice} device - The WebGPU device. */ constructor( device ) { super(); /** * The WebGPU device. * * @type {GPUDevice} */ this.device = device; const mipmapVertexSource = ` struct VarysStruct { @builtin( position ) Position: vec4<f32>, @location( 0 ) vTex : vec2<f32> }; @vertex fn main( @builtin( vertex_index ) vertexIndex : u32 ) -> VarysStruct { var Varys : VarysStruct; var pos = array< vec2<f32>, 4 >( vec2<f32>( -1.0, 1.0 ), vec2<f32>( 1.0, 1.0 ), vec2<f32>( -1.0, -1.0 ), vec2<f32>( 1.0, -1.0 ) ); var tex = array< vec2<f32>, 4 >( vec2<f32>( 0.0, 0.0 ), vec2<f32>( 1.0, 0.0 ), vec2<f32>( 0.0, 1.0 ), vec2<f32>( 1.0, 1.0 ) ); Varys.vTex = tex[ vertexIndex ]; Varys.Position = vec4<f32>( pos[ vertexIndex ], 0.0, 1.0 ); return Varys; } `; const mipmapFragmentSource = ` @group( 0 ) @binding( 0 ) var imgSampler : sampler; @group( 0 ) @binding( 1 ) var img : texture_2d<f32>; @fragment fn main( @location( 0 ) vTex : vec2<f32> ) -> @location( 0 ) vec4<f32> { return textureSample( img, imgSampler, vTex ); } `; const flipYFragmentSource = ` @group( 0 ) @binding( 0 ) var imgSampler : sampler; @group( 0 ) @binding( 1 ) var img : texture_2d<f32>; @fragment fn main( @location( 0 ) vTex : vec2<f32> ) -> @location( 0 ) vec4<f32> { return textureSample( img, imgSampler, vec2( vTex.x, 1.0 - vTex.y ) ); } `; /** * The mipmap GPU sampler. * * @type {GPUSampler} */ this.mipmapSampler = device.createSampler( { minFilter: GPUFilterMode.Linear } ); /** * The flipY GPU sampler. * * @type {GPUSampler} */ this.flipYSampler = device.createSampler( { minFilter: GPUFilterMode.Nearest } ); //@TODO?: Consider using textureLoad() /** * A cache for GPU render pipelines used for copy/transfer passes. * Every texture format requires a unique pipeline. * * @type {Object<String,GPURenderPipeline>} */ this.transferPipelines = {}; /** * A cache for GPU render pipelines used for flipY passes. * Every texture format requires a unique pipeline. * * @type {Object<String,GPURenderPipeline>} */ this.flipYPipelines = {}; /** * The mipmap vertex shader module. * * @type {GPUShaderModule} */ this.mipmapVertexShaderModule = device.createShaderModule( { label: 'mipmapVertex', code: mipmapVertexSource } ); /** * The mipmap fragment shader module. * * @type {GPUShaderModule} */ this.mipmapFragmentShaderModule = device.createShaderModule( { label: 'mipmapFragment', code: mipmapFragmentSource } ); /** * The flipY fragment shader module. * * @type {GPUShaderModule} */ this.flipYFragmentShaderModule = device.createShaderModule( { label: 'flipYFragment', code: flipYFragmentSource } ); } /** * Returns a render pipeline for the internal copy render pass. The pass * requires a unique render pipeline for each texture format. * * @param {String} format - The GPU texture format * @return {GPURenderPipeline} The GPU render pipeline. */ getTransferPipeline( format ) { let pipeline = this.transferPipelines[ format ]; if ( pipeline === undefined ) { pipeline = this.device.createRenderPipeline( { label: `mipmap-${ format }`, vertex: { module: this.mipmapVertexShaderModule, entryPoint: 'main' }, fragment: { module: this.mipmapFragmentShaderModule, entryPoint: 'main', targets: [ { format } ] }, primitive: { topology: GPUPrimitiveTopology.TriangleStrip, stripIndexFormat: GPUIndexFormat.Uint32 }, layout: 'auto' } ); this.transferPipelines[ format ] = pipeline; } return pipeline; } /** * Returns a render pipeline for the flipY render pass. The pass * requires a unique render pipeline for each texture format. * * @param {String} format - The GPU texture format * @return {GPURenderPipeline} The GPU render pipeline. */ getFlipYPipeline( format ) { let pipeline = this.flipYPipelines[ format ]; if ( pipeline === undefined ) { pipeline = this.device.createRenderPipeline( { label: `flipY-${ format }`, vertex: { module: this.mipmapVertexShaderModule, entryPoint: 'main' }, fragment: { module: this.flipYFragmentShaderModule, entryPoint: 'main', targets: [ { format } ] }, primitive: { topology: GPUPrimitiveTopology.TriangleStrip, stripIndexFormat: GPUIndexFormat.Uint32 }, layout: 'auto' } ); this.flipYPipelines[ format ] = pipeline; } return pipeline; } /** * Flip the contents of the given GPU texture along its vertical axis. * * @param {GPUTexture} textureGPU - The GPU texture object. * @param {Object} textureGPUDescriptor - The texture descriptor. * @param {Number} [baseArrayLayer=0] - The index of the first array layer accessible to the texture view. */ flipY( textureGPU, textureGPUDescriptor, baseArrayLayer = 0 ) { const format = textureGPUDescriptor.format; const { width, height } = textureGPUDescriptor.size; const transferPipeline = this.getTransferPipeline( format ); const flipYPipeline = this.getFlipYPipeline( format ); const tempTexture = this.device.createTexture( { size: { width, height, depthOrArrayLayers: 1 }, format, usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING } ); const srcView = textureGPU.createView( { baseMipLevel: 0, mipLevelCount: 1, dimension: GPUTextureViewDimension.TwoD, baseArrayLayer } ); const dstView = tempTexture.createView( { baseMipLevel: 0, mipLevelCount: 1, dimension: GPUTextureViewDimension.TwoD, baseArrayLayer: 0 } ); const commandEncoder = this.device.createCommandEncoder( {} ); const pass = ( pipeline, sourceView, destinationView ) => { const bindGroupLayout = pipeline.getBindGroupLayout( 0 ); // @TODO: Consider making this static. const bindGroup = this.device.createBindGroup( { layout: bindGroupLayout, entries: [ { binding: 0, resource: this.flipYSampler }, { binding: 1, resource: sourceView } ] } ); const passEncoder = commandEncoder.beginRenderPass( { colorAttachments: [ { view: destinationView, loadOp: GPULoadOp.Clear, storeOp: GPUStoreOp.Store, clearValue: [ 0, 0, 0, 0 ] } ] } ); passEncoder.setPipeline( pipeline ); passEncoder.setBindGroup( 0, bindGroup ); passEncoder.draw( 4, 1, 0, 0 ); passEncoder.end(); }; pass( transferPipeline, srcView, dstView ); pass( flipYPipeline, dstView, srcView ); this.device.queue.submit( [ commandEncoder.finish() ] ); tempTexture.destroy(); } /** * Generates mipmaps for the given GPU texture. * * @param {GPUTexture} textureGPU - The GPU texture object. * @param {Object} textureGPUDescriptor - The texture descriptor. * @param {Number} [baseArrayLayer=0] - The index of the first array layer accessible to the texture view. */ generateMipmaps( textureGPU, textureGPUDescriptor, baseArrayLayer = 0 ) { const textureData = this.get( textureGPU ); if ( textureData.useCount === undefined ) { textureData.useCount = 0; textureData.layers = []; } const passes = textureData.layers[ baseArrayLayer ] || this._mipmapCreateBundles( textureGPU, textureGPUDescriptor, baseArrayLayer ); const commandEncoder = this.device.createCommandEncoder( {} ); this._mipmapRunBundles( commandEncoder, passes ); this.device.queue.submit( [ commandEncoder.finish() ] ); if ( textureData.useCount !== 0 ) textureData.layers[ baseArrayLayer ] = passes; textureData.useCount ++; } /** * Since multiple copy render passes are required to generate mipmaps, the passes * are managed as render bundles to improve performance. * * @param {GPUTexture} textureGPU - The GPU texture object. * @param {Object} textureGPUDescriptor - The texture descriptor. * @param {Number} baseArrayLayer - The index of the first array layer accessible to the texture view. * @return {Array} An array of render bundles. */ _mipmapCreateBundles( textureGPU, textureGPUDescriptor, baseArrayLayer ) { const pipeline = this.getTransferPipeline( textureGPUDescriptor.format ); const bindGroupLayout = pipeline.getBindGroupLayout( 0 ); // @TODO: Consider making this static. let srcView = textureGPU.createView( { baseMipLevel: 0, mipLevelCount: 1, dimension: GPUTextureViewDimension.TwoD, baseArrayLayer } ); const passes = []; for ( let i = 1; i < textureGPUDescriptor.mipLevelCount; i ++ ) { const bindGroup = this.device.createBindGroup( { layout: bindGroupLayout, entries: [ { binding: 0, resource: this.mipmapSampler }, { binding: 1, resource: srcView } ] } ); const dstView = textureGPU.createView( { baseMipLevel: i, mipLevelCount: 1, dimension: GPUTextureViewDimension.TwoD, baseArrayLayer } ); const passDescriptor = { colorAttachments: [ { view: dstView, loadOp: GPULoadOp.Clear, storeOp: GPUStoreOp.Store, clearValue: [ 0, 0, 0, 0 ] } ] }; const passEncoder = this.device.createRenderBundleEncoder( { colorFormats: [ textureGPUDescriptor.format ] } ); passEncoder.setPipeline( pipeline ); passEncoder.setBindGroup( 0, bindGroup ); passEncoder.draw( 4, 1, 0, 0 ); passes.push( { renderBundles: [ passEncoder.finish() ], passDescriptor } ); srcView = dstView; } return passes; } /** * Executes the render bundles. * * @param {GPUCommandEncoder} commandEncoder - The GPU command encoder. * @param {Array} passes - An array of render bundles. */ _mipmapRunBundles( commandEncoder, passes ) { const levels = passes.length; for ( let i = 0; i < levels; i ++ ) { const pass = passes[ i ]; const passEncoder = commandEncoder.beginRenderPass( pass.passDescriptor ); passEncoder.executeBundles( pass.renderBundles ); passEncoder.end(); } } } export default WebGPUTexturePassUtils;