ebiten: Bug fix: Stencil buffers should not be cleared until all the vertices are rendered

Updates #1684
This commit is contained in:
Hajime Hoshi 2021-07-02 19:26:09 +09:00
parent b466a0cbd7
commit daa883d799
5 changed files with 139 additions and 12 deletions

View File

@ -2240,3 +2240,130 @@ func TestImageSubImageFill(t *testing.T) {
} }
} }
} }
func TestImageEvenOdd(t *testing.T) {
emptyImage := NewImage(3, 3)
emptyImage.Fill(color.White)
emptySubImage := emptyImage.SubImage(image.Rect(1, 1, 2, 2)).(*Image)
vs0 := []Vertex{
{
DstX: 1, DstY: 1, SrcX: 1, SrcY: 1,
ColorR: 1, ColorG: 0, ColorB: 0, ColorA: 1,
},
{
DstX: 15, DstY: 1, SrcX: 1, SrcY: 1,
ColorR: 1, ColorG: 0, ColorB: 0, ColorA: 1,
},
{
DstX: 1, DstY: 15, SrcX: 1, SrcY: 1,
ColorR: 1, ColorG: 0, ColorB: 0, ColorA: 1,
},
{
DstX: 15, DstY: 15, SrcX: 1, SrcY: 1,
ColorR: 1, ColorG: 0, ColorB: 0, ColorA: 1,
},
}
is0 := []uint16{0, 1, 2, 1, 2, 3}
vs1 := []Vertex{
{
DstX: 2, DstY: 2, SrcX: 1, SrcY: 1,
ColorR: 0, ColorG: 1, ColorB: 0, ColorA: 1,
},
{
DstX: 14, DstY: 2, SrcX: 1, SrcY: 1,
ColorR: 0, ColorG: 1, ColorB: 0, ColorA: 1,
},
{
DstX: 2, DstY: 14, SrcX: 1, SrcY: 1,
ColorR: 0, ColorG: 1, ColorB: 0, ColorA: 1,
},
{
DstX: 14, DstY: 14, SrcX: 1, SrcY: 1,
ColorR: 0, ColorG: 1, ColorB: 0, ColorA: 1,
},
}
is1 := []uint16{4, 5, 6, 5, 6, 7}
vs2 := []Vertex{
{
DstX: 3, DstY: 3, SrcX: 1, SrcY: 1,
ColorR: 0, ColorG: 0, ColorB: 1, ColorA: 1,
},
{
DstX: 13, DstY: 3, SrcX: 1, SrcY: 1,
ColorR: 0, ColorG: 0, ColorB: 1, ColorA: 1,
},
{
DstX: 3, DstY: 13, SrcX: 1, SrcY: 1,
ColorR: 0, ColorG: 0, ColorB: 1, ColorA: 1,
},
{
DstX: 13, DstY: 13, SrcX: 1, SrcY: 1,
ColorR: 0, ColorG: 0, ColorB: 1, ColorA: 1,
},
}
is2 := []uint16{8, 9, 10, 9, 10, 11}
// Draw all the vertices once. The even-odd rule is applied for all the vertices once.
dst := NewImage(16, 16)
op := &DrawTrianglesOptions{
EvenOdd: true,
}
dst.DrawTriangles(append(append(vs0, vs1...), vs2...), append(append(is0, is1...), is2...), emptySubImage, op)
for j := 0; j < 16; j++ {
for i := 0; i < 16; i++ {
got := dst.At(i, j)
var want color.RGBA
switch {
case 3 <= i && i < 13 && 3 <= j && j < 13:
want = color.RGBA{0, 0, 0xff, 0xff}
case 2 <= i && i < 14 && 2 <= j && j < 14:
want = color.RGBA{0, 0, 0, 0}
case 1 <= i && i < 15 && 1 <= j && j < 15:
want = color.RGBA{0xff, 0, 0, 0xff}
default:
want = color.RGBA{0, 0, 0, 0}
}
if got != want {
t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
}
}
}
// Do the same thing with a little shift. This confirms that the underlying stencil buffer is cleared correctly.
for i := range vs0 {
vs0[i].DstX++
vs0[i].DstY++
}
for i := range vs1 {
vs1[i].DstX++
vs1[i].DstY++
}
for i := range vs2 {
vs2[i].DstX++
vs2[i].DstY++
}
dst.Clear()
dst.DrawTriangles(append(append(vs0, vs1...), vs2...), append(append(is0, is1...), is2...), emptySubImage, op)
for j := 0; j < 16; j++ {
for i := 0; i < 16; i++ {
got := dst.At(i, j)
var want color.RGBA
switch {
case 4 <= i && i < 14 && 4 <= j && j < 14:
want = color.RGBA{0, 0, 0xff, 0xff}
case 3 <= i && i < 15 && 3 <= j && j < 15:
want = color.RGBA{0, 0, 0, 0}
case 2 <= i && i < 16 && 2 <= j && j < 16:
want = color.RGBA{0xff, 0, 0, 0xff}
default:
want = color.RGBA{0, 0, 0, 0}
}
if got != want {
t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
}
}
}
}

View File

@ -745,15 +745,15 @@ func (g *Graphics) draw(rps mtl.RenderPipelineState, dst *Image, dstRegion drive
case drawWithStencil: case drawWithStencil:
desc := mtl.DepthStencilDescriptor{ desc := mtl.DepthStencilDescriptor{
BackFaceStencil: mtl.StencilDescriptor{ BackFaceStencil: mtl.StencilDescriptor{
StencilFailureOperation: mtl.StencilOperationZero, StencilFailureOperation: mtl.StencilOperationKeep,
DepthFailureOperation: mtl.StencilOperationZero, DepthFailureOperation: mtl.StencilOperationKeep,
DepthStencilPassOperation: mtl.StencilOperationZero, DepthStencilPassOperation: mtl.StencilOperationKeep,
StencilCompareFunction: mtl.CompareFunctionNotEqual, StencilCompareFunction: mtl.CompareFunctionNotEqual,
}, },
FrontFaceStencil: mtl.StencilDescriptor{ FrontFaceStencil: mtl.StencilDescriptor{
StencilFailureOperation: mtl.StencilOperationZero, StencilFailureOperation: mtl.StencilOperationKeep,
DepthFailureOperation: mtl.StencilOperationZero, DepthFailureOperation: mtl.StencilOperationKeep,
DepthStencilPassOperation: mtl.StencilOperationZero, DepthStencilPassOperation: mtl.StencilOperationKeep,
StencilCompareFunction: mtl.CompareFunctionNotEqual, StencilCompareFunction: mtl.CompareFunctionNotEqual,
}, },
} }

View File

@ -264,7 +264,6 @@ func (c *context) bindStencilBuffer(f framebufferNative, r renderbufferNative) e
if s := gl.CheckFramebufferStatusEXT(gl.FRAMEBUFFER); s != gl.FRAMEBUFFER_COMPLETE { if s := gl.CheckFramebufferStatusEXT(gl.FRAMEBUFFER); s != gl.FRAMEBUFFER_COMPLETE {
return errors.New(fmt.Sprintf("opengl: glFramebufferRenderbuffer failed: %d", s)) return errors.New(fmt.Sprintf("opengl: glFramebufferRenderbuffer failed: %d", s))
} }
gl.Clear(gl.STENCIL_BUFFER_BIT)
return nil return nil
} }
@ -550,6 +549,7 @@ func (c *context) disableStencilTest() {
} }
func (c *context) beginStencilWithEvenOddRule() { func (c *context) beginStencilWithEvenOddRule() {
gl.Clear(gl.STENCIL_BUFFER_BIT)
gl.StencilFunc(gl.ALWAYS, 0x00, 0xff) gl.StencilFunc(gl.ALWAYS, 0x00, 0xff)
gl.StencilOp(gl.KEEP, gl.KEEP, gl.INVERT) gl.StencilOp(gl.KEEP, gl.KEEP, gl.INVERT)
gl.ColorMask(false, false, false, false) gl.ColorMask(false, false, false, false)
@ -557,6 +557,6 @@ func (c *context) beginStencilWithEvenOddRule() {
func (c *context) endStencilWithEvenOddRule() { func (c *context) endStencilWithEvenOddRule() {
gl.StencilFunc(gl.NOTEQUAL, 0x00, 0xff) gl.StencilFunc(gl.NOTEQUAL, 0x00, 0xff)
gl.StencilOp(gl.ZERO, gl.ZERO, gl.ZERO) gl.StencilOp(gl.KEEP, gl.KEEP, gl.KEEP)
gl.ColorMask(true, true, true, true) gl.ColorMask(true, true, true, true)
} }

View File

@ -298,7 +298,6 @@ func (c *context) bindStencilBuffer(f framebufferNative, r renderbufferNative) e
if s := gl.checkFramebufferStatus.Invoke(gles.FRAMEBUFFER); s.Int() != gles.FRAMEBUFFER_COMPLETE { if s := gl.checkFramebufferStatus.Invoke(gles.FRAMEBUFFER); s.Int() != gles.FRAMEBUFFER_COMPLETE {
return errors.New(fmt.Sprintf("opengl: framebufferRenderbuffer failed: %d", s.Int())) return errors.New(fmt.Sprintf("opengl: framebufferRenderbuffer failed: %d", s.Int()))
} }
gl.clear.Invoke(gles.STENCIL_BUFFER_BIT)
return nil return nil
} }
@ -660,6 +659,7 @@ func (c *context) disableStencilTest() {
func (c *context) beginStencilWithEvenOddRule() { func (c *context) beginStencilWithEvenOddRule() {
gl := c.gl gl := c.gl
gl.clear.Invoke(gles.STENCIL_BUFFER_BIT)
gl.stencilFunc.Invoke(gles.ALWAYS, 0x00, 0xff) gl.stencilFunc.Invoke(gles.ALWAYS, 0x00, 0xff)
gl.stencilOp.Invoke(gles.KEEP, gles.KEEP, gles.INVERT) gl.stencilOp.Invoke(gles.KEEP, gles.KEEP, gles.INVERT)
gl.colorMask.Invoke(false, false, false, false) gl.colorMask.Invoke(false, false, false, false)
@ -668,6 +668,6 @@ func (c *context) beginStencilWithEvenOddRule() {
func (c *context) endStencilWithEvenOddRule() { func (c *context) endStencilWithEvenOddRule() {
gl := c.gl gl := c.gl
gl.stencilFunc.Invoke(gles.NOTEQUAL, 0x00, 0xff) gl.stencilFunc.Invoke(gles.NOTEQUAL, 0x00, 0xff)
gl.stencilOp.Invoke(gles.ZERO, gles.ZERO, gles.ZERO) gl.stencilOp.Invoke(gles.KEEP, gles.KEEP, gles.KEEP)
gl.colorMask.Invoke(true, true, true, true) gl.colorMask.Invoke(true, true, true, true)
} }

View File

@ -246,7 +246,6 @@ func (c *context) bindStencilBuffer(f framebufferNative, r renderbufferNative) e
if s := c.ctx.CheckFramebufferStatus(gles.FRAMEBUFFER); s != gles.FRAMEBUFFER_COMPLETE { if s := c.ctx.CheckFramebufferStatus(gles.FRAMEBUFFER); s != gles.FRAMEBUFFER_COMPLETE {
return errors.New(fmt.Sprintf("opengl: glFramebufferRenderbuffer failed: %d", s)) return errors.New(fmt.Sprintf("opengl: glFramebufferRenderbuffer failed: %d", s))
} }
c.ctx.Clear(gles.STENCIL_BUFFER_BIT)
return nil return nil
} }
@ -511,6 +510,7 @@ func (c *context) disableStencilTest() {
} }
func (c *context) beginStencilWithEvenOddRule() { func (c *context) beginStencilWithEvenOddRule() {
c.ctx.Clear(gles.STENCIL_BUFFER_BIT)
c.ctx.StencilFunc(gles.ALWAYS, 0x00, 0xff) c.ctx.StencilFunc(gles.ALWAYS, 0x00, 0xff)
c.ctx.StencilOp(gles.KEEP, gles.KEEP, gles.INVERT) c.ctx.StencilOp(gles.KEEP, gles.KEEP, gles.INVERT)
c.ctx.ColorMask(false, false, false, false) c.ctx.ColorMask(false, false, false, false)
@ -518,6 +518,6 @@ func (c *context) beginStencilWithEvenOddRule() {
func (c *context) endStencilWithEvenOddRule() { func (c *context) endStencilWithEvenOddRule() {
c.ctx.StencilFunc(gles.NOTEQUAL, 0x00, 0xff) c.ctx.StencilFunc(gles.NOTEQUAL, 0x00, 0xff)
c.ctx.StencilOp(gles.ZERO, gles.ZERO, gles.ZERO) c.ctx.StencilOp(gles.KEEP, gles.KEEP, gles.KEEP)
c.ctx.ColorMask(true, true, true, true) c.ctx.ColorMask(true, true, true, true)
} }