ebiten: add a new FillRule: NonZero

Closes #2782
This commit is contained in:
Hajime Hoshi 2023-11-06 09:18:08 +09:00
parent 358106bdc0
commit 3ca6184294
21 changed files with 363 additions and 92 deletions

View File

@ -72,7 +72,7 @@ type DrawTrianglesOptions struct {
// FillRule indicates the rule how an overlapped region is rendered.
//
// The rule EvenOdd is useful when you want to render a complex polygon.
// The rules NonZero and EvenOdd are useful when you want to render a complex polygon.
// A complex polygon is a non-convex polygon like a concave polygon, a polygon with holes, or a self-intersecting polygon.
// See examples/vector for actual usages.
//

View File

@ -141,7 +141,10 @@ func drawEbitenText(screen *ebiten.Image, x, y int, aa bool, line bool) {
op := &ebiten.DrawTrianglesOptions{}
op.AntiAlias = aa
if !line {
op.FillRule = ebiten.EvenOdd
// ebiten.EvenOdd is also fine here.
// NonZero and EvenOdd differ when rendering a complex polygons with self-intersections and/or holes.
// See https://en.wikipedia.org/wiki/Nonzero-rule and https://en.wikipedia.org/wiki/Even%E2%80%93odd_rule .
op.FillRule = ebiten.NonZero
}
screen.DrawTriangles(vs, is, whiteSubImage, op)
}
@ -197,7 +200,7 @@ func drawEbitenLogo(screen *ebiten.Image, x, y int, aa bool, line bool) {
op := &ebiten.DrawTrianglesOptions{}
op.AntiAlias = aa
if !line {
op.FillRule = ebiten.EvenOdd
op.FillRule = ebiten.NonZero
}
screen.DrawTriangles(vs, is, whiteSubImage, op)
}
@ -241,7 +244,7 @@ func drawArc(screen *ebiten.Image, count int, aa bool, line bool) {
op := &ebiten.DrawTrianglesOptions{}
op.AntiAlias = aa
if !line {
op.FillRule = ebiten.EvenOdd
op.FillRule = ebiten.NonZero
}
screen.DrawTriangles(vs, is, whiteSubImage, op)
}
@ -298,7 +301,7 @@ func drawWave(screen *ebiten.Image, counter int, aa bool, line bool) {
op := &ebiten.DrawTrianglesOptions{}
op.AntiAlias = aa
if !line {
op.FillRule = ebiten.EvenOdd
op.FillRule = ebiten.NonZero
}
screen.DrawTriangles(vs, is, whiteSubImage, op)
}

View File

@ -314,6 +314,10 @@ const (
// FillAll indicates all the triangles are rendered regardless of overlaps.
FillAll FillRule = FillRule(graphicsdriver.FillAll)
// NonZero means that triangles are rendered based on the non-zero rule.
// If and only if the number of overlaps is not 0, the region is rendered.
NonZero FillRule = FillRule(graphicsdriver.NonZero)
// EvenOdd means that triangles are rendered based on the even-odd rule.
// If and only if the number of overlaps is odd, the region is rendered.
EvenOdd FillRule = FillRule(graphicsdriver.EvenOdd)
@ -367,7 +371,7 @@ type DrawTrianglesOptions struct {
// FillRule indicates the rule how an overlapped region is rendered.
//
// The rule EvenOdd is useful when you want to render a complex polygon.
// The rules NonZero and EvenOdd are useful when you want to render a complex polygon.
// A complex polygon is a non-convex polygon like a concave polygon, a polygon with holes, or a self-intersecting polygon.
// See examples/vector for actual usages.
//
@ -543,7 +547,7 @@ type DrawTrianglesShaderOptions struct {
// FillRule indicates the rule how an overlapped region is rendered.
//
// The rule EvenOdd is useful when you want to render a complex polygon.
// The rules NonZero and EvenOdd are useful when you want to render a complex polygon.
// A complex polygon is a non-convex polygon like a concave polygon, a polygon with holes, or a self-intersecting polygon.
// See examples/vector for actual usages.
//

View File

@ -2793,6 +2793,189 @@ func TestImageEvenOdd(t *testing.T) {
}
}
func TestImageFillRule(t *testing.T) {
for _, fillRule := range []ebiten.FillRule{ebiten.FillAll, ebiten.NonZero, ebiten.EvenOdd} {
fillRule := fillRule
var name string
switch fillRule {
case ebiten.FillAll:
name = "FillAll"
case ebiten.NonZero:
name = "NonZero"
case ebiten.EvenOdd:
name = "EvenOdd"
}
t.Run(name, func(t *testing.T) {
whiteImage := ebiten.NewImage(3, 3)
whiteImage.Fill(color.White)
emptySubImage := whiteImage.SubImage(image.Rect(1, 1, 2, 2)).(*ebiten.Image)
// The outside rectangle (clockwise)
vs0 := []ebiten.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: 15, DstY: 15, 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,
},
}
is0 := []uint16{0, 1, 2, 2, 3, 0}
// An inside rectangle (clockwise)
vs1 := []ebiten.Vertex{
{
DstX: 2, DstY: 2, SrcX: 1, SrcY: 1,
ColorR: 0, ColorG: 1, ColorB: 0, ColorA: 1,
},
{
DstX: 7, DstY: 2, SrcX: 1, SrcY: 1,
ColorR: 0, ColorG: 1, ColorB: 0, ColorA: 1,
},
{
DstX: 7, DstY: 7, SrcX: 1, SrcY: 1,
ColorR: 0, ColorG: 1, ColorB: 0, ColorA: 1,
},
{
DstX: 2, DstY: 7, SrcX: 1, SrcY: 1,
ColorR: 0, ColorG: 1, ColorB: 0, ColorA: 1,
},
}
is1 := []uint16{4, 5, 6, 6, 7, 4}
// An inside rectangle (counter-clockwise)
vs2 := []ebiten.Vertex{
{
DstX: 9, DstY: 9, SrcX: 1, SrcY: 1,
ColorR: 0, ColorG: 0, ColorB: 1, ColorA: 1,
},
{
DstX: 14, DstY: 9, SrcX: 1, SrcY: 1,
ColorR: 0, ColorG: 0, ColorB: 1, ColorA: 1,
},
{
DstX: 14, DstY: 14, SrcX: 1, SrcY: 1,
ColorR: 0, ColorG: 0, ColorB: 1, ColorA: 1,
},
{
DstX: 9, DstY: 14, SrcX: 1, SrcY: 1,
ColorR: 0, ColorG: 0, ColorB: 1, ColorA: 1,
},
}
is2 := []uint16{8, 11, 10, 10, 9, 8}
// Draw all the vertices once. The even-odd rule is applied for all the vertices once.
dst := ebiten.NewImage(16, 16)
op := &ebiten.DrawTrianglesOptions{
FillRule: fillRule,
}
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 2 <= i && i < 7 && 2 <= j && j < 7:
if fillRule != ebiten.EvenOdd {
want = color.RGBA{G: 0xff, A: 0xff}
}
case 9 <= i && i < 14 && 9 <= j && j < 14:
if fillRule == ebiten.FillAll {
want = color.RGBA{B: 0xff, A: 0xff}
}
case 1 <= i && i < 15 && 1 <= j && j < 15:
want = color.RGBA{R: 0xff, A: 0xff}
}
if got != want {
t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
}
}
}
// Do the same thing but 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 3 <= i && i < 8 && 3 <= j && j < 8:
if fillRule != ebiten.EvenOdd {
want = color.RGBA{G: 0xff, A: 0xff}
}
case 10 <= i && i < 15 && 10 <= j && j < 15:
if fillRule == ebiten.FillAll {
want = color.RGBA{B: 0xff, A: 0xff}
}
case 2 <= i && i < 16 && 2 <= j && j < 16:
want = color.RGBA{R: 0xff, A: 0xff}
}
if got != want {
t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
}
}
}
// Do the same thing but with split DrawTriangle calls. This confirms that fill rules are applied for one call.
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(vs0, []uint16{0, 1, 2, 2, 3, 0}, emptySubImage, op)
dst.DrawTriangles(vs1, []uint16{0, 1, 2, 2, 3, 0}, emptySubImage, op)
dst.DrawTriangles(vs2, []uint16{0, 3, 2, 2, 1, 0}, 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 2 <= i && i < 7 && 2 <= j && j < 7:
want = color.RGBA{G: 0xff, A: 0xff}
case 9 <= i && i < 14 && 9 <= j && j < 14:
want = color.RGBA{B: 0xff, A: 0xff}
case 1 <= i && i < 15 && 1 <= j && j < 15:
want = color.RGBA{R: 0xff, A: 0xff}
}
if got != want {
t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
}
}
}
})
}
}
// #1658
func BenchmarkColorMScale(b *testing.B) {
r := rand.Float64

View File

@ -166,7 +166,7 @@ func (c *drawTrianglesCommand) CanMergeWithDrawTrianglesCommand(dst *Image, srcs
if c.fillRule != fillRule {
return false
}
if c.fillRule == graphicsdriver.EvenOdd && mightOverlapDstRegions(c.vertices, vertices) {
if c.fillRule != graphicsdriver.FillAll && mightOverlapDstRegions(c.vertices, vertices) {
return false
}
return true

View File

@ -584,25 +584,39 @@ func (g *graphics11) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphic
switch fillRule {
case graphicsdriver.FillAll:
g.deviceContext.DrawIndexed(uint32(dstRegion.IndexCount), uint32(indexOffset), 0)
case graphicsdriver.EvenOdd:
bs, err := g.blendState(blend, prepareStencil)
case graphicsdriver.NonZero:
bs, err := g.blendState(blend, incrementStencil)
if err != nil {
return err
}
g.deviceContext.OMSetBlendState(bs, nil, 0xffffffff)
dss, err := g.depthStencilState(prepareStencil)
dss, err := g.depthStencilState(incrementStencil)
if err != nil {
return err
}
g.deviceContext.OMSetDepthStencilState(dss, 0)
g.deviceContext.DrawIndexed(uint32(dstRegion.IndexCount), uint32(indexOffset), 0)
bs, err = g.blendState(blend, drawWithStencil)
case graphicsdriver.EvenOdd:
bs, err := g.blendState(blend, invertStencil)
if err != nil {
return err
}
g.deviceContext.OMSetBlendState(bs, nil, 0xffffffff)
dss, err = g.depthStencilState(drawWithStencil)
dss, err := g.depthStencilState(invertStencil)
if err != nil {
return err
}
g.deviceContext.OMSetDepthStencilState(dss, 0)
g.deviceContext.DrawIndexed(uint32(dstRegion.IndexCount), uint32(indexOffset), 0)
}
if fillRule != graphicsdriver.FillAll {
bs, err := g.blendState(blend, drawWithStencil)
if err != nil {
return err
}
g.deviceContext.OMSetBlendState(bs, nil, 0xffffffff)
dss, err := g.depthStencilState(drawWithStencil)
if err != nil {
return err
}
@ -627,9 +641,9 @@ func (g *graphics11) genNextShaderID() graphicsdriver.ShaderID {
}
func (g *graphics11) blendState(blend graphicsdriver.Blend, stencilMode stencilMode) (*_ID3D11BlendState, error) {
writeMask := uint8(_D3D11_COLOR_WRITE_ENABLE_ALL)
if stencilMode == prepareStencil {
writeMask = 0
var writeMask uint8
if stencilMode == noStencil || stencilMode == drawWithStencil {
writeMask = uint8(_D3D11_COLOR_WRITE_ENABLE_ALL)
}
key := blendStateKey{
@ -693,7 +707,11 @@ func (g *graphics11) depthStencilState(mode stencilMode) (*_ID3D11DepthStencilSt
},
}
switch mode {
case prepareStencil:
case incrementStencil:
desc.StencilEnable = 1
desc.FrontFace.StencilPassOp = _D3D11_STENCIL_OP_INCR
desc.BackFace.StencilPassOp = _D3D11_STENCIL_OP_DECR
case invertStencil:
desc.StencilEnable = 1
desc.FrontFace.StencilPassOp = _D3D11_STENCIL_OP_INVERT
desc.BackFace.StencilPassOp = _D3D11_STENCIL_OP_INVERT

View File

@ -1097,7 +1097,7 @@ func (g *graphics12) DrawTriangles(dstID graphicsdriver.ImageID, srcs [graphics.
// Release constant buffers when too many ones will be created.
numPipelines := 1
if fillRule == graphicsdriver.EvenOdd {
if fillRule != graphicsdriver.FillAll {
numPipelines = 2
}
if len(g.pipelineStates.constantBuffers[g.frameIndex])+numPipelines > numDescriptorsPerFrame {

View File

@ -35,9 +35,10 @@ import (
type stencilMode int
const (
prepareStencil stencilMode = iota
noStencil stencilMode = iota
incrementStencil
invertStencil
drawWithStencil
noStencil
)
const frameCount = 2

View File

@ -309,21 +309,31 @@ func (p *pipelineStates) drawTriangles(device *_ID3D12Device, commandList *_ID3D
switch fillRule {
case graphicsdriver.FillAll:
commandList.DrawIndexedInstanced(uint32(dstRegion.IndexCount), 1, uint32(indexOffset), 0, 0)
case graphicsdriver.EvenOdd:
s, err := shader.pipelineState(blend, prepareStencil, screen)
case graphicsdriver.NonZero:
s, err := shader.pipelineState(blend, incrementStencil, screen)
if err != nil {
return err
}
commandList.SetPipelineState(s)
commandList.DrawIndexedInstanced(uint32(dstRegion.IndexCount), 1, uint32(indexOffset), 0, 0)
s, err = shader.pipelineState(blend, drawWithStencil, screen)
case graphicsdriver.EvenOdd:
s, err := shader.pipelineState(blend, invertStencil, screen)
if err != nil {
return err
}
commandList.SetPipelineState(s)
commandList.DrawIndexedInstanced(uint32(dstRegion.IndexCount), 1, uint32(indexOffset), 0, 0)
}
if fillRule != graphicsdriver.FillAll {
s, err := shader.pipelineState(blend, drawWithStencil, screen)
if err != nil {
return err
}
commandList.SetPipelineState(s)
commandList.DrawIndexedInstanced(uint32(dstRegion.IndexCount), 1, uint32(indexOffset), 0, 0)
}
indexOffset += dstRegion.IndexCount
}
@ -443,14 +453,21 @@ func (p *pipelineStates) newPipelineState(device *_ID3D12Device, vsh, psh *_ID3D
StencilFunc: _D3D12_COMPARISON_FUNC_ALWAYS,
},
}
writeMask := uint8(_D3D12_COLOR_WRITE_ENABLE_ALL)
var writeMask uint8
if stencilMode == noStencil || stencilMode == drawWithStencil {
writeMask = uint8(_D3D12_COLOR_WRITE_ENABLE_ALL)
}
switch stencilMode {
case prepareStencil:
case incrementStencil:
depthStencilDesc.StencilEnable = 1
depthStencilDesc.FrontFace.StencilPassOp = _D3D12_STENCIL_OP_INCR
depthStencilDesc.BackFace.StencilPassOp = _D3D12_STENCIL_OP_DECR
case invertStencil:
depthStencilDesc.StencilEnable = 1
depthStencilDesc.FrontFace.StencilPassOp = _D3D12_STENCIL_OP_INVERT
depthStencilDesc.BackFace.StencilPassOp = _D3D12_STENCIL_OP_INVERT
writeMask = 0
case drawWithStencil:
depthStencilDesc.StencilEnable = 1
depthStencilDesc.FrontFace.StencilFunc = _D3D12_COMPARISON_FUNC_NOT_EQUAL

View File

@ -31,6 +31,7 @@ type FillRule int
const (
FillAll FillRule = iota
NonZero
EvenOdd
)
@ -38,6 +39,8 @@ func (f FillRule) String() string {
switch f {
case FillAll:
return "FillAll"
case NonZero:
return "NonZero"
case EvenOdd:
return "EvenOdd"
default:

View File

@ -70,9 +70,10 @@ type Graphics struct {
type stencilMode int
const (
prepareStencil stencilMode = iota
noStencil stencilMode = iota
incrementStencil
invertStencil
drawWithStencil
noStencil
)
var (
@ -412,7 +413,35 @@ func (g *Graphics) Initialize() error {
}
// The stencil reference value is always 0 (default).
g.dsss[prepareStencil] = g.view.getMTLDevice().MakeDepthStencilState(mtl.DepthStencilDescriptor{
g.dsss[noStencil] = g.view.getMTLDevice().MakeDepthStencilState(mtl.DepthStencilDescriptor{
BackFaceStencil: mtl.StencilDescriptor{
StencilFailureOperation: mtl.StencilOperationKeep,
DepthFailureOperation: mtl.StencilOperationKeep,
DepthStencilPassOperation: mtl.StencilOperationKeep,
StencilCompareFunction: mtl.CompareFunctionAlways,
},
FrontFaceStencil: mtl.StencilDescriptor{
StencilFailureOperation: mtl.StencilOperationKeep,
DepthFailureOperation: mtl.StencilOperationKeep,
DepthStencilPassOperation: mtl.StencilOperationKeep,
StencilCompareFunction: mtl.CompareFunctionAlways,
},
})
g.dsss[incrementStencil] = g.view.getMTLDevice().MakeDepthStencilState(mtl.DepthStencilDescriptor{
BackFaceStencil: mtl.StencilDescriptor{
StencilFailureOperation: mtl.StencilOperationKeep,
DepthFailureOperation: mtl.StencilOperationKeep,
DepthStencilPassOperation: mtl.StencilOperationDecrementWrap,
StencilCompareFunction: mtl.CompareFunctionAlways,
},
FrontFaceStencil: mtl.StencilDescriptor{
StencilFailureOperation: mtl.StencilOperationKeep,
DepthFailureOperation: mtl.StencilOperationKeep,
DepthStencilPassOperation: mtl.StencilOperationIncrementWrap,
StencilCompareFunction: mtl.CompareFunctionAlways,
},
})
g.dsss[invertStencil] = g.view.getMTLDevice().MakeDepthStencilState(mtl.DepthStencilDescriptor{
BackFaceStencil: mtl.StencilDescriptor{
StencilFailureOperation: mtl.StencilOperationKeep,
DepthFailureOperation: mtl.StencilOperationKeep,
@ -440,20 +469,6 @@ func (g *Graphics) Initialize() error {
StencilCompareFunction: mtl.CompareFunctionNotEqual,
},
})
g.dsss[noStencil] = g.view.getMTLDevice().MakeDepthStencilState(mtl.DepthStencilDescriptor{
BackFaceStencil: mtl.StencilDescriptor{
StencilFailureOperation: mtl.StencilOperationKeep,
DepthFailureOperation: mtl.StencilOperationKeep,
DepthStencilPassOperation: mtl.StencilOperationKeep,
StencilCompareFunction: mtl.CompareFunctionAlways,
},
FrontFaceStencil: mtl.StencilDescriptor{
StencilFailureOperation: mtl.StencilOperationKeep,
DepthFailureOperation: mtl.StencilOperationKeep,
DepthStencilPassOperation: mtl.StencilOperationKeep,
StencilCompareFunction: mtl.CompareFunctionAlways,
},
})
g.cq = g.view.getMTLDevice().MakeCommandQueue()
return nil
@ -472,7 +487,7 @@ func (g *Graphics) draw(dst *Image, dstRegions []graphicsdriver.DstRegion, srcs
// When preparing a stencil buffer, flush the current render command encoder
// to make sure the stencil buffer is cleared when loading.
// TODO: What about clearing the stencil buffer by vertices?
if g.lastDst != dst || g.lastFillRule != fillRule || fillRule == graphicsdriver.EvenOdd {
if g.lastDst != dst || g.lastFillRule != fillRule || fillRule != graphicsdriver.FillAll {
g.flushRenderCommandEncoderIfNeeded()
}
g.lastDst = dst
@ -498,7 +513,7 @@ func (g *Graphics) draw(dst *Image, dstRegions []graphicsdriver.DstRegion, srcs
rpd.ColorAttachments[0].Texture = t
rpd.ColorAttachments[0].ClearColor = mtl.ClearColor{}
if fillRule == graphicsdriver.EvenOdd {
if fillRule != graphicsdriver.FillAll {
dst.ensureStencil()
rpd.StencilAttachment.LoadAction = mtl.LoadActionClear
rpd.StencilAttachment.StoreAction = mtl.StoreActionDontCare
@ -539,9 +554,10 @@ func (g *Graphics) draw(dst *Image, dstRegions []graphicsdriver.DstRegion, srcs
}
var (
prepareStencilRpss mtl.RenderPipelineState
drawWithStencilRpss mtl.RenderPipelineState
noStencilRpss mtl.RenderPipelineState
noStencilRpss mtl.RenderPipelineState
incrementStencilRpss mtl.RenderPipelineState
invertStencilRpss mtl.RenderPipelineState
drawWithStencilRpss mtl.RenderPipelineState
)
switch fillRule {
case graphicsdriver.FillAll:
@ -550,14 +566,21 @@ func (g *Graphics) draw(dst *Image, dstRegions []graphicsdriver.DstRegion, srcs
return err
}
noStencilRpss = s
case graphicsdriver.EvenOdd:
s, err := shader.RenderPipelineState(&g.view, blend, prepareStencil, dst.screen)
case graphicsdriver.NonZero:
s, err := shader.RenderPipelineState(&g.view, blend, incrementStencil, dst.screen)
if err != nil {
return err
}
prepareStencilRpss = s
s, err = shader.RenderPipelineState(&g.view, blend, drawWithStencil, dst.screen)
incrementStencilRpss = s
case graphicsdriver.EvenOdd:
s, err := shader.RenderPipelineState(&g.view, blend, invertStencil, dst.screen)
if err != nil {
return err
}
invertStencilRpss = s
}
if fillRule != graphicsdriver.FillAll {
s, err := shader.RenderPipelineState(&g.view, blend, drawWithStencil, dst.screen)
if err != nil {
return err
}
@ -577,11 +600,16 @@ func (g *Graphics) draw(dst *Image, dstRegions []graphicsdriver.DstRegion, srcs
g.rce.SetDepthStencilState(g.dsss[noStencil])
g.rce.SetRenderPipelineState(noStencilRpss)
g.rce.DrawIndexedPrimitives(mtl.PrimitiveTypeTriangle, dstRegion.IndexCount, mtl.IndexTypeUInt32, g.ib, indexOffset*int(unsafe.Sizeof(uint32(0))))
case graphicsdriver.EvenOdd:
g.rce.SetDepthStencilState(g.dsss[prepareStencil])
g.rce.SetRenderPipelineState(prepareStencilRpss)
case graphicsdriver.NonZero:
g.rce.SetDepthStencilState(g.dsss[incrementStencil])
g.rce.SetRenderPipelineState(incrementStencilRpss)
g.rce.DrawIndexedPrimitives(mtl.PrimitiveTypeTriangle, dstRegion.IndexCount, mtl.IndexTypeUInt32, g.ib, indexOffset*int(unsafe.Sizeof(uint32(0))))
case graphicsdriver.EvenOdd:
g.rce.SetDepthStencilState(g.dsss[invertStencil])
g.rce.SetRenderPipelineState(invertStencilRpss)
g.rce.DrawIndexedPrimitives(mtl.PrimitiveTypeTriangle, dstRegion.IndexCount, mtl.IndexTypeUInt32, g.ib, indexOffset*int(unsafe.Sizeof(uint32(0))))
}
if fillRule != graphicsdriver.FillAll {
g.rce.SetDepthStencilState(g.dsss[drawWithStencil])
g.rce.SetRenderPipelineState(drawWithStencilRpss)
g.rce.DrawIndexedPrimitives(mtl.PrimitiveTypeTriangle, dstRegion.IndexCount, mtl.IndexTypeUInt32, g.ib, indexOffset*int(unsafe.Sizeof(uint32(0))))

View File

@ -119,10 +119,10 @@ func (s *Shader) RenderPipelineState(view *view, blend graphicsdriver.Blend, ste
rpld.ColorAttachments[0].AlphaBlendOperation = blendOperationToMetalBlendOperation(blend.BlendOperationAlpha)
rpld.ColorAttachments[0].RGBBlendOperation = blendOperationToMetalBlendOperation(blend.BlendOperationRGB)
if stencilMode == prepareStencil {
rpld.ColorAttachments[0].WriteMask = mtl.ColorWriteMaskNone
} else {
if stencilMode == noStencil || stencilMode == drawWithStencil {
rpld.ColorAttachments[0].WriteMask = mtl.ColorWriteMaskAll
} else {
rpld.ColorAttachments[0].WriteMask = mtl.ColorWriteMaskNone
}
rps, err := view.getMTLDevice().MakeRenderPipelineState(rpld)

View File

@ -17,10 +17,12 @@ package gl
const (
ALWAYS = 0x0207
ARRAY_BUFFER = 0x8892
BACK = 0x0405
BLEND = 0x0BE2
CLAMP_TO_EDGE = 0x812F
COLOR_ATTACHMENT0 = 0x8CE0
COMPILE_STATUS = 0x8B81
DECR_WRAP = 0x8508
DEPTH24_STENCIL8 = 0x88F0
DST_ALPHA = 0x0304
DST_COLOR = 0x0306
@ -32,10 +34,13 @@ const (
FRAMEBUFFER = 0x8D40
FRAMEBUFFER_BINDING = 0x8CA6
FRAMEBUFFER_COMPLETE = 0x8CD5
FRONT = 0x0404
FRONT_AND_BACK = 0x0408
FUNC_ADD = 0x8006
FUNC_REVERSE_SUBTRACT = 0x800b
FUNC_SUBTRACT = 0x800a
HIGH_FLOAT = 0x8DF2
INCR_WRAP = 0x8507
INFO_LOG_LENGTH = 0x8B84
INVERT = 0x150A
KEEP = 0x1E00

View File

@ -501,11 +501,11 @@ func (d *DebugContext) StencilFunc(arg0 uint32, arg1 int32, arg2 uint32) {
}
}
func (d *DebugContext) StencilOp(arg0 uint32, arg1 uint32, arg2 uint32) {
d.Context.StencilOp(arg0, arg1, arg2)
fmt.Fprintln(os.Stderr, "StencilOp")
func (d *DebugContext) StencilOpSeparate(arg0 uint32, arg1 uint32, arg2 uint32, arg3 uint32) {
d.Context.StencilOpSeparate(arg0, arg1, arg2, arg3)
fmt.Fprintln(os.Stderr, "StencilOpSeparate")
if e := d.Context.GetError(); e != NO_ERROR {
panic(fmt.Sprintf("gl: GetError() returned %d at StencilOp", e))
panic(fmt.Sprintf("gl: GetError() returned %d at StencilOpSeparate", e))
}
}

View File

@ -244,9 +244,9 @@ package gl
// typedef void (*fn)(GLenum func, GLint ref, GLuint mask);
// ((fn)(fnptr))(func, ref, mask);
// }
// static void glowStencilOp(uintptr_t fnptr, GLenum fail, GLenum zfail, GLenum zpass) {
// typedef void (*fn)(GLenum fail, GLenum zfail, GLenum zpass);
// ((fn)(fnptr))(fail, zfail, zpass);
// static void glowStencilOpSeparate(uintptr_t fnptr, GLenum face, GLenum fail, GLenum zfail, GLenum zpass) {
// typedef void (*fn)(GLenum face, GLenum fail, GLenum zfail, GLenum zpass);
// ((fn)(fnptr))(face, fail, zfail, zpass);
// }
// static void glowTexImage2D(uintptr_t fnptr, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void* pixels) {
// typedef void (*fn)(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void* pixels);
@ -384,7 +384,7 @@ type defaultContext struct {
gpScissor C.uintptr_t
gpShaderSource C.uintptr_t
gpStencilFunc C.uintptr_t
gpStencilOp C.uintptr_t
gpStencilOpSeparate C.uintptr_t
gpTexImage2D C.uintptr_t
gpTexParameteri C.uintptr_t
gpTexSubImage2D C.uintptr_t
@ -688,8 +688,8 @@ func (c *defaultContext) StencilFunc(xfunc uint32, ref int32, mask uint32) {
C.glowStencilFunc(c.gpStencilFunc, C.GLenum(xfunc), C.GLint(ref), C.GLuint(mask))
}
func (c *defaultContext) StencilOp(fail uint32, zfail uint32, zpass uint32) {
C.glowStencilOp(c.gpStencilOp, C.GLenum(fail), C.GLenum(zfail), C.GLenum(zpass))
func (c *defaultContext) StencilOpSeparate(face uint32, fail uint32, zfail uint32, zpass uint32) {
C.glowStencilOpSeparate(c.gpStencilOpSeparate, C.GLenum(face), C.GLenum(fail), C.GLenum(zfail), C.GLenum(zpass))
}
func (c *defaultContext) TexImage2D(target uint32, level int32, internalformat int32, width int32, height int32, format uint32, xtype uint32, pixels []byte) {
@ -840,7 +840,7 @@ func (c *defaultContext) LoadFunctions() error {
c.gpScissor = C.uintptr_t(g.get("glScissor"))
c.gpShaderSource = C.uintptr_t(g.get("glShaderSource"))
c.gpStencilFunc = C.uintptr_t(g.get("glStencilFunc"))
c.gpStencilOp = C.uintptr_t(g.get("glStencilOp"))
c.gpStencilOpSeparate = C.uintptr_t(g.get("glStencilOpSeparate"))
c.gpTexImage2D = C.uintptr_t(g.get("glTexImage2D"))
c.gpTexParameteri = C.uintptr_t(g.get("glTexParameteri"))
c.gpTexSubImage2D = C.uintptr_t(g.get("glTexSubImage2D"))

View File

@ -79,7 +79,7 @@ type defaultContext struct {
fnShaderSource js.Value
fnStencilFunc js.Value
fnStencilMask js.Value
fnStencilOp js.Value
fnStencilOpSeparate js.Value
fnTexImage2D js.Value
fnTexSubImage2D js.Value
fnTexParameteri js.Value
@ -210,7 +210,7 @@ func NewDefaultContext(v js.Value) (Context, error) {
fnShaderSource: v.Get("shaderSource").Call("bind", v),
fnStencilFunc: v.Get("stencilFunc").Call("bind", v),
fnStencilMask: v.Get("stencilMask").Call("bind", v),
fnStencilOp: v.Get("stencilOp").Call("bind", v),
fnStencilOpSeparate: v.Get("stencilOpSeparate").Call("bind", v),
fnTexImage2D: v.Get("texImage2D").Call("bind", v),
fnTexSubImage2D: v.Get("texSubImage2D").Call("bind", v),
fnTexParameteri: v.Get("texParameteri").Call("bind", v),
@ -533,8 +533,8 @@ func (c *defaultContext) StencilFunc(func_ uint32, ref int32, mask uint32) {
c.fnStencilFunc.Invoke(func_, ref, mask)
}
func (c *defaultContext) StencilOp(sfail, dpfail, dppass uint32) {
c.fnStencilOp.Invoke(sfail, dpfail, dppass)
func (c *defaultContext) StencilOpSeparate(face, sfail, dpfail, dppass uint32) {
c.fnStencilOpSeparate.Invoke(face, sfail, dpfail, dppass)
}
func (c *defaultContext) TexImage2D(target uint32, level int32, internalformat int32, width int32, height int32, format uint32, xtype uint32, pixels []byte) {

View File

@ -80,7 +80,7 @@ type defaultContext struct {
gpScissor uintptr
gpShaderSource uintptr
gpStencilFunc uintptr
gpStencilOp uintptr
gpStencilOpSeparate uintptr
gpTexImage2D uintptr
gpTexParameteri uintptr
gpTexSubImage2D uintptr
@ -384,8 +384,8 @@ func (c *defaultContext) StencilFunc(xfunc uint32, ref int32, mask uint32) {
purego.SyscallN(c.gpStencilFunc, uintptr(xfunc), uintptr(ref), uintptr(mask))
}
func (c *defaultContext) StencilOp(fail uint32, zfail uint32, zpass uint32) {
purego.SyscallN(c.gpStencilOp, uintptr(fail), uintptr(zfail), uintptr(zpass))
func (c *defaultContext) StencilOpSeparate(face uint32, fail uint32, zfail uint32, zpass uint32) {
purego.SyscallN(c.gpStencilOpSeparate, uintptr(face), uintptr(fail), uintptr(zfail), uintptr(zpass))
}
func (c *defaultContext) TexImage2D(target uint32, level int32, internalformat int32, width int32, height int32, format uint32, xtype uint32, pixels []byte) {
@ -536,7 +536,7 @@ func (c *defaultContext) LoadFunctions() error {
c.gpScissor = g.get("glScissor")
c.gpShaderSource = g.get("glShaderSource")
c.gpStencilFunc = g.get("glStencilFunc")
c.gpStencilOp = g.get("glStencilOp")
c.gpStencilOpSeparate = g.get("glStencilOpSeparate")
c.gpTexImage2D = g.get("glTexImage2D")
c.gpTexParameteri = g.get("glTexParameteri")
c.gpTexSubImage2D = g.get("glTexSubImage2D")

View File

@ -267,8 +267,8 @@ func (g *gomobileContext) StencilFunc(func_ uint32, ref int32, mask uint32) {
g.ctx.StencilFunc(gl.Enum(func_), int(ref), mask)
}
func (g *gomobileContext) StencilOp(sfail, dpfail, dppass uint32) {
g.ctx.StencilOp(gl.Enum(sfail), gl.Enum(dpfail), gl.Enum(dppass))
func (g *gomobileContext) StencilOpSeparate(face, sfail, dpfail, dppass uint32) {
g.ctx.StencilOpSeparate(gl.Enum(face), gl.Enum(sfail), gl.Enum(dpfail), gl.Enum(dppass))
}
func (g *gomobileContext) TexImage2D(target uint32, level int32, internalformat int32, width int32, height int32, format uint32, xtype uint32, pixels []byte) {

View File

@ -81,7 +81,7 @@ type Context interface {
Scissor(x, y, width, height int32)
ShaderSource(shader uint32, xstring string)
StencilFunc(func_ uint32, ref int32, mask uint32)
StencilOp(sfail, dpfail, dppass uint32)
StencilOpSeparate(face, sfail, dpfail, dppass uint32)
TexImage2D(target uint32, level int32, internalformat int32, width int32, height int32, format uint32, xtype uint32, pixels []byte)
TexParameteri(target uint32, pname uint32, param int32)
TexSubImage2D(target uint32, level int32, xoffset int32, yoffset int32, width int32, height int32, format uint32, xtype uint32, pixels []byte)

View File

@ -243,7 +243,7 @@ func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.
}
g.uniformVars = g.uniformVars[:0]
if fillRule == graphicsdriver.EvenOdd {
if fillRule != graphicsdriver.FillAll {
if err := destination.ensureStencilBuffer(); err != nil {
return err
}
@ -257,23 +257,32 @@ func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.
int32(dstRegion.Region.Dx()),
int32(dstRegion.Region.Dy()),
)
if fillRule == graphicsdriver.EvenOdd {
switch fillRule {
case graphicsdriver.NonZero:
g.context.ctx.Clear(gl.STENCIL_BUFFER_BIT)
g.context.ctx.StencilFunc(gl.ALWAYS, 0x00, 0xff)
g.context.ctx.StencilOp(gl.KEEP, gl.KEEP, gl.INVERT)
g.context.ctx.StencilOpSeparate(gl.FRONT, gl.KEEP, gl.KEEP, gl.INCR_WRAP)
g.context.ctx.StencilOpSeparate(gl.BACK, gl.KEEP, gl.KEEP, gl.DECR_WRAP)
g.context.ctx.ColorMask(false, false, false, false)
g.context.ctx.DrawElements(gl.TRIANGLES, int32(dstRegion.IndexCount), gl.UNSIGNED_INT, indexOffset*int(unsafe.Sizeof(uint32(0))))
case graphicsdriver.EvenOdd:
g.context.ctx.Clear(gl.STENCIL_BUFFER_BIT)
g.context.ctx.StencilFunc(gl.ALWAYS, 0x00, 0xff)
g.context.ctx.StencilOpSeparate(gl.FRONT_AND_BACK, gl.KEEP, gl.KEEP, gl.INVERT)
g.context.ctx.ColorMask(false, false, false, false)
g.context.ctx.DrawElements(gl.TRIANGLES, int32(dstRegion.IndexCount), gl.UNSIGNED_INT, indexOffset*int(unsafe.Sizeof(uint32(0))))
}
if fillRule != graphicsdriver.FillAll {
g.context.ctx.StencilFunc(gl.NOTEQUAL, 0x00, 0xff)
g.context.ctx.StencilOp(gl.KEEP, gl.KEEP, gl.KEEP)
g.context.ctx.StencilOpSeparate(gl.FRONT_AND_BACK, gl.KEEP, gl.KEEP, gl.KEEP)
g.context.ctx.ColorMask(true, true, true, true)
}
g.context.ctx.DrawElements(gl.TRIANGLES, int32(dstRegion.IndexCount), gl.UNSIGNED_INT, indexOffset*int(unsafe.Sizeof(uint32(0))))
indexOffset += dstRegion.IndexCount
}
if fillRule == graphicsdriver.EvenOdd {
if fillRule != graphicsdriver.FillAll {
g.context.ctx.Disable(gl.STENCIL_TEST)
}

View File

@ -396,7 +396,7 @@ func (p *Path) Close() {
//
// The returned vertice's SrcX and SrcY are 0, and ColorR, ColorG, ColorB, and ColorA are 1.
//
// The returned values are intended to be passed to DrawTriangles or DrawTrianglesShader with the EvenOdd fill rule
// The returned values are intended to be passed to DrawTriangles or DrawTrianglesShader with the fill rule NonZero or EvenOdd
// in order to render a complex polygon like a concave polygon, a polygon with holes, or a self-intersecting polygon.
//
// The returned vertices and indices should be rendered with a solid (non-transparent) color with the default Blend (source-over).
@ -480,7 +480,7 @@ type StrokeOptions struct {
// The returned vertice's SrcX and SrcY are 0, and ColorR, ColorG, ColorB, and ColorA are 1.
//
// The returned values are intended to be passed to DrawTriangles or DrawTrianglesShader with a solid (non-transparent) color
// with the FillAll fill rule (not the EvenOdd fill rule).
// with the FillAll fill rule (not NonZero or EvenOdd fill rule).
func (p *Path) AppendVerticesAndIndicesForStroke(vertices []ebiten.Vertex, indices []uint16, op *StrokeOptions) ([]ebiten.Vertex, []uint16) {
if op == nil {
return vertices, indices