diff --git a/image_test.go b/image_test.go index 51bd30e56..38797af0b 100644 --- a/image_test.go +++ b/image_test.go @@ -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) + } + } + } +} diff --git a/internal/graphicsdriver/metal/graphics.go b/internal/graphicsdriver/metal/graphics.go index d0357e326..86a0c7682 100644 --- a/internal/graphicsdriver/metal/graphics.go +++ b/internal/graphicsdriver/metal/graphics.go @@ -745,15 +745,15 @@ func (g *Graphics) draw(rps mtl.RenderPipelineState, dst *Image, dstRegion drive case drawWithStencil: desc := mtl.DepthStencilDescriptor{ BackFaceStencil: mtl.StencilDescriptor{ - StencilFailureOperation: mtl.StencilOperationZero, - DepthFailureOperation: mtl.StencilOperationZero, - DepthStencilPassOperation: mtl.StencilOperationZero, + StencilFailureOperation: mtl.StencilOperationKeep, + DepthFailureOperation: mtl.StencilOperationKeep, + DepthStencilPassOperation: mtl.StencilOperationKeep, StencilCompareFunction: mtl.CompareFunctionNotEqual, }, FrontFaceStencil: mtl.StencilDescriptor{ - StencilFailureOperation: mtl.StencilOperationZero, - DepthFailureOperation: mtl.StencilOperationZero, - DepthStencilPassOperation: mtl.StencilOperationZero, + StencilFailureOperation: mtl.StencilOperationKeep, + DepthFailureOperation: mtl.StencilOperationKeep, + DepthStencilPassOperation: mtl.StencilOperationKeep, StencilCompareFunction: mtl.CompareFunctionNotEqual, }, } diff --git a/internal/graphicsdriver/opengl/context_desktop.go b/internal/graphicsdriver/opengl/context_desktop.go index c700b5710..e57b38696 100644 --- a/internal/graphicsdriver/opengl/context_desktop.go +++ b/internal/graphicsdriver/opengl/context_desktop.go @@ -264,7 +264,6 @@ func (c *context) bindStencilBuffer(f framebufferNative, r renderbufferNative) e if s := gl.CheckFramebufferStatusEXT(gl.FRAMEBUFFER); s != gl.FRAMEBUFFER_COMPLETE { return errors.New(fmt.Sprintf("opengl: glFramebufferRenderbuffer failed: %d", s)) } - gl.Clear(gl.STENCIL_BUFFER_BIT) return nil } @@ -550,6 +549,7 @@ func (c *context) disableStencilTest() { } func (c *context) beginStencilWithEvenOddRule() { + gl.Clear(gl.STENCIL_BUFFER_BIT) gl.StencilFunc(gl.ALWAYS, 0x00, 0xff) gl.StencilOp(gl.KEEP, gl.KEEP, gl.INVERT) gl.ColorMask(false, false, false, false) @@ -557,6 +557,6 @@ func (c *context) beginStencilWithEvenOddRule() { func (c *context) endStencilWithEvenOddRule() { 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) } diff --git a/internal/graphicsdriver/opengl/context_js.go b/internal/graphicsdriver/opengl/context_js.go index 6ec65c33d..ed6c0ef5f 100644 --- a/internal/graphicsdriver/opengl/context_js.go +++ b/internal/graphicsdriver/opengl/context_js.go @@ -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 { return errors.New(fmt.Sprintf("opengl: framebufferRenderbuffer failed: %d", s.Int())) } - gl.clear.Invoke(gles.STENCIL_BUFFER_BIT) return nil } @@ -660,6 +659,7 @@ func (c *context) disableStencilTest() { func (c *context) beginStencilWithEvenOddRule() { gl := c.gl + gl.clear.Invoke(gles.STENCIL_BUFFER_BIT) gl.stencilFunc.Invoke(gles.ALWAYS, 0x00, 0xff) gl.stencilOp.Invoke(gles.KEEP, gles.KEEP, gles.INVERT) gl.colorMask.Invoke(false, false, false, false) @@ -668,6 +668,6 @@ func (c *context) beginStencilWithEvenOddRule() { func (c *context) endStencilWithEvenOddRule() { gl := c.gl 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) } diff --git a/internal/graphicsdriver/opengl/context_mobile.go b/internal/graphicsdriver/opengl/context_mobile.go index 740994f17..4f48f1b27 100644 --- a/internal/graphicsdriver/opengl/context_mobile.go +++ b/internal/graphicsdriver/opengl/context_mobile.go @@ -246,7 +246,6 @@ func (c *context) bindStencilBuffer(f framebufferNative, r renderbufferNative) e if s := c.ctx.CheckFramebufferStatus(gles.FRAMEBUFFER); s != gles.FRAMEBUFFER_COMPLETE { return errors.New(fmt.Sprintf("opengl: glFramebufferRenderbuffer failed: %d", s)) } - c.ctx.Clear(gles.STENCIL_BUFFER_BIT) return nil } @@ -511,6 +510,7 @@ func (c *context) disableStencilTest() { } func (c *context) beginStencilWithEvenOddRule() { + c.ctx.Clear(gles.STENCIL_BUFFER_BIT) c.ctx.StencilFunc(gles.ALWAYS, 0x00, 0xff) c.ctx.StencilOp(gles.KEEP, gles.KEEP, gles.INVERT) c.ctx.ColorMask(false, false, false, false) @@ -518,6 +518,6 @@ func (c *context) beginStencilWithEvenOddRule() { func (c *context) endStencilWithEvenOddRule() { 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) }