From e2f59117296b54357719731742c4b118d135732e Mon Sep 17 00:00:00 2001 From: sunag Date: Mon, 25 Sep 2023 23:45:33 -0300 Subject: [PATCH] WebGPURenderer: GPU FlipY (#26818) * GPU FlipY * use tempTexture.destroy() * fix name * Rename to `WebGPUTexturePassUtils` and `_getPassUtils()` --- .../webgpu/utils/WebGPUTextureMipmapUtils.js | 163 ---------- .../webgpu/utils/WebGPUTexturePassUtils.js | 285 ++++++++++++++++++ .../webgpu/utils/WebGPUTextureUtils.js | 58 +++- 3 files changed, 329 insertions(+), 177 deletions(-) delete mode 100644 examples/jsm/renderers/webgpu/utils/WebGPUTextureMipmapUtils.js create mode 100644 examples/jsm/renderers/webgpu/utils/WebGPUTexturePassUtils.js diff --git a/examples/jsm/renderers/webgpu/utils/WebGPUTextureMipmapUtils.js b/examples/jsm/renderers/webgpu/utils/WebGPUTextureMipmapUtils.js deleted file mode 100644 index c638581db20d3f..00000000000000 --- a/examples/jsm/renderers/webgpu/utils/WebGPUTextureMipmapUtils.js +++ /dev/null @@ -1,163 +0,0 @@ -import { GPUTextureViewDimension, GPUIndexFormat, GPUFilterMode, GPUPrimitiveTopology, GPULoadOp, GPUStoreOp } from './WebGPUConstants.js'; - -class WebGPUTextureMipmapUtils { - - constructor( device ) { - - this.device = device; - - const mipmapVertexSource = ` -struct VarysStruct { - @builtin( position ) Position: vec4, - @location( 0 ) vTex : vec2 -}; - -@vertex -fn main( @builtin( vertex_index ) vertexIndex : u32 ) -> VarysStruct { - - var Varys : VarysStruct; - - var pos = array< vec2, 4 >( - vec2( -1.0, 1.0 ), - vec2( 1.0, 1.0 ), - vec2( -1.0, -1.0 ), - vec2( 1.0, -1.0 ) - ); - - var tex = array< vec2, 4 >( - vec2( 0.0, 0.0 ), - vec2( 1.0, 0.0 ), - vec2( 0.0, 1.0 ), - vec2( 1.0, 1.0 ) - ); - - Varys.vTex = tex[ vertexIndex ]; - Varys.Position = vec4( 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; - -@fragment -fn main( @location( 0 ) vTex : vec2 ) -> @location( 0 ) vec4 { - - return textureSample( img, imgSampler, vTex ); - -} -`; - - this.sampler = device.createSampler( { minFilter: GPUFilterMode.Linear } ); - - // We'll need a new pipeline for every texture format used. - this.pipelines = {}; - - this.mipmapVertexShaderModule = device.createShaderModule( { - label: 'mipmapVertex', - code: mipmapVertexSource - } ); - - this.mipmapFragmentShaderModule = device.createShaderModule( { - label: 'mipmapFragment', - code: mipmapFragmentSource - } ); - - } - - getMipmapPipeline( format ) { - - let pipeline = this.pipelines[ format ]; - - if ( pipeline === undefined ) { - - pipeline = this.device.createRenderPipeline( { - vertex: { - module: this.mipmapVertexShaderModule, - entryPoint: 'main' - }, - fragment: { - module: this.mipmapFragmentShaderModule, - entryPoint: 'main', - targets: [ { format } ] - }, - primitive: { - topology: GPUPrimitiveTopology.TriangleStrip, - stripIndexFormat: GPUIndexFormat.Uint32 - }, - layout: 'auto' - } ); - - this.pipelines[ format ] = pipeline; - - } - - return pipeline; - - } - - generateMipmaps( textureGPU, textureGPUDescriptor, baseArrayLayer = 0 ) { - - const pipeline = this.getMipmapPipeline( textureGPUDescriptor.format ); - - const commandEncoder = this.device.createCommandEncoder( {} ); - const bindGroupLayout = pipeline.getBindGroupLayout( 0 ); // @TODO: Consider making this static. - - let srcView = textureGPU.createView( { - baseMipLevel: 0, - mipLevelCount: 1, - dimension: GPUTextureViewDimension.TwoD, - baseArrayLayer - } ); - - for ( let i = 1; i < textureGPUDescriptor.mipLevelCount; i ++ ) { - - const dstView = textureGPU.createView( { - baseMipLevel: i, - mipLevelCount: 1, - dimension: GPUTextureViewDimension.TwoD, - baseArrayLayer - } ); - - const passEncoder = commandEncoder.beginRenderPass( { - colorAttachments: [ { - view: dstView, - loadOp: GPULoadOp.Clear, - storeOp: GPUStoreOp.Store, - clearValue: [ 0, 0, 0, 0 ] - } ] - } ); - - const bindGroup = this.device.createBindGroup( { - layout: bindGroupLayout, - entries: [ { - binding: 0, - resource: this.sampler - }, { - binding: 1, - resource: srcView - } ] - } ); - - passEncoder.setPipeline( pipeline ); - passEncoder.setBindGroup( 0, bindGroup ); - passEncoder.draw( 4, 1, 0, 0 ); - passEncoder.end(); - - srcView = dstView; - - } - - this.device.queue.submit( [ commandEncoder.finish() ] ); - - } - -} - -export default WebGPUTextureMipmapUtils; diff --git a/examples/jsm/renderers/webgpu/utils/WebGPUTexturePassUtils.js b/examples/jsm/renderers/webgpu/utils/WebGPUTexturePassUtils.js new file mode 100644 index 00000000000000..d46e43715ea722 --- /dev/null +++ b/examples/jsm/renderers/webgpu/utils/WebGPUTexturePassUtils.js @@ -0,0 +1,285 @@ +import { GPUTextureViewDimension, GPUIndexFormat, GPUFilterMode, GPUPrimitiveTopology, GPULoadOp, GPUStoreOp } from './WebGPUConstants.js'; + +class WebGPUTexturePassUtils { + + constructor( device ) { + + this.device = device; + + const mipmapVertexSource = ` +struct VarysStruct { + @builtin( position ) Position: vec4, + @location( 0 ) vTex : vec2 +}; + +@vertex +fn main( @builtin( vertex_index ) vertexIndex : u32 ) -> VarysStruct { + + var Varys : VarysStruct; + + var pos = array< vec2, 4 >( + vec2( -1.0, 1.0 ), + vec2( 1.0, 1.0 ), + vec2( -1.0, -1.0 ), + vec2( 1.0, -1.0 ) + ); + + var tex = array< vec2, 4 >( + vec2( 0.0, 0.0 ), + vec2( 1.0, 0.0 ), + vec2( 0.0, 1.0 ), + vec2( 1.0, 1.0 ) + ); + + Varys.vTex = tex[ vertexIndex ]; + Varys.Position = vec4( 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; + +@fragment +fn main( @location( 0 ) vTex : vec2 ) -> @location( 0 ) vec4 { + + return textureSample( img, imgSampler, vTex ); + +} +`; + + const flipYFragmentSource = ` +@group( 0 ) @binding( 0 ) +var imgSampler : sampler; + +@group( 0 ) @binding( 1 ) +var img : texture_2d; + +@fragment +fn main( @location( 0 ) vTex : vec2 ) -> @location( 0 ) vec4 { + + return textureSample( img, imgSampler, vec2( vTex.x, 1.0 - vTex.y ) ); + +} +`; + this.mipmapSampler = device.createSampler( { minFilter: GPUFilterMode.Linear } ); + this.flipYSampler = device.createSampler( { minFilter: GPUFilterMode.Nearest } ); //@TODO?: Consider using textureLoad() + + // We'll need a new pipeline for every texture format used. + this.transferPipelines = {}; + this.flipYPipelines = {}; + + this.mipmapVertexShaderModule = device.createShaderModule( { + label: 'mipmapVertex', + code: mipmapVertexSource + } ); + + this.mipmapFragmentShaderModule = device.createShaderModule( { + label: 'mipmapFragment', + code: mipmapFragmentSource + } ); + + this.flipYFragmentShaderModule = device.createShaderModule( { + label: 'flipYFragment', + code: flipYFragmentSource + } ); + + } + + getTransferPipeline( format ) { + + let pipeline = this.transferPipelines[ format ]; + + if ( pipeline === undefined ) { + + pipeline = this.device.createRenderPipeline( { + 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; + + } + + getFlipYPipeline( format ) { + + let pipeline = this.flipYPipelines[ format ]; + + if ( pipeline === undefined ) { + + pipeline = this.device.createRenderPipeline( { + 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; + + } + + 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(); + + } + + generateMipmaps( textureGPU, textureGPUDescriptor, baseArrayLayer = 0 ) { + + const pipeline = this.getTransferPipeline( textureGPUDescriptor.format ); + + const commandEncoder = this.device.createCommandEncoder( {} ); + const bindGroupLayout = pipeline.getBindGroupLayout( 0 ); // @TODO: Consider making this static. + + let srcView = textureGPU.createView( { + baseMipLevel: 0, + mipLevelCount: 1, + dimension: GPUTextureViewDimension.TwoD, + baseArrayLayer + } ); + + 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 passEncoder = commandEncoder.beginRenderPass( { + colorAttachments: [ { + view: dstView, + 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(); + + srcView = dstView; + + } + + this.device.queue.submit( [ commandEncoder.finish() ] ); + + } + +} + +export default WebGPUTexturePassUtils; diff --git a/examples/jsm/renderers/webgpu/utils/WebGPUTextureUtils.js b/examples/jsm/renderers/webgpu/utils/WebGPUTextureUtils.js index 549021f9904027..d81dd92e092dee 100644 --- a/examples/jsm/renderers/webgpu/utils/WebGPUTextureUtils.js +++ b/examples/jsm/renderers/webgpu/utils/WebGPUTextureUtils.js @@ -15,7 +15,7 @@ import { import { CubeReflectionMapping, CubeRefractionMapping, EquirectangularReflectionMapping, EquirectangularRefractionMapping } from 'three'; -import WebGPUTextureMipmapUtils from './WebGPUTextureMipmapUtils.js'; +import WebGPUTexturePassUtils from './WebGPUTexturePassUtils.js'; const _compareToWebGPU = { [ NeverCompare ]: 'never', @@ -28,13 +28,15 @@ const _compareToWebGPU = { [ NotEqualCompare ]: 'not-equal' }; +const _flipMap = [ 0, 1, 3, 2, 4, 5 ]; + class WebGPUTextureUtils { constructor( backend ) { this.backend = backend; - this.mipmapUtils = null; + this._passUtils = null; this.defaultTexture = null; this.defaultCubeTexture = null; @@ -237,7 +239,7 @@ class WebGPUTextureUtils { if ( texture.isDataTexture || texture.isDataArrayTexture || texture.isData3DTexture ) { - this._copyBufferToTexture( options.image, textureData.texture, textureDescriptorGPU ); + this._copyBufferToTexture( options.image, textureData.texture, textureDescriptorGPU, 0, texture.flipY ); } else if ( texture.isCompressedTexture ) { @@ -245,7 +247,7 @@ class WebGPUTextureUtils { } else if ( texture.isCubeTexture ) { - this._copyCubeMapToTexture( options.images, texture, textureData.texture, textureDescriptorGPU ); + this._copyCubeMapToTexture( options.images, textureData.texture, textureDescriptorGPU, texture.flipY ); } else if ( texture.isVideoTexture ) { @@ -255,7 +257,7 @@ class WebGPUTextureUtils { } else { - this._copyImageToTexture( options.image, textureData.texture ); + this._copyImageToTexture( options.image, textureData.texture, textureDescriptorGPU, 0, texture.flipY ); } @@ -361,19 +363,21 @@ class WebGPUTextureUtils { } - _copyCubeMapToTexture( images, texture, textureGPU, textureDescriptorGPU ) { + _copyCubeMapToTexture( images, textureGPU, textureDescriptorGPU, flipY ) { for ( let i = 0; i < 6; i ++ ) { const image = images[ i ]; + const flipIndex = flipY === true ? _flipMap[ i ] : i; + if ( image.isDataTexture ) { - this._copyBufferToTexture( image.image, textureGPU, textureDescriptorGPU, i ); + this._copyBufferToTexture( image.image, textureGPU, textureDescriptorGPU, flipIndex, flipY ); } else { - this._copyImageToTexture( image, textureGPU, i ); + this._copyImageToTexture( image, textureGPU, textureDescriptorGPU, flipIndex, flipY ); } @@ -381,7 +385,7 @@ class WebGPUTextureUtils { } - _copyImageToTexture( image, textureGPU, originDepth = 0 ) { + _copyImageToTexture( image, textureGPU, textureDescriptorGPU, originDepth, flipY ) { const device = this.backend.device; @@ -399,21 +403,41 @@ class WebGPUTextureUtils { } ); + if ( flipY === true ) { + + this._flipY( textureGPU, textureDescriptorGPU, originDepth ); + + } + } - _generateMipmaps( textureGPU, textureDescriptorGPU, baseArrayLayer = 0 ) { + _getPassUtils() { + + let passUtils = this._passUtils; - if ( this.mipmapUtils === null ) { + if ( passUtils === null ) { - this.mipmapUtils = new WebGPUTextureMipmapUtils( this.backend.device ); + this._passUtils = passUtils = new WebGPUTexturePassUtils( this.backend.device ); } - this.mipmapUtils.generateMipmaps( textureGPU, textureDescriptorGPU, baseArrayLayer ); + return passUtils; } - _copyBufferToTexture( image, textureGPU, textureDescriptorGPU, originDepth = 0 ) { + _generateMipmaps( textureGPU, textureDescriptorGPU, baseArrayLayer = 0 ) { + + this._getPassUtils().generateMipmaps( textureGPU, textureDescriptorGPU, baseArrayLayer ); + + } + + _flipY( textureGPU, textureDescriptorGPU, originDepth = 0 ) { + + this._getPassUtils().flipY( textureGPU, textureDescriptorGPU, originDepth ); + + } + + _copyBufferToTexture( image, textureGPU, textureDescriptorGPU, originDepth, flipY ) { // @TODO: Consider to use GPUCommandEncoder.copyBufferToTexture() // @TODO: Consider to support valid buffer layouts with other formats like RGB @@ -442,6 +466,12 @@ class WebGPUTextureUtils { depthOrArrayLayers: ( image.depth !== undefined ) ? image.depth : 1 } ); + if ( flipY === true ) { + + this._flipY( textureGPU, textureDescriptorGPU, originDepth ); + + } + } _copyCompressedBufferToTexture( mipmaps, textureGPU, textureDescriptorGPU ) {