Skip to content

Commit e2f5911

Browse files
authoredSep 26, 2023
WebGPURenderer: GPU FlipY (#26818)
* GPU FlipY * use tempTexture.destroy() * fix name * Rename to `WebGPUTexturePassUtils` and `_getPassUtils()`
1 parent 0f65abb commit e2f5911

File tree

3 files changed

+329
-177
lines changed

3 files changed

+329
-177
lines changed
 

‎examples/jsm/renderers/webgpu/utils/WebGPUTextureMipmapUtils.js

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

‎examples/jsm/renderers/webgpu/utils/WebGPUTextureUtils.js

+44-14
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515

1616
import { CubeReflectionMapping, CubeRefractionMapping, EquirectangularReflectionMapping, EquirectangularRefractionMapping } from 'three';
1717

18-
import WebGPUTextureMipmapUtils from './WebGPUTextureMipmapUtils.js';
18+
import WebGPUTexturePassUtils from './WebGPUTexturePassUtils.js';
1919

2020
const _compareToWebGPU = {
2121
[ NeverCompare ]: 'never',
@@ -28,13 +28,15 @@ const _compareToWebGPU = {
2828
[ NotEqualCompare ]: 'not-equal'
2929
};
3030

31+
const _flipMap = [ 0, 1, 3, 2, 4, 5 ];
32+
3133
class WebGPUTextureUtils {
3234

3335
constructor( backend ) {
3436

3537
this.backend = backend;
3638

37-
this.mipmapUtils = null;
39+
this._passUtils = null;
3840

3941
this.defaultTexture = null;
4042
this.defaultCubeTexture = null;
@@ -237,15 +239,15 @@ class WebGPUTextureUtils {
237239

238240
if ( texture.isDataTexture || texture.isDataArrayTexture || texture.isData3DTexture ) {
239241

240-
this._copyBufferToTexture( options.image, textureData.texture, textureDescriptorGPU );
242+
this._copyBufferToTexture( options.image, textureData.texture, textureDescriptorGPU, 0, texture.flipY );
241243

242244
} else if ( texture.isCompressedTexture ) {
243245

244246
this._copyCompressedBufferToTexture( texture.mipmaps, textureData.texture, textureDescriptorGPU );
245247

246248
} else if ( texture.isCubeTexture ) {
247249

248-
this._copyCubeMapToTexture( options.images, texture, textureData.texture, textureDescriptorGPU );
250+
this._copyCubeMapToTexture( options.images, textureData.texture, textureDescriptorGPU, texture.flipY );
249251

250252
} else if ( texture.isVideoTexture ) {
251253

@@ -255,7 +257,7 @@ class WebGPUTextureUtils {
255257

256258
} else {
257259

258-
this._copyImageToTexture( options.image, textureData.texture );
260+
this._copyImageToTexture( options.image, textureData.texture, textureDescriptorGPU, 0, texture.flipY );
259261

260262
}
261263

@@ -361,27 +363,29 @@ class WebGPUTextureUtils {
361363

362364
}
363365

364-
_copyCubeMapToTexture( images, texture, textureGPU, textureDescriptorGPU ) {
366+
_copyCubeMapToTexture( images, textureGPU, textureDescriptorGPU, flipY ) {
365367

366368
for ( let i = 0; i < 6; i ++ ) {
367369

368370
const image = images[ i ];
369371

372+
const flipIndex = flipY === true ? _flipMap[ i ] : i;
373+
370374
if ( image.isDataTexture ) {
371375

372-
this._copyBufferToTexture( image.image, textureGPU, textureDescriptorGPU, i );
376+
this._copyBufferToTexture( image.image, textureGPU, textureDescriptorGPU, flipIndex, flipY );
373377

374378
} else {
375379

376-
this._copyImageToTexture( image, textureGPU, i );
380+
this._copyImageToTexture( image, textureGPU, textureDescriptorGPU, flipIndex, flipY );
377381

378382
}
379383

380384
}
381385

382386
}
383387

384-
_copyImageToTexture( image, textureGPU, originDepth = 0 ) {
388+
_copyImageToTexture( image, textureGPU, textureDescriptorGPU, originDepth, flipY ) {
385389

386390
const device = this.backend.device;
387391

@@ -399,21 +403,41 @@ class WebGPUTextureUtils {
399403
}
400404
);
401405

406+
if ( flipY === true ) {
407+
408+
this._flipY( textureGPU, textureDescriptorGPU, originDepth );
409+
410+
}
411+
402412
}
403413

404-
_generateMipmaps( textureGPU, textureDescriptorGPU, baseArrayLayer = 0 ) {
414+
_getPassUtils() {
415+
416+
let passUtils = this._passUtils;
405417

406-
if ( this.mipmapUtils === null ) {
418+
if ( passUtils === null ) {
407419

408-
this.mipmapUtils = new WebGPUTextureMipmapUtils( this.backend.device );
420+
this._passUtils = passUtils = new WebGPUTexturePassUtils( this.backend.device );
409421

410422
}
411423

412-
this.mipmapUtils.generateMipmaps( textureGPU, textureDescriptorGPU, baseArrayLayer );
424+
return passUtils;
413425

414426
}
415427

416-
_copyBufferToTexture( image, textureGPU, textureDescriptorGPU, originDepth = 0 ) {
428+
_generateMipmaps( textureGPU, textureDescriptorGPU, baseArrayLayer = 0 ) {
429+
430+
this._getPassUtils().generateMipmaps( textureGPU, textureDescriptorGPU, baseArrayLayer );
431+
432+
}
433+
434+
_flipY( textureGPU, textureDescriptorGPU, originDepth = 0 ) {
435+
436+
this._getPassUtils().flipY( textureGPU, textureDescriptorGPU, originDepth );
437+
438+
}
439+
440+
_copyBufferToTexture( image, textureGPU, textureDescriptorGPU, originDepth, flipY ) {
417441

418442
// @TODO: Consider to use GPUCommandEncoder.copyBufferToTexture()
419443
// @TODO: Consider to support valid buffer layouts with other formats like RGB
@@ -442,6 +466,12 @@ class WebGPUTextureUtils {
442466
depthOrArrayLayers: ( image.depth !== undefined ) ? image.depth : 1
443467
} );
444468

469+
if ( flipY === true ) {
470+
471+
this._flipY( textureGPU, textureDescriptorGPU, originDepth );
472+
473+
}
474+
445475
}
446476

447477
_copyCompressedBufferToTexture( mipmaps, textureGPU, textureDescriptorGPU ) {

0 commit comments

Comments
 (0)
Please sign in to comment.