internal/graphicsdriver/opengl: fix state tracking of glBindTexture / glActiveTexture interaction (#2526)

After switching texture units using glActiveTexture, a different texture may
be bound. For now, let's just force the active texture state variable to zero
so the next glBindTexture call isn't skipped.

Closes #2525
This commit is contained in:
divVerent 2023-01-07 06:13:14 -08:00 committed by GitHub
parent 95f1ef0fb9
commit abece041f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 72 additions and 0 deletions

View File

@ -219,6 +219,7 @@ func (g *Graphics) useProgram(program program, uniforms []uniformVariable, textu
}
g.state.lastActiveTexture = 0
g.context.ctx.ActiveTexture(gl.TEXTURE0)
g.context.lastTexture = 0 // Make sure next bindTexture call actually does something.
}
for _, u := range uniforms {
@ -267,6 +268,7 @@ loop:
if g.state.lastActiveTexture != idx {
g.context.ctx.ActiveTexture(uint32(gl.TEXTURE0 + idx))
g.state.lastActiveTexture = idx
g.context.lastTexture = 0 // Make sure next bindTexture call actually does something.
}
// Apparently, a texture must be bound every time. The cache is not used here.

View File

@ -87,6 +87,76 @@ func Fragment(position vec4, texCoord vec2, color vec4) vec4 {
}
}
// Issue #2525
func TestShaderWithDrawImageDoesNotWreckTextureUnits(t *testing.T) {
const w, h = 16, 16
rect := image.Rectangle{Max: image.Point{X: w, Y: h}}
dst := ebiten.NewImageWithOptions(rect, &ebiten.NewImageOptions{Unmanaged: true})
s, err := ebiten.NewShader([]byte(`package main
func Fragment(position vec4, texCoord vec2, color vec4) vec4 {
return imageSrc0At(texCoord)
}
`))
if err != nil {
t.Fatal(err)
}
src0 := ebiten.NewImageWithOptions(rect, &ebiten.NewImageOptions{Unmanaged: true})
src0.Fill(color.RGBA{25, 0xff, 25, 0xff})
src1 := ebiten.NewImageWithOptions(rect, &ebiten.NewImageOptions{Unmanaged: true})
src1.Fill(color.RGBA{0xff, 0, 0, 0xff})
op := &ebiten.DrawRectShaderOptions{}
op.CompositeMode = ebiten.CompositeModeCopy
op.Images[0] = src0
op.Images[1] = src1
dst.DrawRectShader(w, h, s, op)
op.Images[0] = src1
op.Images[1] = nil
dst.DrawRectShader(w, h, s, op) // dst should now be identical to src1.
// With issue #2525, instead, GL_TEXTURE0 is active but with src0 bound
// while binding src1 gets skipped!
// This means that src0, not src1, got copied to dst.
// Demonstrate the bug with a write to src1, which will actually end up on src0.
// Validated later.
var buf []byte
for i := 0; i < w*h; i++ {
buf = append(buf, 2, 5, 2, 5)
}
src1.WritePixels(buf)
// Verify that src1 was copied to dst.
for j := 0; j < h; j++ {
for i := 0; i < w; i++ {
got := dst.At(i, j).(color.RGBA)
want := color.RGBA{0xff, 0, 0, 0xff}
if got != want {
t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
}
}
}
// Fix up texture unit assignment by binding a different texture.
op.Images[0] = src1
dst.DrawRectShader(w, h, s, op)
op.Images[0] = src0
dst.DrawRectShader(w, h, s, op)
// Verify that src0 was copied to dst and not overwritten above.
for j := 0; j < h; j++ {
for i := 0; i < w; i++ {
got := dst.At(i, j).(color.RGBA)
want := color.RGBA{25, 0xff, 25, 0xff}
if got != want {
t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
}
}
}
}
func TestShaderFillWithDrawTriangles(t *testing.T) {
const w, h = 16, 16