Skip to content

Commit ee3fe43

Browse files
authored
Merge pull request #20284 from Mugen87/dev53
WebGPUTextures: Add support for mipmap computation.
2 parents 4482c46 + fde434c commit ee3fe43

File tree

3 files changed

+207
-17
lines changed

3 files changed

+207
-17
lines changed

examples/jsm/renderers/webgpu/WebGPURenderer.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -744,7 +744,7 @@ async function initWebGPU( scope ) {
744744
scope._properties = new WebGPUProperties();
745745
scope._attributes = new WebGPUAttributes( device );
746746
scope._geometries = new WebGPUGeometries( scope._attributes, scope._info );
747-
scope._textures = new WebGPUTextures( device, scope._properties, scope._info );
747+
scope._textures = new WebGPUTextures( device, scope._properties, scope._info, compiler );
748748
scope._bindings = new WebGPUBindings( device, scope._info, scope._properties, scope._textures );
749749
scope._objects = new WebGPUObjects( scope._geometries, scope._info );
750750
scope._renderPipelines = new WebGPURenderPipelines( device, compiler, scope._bindings, scope.parameters.sampleCount );
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
// Copyright 2020 Brandon Jones
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy
4+
// of this software and associated documentation files (the "Software"), to deal
5+
// in the Software without restriction, including without limitation the rights
6+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
// copies of the Software, and to permit persons to whom the Software is
8+
// furnished to do so, subject to the following conditions:
9+
10+
// The above copyright notice and this permission notice shall be included in
11+
// all copies or substantial portions of the Software.
12+
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19+
// SOFTWARE.
20+
21+
import { GPUIndexFormat, GPUFilterMode, GPUPrimitiveTopology } from './constants.js';
22+
23+
// ported from https://github.com/toji/web-texture-tool/blob/master/src/webgpu-mipmap-generator.js
24+
25+
class WebGPUTextureUtils {
26+
27+
constructor( device, glslang ) {
28+
29+
this.device = device;
30+
31+
const mipmapVertexSource = `#version 450
32+
const vec2 pos[4] = vec2[4](vec2(-1.0f, 1.0f), vec2(1.0f, 1.0f), vec2(-1.0f, -1.0f), vec2(1.0f, -1.0f));
33+
const vec2 tex[4] = vec2[4](vec2(0.0f, 0.0f), vec2(1.0f, 0.0f), vec2(0.0f, 1.0f), vec2(1.0f, 1.0f));
34+
layout(location = 0) out vec2 vTex;
35+
void main() {
36+
vTex = tex[gl_VertexIndex];
37+
gl_Position = vec4(pos[gl_VertexIndex], 0.0, 1.0);
38+
}
39+
`;
40+
41+
const mipmapFragmentSource = `#version 450
42+
layout(set = 0, binding = 0) uniform sampler imgSampler;
43+
layout(set = 0, binding = 1) uniform texture2D img;
44+
layout(location = 0) in vec2 vTex;
45+
layout(location = 0) out vec4 outColor;
46+
void main() {
47+
outColor = texture(sampler2D(img, imgSampler), vTex);
48+
}`;
49+
50+
this.sampler = device.createSampler( { minFilter: GPUFilterMode.Linear } );
51+
52+
// We'll need a new pipeline for every texture format used.
53+
this.pipelines = {};
54+
55+
this.mipmapVertexShaderModule = device.createShaderModule( {
56+
code: glslang.compileGLSL( mipmapVertexSource, 'vertex' ),
57+
} );
58+
this.mipmapFragmentShaderModule = device.createShaderModule( {
59+
code: glslang.compileGLSL( mipmapFragmentSource, 'fragment' ),
60+
} );
61+
62+
}
63+
64+
getMipmapPipeline( format ) {
65+
66+
let pipeline = this.pipelines[ format ];
67+
68+
if ( pipeline === undefined ) {
69+
70+
pipeline = this.device.createRenderPipeline( {
71+
vertexStage: {
72+
module: this.mipmapVertexShaderModule,
73+
entryPoint: 'main',
74+
},
75+
fragmentStage: {
76+
module: this.mipmapFragmentShaderModule,
77+
entryPoint: 'main',
78+
},
79+
primitiveTopology: GPUPrimitiveTopology.TriangleStrip,
80+
vertexState: {
81+
indexFormat: GPUIndexFormat.Uint32
82+
},
83+
colorStates: [ { format } ],
84+
} );
85+
this.pipelines[ format ] = pipeline;
86+
87+
}
88+
89+
return pipeline;
90+
91+
}
92+
93+
generateMipmappedTexture( imageBitmap, textureGPU, textureGPUDescriptor ) {
94+
95+
const pipeline = this.getMipmapPipeline( textureGPUDescriptor.format );
96+
97+
const commandEncoder = this.device.createCommandEncoder( {} );
98+
const bindGroupLayout = pipeline.getBindGroupLayout( 0 ); // @TODO: Consider making this static.
99+
100+
let srcView = textureGPU.createView( {
101+
baseMipLevel: 0,
102+
mipLevelCount: 1,
103+
} );
104+
105+
for ( let i = 1; i < textureGPUDescriptor.mipLevelCount; i ++ ) {
106+
107+
const dstView = textureGPU.createView( {
108+
baseMipLevel: i,
109+
mipLevelCount: 1,
110+
} );
111+
112+
const passEncoder = commandEncoder.beginRenderPass( {
113+
colorAttachments: [ {
114+
attachment: dstView,
115+
loadValue: [ 0, 0, 0, 0 ],
116+
} ],
117+
} );
118+
119+
const bindGroup = this.device.createBindGroup( {
120+
layout: bindGroupLayout,
121+
entries: [ {
122+
binding: 0,
123+
resource: this.sampler,
124+
}, {
125+
binding: 1,
126+
resource: srcView,
127+
} ],
128+
} );
129+
130+
passEncoder.setPipeline( pipeline );
131+
passEncoder.setBindGroup( 0, bindGroup );
132+
passEncoder.draw( 4, 1, 0, 0 );
133+
passEncoder.endPass();
134+
135+
srcView = dstView;
136+
137+
}
138+
139+
this.device.defaultQueue.submit( [ commandEncoder.finish() ] );
140+
141+
}
142+
143+
}
144+
145+
export default WebGPUTextureUtils;

examples/jsm/renderers/webgpu/WebGPUTextures.js

Lines changed: 61 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
import { GPUTextureFormat, GPUAddressMode, GPUFilterMode } from './constants.js';
2-
import { Texture, NearestFilter, NearestMipmapNearestFilter, NearestMipmapLinearFilter, RepeatWrapping, MirroredRepeatWrapping, FloatType, HalfFloatType } from '../../../../build/three.module.js';
2+
import { Texture, NearestFilter, NearestMipmapNearestFilter, NearestMipmapLinearFilter, LinearFilter, RepeatWrapping, MirroredRepeatWrapping, FloatType, HalfFloatType } from '../../../../build/three.module.js';
3+
import WebGPUTextureUtils from './WebGPUTextureUtils.js';
34

45
class WebGPUTextures {
56

6-
constructor( device, properties, info ) {
7+
constructor( device, properties, info, glslang ) {
78

89
this.device = device;
910
this.properties = properties;
1011
this.info = info;
12+
this.glslang = glslang;
1113

1214
this.defaultTexture = null;
1315
this.defaultSampler = null;
1416

1517
this.samplerCache = new Map();
18+
this.utils = null;
1619

1720
}
1821

@@ -229,6 +232,12 @@ class WebGPUTextures {
229232

230233
}
231234

235+
_computeMipLevelCount( width, height ) {
236+
237+
return Math.floor( Math.log2( Math.max( width, height ) ) ) + 1;
238+
239+
}
240+
232241
_convertAddressMode( value ) {
233242

234243
let addressMode = GPUAddressMode.ClampToEdge;
@@ -288,16 +297,32 @@ class WebGPUTextures {
288297
const height = ( image !== undefined ) ? image.height : 1;
289298

290299
const format = this._convertFormat( texture.type );
300+
const needsMipmaps = this._needsMipmaps( texture );
301+
const mipLevelCount = ( needsMipmaps === true ) ? this._computeMipLevelCount( width, height ) : undefined;
302+
303+
let usage = GPUTextureUsage.SAMPLED | GPUTextureUsage.COPY_DST;
304+
305+
if ( needsMipmaps === true ) {
291306

292-
const textureGPU = device.createTexture( {
307+
usage |= GPUTextureUsage.OUTPUT_ATTACHMENT;
308+
309+
}
310+
311+
// texture creation
312+
313+
const textureGPUDescriptor = {
293314
size: {
294315
width: width,
295316
height: height,
296317
depth: 1,
297318
},
298319
format: format,
299-
usage: GPUTextureUsage.SAMPLED | GPUTextureUsage.COPY_DST,
300-
} );
320+
usage: usage,
321+
mipLevelCount: mipLevelCount
322+
};
323+
const textureGPU = device.createTexture( textureGPUDescriptor );
324+
325+
// transfer texture data
301326

302327
if ( texture.isDataTexture ) {
303328

@@ -317,7 +342,7 @@ class WebGPUTextures {
317342

318343
createImageBitmap( image, 0, 0, width, height, options ).then( imageBitmap => {
319344

320-
this._copyImageBitmapToTexture( imageBitmap, textureGPU );
345+
this._copyImageBitmapToTexture( imageBitmap, textureGPU, needsMipmaps, textureGPUDescriptor );
321346

322347
} );
323348

@@ -327,7 +352,7 @@ class WebGPUTextures {
327352

328353
// assuming ImageBitmap. Directly start copy operation of the contents of ImageBitmap into the destination texture
329354

330-
this._copyImageBitmapToTexture( image, textureGPU );
355+
this._copyImageBitmapToTexture( image, textureGPU, needsMipmaps, textureGPUDescriptor );
331356

332357
}
333358

@@ -342,7 +367,9 @@ class WebGPUTextures {
342367
_copyBufferToTexture( image, format, textureGPU ) {
343368

344369
// this code assumes data textures in RGBA format
370+
345371
// @TODO: Consider to support valid buffer layouts with other formats like RGB
372+
// @TODO: Support mipmaps
346373

347374
const device = this.device;
348375
const data = image.data;
@@ -377,15 +404,7 @@ class WebGPUTextures {
377404

378405
}
379406

380-
_getBytesPerTexel( format ) {
381-
382-
if ( format === GPUTextureFormat.RGBA8Unorm ) return 4;
383-
if ( format === GPUTextureFormat.RGBA16Float ) return 8;
384-
if ( format === GPUTextureFormat.RGBA32Float ) return 16;
385-
386-
}
387-
388-
_copyImageBitmapToTexture( imageBitmap, textureGPU ) {
407+
_copyImageBitmapToTexture( imageBitmap, textureGPU, needsMipmaps, textureGPUDescriptor ) {
389408

390409
const device = this.device;
391410

@@ -401,6 +420,32 @@ class WebGPUTextures {
401420
}
402421
);
403422

423+
if ( needsMipmaps === true ) {
424+
425+
if ( this.utils === null ) {
426+
427+
this.utils = new WebGPUTextureUtils( this.device, this.glslang ); // only create this helper if necessary
428+
429+
}
430+
431+
this.utils.generateMipmappedTexture( imageBitmap, textureGPU, textureGPUDescriptor );
432+
433+
}
434+
435+
}
436+
437+
_getBytesPerTexel( format ) {
438+
439+
if ( format === GPUTextureFormat.RGBA8Unorm ) return 4;
440+
if ( format === GPUTextureFormat.RGBA16Float ) return 8;
441+
if ( format === GPUTextureFormat.RGBA32Float ) return 16;
442+
443+
}
444+
445+
_needsMipmaps( texture ) {
446+
447+
return ( texture.generateMipmaps === true ) && ( texture.minFilter !== NearestFilter ) && ( texture.minFilter !== LinearFilter );
448+
404449
}
405450

406451
}

0 commit comments

Comments
 (0)