Skip to content

WebGLRenderer: Fix active texture setting in copyTextureToTexture().#33153

Merged
Mugen87 merged 2 commits into
mrdoob:devfrom
geekuillaume:fix/copyTextureToTextureActiveSlot
Mar 12, 2026
Merged

WebGLRenderer: Fix active texture setting in copyTextureToTexture().#33153
Mugen87 merged 2 commits into
mrdoob:devfrom
geekuillaume:fix/copyTextureToTextureActiveSlot

Conversation

@geekuillaume

@geekuillaume geekuillaume commented Mar 11, 2026

Copy link
Copy Markdown
Contributor

Fixed #25618.

Description

In some cases, calling gl.copyTextureToTexture can target a different texture than the one actually passed as parameter. This happens if:

  • the target texture is already in slot 0
  • the active slot is not slot 0

In this case, when copyTextureToTexture calls textures.setTexture2D, state.bindTexture is called but because this texture was already in slot 0, state.bindTexture is a noop. It means that the active slot is not changed and if it's not 0, then the target of copyTextureToTexture is invalid.

The fix in this PR solves the problem for copyTextureToTexture specifically but maybe we should fix state.bindTexture instead. The current behavior of state.bindTexture can create problems because it only set the active slot if the texture was not already bound to this slot.

Here's what it currently does:

if ( boundTexture.type !== webglType || boundTexture.texture !== webglTexture ) {

	if ( currentTextureSlot !== webglSlot ) {

		gl.activeTexture( webglSlot );
		currentTextureSlot = webglSlot;

	}

	gl.bindTexture( webglType, webglTexture || emptyTextures[ webglType ] );

	boundTexture.type = webglType;
	boundTexture.texture = webglTexture;

}

But instead we could do:

if ( currentTextureSlot !== webglSlot ) {

	gl.activeTexture( webglSlot );
	currentTextureSlot = webglSlot;

}

if ( boundTexture.type !== webglType || boundTexture.texture !== webglTexture ) {

	gl.bindTexture( webglType, webglTexture || emptyTextures[ webglType ] );

	boundTexture.type = webglType;
	boundTexture.texture = webglTexture;

}

I'm not sure if this will cause some other side-effect. Let me know what you think!

@github-actions

github-actions Bot commented Mar 11, 2026

Copy link
Copy Markdown

📦 Bundle size

Full ESM build, minified and gzipped.

Before After Diff
WebGL 359.27
85.31
359.3
85.32
+30 B
+10 B
WebGPU 630.15
174.95
630.15
174.95
+0 B
+0 B
WebGPU Nodes 628.73
174.7
628.73
174.7
+0 B
+0 B

🌳 Bundle size after tree-shaking

Minimal build including a renderer, camera, empty scene, and dependencies.

Before After Diff
WebGL 491.47
119.97
491.5
119.98
+30 B
+13 B
WebGPU 703.37
189.9
703.37
189.9
+0 B
+0 B
WebGPU Nodes 652.59
177.29
652.59
177.29
+0 B
+0 B

@Mugen87

Mugen87 commented Mar 11, 2026

Copy link
Copy Markdown
Collaborator

Can you please reproduce the issue with a live example?

Use https://jsfiddle.net/3mrkqyea/ as a starter template.

We must be able to reproduce the issue so we have a chance to verify if the PR actually fixes it.

@geekuillaume

Copy link
Copy Markdown
Contributor Author

Here is the reproduction case: https://jsfiddle.net/xko5s6zf/5/

You should see a green sphere on top and a blue sphere, but actually you see a blue sphere on top of a red one. Try commenting the renderer.copyTextureToTexture call. It will change the color of the top sphere even if this call doesn't impact the renderTarget used to texture the sphere.

@Mugen87

Mugen87 commented Mar 12, 2026

Copy link
Copy Markdown
Collaborator

While studying the solution I think it's indeed better to fix the issue in WebGLState as you mentioned in #33153 (comment). The fix in copyTextureToTexture() looks more like a bandage that "hides" the real root cause.

WebGLState.bindTexture() should not only update the active texture slot when a gl.bindTexture() happens. Even without this it must check if the texture is registered in the correct slot.

Would you please update the PR and add your proposed fix to WebGLState instead? Can you also update the WebGL backend of WebGPURenderer? It requires the same fix, see:

if ( currentTextureSlot !== webglSlot ) {
gl.activeTexture( webglSlot );
this.currentTextureSlot = webglSlot;
}

@geekuillaume geekuillaume force-pushed the fix/copyTextureToTextureActiveSlot branch from eb09f12 to 61c0af9 Compare March 12, 2026 10:04
@geekuillaume geekuillaume changed the title Make sure slot 0 is active when calling copyTextureToTexture Ensure requested slot is active during call of WebGL state bindTexture Mar 12, 2026
@geekuillaume geekuillaume force-pushed the fix/copyTextureToTextureActiveSlot branch 2 times, most recently from 1864769 to a623478 Compare March 12, 2026 10:08
@geekuillaume

Copy link
Copy Markdown
Contributor Author

Done! I also changed the title of the PR to reflect the actual change and not mention copyTextureToTexture directly

@Mugen87 Mugen87 changed the title Ensure requested slot is active during call of WebGL state bindTexture WebGLState: Ensure requested slot is active when calling bindTexture(). Mar 12, 2026
@Mugen87 Mugen87 added this to the r184 milestone Mar 12, 2026
@Mugen87

Mugen87 commented Mar 12, 2026

Copy link
Copy Markdown
Collaborator

I did a performance review and it turns out this change introduces unnecessary gl.activeTexture() calls. I then let Gemini review the PR with regards on performance and it does not recommend to make this change.

I'm sorry for the back and forth but could you restore the previous version and just modify copyTextureToTexture()? If we stumble across this issue elsewhere, we can think of a different approach in WebGLState. But for now, let's fix this locally and don't risk performance regressions.

To further explain the design issue: If a texture is just used for rendering, a simple bind is sufficient. But when mutating the texture (like copyTextureToTexture() does), gl.activeTexture() is required so commands like gl.texImage2D() and gl.texParameteri() target the correct texture. state.bindTexture() does currently do both which is not necessarily correct.

@geekuillaume geekuillaume force-pushed the fix/copyTextureToTextureActiveSlot branch 2 times, most recently from 9278d14 to 18a7aee Compare March 12, 2026 12:03
@geekuillaume geekuillaume force-pushed the fix/copyTextureToTextureActiveSlot branch from 18a7aee to b228a7d Compare March 12, 2026 12:03
@geekuillaume

Copy link
Copy Markdown
Contributor Author

No problem, I force pushed the first version. I'll let you edit the title of the PR however you see fit.

Comment thread src/renderers/WebGLRenderer.js Outdated

textures.setTexture2D( dstTexture, 0 );
glTarget = _gl.TEXTURE_2D;
state.activeTexture( _gl.TEXTURE0 );

@Mugen87 Mugen87 Mar 12, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move this below the else block so it also works for 3d and array textures?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Edit: I think WebGPURenderer does not need the change because the WebGL backend does not explicitly set 0 for the texture unit.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, WebGPURenderer works fine for both backends: https://jsfiddle.net/wenck4aq/

@Mugen87 Mugen87 changed the title WebGLState: Ensure requested slot is active when calling bindTexture(). Renderers: Fix active texture setting in copyTextureToTexture(). Mar 12, 2026
@Mugen87 Mugen87 changed the title Renderers: Fix active texture setting in copyTextureToTexture(). WebGLRenderer: Fix active texture setting in copyTextureToTexture(). Mar 12, 2026
@Mugen87

Mugen87 commented Mar 12, 2026

Copy link
Copy Markdown
Collaborator

I've made a small update and also added a comment to this PR so it's more clear why the line has been added.

@geekuillaume

Copy link
Copy Markdown
Contributor Author

Okay, thanks for taking care of this!

@Mugen87 Mugen87 merged commit 2e69815 into mrdoob:dev Mar 12, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rendering an object with multiple textures causes issues with later copyTextureToTexture calls

2 participants