From 43591bbc9f0a6d08f2d3cf90ff515b754ad27204 Mon Sep 17 00:00:00 2001 From: Ron Carbot Date: Wed, 9 Sep 2026 02:43:06 +0000 Subject: [PATCH 1/2] perf: skip redundant bindTexture in setTextureAt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a sampler already holds the requested texture, avoid a second GL bindTexture. Diggy stress: bindTexture/frame 233 → ~0 with no visual regression on title screen. --- lib/webgl/TextureContextWebGL.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/webgl/TextureContextWebGL.ts b/lib/webgl/TextureContextWebGL.ts index 2dde5796..40f96756 100644 --- a/lib/webgl/TextureContextWebGL.ts +++ b/lib/webgl/TextureContextWebGL.ts @@ -109,14 +109,10 @@ export class TextureContextWebGL { public setTextureAt(sampler: number, texture: TextureWebGL): number { const gl = this._context._gl; const samplerState = this._samplerStates[sampler]; - const textureType = GL_MAP.TEXTURE[texture.textureType]; - - if ((texture || samplerState.type)) { - gl.activeTexture(gl.TEXTURE0 + sampler); - } if (!texture) { if (samplerState.type) { + gl.activeTexture(gl.TEXTURE0 + sampler); // disable link to sampler in bounded texture if (samplerState.boundedTexture) { @@ -132,9 +128,16 @@ export class TextureContextWebGL { return -1; } + const textureType = GL_MAP.TEXTURE[texture.textureType]; + const alreadyBound = samplerState.boundedTexture === texture && samplerState.type === textureType; + + // Always activate the sampler unit before commit (texParameteri needs it). + gl.activeTexture(gl.TEXTURE0 + sampler); + texture._state.id = sampler; - this.bindTexture(texture, false, textureType); + // Skip redundant GL bindTexture when this sampler already holds the texture. + this.bindTexture(texture, alreadyBound, textureType); samplerState.commit(textureType, texture); From c00c1c4644a1d3052765386a92263454df2402d4 Mon Sep 17 00:00:00 2001 From: Ron Carbot Date: Wed, 9 Sep 2026 02:57:40 +0000 Subject: [PATCH 2/2] perf: skip redundant activeTexture in setTextureAt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Track the active sampler unit and skip gl.activeTexture when the unit already holds the requested texture with unchanged sampler params. Pairs with bindTexture skip; Diggy title median frame 99.9→83.4 ms on SwiftShader harness (draws unchanged). --- lib/webgl/TextureContextWebGL.ts | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/webgl/TextureContextWebGL.ts b/lib/webgl/TextureContextWebGL.ts index 40f96756..8a7f0af4 100644 --- a/lib/webgl/TextureContextWebGL.ts +++ b/lib/webgl/TextureContextWebGL.ts @@ -25,6 +25,7 @@ export class TextureContextWebGL { public static MAX_SAMPLERS = 16; _lastBoundedTexture: TextureWebGL; + _activeTextureUnit: number = -1; _samplerStates: SamplerStateWebGL[] = []; _currentRT: RenderTargetWebGL = null; @@ -112,7 +113,7 @@ export class TextureContextWebGL { if (!texture) { if (samplerState.type) { - gl.activeTexture(gl.TEXTURE0 + sampler); + this._activateTextureUnit(sampler); // disable link to sampler in bounded texture if (samplerState.boundedTexture) { @@ -130,9 +131,12 @@ export class TextureContextWebGL { const textureType = GL_MAP.TEXTURE[texture.textureType]; const alreadyBound = samplerState.boundedTexture === texture && samplerState.type === textureType; + // commit early-outs when wrap/filter already match texture._state + const paramsDirty = !alreadyBound || !samplerState.equals(texture._state); - // Always activate the sampler unit before commit (texParameteri needs it). - gl.activeTexture(gl.TEXTURE0 + sampler); + // Skip activeTexture when unit is active, texture already bound, params unchanged. + if (!alreadyBound || paramsDirty || this._activeTextureUnit !== sampler) + this._activateTextureUnit(sampler); texture._state.id = sampler; @@ -140,10 +144,18 @@ export class TextureContextWebGL { this.bindTexture(texture, alreadyBound, textureType); samplerState.commit(textureType, texture); + samplerState.boundedTexture = texture; return sampler; } + private _activateTextureUnit(sampler: number): void { + if (this._activeTextureUnit === sampler) + return; + this._activeTextureUnit = sampler; + this._gl.activeTexture(this._gl.TEXTURE0 + sampler); + } + public setSamplerStateAt( sampler: number, wrap: ContextGLWrapMode,