In nested transparency layers, bindTexture samples the wrong texture.
A layer’s FBO texture (name 6) is bound → cached. glDeleteTextures resets its GL binding to 0 without updating the ActiveTextures shadow cache. glGenTextures recycles name 6 for the next layer. bindTexture(6) sees currentTextureID[unit] == 6, skips the real glBindTexture, and samples the zero texture:
DESYNC unit=0 requested=6 actualBinding=0 glIsTexture(requested)=1 cache=[6,0,0]
Correctness bug, not just a debug assert (release composites the wrong texture).
Local fix: state->activeTextures.clear() in endTransparencyLayer before compositing.
Proper fix: invalidate the ActiveTextures cache whenever a texture name is deleted, since GL silently unbinds it.
Thanks !
FWIW, this creates glitch as soon as we enable the OpenGL renderer in Falcon.
Like for example the weird dark shadow in this screenshot:
reuk
June 30, 2026, 6:50pm
3
I’ve tried to reproduce the issue, but without any luck so far.
Just to check, are you testing with the develop branch, or at least a branch that contains this change?
committed 01:13PM - 14 Aug 25 UTC
A change introduced in 00836d1e94dcd45949da5be7f9266258c5d0dfe5 meant
that GL re… nderers could sometimes assert in
StateHelpers::ActiveTextures::bindTexture() when ending a transparency
layer.
Specifically, the issue was provoked by adding the ScopedTextureBinding
in the constructor of OpenGLFrameBuffer. This reset the bound texture
after creating a new transparency layer.
I think the previous implementation worked by accident, not by design.
It so happens that when rendering multiple transparency layers within
the same frame (i.e. calling begin/end several times in that order,
*not* nesting the calls), the same texture ID will generally get reused.
From the graphics context's (GC's) perspective, we create a texture with
ID "2", then call bindTexture() to bind it, and the GC remembers that
this ID is bound. We draw the frame, and the texture gets destroyed. The
call to glDeleteTextures() will cause the default texture, "0", to be
bound if the texture being destroyed is bound at the point of
destruction. So, after the texture is destroyed, the GC's stored binding
no longer reflects reality, since texture "0" is now bound.
The next time we create a texture, that texture also gets created with
ID "2". Previously, we would leave this texture bound after
construction, but now we re-bind the previously-bound texture, "0". This
causes the assertion in bindTexture() to fire when we next attempt to
bind texture "2", since the actual bound texture is "0".
The solution added in this patch will bind texture "0" when the
transparency layer image is destroyed, in order to keep the GC's view of
the GL context consistent with the real state.
If you’re seeing the issue on the develop branch, it would be helpful if you could supply a paint() implementation that demonstrates the issue, since I haven’t been able to come up with an example that looks different between the d2d renderer and the OpenGL renderer on Windows.
using develop from last week. I try to find a simple exemple to reproduce
same issue with regular image
GLTransparencyLayerRecycleRepro.h (4.9 KB)
reuk
July 3, 2026, 3:08pm
7
Thanks for the minimal repro, that made it much quicker for me to track down the problem. Hopefully that’s fixed here:
committed 01:58PM - 03 Jul 26 UTC
1 Like