WebGPUTexturePassUtils.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. import { GPUTextureViewDimension, GPUIndexFormat, GPUFilterMode, GPUPrimitiveTopology, GPULoadOp, GPUStoreOp } from './WebGPUConstants.js';
  2. class WebGPUTexturePassUtils {
  3. constructor( device ) {
  4. this.device = device;
  5. const mipmapVertexSource = `
  6. struct VarysStruct {
  7. @builtin( position ) Position: vec4<f32>,
  8. @location( 0 ) vTex : vec2<f32>
  9. };
  10. @vertex
  11. fn main( @builtin( vertex_index ) vertexIndex : u32 ) -> VarysStruct {
  12. var Varys : VarysStruct;
  13. var pos = array< vec2<f32>, 4 >(
  14. vec2<f32>( -1.0, 1.0 ),
  15. vec2<f32>( 1.0, 1.0 ),
  16. vec2<f32>( -1.0, -1.0 ),
  17. vec2<f32>( 1.0, -1.0 )
  18. );
  19. var tex = array< vec2<f32>, 4 >(
  20. vec2<f32>( 0.0, 0.0 ),
  21. vec2<f32>( 1.0, 0.0 ),
  22. vec2<f32>( 0.0, 1.0 ),
  23. vec2<f32>( 1.0, 1.0 )
  24. );
  25. Varys.vTex = tex[ vertexIndex ];
  26. Varys.Position = vec4<f32>( pos[ vertexIndex ], 0.0, 1.0 );
  27. return Varys;
  28. }
  29. `;
  30. const mipmapFragmentSource = `
  31. @group( 0 ) @binding( 0 )
  32. var imgSampler : sampler;
  33. @group( 0 ) @binding( 1 )
  34. var img : texture_2d<f32>;
  35. @fragment
  36. fn main( @location( 0 ) vTex : vec2<f32> ) -> @location( 0 ) vec4<f32> {
  37. return textureSample( img, imgSampler, vTex );
  38. }
  39. `;
  40. const flipYFragmentSource = `
  41. @group( 0 ) @binding( 0 )
  42. var imgSampler : sampler;
  43. @group( 0 ) @binding( 1 )
  44. var img : texture_2d<f32>;
  45. @fragment
  46. fn main( @location( 0 ) vTex : vec2<f32> ) -> @location( 0 ) vec4<f32> {
  47. return textureSample( img, imgSampler, vec2( vTex.x, 1.0 - vTex.y ) );
  48. }
  49. `;
  50. this.mipmapSampler = device.createSampler( { minFilter: GPUFilterMode.Linear } );
  51. this.flipYSampler = device.createSampler( { minFilter: GPUFilterMode.Nearest } ); //@TODO?: Consider using textureLoad()
  52. // We'll need a new pipeline for every texture format used.
  53. this.transferPipelines = {};
  54. this.flipYPipelines = {};
  55. this.mipmapVertexShaderModule = device.createShaderModule( {
  56. label: 'mipmapVertex',
  57. code: mipmapVertexSource
  58. } );
  59. this.mipmapFragmentShaderModule = device.createShaderModule( {
  60. label: 'mipmapFragment',
  61. code: mipmapFragmentSource
  62. } );
  63. this.flipYFragmentShaderModule = device.createShaderModule( {
  64. label: 'flipYFragment',
  65. code: flipYFragmentSource
  66. } );
  67. }
  68. getTransferPipeline( format ) {
  69. let pipeline = this.transferPipelines[ format ];
  70. if ( pipeline === undefined ) {
  71. pipeline = this.device.createRenderPipeline( {
  72. vertex: {
  73. module: this.mipmapVertexShaderModule,
  74. entryPoint: 'main'
  75. },
  76. fragment: {
  77. module: this.mipmapFragmentShaderModule,
  78. entryPoint: 'main',
  79. targets: [ { format } ]
  80. },
  81. primitive: {
  82. topology: GPUPrimitiveTopology.TriangleStrip,
  83. stripIndexFormat: GPUIndexFormat.Uint32
  84. },
  85. layout: 'auto'
  86. } );
  87. this.transferPipelines[ format ] = pipeline;
  88. }
  89. return pipeline;
  90. }
  91. getFlipYPipeline( format ) {
  92. let pipeline = this.flipYPipelines[ format ];
  93. if ( pipeline === undefined ) {
  94. pipeline = this.device.createRenderPipeline( {
  95. vertex: {
  96. module: this.mipmapVertexShaderModule,
  97. entryPoint: 'main'
  98. },
  99. fragment: {
  100. module: this.flipYFragmentShaderModule,
  101. entryPoint: 'main',
  102. targets: [ { format } ]
  103. },
  104. primitive: {
  105. topology: GPUPrimitiveTopology.TriangleStrip,
  106. stripIndexFormat: GPUIndexFormat.Uint32
  107. },
  108. layout: 'auto'
  109. } );
  110. this.flipYPipelines[ format ] = pipeline;
  111. }
  112. return pipeline;
  113. }
  114. flipY( textureGPU, textureGPUDescriptor, baseArrayLayer = 0 ) {
  115. const format = textureGPUDescriptor.format;
  116. const { width, height } = textureGPUDescriptor.size;
  117. const transferPipeline = this.getTransferPipeline( format );
  118. const flipYPipeline = this.getFlipYPipeline( format );
  119. const tempTexture = this.device.createTexture( {
  120. size: { width, height, depthOrArrayLayers: 1 },
  121. format,
  122. usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
  123. } );
  124. const srcView = textureGPU.createView( {
  125. baseMipLevel: 0,
  126. mipLevelCount: 1,
  127. dimension: GPUTextureViewDimension.TwoD,
  128. baseArrayLayer
  129. } );
  130. const dstView = tempTexture.createView( {
  131. baseMipLevel: 0,
  132. mipLevelCount: 1,
  133. dimension: GPUTextureViewDimension.TwoD,
  134. baseArrayLayer: 0
  135. } );
  136. const commandEncoder = this.device.createCommandEncoder( {} );
  137. const pass = ( pipeline, sourceView, destinationView ) => {
  138. const bindGroupLayout = pipeline.getBindGroupLayout( 0 ); // @TODO: Consider making this static.
  139. const bindGroup = this.device.createBindGroup( {
  140. layout: bindGroupLayout,
  141. entries: [ {
  142. binding: 0,
  143. resource: this.flipYSampler
  144. }, {
  145. binding: 1,
  146. resource: sourceView
  147. } ]
  148. } );
  149. const passEncoder = commandEncoder.beginRenderPass( {
  150. colorAttachments: [ {
  151. view: destinationView,
  152. loadOp: GPULoadOp.Clear,
  153. storeOp: GPUStoreOp.Store,
  154. clearValue: [ 0, 0, 0, 0 ]
  155. } ]
  156. } );
  157. passEncoder.setPipeline( pipeline );
  158. passEncoder.setBindGroup( 0, bindGroup );
  159. passEncoder.draw( 4, 1, 0, 0 );
  160. passEncoder.end();
  161. };
  162. pass( transferPipeline, srcView, dstView );
  163. pass( flipYPipeline, dstView, srcView );
  164. this.device.queue.submit( [ commandEncoder.finish() ] );
  165. tempTexture.destroy();
  166. }
  167. generateMipmaps( textureGPU, textureGPUDescriptor, baseArrayLayer = 0 ) {
  168. const pipeline = this.getTransferPipeline( textureGPUDescriptor.format );
  169. const commandEncoder = this.device.createCommandEncoder( {} );
  170. const bindGroupLayout = pipeline.getBindGroupLayout( 0 ); // @TODO: Consider making this static.
  171. let srcView = textureGPU.createView( {
  172. baseMipLevel: 0,
  173. mipLevelCount: 1,
  174. dimension: GPUTextureViewDimension.TwoD,
  175. baseArrayLayer
  176. } );
  177. for ( let i = 1; i < textureGPUDescriptor.mipLevelCount; i ++ ) {
  178. const bindGroup = this.device.createBindGroup( {
  179. layout: bindGroupLayout,
  180. entries: [ {
  181. binding: 0,
  182. resource: this.mipmapSampler
  183. }, {
  184. binding: 1,
  185. resource: srcView
  186. } ]
  187. } );
  188. const dstView = textureGPU.createView( {
  189. baseMipLevel: i,
  190. mipLevelCount: 1,
  191. dimension: GPUTextureViewDimension.TwoD,
  192. baseArrayLayer
  193. } );
  194. const passEncoder = commandEncoder.beginRenderPass( {
  195. colorAttachments: [ {
  196. view: dstView,
  197. loadOp: GPULoadOp.Clear,
  198. storeOp: GPUStoreOp.Store,
  199. clearValue: [ 0, 0, 0, 0 ]
  200. } ]
  201. } );
  202. passEncoder.setPipeline( pipeline );
  203. passEncoder.setBindGroup( 0, bindGroup );
  204. passEncoder.draw( 4, 1, 0, 0 );
  205. passEncoder.end();
  206. srcView = dstView;
  207. }
  208. this.device.queue.submit( [ commandEncoder.finish() ] );
  209. }
  210. }
  211. export default WebGPUTexturePassUtils;