Buckets:
| 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<Object>} 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<Object>} 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; | |
Xet Storage Details
- Size:
- 10.5 kB
- Xet hash:
- d088af618032af73032303940df835fefe8e5c9d075551c6c4955f0a40d0b35b
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.