Skip to content

Commit 56aa339

Browse files
authored
WebGPURenderer: Decouple samplers from textures. (#31899)
1 parent 28fa9c1 commit 56aa339

File tree

8 files changed

+150
-80
lines changed

8 files changed

+150
-80
lines changed

src/renderers/common/Backend.js

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -273,20 +273,13 @@ class Backend {
273273
// textures
274274

275275
/**
276-
* Creates a GPU sampler for the given texture.
276+
* Updates a GPU sampler for the given texture.
277277
*
278278
* @abstract
279-
* @param {Texture} texture - The texture to create the sampler for.
279+
* @param {Texture} texture - The texture to update the sampler for.
280+
* @return {string} The current sampler key.
280281
*/
281-
createSampler( /*texture*/ ) { }
282-
283-
/**
284-
* Destroys the GPU sampler for the given texture.
285-
*
286-
* @abstract
287-
* @param {Texture} texture - The texture to destroy the sampler for.
288-
*/
289-
destroySampler( /*texture*/ ) {}
282+
updateSampler( /*texture*/ ) { }
290283

291284
/**
292285
* Creates a default texture for the given texture that can be used

src/renderers/common/Bindings.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,10 @@ class Bindings extends DataMap {
215215

216216
this.textures.updateTexture( binding.texture );
217217

218+
} else if ( binding.isSampler ) {
219+
220+
this.textures.updateSampler( binding.texture );
221+
218222
} else if ( binding.isStorageBuffer ) {
219223

220224
const attribute = binding.attribute;
@@ -340,7 +344,23 @@ class Bindings extends DataMap {
340344

341345
} else if ( binding.isSampler ) {
342346

343-
binding.update();
347+
const updated = binding.update();
348+
349+
if ( updated ) {
350+
351+
const samplerKey = this.textures.updateSampler( binding.texture );
352+
353+
if ( binding.samplerKey !== samplerKey ) {
354+
355+
binding.samplerKey = samplerKey;
356+
357+
needsBindingsUpdate = true;
358+
359+
cacheBindings = false;
360+
361+
}
362+
363+
}
344364

345365
}
346366

src/renderers/common/Sampler.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ class Sampler extends Binding {
5858
*/
5959
this.generation = null;
6060

61+
/**
62+
* The binding's sampler key.
63+
*
64+
* @type {string}
65+
* @default ''
66+
*/
67+
this.samplerKey = '';
68+
6169
/**
6270
* This flag can be used for type testing.
6371
*

src/renderers/common/Textures.js

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,6 @@ class Textures extends DataMap {
210210

211211
// it's an update
212212

213-
backend.destroySampler( texture );
214213
backend.destroyTexture( texture );
215214

216215
}
@@ -253,17 +252,12 @@ class Textures extends DataMap {
253252

254253
if ( isRenderTarget || texture.isStorageTexture === true || texture.isExternalTexture === true ) {
255254

256-
backend.createSampler( texture );
257255
backend.createTexture( texture, options );
258256

259257
textureData.generation = texture.version;
260258

261259
} else {
262260

263-
const needsCreate = textureData.initialized !== true;
264-
265-
if ( needsCreate ) backend.createSampler( texture );
266-
267261
if ( texture.version > 0 ) {
268262

269263
const image = texture.image;
@@ -365,6 +359,24 @@ class Textures extends DataMap {
365359

366360
}
367361

362+
/**
363+
* Updates the sampler for the given texture. This method has no effect
364+
* for the WebGL backend since it has no concept of samplers. Texture
365+
* parameters are configured with the `texParameter()` command for each
366+
* texture.
367+
*
368+
* In WebGPU, samplers are objects like textures and it's possible to share
369+
* them when the texture parameters match.
370+
*
371+
* @param {Texture} texture - The texture to update the sampler for.
372+
* @return {string} The current sampler key.
373+
*/
374+
updateSampler( texture ) {
375+
376+
return this.backend.updateSampler( texture );
377+
378+
}
379+
368380
/**
369381
* Computes the size of the given texture and writes the result
370382
* into the target vector. This vector is also returned by the
@@ -481,8 +493,6 @@ class Textures extends DataMap {
481493
// other textures.
482494

483495
const isDefaultTexture = this.get( texture ).isDefaultTexture;
484-
485-
this.backend.destroySampler( texture );
486496
this.backend.destroyTexture( texture, isDefaultTexture );
487497

488498
this.delete( texture );

src/renderers/common/nodes/NodeSampler.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,15 @@ class NodeSampler extends Sampler {
3838

3939
/**
4040
* Updates the texture value of this sampler.
41+
*
42+
* @return {boolean} Whether the sampler needs an update or not.
4143
*/
4244
update() {
4345

4446
this.texture = this.textureNode.value;
4547

48+
return super.update();
49+
4650
}
4751

4852
}

src/renderers/webgl-fallback/WebGLBackend.js

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1331,21 +1331,15 @@ class WebGLBackend extends Backend {
13311331
/**
13321332
* This method does nothing since WebGL 2 has no concept of samplers.
13331333
*
1334-
* @param {Texture} texture - The texture to create the sampler for.
1334+
* @param {Texture} texture - The texture to update the sampler for.
1335+
* @return {string} The current sampler key.
13351336
*/
1336-
createSampler( /*texture*/ ) {
1337+
updateSampler( /*texture*/ ) {
13371338

1338-
//warn( 'Abstract class.' );
1339+
return '';
13391340

13401341
}
13411342

1342-
/**
1343-
* This method does nothing since WebGL 2 has no concept of samplers.
1344-
*
1345-
* @param {Texture} texture - The texture to destroy the sampler for.
1346-
*/
1347-
destroySampler( /*texture*/ ) {}
1348-
13491343
// node builder
13501344

13511345
/**

src/renderers/webgpu/WebGPUBackend.js

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1834,24 +1834,14 @@ class WebGPUBackend extends Backend {
18341834
// textures
18351835

18361836
/**
1837-
* Creates a GPU sampler for the given texture.
1837+
* Updates a GPU sampler for the given texture.
18381838
*
1839-
* @param {Texture} texture - The texture to create the sampler for.
1839+
* @param {Texture} texture - The texture to update the sampler for.
1840+
* @return {string} The current sampler key.
18401841
*/
1841-
createSampler( texture ) {
1842+
updateSampler( texture ) {
18421843

1843-
this.textureUtils.createSampler( texture );
1844-
1845-
}
1846-
1847-
/**
1848-
* Destroys the GPU sampler for the given texture.
1849-
*
1850-
* @param {Texture} texture - The texture to destroy the sampler for.
1851-
*/
1852-
destroySampler( texture ) {
1853-
1854-
this.textureUtils.destroySampler( texture );
1844+
return this.textureUtils.updateSampler( texture );
18551845

18561846
}
18571847

@@ -1860,10 +1850,11 @@ class WebGPUBackend extends Backend {
18601850
* as a placeholder until the actual texture is ready for usage.
18611851
*
18621852
* @param {Texture} texture - The texture to create a default texture for.
1853+
* @return {boolean} Whether the sampler has been updated or not.
18631854
*/
18641855
createDefaultTexture( texture ) {
18651856

1866-
this.textureUtils.createDefaultTexture( texture );
1857+
return this.textureUtils.createDefaultTexture( texture );
18671858

18681859
}
18691860

@@ -2452,6 +2443,12 @@ class WebGPUBackend extends Backend {
24522443

24532444
}
24542445

2446+
dispose() {
2447+
2448+
this.textureUtils.dispose();
2449+
2450+
}
2451+
24552452
}
24562453

24572454
export default WebGPUBackend;

src/renderers/webgpu/utils/WebGPUTextureUtils.js

Lines changed: 78 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -103,45 +103,94 @@ class WebGPUTextureUtils {
103103
this.depthTexture = new DepthTexture();
104104
this.depthTexture.name = 'depthBuffer';
105105

106+
/**
107+
* A cache of shared texture samplers.
108+
*
109+
* @type {Map<string, Object>}
110+
*/
111+
this._samplerCache = new Map();
112+
106113
}
107114

108115
/**
109116
* Creates a GPU sampler for the given texture.
110117
*
111118
* @param {Texture} texture - The texture to create the sampler for.
119+
* @return {string} The current sampler key.
112120
*/
113-
createSampler( texture ) {
121+
updateSampler( texture ) {
114122

115123
const backend = this.backend;
116-
const device = backend.device;
117-
118-
const textureGPU = backend.get( texture );
119-
120-
const samplerDescriptorGPU = {
121-
addressModeU: this._convertAddressMode( texture.wrapS ),
122-
addressModeV: this._convertAddressMode( texture.wrapT ),
123-
addressModeW: this._convertAddressMode( texture.wrapR ),
124-
magFilter: this._convertFilterMode( texture.magFilter ),
125-
minFilter: this._convertFilterMode( texture.minFilter ),
126-
mipmapFilter: this._convertFilterMode( texture.minFilter ),
127-
maxAnisotropy: 1
128-
};
129124

130-
// anisotropy can only be used when all filter modes are set to linear.
125+
const samplerKey = texture.minFilter + '-' + texture.magFilter + '-' +
126+
texture.wrapS + '-' + texture.wrapT + '-' + ( texture.wrapR || '0' ) + '-' +
127+
texture.anisotropy + '-' + ( texture.compareFunction || 0 );
128+
129+
let samplerData = this._samplerCache.get( samplerKey );
130+
131+
if ( samplerData === undefined ) {
132+
133+
const samplerDescriptorGPU = {
134+
addressModeU: this._convertAddressMode( texture.wrapS ),
135+
addressModeV: this._convertAddressMode( texture.wrapT ),
136+
addressModeW: this._convertAddressMode( texture.wrapR ),
137+
magFilter: this._convertFilterMode( texture.magFilter ),
138+
minFilter: this._convertFilterMode( texture.minFilter ),
139+
mipmapFilter: this._convertFilterMode( texture.minFilter ),
140+
maxAnisotropy: 1
141+
};
142+
143+
// anisotropy can only be used when all filter modes are set to linear.
144+
145+
if ( samplerDescriptorGPU.magFilter === GPUFilterMode.Linear && samplerDescriptorGPU.minFilter === GPUFilterMode.Linear && samplerDescriptorGPU.mipmapFilter === GPUFilterMode.Linear ) {
146+
147+
samplerDescriptorGPU.maxAnisotropy = texture.anisotropy;
148+
149+
}
150+
151+
if ( texture.isDepthTexture && texture.compareFunction !== null ) {
152+
153+
samplerDescriptorGPU.compare = _compareToWebGPU[ texture.compareFunction ];
154+
155+
}
156+
157+
const sampler = backend.device.createSampler( samplerDescriptorGPU );
131158

132-
if ( samplerDescriptorGPU.magFilter === GPUFilterMode.Linear && samplerDescriptorGPU.minFilter === GPUFilterMode.Linear && samplerDescriptorGPU.mipmapFilter === GPUFilterMode.Linear ) {
159+
samplerData = { sampler, usedTimes: 0 };
133160

134-
samplerDescriptorGPU.maxAnisotropy = texture.anisotropy;
161+
this._samplerCache.set( samplerKey, samplerData );
135162

136163
}
137164

138-
if ( texture.isDepthTexture && texture.compareFunction !== null ) {
165+
const textureData = backend.get( texture );
166+
167+
if ( textureData.sampler !== samplerData.sampler ) {
168+
169+
// check if previous sampler is unused so it can be deleted
170+
171+
if ( textureData.sampler !== undefined ) {
172+
173+
const oldSamplerData = this._samplerCache.get( textureData.samplerKey );
174+
oldSamplerData.usedTimes --;
175+
176+
if ( oldSamplerData.usedTimes === 0 ) {
139177

140-
samplerDescriptorGPU.compare = _compareToWebGPU[ texture.compareFunction ];
178+
this._samplerCache.delete( textureData.samplerKey );
179+
180+
}
181+
182+
}
183+
184+
// update to new sampler data
185+
186+
textureData.samplerKey = samplerKey;
187+
textureData.sampler = samplerData.sampler;
188+
189+
samplerData.usedTimes ++;
141190

142191
}
143192

144-
textureGPU.sampler = device.createSampler( samplerDescriptorGPU );
193+
return samplerKey;
145194

146195
}
147196

@@ -308,20 +357,6 @@ class WebGPUTextureUtils {
308357

309358
}
310359

311-
/**
312-
* Destroys the GPU sampler for the given texture.
313-
*
314-
* @param {Texture} texture - The texture to destroy the sampler for.
315-
*/
316-
destroySampler( texture ) {
317-
318-
const backend = this.backend;
319-
const textureData = backend.get( texture );
320-
321-
delete textureData.sampler;
322-
323-
}
324-
325360
/**
326361
* Generates mipmaps for the given texture.
327362
*
@@ -577,6 +612,15 @@ class WebGPUTextureUtils {
577612

578613
}
579614

615+
/**
616+
* Frees all internal resources.
617+
*/
618+
dispose() {
619+
620+
this._samplerCache.clear();
621+
622+
}
623+
580624
/**
581625
* Returns the default GPU texture for the given format.
582626
*

0 commit comments

Comments
 (0)