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. // 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. // 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. // 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 := &ebiten.DrawTrianglesOptions{}
op.AntiAlias = aa op.AntiAlias = aa
if !line { 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) 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 := &ebiten.DrawTrianglesOptions{}
op.AntiAlias = aa op.AntiAlias = aa
if !line { if !line {
op.FillRule = ebiten.EvenOdd op.FillRule = ebiten.NonZero
} }
screen.DrawTriangles(vs, is, whiteSubImage, op) 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 := &ebiten.DrawTrianglesOptions{}
op.AntiAlias = aa op.AntiAlias = aa
if !line { if !line {
op.FillRule = ebiten.EvenOdd op.FillRule = ebiten.NonZero
} }
screen.DrawTriangles(vs, is, whiteSubImage, op) 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 := &ebiten.DrawTrianglesOptions{}
op.AntiAlias = aa op.AntiAlias = aa
if !line { if !line {
op.FillRule = ebiten.EvenOdd op.FillRule = ebiten.NonZero
} }
screen.DrawTriangles(vs, is, whiteSubImage, op) screen.DrawTriangles(vs, is, whiteSubImage, op)
} }

View File

@ -314,6 +314,10 @@ const (
// FillAll indicates all the triangles are rendered regardless of overlaps. // FillAll indicates all the triangles are rendered regardless of overlaps.
FillAll FillRule = FillRule(graphicsdriver.FillAll) 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. // 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. // If and only if the number of overlaps is odd, the region is rendered.
EvenOdd FillRule = FillRule(graphicsdriver.EvenOdd) EvenOdd FillRule = FillRule(graphicsdriver.EvenOdd)
@ -367,7 +371,7 @@ type DrawTrianglesOptions struct {
// FillRule indicates the rule how an overlapped region is rendered. // 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. // 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. // See examples/vector for actual usages.
// //
@ -543,7 +547,7 @@ type DrawTrianglesShaderOptions struct {
// FillRule indicates the rule how an overlapped region is rendered. // 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. // 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. // 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 // #1658
func BenchmarkColorMScale(b *testing.B) { func BenchmarkColorMScale(b *testing.B) {
r := rand.Float64 r := rand.Float64

View File

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

View File

@ -584,25 +584,39 @@ func (g *graphics11) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphic
switch fillRule { switch fillRule {
case graphicsdriver.FillAll: case graphicsdriver.FillAll:
g.deviceContext.DrawIndexed(uint32(dstRegion.IndexCount), uint32(indexOffset), 0) g.deviceContext.DrawIndexed(uint32(dstRegion.IndexCount), uint32(indexOffset), 0)
case graphicsdriver.EvenOdd: case graphicsdriver.NonZero:
bs, err := g.blendState(blend, prepareStencil) bs, err := g.blendState(blend, incrementStencil)
if err != nil { if err != nil {
return err return err
} }
g.deviceContext.OMSetBlendState(bs, nil, 0xffffffff) g.deviceContext.OMSetBlendState(bs, nil, 0xffffffff)
dss, err := g.depthStencilState(prepareStencil) dss, err := g.depthStencilState(incrementStencil)
if err != nil { if err != nil {
return err return err
} }
g.deviceContext.OMSetDepthStencilState(dss, 0) g.deviceContext.OMSetDepthStencilState(dss, 0)
g.deviceContext.DrawIndexed(uint32(dstRegion.IndexCount), uint32(indexOffset), 0) g.deviceContext.DrawIndexed(uint32(dstRegion.IndexCount), uint32(indexOffset), 0)
case graphicsdriver.EvenOdd:
bs, err = g.blendState(blend, drawWithStencil) bs, err := g.blendState(blend, invertStencil)
if err != nil { if err != nil {
return err return err
} }
g.deviceContext.OMSetBlendState(bs, nil, 0xffffffff) 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 { if err != nil {
return err return err
} }
@ -627,9 +641,9 @@ func (g *graphics11) genNextShaderID() graphicsdriver.ShaderID {
} }
func (g *graphics11) blendState(blend graphicsdriver.Blend, stencilMode stencilMode) (*_ID3D11BlendState, error) { func (g *graphics11) blendState(blend graphicsdriver.Blend, stencilMode stencilMode) (*_ID3D11BlendState, error) {
writeMask := uint8(_D3D11_COLOR_WRITE_ENABLE_ALL) var writeMask uint8
if stencilMode == prepareStencil { if stencilMode == noStencil || stencilMode == drawWithStencil {
writeMask = 0 writeMask = uint8(_D3D11_COLOR_WRITE_ENABLE_ALL)
} }
key := blendStateKey{ key := blendStateKey{
@ -693,7 +707,11 @@ func (g *graphics11) depthStencilState(mode stencilMode) (*_ID3D11DepthStencilSt
}, },
} }
switch mode { 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.StencilEnable = 1
desc.FrontFace.StencilPassOp = _D3D11_STENCIL_OP_INVERT desc.FrontFace.StencilPassOp = _D3D11_STENCIL_OP_INVERT
desc.BackFace.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. // Release constant buffers when too many ones will be created.
numPipelines := 1 numPipelines := 1
if fillRule == graphicsdriver.EvenOdd { if fillRule != graphicsdriver.FillAll {
numPipelines = 2 numPipelines = 2
} }
if len(g.pipelineStates.constantBuffers[g.frameIndex])+numPipelines > numDescriptorsPerFrame { if len(g.pipelineStates.constantBuffers[g.frameIndex])+numPipelines > numDescriptorsPerFrame {

View File

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

View File

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

View File

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

View File

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

View File

@ -17,10 +17,12 @@ package gl
const ( const (
ALWAYS = 0x0207 ALWAYS = 0x0207
ARRAY_BUFFER = 0x8892 ARRAY_BUFFER = 0x8892
BACK = 0x0405
BLEND = 0x0BE2 BLEND = 0x0BE2
CLAMP_TO_EDGE = 0x812F CLAMP_TO_EDGE = 0x812F
COLOR_ATTACHMENT0 = 0x8CE0 COLOR_ATTACHMENT0 = 0x8CE0
COMPILE_STATUS = 0x8B81 COMPILE_STATUS = 0x8B81
DECR_WRAP = 0x8508
DEPTH24_STENCIL8 = 0x88F0 DEPTH24_STENCIL8 = 0x88F0
DST_ALPHA = 0x0304 DST_ALPHA = 0x0304
DST_COLOR = 0x0306 DST_COLOR = 0x0306
@ -32,10 +34,13 @@ const (
FRAMEBUFFER = 0x8D40 FRAMEBUFFER = 0x8D40
FRAMEBUFFER_BINDING = 0x8CA6 FRAMEBUFFER_BINDING = 0x8CA6
FRAMEBUFFER_COMPLETE = 0x8CD5 FRAMEBUFFER_COMPLETE = 0x8CD5
FRONT = 0x0404
FRONT_AND_BACK = 0x0408
FUNC_ADD = 0x8006 FUNC_ADD = 0x8006
FUNC_REVERSE_SUBTRACT = 0x800b FUNC_REVERSE_SUBTRACT = 0x800b
FUNC_SUBTRACT = 0x800a FUNC_SUBTRACT = 0x800a
HIGH_FLOAT = 0x8DF2 HIGH_FLOAT = 0x8DF2
INCR_WRAP = 0x8507
INFO_LOG_LENGTH = 0x8B84 INFO_LOG_LENGTH = 0x8B84
INVERT = 0x150A INVERT = 0x150A
KEEP = 0x1E00 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) { func (d *DebugContext) StencilOpSeparate(arg0 uint32, arg1 uint32, arg2 uint32, arg3 uint32) {
d.Context.StencilOp(arg0, arg1, arg2) d.Context.StencilOpSeparate(arg0, arg1, arg2, arg3)
fmt.Fprintln(os.Stderr, "StencilOp") fmt.Fprintln(os.Stderr, "StencilOpSeparate")
if e := d.Context.GetError(); e != NO_ERROR { 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); // typedef void (*fn)(GLenum func, GLint ref, GLuint mask);
// ((fn)(fnptr))(func, ref, mask); // ((fn)(fnptr))(func, ref, mask);
// } // }
// static void glowStencilOp(uintptr_t fnptr, GLenum fail, GLenum zfail, GLenum zpass) { // static void glowStencilOpSeparate(uintptr_t fnptr, GLenum face, GLenum fail, GLenum zfail, GLenum zpass) {
// typedef void (*fn)(GLenum fail, GLenum zfail, GLenum zpass); // typedef void (*fn)(GLenum face, GLenum fail, GLenum zfail, GLenum zpass);
// ((fn)(fnptr))(fail, zfail, 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) { // 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); // 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 gpScissor C.uintptr_t
gpShaderSource C.uintptr_t gpShaderSource C.uintptr_t
gpStencilFunc C.uintptr_t gpStencilFunc C.uintptr_t
gpStencilOp C.uintptr_t gpStencilOpSeparate C.uintptr_t
gpTexImage2D C.uintptr_t gpTexImage2D C.uintptr_t
gpTexParameteri C.uintptr_t gpTexParameteri C.uintptr_t
gpTexSubImage2D 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)) C.glowStencilFunc(c.gpStencilFunc, C.GLenum(xfunc), C.GLint(ref), C.GLuint(mask))
} }
func (c *defaultContext) StencilOp(fail uint32, zfail uint32, zpass uint32) { func (c *defaultContext) StencilOpSeparate(face uint32, fail uint32, zfail uint32, zpass uint32) {
C.glowStencilOp(c.gpStencilOp, C.GLenum(fail), C.GLenum(zfail), C.GLenum(zpass)) 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) { 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.gpScissor = C.uintptr_t(g.get("glScissor"))
c.gpShaderSource = C.uintptr_t(g.get("glShaderSource")) c.gpShaderSource = C.uintptr_t(g.get("glShaderSource"))
c.gpStencilFunc = C.uintptr_t(g.get("glStencilFunc")) 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.gpTexImage2D = C.uintptr_t(g.get("glTexImage2D"))
c.gpTexParameteri = C.uintptr_t(g.get("glTexParameteri")) c.gpTexParameteri = C.uintptr_t(g.get("glTexParameteri"))
c.gpTexSubImage2D = C.uintptr_t(g.get("glTexSubImage2D")) c.gpTexSubImage2D = C.uintptr_t(g.get("glTexSubImage2D"))

View File

@ -79,7 +79,7 @@ type defaultContext struct {
fnShaderSource js.Value fnShaderSource js.Value
fnStencilFunc js.Value fnStencilFunc js.Value
fnStencilMask js.Value fnStencilMask js.Value
fnStencilOp js.Value fnStencilOpSeparate js.Value
fnTexImage2D js.Value fnTexImage2D js.Value
fnTexSubImage2D js.Value fnTexSubImage2D js.Value
fnTexParameteri js.Value fnTexParameteri js.Value
@ -210,7 +210,7 @@ func NewDefaultContext(v js.Value) (Context, error) {
fnShaderSource: v.Get("shaderSource").Call("bind", v), fnShaderSource: v.Get("shaderSource").Call("bind", v),
fnStencilFunc: v.Get("stencilFunc").Call("bind", v), fnStencilFunc: v.Get("stencilFunc").Call("bind", v),
fnStencilMask: v.Get("stencilMask").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), fnTexImage2D: v.Get("texImage2D").Call("bind", v),
fnTexSubImage2D: v.Get("texSubImage2D").Call("bind", v), fnTexSubImage2D: v.Get("texSubImage2D").Call("bind", v),
fnTexParameteri: v.Get("texParameteri").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) c.fnStencilFunc.Invoke(func_, ref, mask)
} }
func (c *defaultContext) StencilOp(sfail, dpfail, dppass uint32) { func (c *defaultContext) StencilOpSeparate(face, sfail, dpfail, dppass uint32) {
c.fnStencilOp.Invoke(sfail, dpfail, dppass) 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) { 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 gpScissor uintptr
gpShaderSource uintptr gpShaderSource uintptr
gpStencilFunc uintptr gpStencilFunc uintptr
gpStencilOp uintptr gpStencilOpSeparate uintptr
gpTexImage2D uintptr gpTexImage2D uintptr
gpTexParameteri uintptr gpTexParameteri uintptr
gpTexSubImage2D 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)) purego.SyscallN(c.gpStencilFunc, uintptr(xfunc), uintptr(ref), uintptr(mask))
} }
func (c *defaultContext) StencilOp(fail uint32, zfail uint32, zpass uint32) { func (c *defaultContext) StencilOpSeparate(face uint32, fail uint32, zfail uint32, zpass uint32) {
purego.SyscallN(c.gpStencilOp, uintptr(fail), uintptr(zfail), uintptr(zpass)) 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) { 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.gpScissor = g.get("glScissor")
c.gpShaderSource = g.get("glShaderSource") c.gpShaderSource = g.get("glShaderSource")
c.gpStencilFunc = g.get("glStencilFunc") c.gpStencilFunc = g.get("glStencilFunc")
c.gpStencilOp = g.get("glStencilOp") c.gpStencilOpSeparate = g.get("glStencilOpSeparate")
c.gpTexImage2D = g.get("glTexImage2D") c.gpTexImage2D = g.get("glTexImage2D")
c.gpTexParameteri = g.get("glTexParameteri") c.gpTexParameteri = g.get("glTexParameteri")
c.gpTexSubImage2D = g.get("glTexSubImage2D") 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) g.ctx.StencilFunc(gl.Enum(func_), int(ref), mask)
} }
func (g *gomobileContext) StencilOp(sfail, dpfail, dppass uint32) { func (g *gomobileContext) StencilOpSeparate(face, sfail, dpfail, dppass uint32) {
g.ctx.StencilOp(gl.Enum(sfail), gl.Enum(dpfail), gl.Enum(dppass)) 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) { 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) Scissor(x, y, width, height int32)
ShaderSource(shader uint32, xstring string) ShaderSource(shader uint32, xstring string)
StencilFunc(func_ uint32, ref int32, mask uint32) 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) TexImage2D(target uint32, level int32, internalformat int32, width int32, height int32, format uint32, xtype uint32, pixels []byte)
TexParameteri(target uint32, pname uint32, param int32) 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) 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] g.uniformVars = g.uniformVars[:0]
if fillRule == graphicsdriver.EvenOdd { if fillRule != graphicsdriver.FillAll {
if err := destination.ensureStencilBuffer(); err != nil { if err := destination.ensureStencilBuffer(); err != nil {
return err return err
} }
@ -257,23 +257,32 @@ func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.
int32(dstRegion.Region.Dx()), int32(dstRegion.Region.Dx()),
int32(dstRegion.Region.Dy()), int32(dstRegion.Region.Dy()),
) )
if fillRule == graphicsdriver.EvenOdd { switch fillRule {
case graphicsdriver.NonZero:
g.context.ctx.Clear(gl.STENCIL_BUFFER_BIT) g.context.ctx.Clear(gl.STENCIL_BUFFER_BIT)
g.context.ctx.StencilFunc(gl.ALWAYS, 0x00, 0xff) 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.ColorMask(false, false, false, false)
g.context.ctx.DrawElements(gl.TRIANGLES, int32(dstRegion.IndexCount), gl.UNSIGNED_INT, indexOffset*int(unsafe.Sizeof(uint32(0)))) 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.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.ColorMask(true, true, true, true)
} }
g.context.ctx.DrawElements(gl.TRIANGLES, int32(dstRegion.IndexCount), gl.UNSIGNED_INT, indexOffset*int(unsafe.Sizeof(uint32(0)))) g.context.ctx.DrawElements(gl.TRIANGLES, int32(dstRegion.IndexCount), gl.UNSIGNED_INT, indexOffset*int(unsafe.Sizeof(uint32(0))))
indexOffset += dstRegion.IndexCount indexOffset += dstRegion.IndexCount
} }
if fillRule == graphicsdriver.EvenOdd { if fillRule != graphicsdriver.FillAll {
g.context.ctx.Disable(gl.STENCIL_TEST) 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 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. // 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). // 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 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 // 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) { func (p *Path) AppendVerticesAndIndicesForStroke(vertices []ebiten.Vertex, indices []uint16, op *StrokeOptions) ([]ebiten.Vertex, []uint16) {
if op == nil { if op == nil {
return vertices, indices return vertices, indices