ebiten: Add EvenOdd to DrawTrianglesOptions and DrawShaderTrianglesOptions

Updates #844
Closes #1684
This commit is contained in:
Hajime Hoshi 2021-07-02 19:26:09 +09:00
parent 5c4d3325f6
commit b466a0cbd7
25 changed files with 678 additions and 745 deletions

View File

@ -19,6 +19,7 @@ package main
import (
"fmt"
"image"
"image/color"
"log"
"math"
@ -28,6 +29,15 @@ import (
"github.com/hajimehoshi/ebiten/v2/vector"
)
var (
emptyImage = ebiten.NewImage(3, 3)
emptySubImage = emptyImage.SubImage(image.Rect(1, 1, 2, 2)).(*ebiten.Image)
)
func init() {
emptyImage.Fill(color.White)
}
const (
screenWidth = 640
screenHeight = 480
@ -99,10 +109,18 @@ func drawEbitenText(screen *ebiten.Image) {
path.LineTo(320, 55)
path.LineTo(290, 20)
op := &vector.FillOptions{
Color: color.RGBA{0xdb, 0x56, 0x20, 0xff},
op := &ebiten.DrawTrianglesOptions{
EvenOdd: true,
}
path.Fill(screen, op)
vs, is := path.AppendVerticesAndIndices(nil, nil)
for i := range vs {
vs[i].SrcX = 1
vs[i].SrcY = 1
vs[i].ColorR = 0xdb / float32(0xff)
vs[i].ColorG = 0x56 / float32(0xff)
vs[i].ColorB = 0x20 / float32(0xff)
}
screen.DrawTriangles(vs, is, emptySubImage, op)
}
func drawEbitenLogo(screen *ebiten.Image, x, y int) {
@ -131,10 +149,18 @@ func drawEbitenLogo(screen *ebiten.Image, x, y int) {
path.LineTo(xf+unit, yf+3*unit)
path.LineTo(xf+unit, yf+4*unit)
op := &vector.FillOptions{
Color: color.RGBA{0xdb, 0x56, 0x20, 0xff},
op := &ebiten.DrawTrianglesOptions{
EvenOdd: true,
}
path.Fill(screen, op)
vs, is := path.AppendVerticesAndIndices(nil, nil)
for i := range vs {
vs[i].SrcX = 1
vs[i].SrcY = 1
vs[i].ColorR = 0xdb / float32(0xff)
vs[i].ColorG = 0x56 / float32(0xff)
vs[i].ColorB = 0x20 / float32(0xff)
}
screen.DrawTriangles(vs, is, emptySubImage, op)
}
func maxCounter(index int) int {
@ -166,10 +192,18 @@ func drawWave(screen *ebiten.Image, counter int) {
path.LineTo(screenWidth, screenHeight)
path.LineTo(0, screenHeight)
op := &vector.FillOptions{
Color: color.RGBA{0x33, 0x66, 0xff, 0xff},
op := &ebiten.DrawTrianglesOptions{
EvenOdd: true,
}
path.Fill(screen, op)
vs, is := path.AppendVerticesAndIndices(nil, nil)
for i := range vs {
vs[i].SrcX = 1
vs[i].SrcY = 1
vs[i].ColorR = 0x33 / float32(0xff)
vs[i].ColorG = 0x66 / float32(0xff)
vs[i].ColorB = 0xff / float32(0xff)
}
screen.DrawTriangles(vs, is, emptySubImage, op)
}
type Game struct {

View File

@ -213,7 +213,7 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) {
is := graphics.QuadIndices()
srcs := [graphics.ShaderImageNum]*mipmap.Mipmap{img.mipmap}
i.mipmap.DrawTriangles(srcs, vs, is, options.ColorM.impl, mode, filter, driver.AddressUnsafe, dstRegion, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, canSkipMipmap(options.GeoM, filter))
i.mipmap.DrawTriangles(srcs, vs, is, options.ColorM.impl, mode, filter, driver.AddressUnsafe, dstRegion, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false, canSkipMipmap(options.GeoM, filter))
}
// Vertex represents a vertex passed to DrawTriangles.
@ -271,6 +271,19 @@ type DrawTrianglesOptions struct {
// Address is a sampler address mode.
// The default (zero) value is AddressUnsafe.
Address Address
// EvenOdd represents whether the even-odd rule is applied or not.
//
// If EvenOdd is true, triangles are rendered based on the even-odd rule. If false, triangles are rendered without condition.
// Whether overlapped regions by multiple triangles is rendered or not depends on the number of the overlapping:
// if and only if the number is odd, the region is rendered.
//
// EvenOdd is 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.
//
// The default value is false.
EvenOdd bool
}
// MaxIndicesNum is the maximum number of indices for DrawTriangles.
@ -349,7 +362,7 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o
srcs := [graphics.ShaderImageNum]*mipmap.Mipmap{img.mipmap}
i.mipmap.DrawTriangles(srcs, vs, is, options.ColorM.impl, mode, filter, address, dstRegion, sr, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
i.mipmap.DrawTriangles(srcs, vs, is, options.ColorM.impl, mode, filter, address, dstRegion, sr, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, options.EvenOdd, false)
}
// DrawTrianglesShaderOptions represents options for DrawTrianglesShader.
@ -371,6 +384,19 @@ type DrawTrianglesShaderOptions struct {
// Images is a set of the source images.
// All the image must be the same size.
Images [4]*Image
// EvenOdd represents whether the even-odd rule is applied or not.
//
// If EvenOdd is true, triangles are rendered based on the even-odd rule. If false, triangles are rendered without condition.
// Whether overlapped regions by multiple triangles is rendered or not depends on the number of the overlapping:
// if and only if the number is odd, the region is rendered.
//
// EvenOdd is 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.
//
// The default value is false.
EvenOdd bool
}
func init() {
@ -485,7 +511,7 @@ func (i *Image) DrawTrianglesShader(vertices []Vertex, indices []uint16, shader
}
us := shader.convertUniforms(options.Uniforms)
i.mipmap.DrawTriangles(imgs, vs, is, nil, mode, driver.FilterNearest, driver.AddressUnsafe, dstRegion, sr, offsets, shader.shader, us, false)
i.mipmap.DrawTriangles(imgs, vs, is, nil, mode, driver.FilterNearest, driver.AddressUnsafe, dstRegion, sr, offsets, shader.shader, us, options.EvenOdd, false)
}
// DrawRectShaderOptions represents options for DrawRectShader.
@ -597,7 +623,7 @@ func (i *Image) DrawRectShader(width, height int, shader *Shader, options *DrawR
}
us := shader.convertUniforms(options.Uniforms)
i.mipmap.DrawTriangles(imgs, vs, is, nil, mode, driver.FilterNearest, driver.AddressUnsafe, dstRegion, sr, offsets, shader.shader, us, canSkipMipmap(options.GeoM, driver.FilterNearest))
i.mipmap.DrawTriangles(imgs, vs, is, nil, mode, driver.FilterNearest, driver.AddressUnsafe, dstRegion, sr, offsets, shader.shader, us, false, canSkipMipmap(options.GeoM, driver.FilterNearest))
}
// SubImage returns an image representing the portion of the image p visible through r.

View File

@ -298,7 +298,7 @@ func (i *Image) ensureIsolated() {
Width: float32(w - 2*paddingSize),
Height: float32(h - 2*paddingSize),
}
newImg.DrawTriangles(srcs, offsets, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dstRegion, driver.Region{}, nil, nil)
newImg.DrawTriangles(srcs, offsets, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dstRegion, driver.Region{}, nil, nil, false)
i.dispose(false)
i.backend = &backend{
@ -354,7 +354,7 @@ func (i *Image) putOnAtlas() error {
Width: w,
Height: h,
}
newI.drawTriangles([graphics.ShaderImageNum]*Image{i}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, true)
newI.drawTriangles([graphics.ShaderImageNum]*Image{i}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false, true)
}
newI.moveTo(i)
@ -402,13 +402,13 @@ func (i *Image) processSrc(src *Image) {
// 5: Color G
// 6: Color B
// 7: Color Y
func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms []interface{}) {
func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms []interface{}, evenOdd bool) {
backendsM.Lock()
defer backendsM.Unlock()
i.drawTriangles(srcs, vertices, indices, colorm, mode, filter, address, dstRegion, srcRegion, subimageOffsets, shader, uniforms, false)
i.drawTriangles(srcs, vertices, indices, colorm, mode, filter, address, dstRegion, srcRegion, subimageOffsets, shader, uniforms, evenOdd, false)
}
func (i *Image) drawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms []interface{}, keepOnAtlas bool) {
func (i *Image) drawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms []interface{}, evenOdd bool, keepOnAtlas bool) {
if i.disposed {
panic("atlas: the drawing target image must not be disposed (DrawTriangles)")
}
@ -487,7 +487,7 @@ func (i *Image) drawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []f
}
}
i.backend.restorable.DrawTriangles(imgs, offsets, vertices, indices, colorm, mode, filter, address, dstRegion, srcRegion, s, uniforms)
i.backend.restorable.DrawTriangles(imgs, offsets, vertices, indices, colorm, mode, filter, address, dstRegion, srcRegion, s, uniforms, evenOdd)
for _, src := range srcs {
if src == nil {

View File

@ -102,7 +102,7 @@ func TestEnsureIsolated(t *testing.T) {
Width: size,
Height: size,
}
img4.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil)
img4.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
want := false
if got := img4.IsOnAtlasForTesting(); got != want {
t.Errorf("got: %v, want: %v", got, want)
@ -132,7 +132,7 @@ func TestEnsureIsolated(t *testing.T) {
// Check further drawing doesn't cause panic.
// This bug was fixed by 03dcd948.
img4.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil)
img4.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
}
func TestReputOnAtlas(t *testing.T) {
@ -179,7 +179,7 @@ func TestReputOnAtlas(t *testing.T) {
Width: size,
Height: size,
}
img1.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil)
img1.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
if got, want := img1.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want)
}
@ -191,7 +191,7 @@ func TestReputOnAtlas(t *testing.T) {
if err := PutImagesOnAtlasForTesting(); err != nil {
t.Fatal(err)
}
img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil)
img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
if got, want := img1.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want)
}
@ -219,7 +219,7 @@ func TestReputOnAtlas(t *testing.T) {
}
// img1 is on an atlas again.
img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil)
img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
if got, want := img1.IsOnAtlasForTesting(), true; got != want {
t.Errorf("got: %v, want: %v", got, want)
}
@ -243,7 +243,7 @@ func TestReputOnAtlas(t *testing.T) {
}
// Use img1 as a render target again.
img1.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil)
img1.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
if got, want := img1.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want)
}
@ -255,7 +255,7 @@ func TestReputOnAtlas(t *testing.T) {
t.Fatal(err)
}
img1.ReplacePixels(make([]byte, 4*size*size))
img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil)
img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
if got, want := img1.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want)
}
@ -265,7 +265,7 @@ func TestReputOnAtlas(t *testing.T) {
}
// img1 is not on an atlas due to ReplacePixels.
img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil)
img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
if got, want := img1.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want)
}
@ -275,7 +275,7 @@ func TestReputOnAtlas(t *testing.T) {
if err := PutImagesOnAtlasForTesting(); err != nil {
t.Fatal(err)
}
img0.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil)
img0.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
if got, want := img3.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want)
}
@ -375,7 +375,7 @@ func TestReplacePixelsAfterDrawTriangles(t *testing.T) {
Width: w,
Height: h,
}
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil)
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
dst.ReplacePixels(pix)
pix, err := dst.Pixels(0, 0, w, h)
@ -423,7 +423,7 @@ func TestSmallImages(t *testing.T) {
Width: w,
Height: h,
}
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil)
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
pix, err := dst.Pixels(0, 0, w, h)
if err != nil {
@ -471,7 +471,7 @@ func TestLongImages(t *testing.T) {
Width: dstW,
Height: dstH,
}
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil)
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
pix, err := dst.Pixels(0, 0, dstW, dstH)
if err != nil {
@ -559,7 +559,7 @@ func TestDisposedAndReputOnAtlas(t *testing.T) {
Width: size,
Height: size,
}
src.DrawTriangles([graphics.ShaderImageNum]*Image{src2}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil)
src.DrawTriangles([graphics.ShaderImageNum]*Image{src2}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
if got, want := src.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want)
}
@ -569,7 +569,7 @@ func TestDisposedAndReputOnAtlas(t *testing.T) {
if err := PutImagesOnAtlasForTesting(); err != nil {
t.Fatal(err)
}
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil)
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
if got, want := src.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want)
}
@ -609,7 +609,7 @@ func TestImageIsNotReputOnAtlasWithoutUsingAsSource(t *testing.T) {
}
// Use src2 as a rendering target, and make src2 an independent image.
src2.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil)
src2.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
if got, want := src2.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want)
}
@ -630,7 +630,7 @@ func TestImageIsNotReputOnAtlasWithoutUsingAsSource(t *testing.T) {
if err := PutImagesOnAtlasForTesting(); err != nil {
t.Fatal(err)
}
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src2}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil)
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src2}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
if got, want := src2.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want)
}

View File

@ -202,7 +202,7 @@ func (i *Image) replacePendingPixels(pix []byte, x, y, width, height int) {
// DrawTriangles draws the src image with the given vertices.
//
// Copying vertices and indices is the caller's responsibility.
func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms []interface{}) {
func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms []interface{}, evenOdd bool) {
for _, src := range srcs {
if i == src {
panic("buffered: Image.DrawTriangles: source images must be different from the receiver")
@ -212,7 +212,7 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []f
if maybeCanAddDelayedCommand() {
if tryAddDelayedCommand(func() error {
// Arguments are not copied. Copying is the caller's responsibility.
i.DrawTriangles(srcs, vertices, indices, colorm, mode, filter, address, dstRegion, srcRegion, subimageOffsets, shader, uniforms)
i.DrawTriangles(srcs, vertices, indices, colorm, mode, filter, address, dstRegion, srcRegion, subimageOffsets, shader, uniforms, evenOdd)
return nil
}) {
return
@ -238,7 +238,7 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []f
}
i.resolvePendingPixels(false)
i.img.DrawTriangles(imgs, vertices, indices, colorm, mode, filter, address, dstRegion, srcRegion, subimageOffsets, s, uniforms)
i.img.DrawTriangles(imgs, vertices, indices, colorm, mode, filter, address, dstRegion, srcRegion, subimageOffsets, s, uniforms, evenOdd)
i.invalidatePendingPixels()
}

View File

@ -57,7 +57,7 @@ type Graphics interface {
//
// * float32
// * []float32
DrawTriangles(dst ImageID, srcs [graphics.ShaderImageNum]ImageID, offsets [graphics.ShaderImageNum - 1][2]float32, shader ShaderID, indexLen int, indexOffset int, mode CompositeMode, colorM *affine.ColorM, filter Filter, address Address, dstRegion, srcRegion Region, uniforms []interface{}) error
DrawTriangles(dst ImageID, srcs [graphics.ShaderImageNum]ImageID, offsets [graphics.ShaderImageNum - 1][2]float32, shader ShaderID, indexLen int, indexOffset int, mode CompositeMode, colorM *affine.ColorM, filter Filter, address Address, dstRegion, srcRegion Region, uniforms []interface{}, evenOdd bool) error
}
// GraphicsNotReady represents that the graphics driver is not ready for recovering from the context lost.

View File

@ -55,7 +55,7 @@ type command interface {
NumIndices() int
AddNumVertices(n int)
AddNumIndices(n int)
CanMergeWithDrawTrianglesCommand(dst *Image, src [graphics.ShaderImageNum]*Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader) bool
CanMergeWithDrawTrianglesCommand(dst *Image, src [graphics.ShaderImageNum]*Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, evenOdd bool) bool
}
type size struct {
@ -133,7 +133,7 @@ func (q *commandQueue) appendIndices(indices []uint16, offset uint16) {
}
// EnqueueDrawTrianglesCommand enqueues a drawing-image command.
func (q *commandQueue) EnqueueDrawTrianglesCommand(dst *Image, srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, uniforms []interface{}) {
func (q *commandQueue) EnqueueDrawTrianglesCommand(dst *Image, srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, uniforms []interface{}, evenOdd bool) {
if len(indices) > graphics.IndicesNum {
panic(fmt.Sprintf("graphicscommand: len(indices) must be <= graphics.IndicesNum but not at EnqueueDrawTrianglesCommand: len(indices): %d, graphics.IndicesNum: %d", len(indices), graphics.IndicesNum))
}
@ -167,7 +167,7 @@ func (q *commandQueue) EnqueueDrawTrianglesCommand(dst *Image, srcs [graphics.Sh
// TODO: If dst is the screen, reorder the command to be the last.
if !split && 0 < len(q.commands) {
// TODO: Pass offsets and uniforms when merging considers the shader.
if last := q.commands[len(q.commands)-1]; last.CanMergeWithDrawTrianglesCommand(dst, srcs, color, mode, filter, address, dstRegion, srcRegion, shader) {
if last := q.commands[len(q.commands)-1]; last.CanMergeWithDrawTrianglesCommand(dst, srcs, color, mode, filter, address, dstRegion, srcRegion, shader, evenOdd) {
last.AddNumVertices(len(vertices))
last.AddNumIndices(len(indices))
return
@ -188,6 +188,7 @@ func (q *commandQueue) EnqueueDrawTrianglesCommand(dst *Image, srcs [graphics.Sh
srcRegion: srcRegion,
shader: shader,
uniforms: uniforms,
evenOdd: evenOdd,
}
q.commands = append(q.commands, c)
}
@ -319,6 +320,7 @@ type drawTrianglesCommand struct {
srcRegion driver.Region
shader *Shader
uniforms []interface{}
evenOdd bool
}
func (c *drawTrianglesCommand) String() string {
@ -428,7 +430,7 @@ func (c *drawTrianglesCommand) Exec(indexOffset int) error {
imgs[0] = c.srcs[0].image.ID()
}
return theGraphicsDriver.DrawTriangles(c.dst.image.ID(), imgs, c.offsets, shaderID, c.nindices, indexOffset, c.mode, c.color, c.filter, c.address, c.dstRegion, c.srcRegion, c.uniforms)
return theGraphicsDriver.DrawTriangles(c.dst.image.ID(), imgs, c.offsets, shaderID, c.nindices, indexOffset, c.mode, c.color, c.filter, c.address, c.dstRegion, c.srcRegion, c.uniforms, c.evenOdd)
}
func (c *drawTrianglesCommand) NumVertices() int {
@ -449,7 +451,7 @@ func (c *drawTrianglesCommand) AddNumIndices(n int) {
// CanMergeWithDrawTrianglesCommand returns a boolean value indicating whether the other drawTrianglesCommand can be merged
// with the drawTrianglesCommand c.
func (c *drawTrianglesCommand) CanMergeWithDrawTrianglesCommand(dst *Image, srcs [graphics.ShaderImageNum]*Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader) bool {
func (c *drawTrianglesCommand) CanMergeWithDrawTrianglesCommand(dst *Image, srcs [graphics.ShaderImageNum]*Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, evenOdd bool) bool {
// If a shader is used, commands are not merged.
//
// TODO: Merge shader commands considering uniform variables.
@ -480,6 +482,9 @@ func (c *drawTrianglesCommand) CanMergeWithDrawTrianglesCommand(dst *Image, srcs
if c.srcRegion != srcRegion {
return false
}
if c.evenOdd != evenOdd {
return false
}
return true
}
@ -513,7 +518,7 @@ func (c *replacePixelsCommand) AddNumVertices(n int) {
func (c *replacePixelsCommand) AddNumIndices(n int) {
}
func (c *replacePixelsCommand) CanMergeWithDrawTrianglesCommand(dst *Image, src [graphics.ShaderImageNum]*Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader) bool {
func (c *replacePixelsCommand) CanMergeWithDrawTrianglesCommand(dst *Image, src [graphics.ShaderImageNum]*Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, evenOdd bool) bool {
return false
}
@ -550,7 +555,7 @@ func (c *pixelsCommand) AddNumVertices(n int) {
func (c *pixelsCommand) AddNumIndices(n int) {
}
func (c *pixelsCommand) CanMergeWithDrawTrianglesCommand(dst *Image, src [graphics.ShaderImageNum]*Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader) bool {
func (c *pixelsCommand) CanMergeWithDrawTrianglesCommand(dst *Image, src [graphics.ShaderImageNum]*Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, evenOdd bool) bool {
return false
}
@ -583,7 +588,7 @@ func (c *disposeImageCommand) AddNumVertices(n int) {
func (c *disposeImageCommand) AddNumIndices(n int) {
}
func (c *disposeImageCommand) CanMergeWithDrawTrianglesCommand(dst *Image, src [graphics.ShaderImageNum]*Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader) bool {
func (c *disposeImageCommand) CanMergeWithDrawTrianglesCommand(dst *Image, src [graphics.ShaderImageNum]*Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, evenOdd bool) bool {
return false
}
@ -616,7 +621,7 @@ func (c *disposeShaderCommand) AddNumVertices(n int) {
func (c *disposeShaderCommand) AddNumIndices(n int) {
}
func (c *disposeShaderCommand) CanMergeWithDrawTrianglesCommand(dst *Image, src [graphics.ShaderImageNum]*Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader) bool {
func (c *disposeShaderCommand) CanMergeWithDrawTrianglesCommand(dst *Image, src [graphics.ShaderImageNum]*Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, evenOdd bool) bool {
return false
}
@ -655,7 +660,7 @@ func (c *newImageCommand) AddNumVertices(n int) {
func (c *newImageCommand) AddNumIndices(n int) {
}
func (c *newImageCommand) CanMergeWithDrawTrianglesCommand(dst *Image, src [graphics.ShaderImageNum]*Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader) bool {
func (c *newImageCommand) CanMergeWithDrawTrianglesCommand(dst *Image, src [graphics.ShaderImageNum]*Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, evenOdd bool) bool {
return false
}
@ -691,7 +696,7 @@ func (c *newScreenFramebufferImageCommand) AddNumVertices(n int) {
func (c *newScreenFramebufferImageCommand) AddNumIndices(n int) {
}
func (c *newScreenFramebufferImageCommand) CanMergeWithDrawTrianglesCommand(dst *Image, src [graphics.ShaderImageNum]*Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader) bool {
func (c *newScreenFramebufferImageCommand) CanMergeWithDrawTrianglesCommand(dst *Image, src [graphics.ShaderImageNum]*Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, copmlex bool) bool {
return false
}
@ -726,7 +731,7 @@ func (c *newShaderCommand) AddNumVertices(n int) {
func (c *newShaderCommand) AddNumIndices(n int) {
}
func (c *newShaderCommand) CanMergeWithDrawTrianglesCommand(dst *Image, src [graphics.ShaderImageNum]*Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader) bool {
func (c *newShaderCommand) CanMergeWithDrawTrianglesCommand(dst *Image, src [graphics.ShaderImageNum]*Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, evenOdd bool) bool {
return false
}

View File

@ -139,7 +139,7 @@ func (i *Image) InternalSize() (int, int) {
//
// If the source image is not specified, i.e., src is nil and there is no image in the uniform variables, the
// elements for the source image are not used.
func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, clr *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, uniforms []interface{}) {
func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, clr *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, uniforms []interface{}, evenOdd bool) {
if shader == nil {
// Fast path for rendering without a shader (#1355).
img := srcs[0]
@ -160,7 +160,7 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, offsets [gra
}
i.resolveBufferedReplacePixels()
theCommandQueue.EnqueueDrawTrianglesCommand(i, srcs, offsets, vertices, indices, clr, mode, filter, address, dstRegion, srcRegion, shader, uniforms)
theCommandQueue.EnqueueDrawTrianglesCommand(i, srcs, offsets, vertices, indices, clr, mode, filter, address, dstRegion, srcRegion, shader, uniforms, evenOdd)
}
// Pixels returns the image's pixels.

View File

@ -50,7 +50,7 @@ func TestClear(t *testing.T) {
Width: w,
Height: h,
}
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeClear, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil)
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeClear, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
pix, err := dst.Pixels()
if err != nil {
@ -81,8 +81,8 @@ func TestReplacePixelsPartAfterDrawTriangles(t *testing.T) {
Width: w,
Height: h,
}
dst.DrawTriangles([graphics.ShaderImageNum]*Image{clr}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeClear, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil)
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil)
dst.DrawTriangles([graphics.ShaderImageNum]*Image{clr}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeClear, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
dst.ReplacePixels(make([]byte, 4), 0, 0, 1, 1)
// TODO: Check the result.
@ -100,11 +100,11 @@ func TestShader(t *testing.T) {
Width: w,
Height: h,
}
dst.DrawTriangles([graphics.ShaderImageNum]*Image{clr}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeClear, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil)
dst.DrawTriangles([graphics.ShaderImageNum]*Image{clr}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeClear, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
ir := etesting.ShaderProgramFill(0xff, 0, 0, 0xff)
s := NewShader(&ir)
dst.DrawTriangles([graphics.ShaderImageNum]*Image{}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, s, nil)
dst.DrawTriangles([graphics.ShaderImageNum]*Image{}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, s, nil, false)
pix, err := dst.Pixels()
if err != nil {

View File

@ -296,11 +296,12 @@ FragmentShaderFunc(0, FILTER_SCREEN, ADDRESS_UNSAFE)
`
type rpsKey struct {
useColorM bool
filter driver.Filter
address driver.Address
compositeMode driver.CompositeMode
screen bool
useColorM bool
filter driver.Filter
address driver.Address
compositeMode driver.CompositeMode
colorWriteMask bool
screen bool
}
type Graphics struct {
@ -313,7 +314,9 @@ type Graphics struct {
rce mtl.RenderCommandEncoder
screenDrawable ca.MetalDrawable
lastDstTexture mtl.Texture
lastStencilUse bool
vb mtl.Buffer
ib mtl.Buffer
@ -334,6 +337,18 @@ type Graphics struct {
pool unsafe.Pointer
}
type stencilMode int
const (
prepareStencil stencilMode = iota
drawWithStencil
noStencil
)
func (s stencilMode) useStencil() bool {
return s != noStencil
}
var theGraphics Graphics
func Get() *Graphics {
@ -539,8 +554,9 @@ func (g *Graphics) Reset() error {
return err
}
rpld := mtl.RenderPipelineDescriptor{
VertexFunction: vs,
FragmentFunction: fs,
VertexFunction: vs,
FragmentFunction: fs,
StencilAttachmentPixelFormat: mtl.PixelFormatStencil8,
}
rpld.ColorAttachments[0].PixelFormat = g.view.colorPixelFormat()
rpld.ColorAttachments[0].BlendingEnabled = true
@ -548,6 +564,7 @@ func (g *Graphics) Reset() error {
rpld.ColorAttachments[0].DestinationRGBBlendFactor = mtl.BlendFactorZero
rpld.ColorAttachments[0].SourceAlphaBlendFactor = mtl.BlendFactorOne
rpld.ColorAttachments[0].SourceRGBBlendFactor = mtl.BlendFactorOne
rpld.ColorAttachments[0].WriteMask = mtl.ColorWriteMaskAll
rps, err := g.view.getMTLDevice().MakeRenderPipelineState(rpld)
if err != nil {
return err
@ -566,42 +583,51 @@ func (g *Graphics) Reset() error {
driver.FilterLinear,
} {
for c := driver.CompositeModeSourceOver; c <= driver.CompositeModeMax; c++ {
cmi := 0
if cm {
cmi = 1
}
fs, err := lib.MakeFunction(fmt.Sprintf("FragmentShader_%d_%d_%d", cmi, f, a))
if err != nil {
return err
}
rpld := mtl.RenderPipelineDescriptor{
VertexFunction: vs,
FragmentFunction: fs,
}
for _, cwm := range []bool{false, true} {
cmi := 0
if cm {
cmi = 1
}
fs, err := lib.MakeFunction(fmt.Sprintf("FragmentShader_%d_%d_%d", cmi, f, a))
if err != nil {
return err
}
rpld := mtl.RenderPipelineDescriptor{
VertexFunction: vs,
FragmentFunction: fs,
StencilAttachmentPixelFormat: mtl.PixelFormatStencil8,
}
pix := mtl.PixelFormatRGBA8UNorm
if screen {
pix = g.view.colorPixelFormat()
}
rpld.ColorAttachments[0].PixelFormat = pix
rpld.ColorAttachments[0].BlendingEnabled = true
pix := mtl.PixelFormatRGBA8UNorm
if screen {
pix = g.view.colorPixelFormat()
}
rpld.ColorAttachments[0].PixelFormat = pix
rpld.ColorAttachments[0].BlendingEnabled = true
src, dst := c.Operations()
rpld.ColorAttachments[0].DestinationAlphaBlendFactor = operationToBlendFactor(dst)
rpld.ColorAttachments[0].DestinationRGBBlendFactor = operationToBlendFactor(dst)
rpld.ColorAttachments[0].SourceAlphaBlendFactor = operationToBlendFactor(src)
rpld.ColorAttachments[0].SourceRGBBlendFactor = operationToBlendFactor(src)
rps, err := g.view.getMTLDevice().MakeRenderPipelineState(rpld)
if err != nil {
return err
src, dst := c.Operations()
rpld.ColorAttachments[0].DestinationAlphaBlendFactor = operationToBlendFactor(dst)
rpld.ColorAttachments[0].DestinationRGBBlendFactor = operationToBlendFactor(dst)
rpld.ColorAttachments[0].SourceAlphaBlendFactor = operationToBlendFactor(src)
rpld.ColorAttachments[0].SourceRGBBlendFactor = operationToBlendFactor(src)
if cwm {
rpld.ColorAttachments[0].WriteMask = mtl.ColorWriteMaskAll
} else {
rpld.ColorAttachments[0].WriteMask = mtl.ColorWriteMaskNone
}
rps, err := g.view.getMTLDevice().MakeRenderPipelineState(rpld)
if err != nil {
return err
}
g.rpss[rpsKey{
screen: screen,
useColorM: cm,
filter: f,
address: a,
compositeMode: c,
colorWriteMask: cwm,
}] = rps
}
g.rpss[rpsKey{
screen: screen,
useColorM: cm,
filter: f,
address: a,
compositeMode: c,
}] = rps
}
}
}
@ -619,12 +645,14 @@ func (g *Graphics) flushRenderCommandEncoderIfNeeded() {
g.rce.EndEncoding()
g.rce = mtl.RenderCommandEncoder{}
g.lastDstTexture = mtl.Texture{}
g.lastStencilUse = false
}
func (g *Graphics) draw(rps mtl.RenderPipelineState, dst *Image, dstRegion driver.Region, srcs [graphics.ShaderImageNum]*Image, indexLen int, indexOffset int, uniforms []interface{}) error {
if g.lastDstTexture != dst.mtlTexture() {
func (g *Graphics) draw(rps mtl.RenderPipelineState, dst *Image, dstRegion driver.Region, srcs [graphics.ShaderImageNum]*Image, indexLen int, indexOffset int, uniforms []interface{}, stencilMode stencilMode) error {
if g.lastDstTexture != dst.mtlTexture() || g.lastStencilUse != stencilMode.useStencil() {
g.flushRenderCommandEncoderIfNeeded()
}
if g.rce == (mtl.RenderCommandEncoder{}) {
rpd := mtl.RenderPassDescriptor{}
// Even though the destination pixels are not used, mtl.LoadActionDontCare might cause glitches
@ -640,11 +668,19 @@ func (g *Graphics) draw(rps mtl.RenderPipelineState, dst *Image, dstRegion drive
rpd.ColorAttachments[0].Texture = t
rpd.ColorAttachments[0].ClearColor = mtl.ClearColor{}
if stencilMode.useStencil() {
dst.ensureStencil()
rpd.StencilAttachment.LoadAction = mtl.LoadActionClear
rpd.StencilAttachment.StoreAction = mtl.StoreActionDontCare
rpd.StencilAttachment.Texture = dst.stencil
}
if g.cb == (mtl.CommandBuffer{}) {
g.cb = g.cq.MakeCommandBuffer()
}
g.rce = g.cb.MakeRenderCommandEncoder(rpd)
}
g.lastStencilUse = stencilMode.useStencil()
g.rce.SetRenderPipelineState(rps)
@ -687,11 +723,51 @@ func (g *Graphics) draw(rps mtl.RenderPipelineState, dst *Image, dstRegion drive
g.rce.SetFragmentTexture(mtl.Texture{}, i)
}
}
// The stencil reference value is always 0 (default).
switch stencilMode {
case prepareStencil:
desc := mtl.DepthStencilDescriptor{
BackFaceStencil: mtl.StencilDescriptor{
StencilFailureOperation: mtl.StencilOperationKeep,
DepthFailureOperation: mtl.StencilOperationKeep,
DepthStencilPassOperation: mtl.StencilOperationInvert,
StencilCompareFunction: mtl.CompareFunctionAlways,
},
FrontFaceStencil: mtl.StencilDescriptor{
StencilFailureOperation: mtl.StencilOperationKeep,
DepthFailureOperation: mtl.StencilOperationKeep,
DepthStencilPassOperation: mtl.StencilOperationInvert,
StencilCompareFunction: mtl.CompareFunctionAlways,
},
}
g.rce.SetDepthStencilState(g.view.getMTLDevice().MakeDepthStencilState(desc))
case drawWithStencil:
desc := mtl.DepthStencilDescriptor{
BackFaceStencil: mtl.StencilDescriptor{
StencilFailureOperation: mtl.StencilOperationZero,
DepthFailureOperation: mtl.StencilOperationZero,
DepthStencilPassOperation: mtl.StencilOperationZero,
StencilCompareFunction: mtl.CompareFunctionNotEqual,
},
FrontFaceStencil: mtl.StencilDescriptor{
StencilFailureOperation: mtl.StencilOperationZero,
DepthFailureOperation: mtl.StencilOperationZero,
DepthStencilPassOperation: mtl.StencilOperationZero,
StencilCompareFunction: mtl.CompareFunctionNotEqual,
},
}
g.rce.SetDepthStencilState(g.view.getMTLDevice().MakeDepthStencilState(desc))
case noStencil:
g.rce.SetDepthStencilState(mtl.DepthStencilState{})
}
g.rce.DrawIndexedPrimitives(mtl.PrimitiveTypeTriangle, indexLen, mtl.IndexTypeUInt16, g.ib, indexOffset*2)
return nil
}
func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderImageNum]driver.ImageID, offsets [graphics.ShaderImageNum - 1][2]float32, shaderID driver.ShaderID, indexLen int, indexOffset int, mode driver.CompositeMode, colorM *affine.ColorM, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, uniforms []interface{}) error {
func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderImageNum]driver.ImageID, offsets [graphics.ShaderImageNum - 1][2]float32, shaderID driver.ShaderID, indexLen int, indexOffset int, mode driver.CompositeMode, colorM *affine.ColorM, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, uniforms []interface{}, evenOdd bool) error {
dst := g.images[dstID]
if dst.screen {
@ -703,18 +779,28 @@ func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderIm
srcs[i] = g.images[srcID]
}
var rps mtl.RenderPipelineState
var rpss [2]mtl.RenderPipelineState
var uniformVars []interface{}
if shaderID == driver.InvalidShaderID {
if dst.screen && filter == driver.FilterScreen {
rps = g.screenRPS
rpss[1] = g.screenRPS
} else {
rps = g.rpss[rpsKey{
screen: dst.screen,
useColorM: colorM != nil,
filter: filter,
address: address,
compositeMode: mode,
useColorM := colorM != nil
rpss[0] = g.rpss[rpsKey{
screen: dst.screen,
useColorM: useColorM,
filter: filter,
address: address,
compositeMode: mode,
colorWriteMask: false,
}]
rpss[1] = g.rpss[rpsKey{
screen: dst.screen,
useColorM: useColorM,
filter: filter,
address: address,
compositeMode: mode,
colorWriteMask: true,
}]
}
@ -745,7 +831,11 @@ func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderIm
}
} else {
var err error
rps, err = g.shaders[shaderID].RenderPipelineState(g.view.getMTLDevice(), mode)
rpss[0], err = g.shaders[shaderID].RenderPipelineState(g.view.getMTLDevice(), mode, false)
if err != nil {
return err
}
rpss[1], err = g.shaders[shaderID].RenderPipelineState(g.view.getMTLDevice(), mode, true)
if err != nil {
return err
}
@ -798,8 +888,17 @@ func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderIm
}
}
if err := g.draw(rps, dst, dstRegion, srcs, indexLen, indexOffset, uniformVars); err != nil {
return err
if evenOdd {
if err := g.draw(rpss[0], dst, dstRegion, srcs, indexLen, indexOffset, uniformVars, prepareStencil); err != nil {
return err
}
if err := g.draw(rpss[1], dst, dstRegion, srcs, indexLen, indexOffset, uniformVars, drawWithStencil); err != nil {
return err
}
} else {
if err := g.draw(rpss[1], dst, dstRegion, srcs, indexLen, indexOffset, uniformVars, noStencil); err != nil {
return err
}
}
return nil
@ -889,6 +988,7 @@ type Image struct {
height int
screen bool
texture mtl.Texture
stencil mtl.Texture
}
func (i *Image) ID() driver.ImageID {
@ -903,6 +1003,10 @@ func (i *Image) internalSize() (int, int) {
}
func (i *Image) Dispose() {
if i.stencil != (mtl.Texture{}) {
i.stencil.Release()
i.stencil = mtl.Texture{}
}
if i.texture != (mtl.Texture{}) {
i.texture.Release()
i.texture = mtl.Texture{}
@ -1022,3 +1126,19 @@ func (i *Image) mtlTexture() mtl.Texture {
}
return i.texture
}
func (i *Image) ensureStencil() {
if i.stencil != (mtl.Texture{}) {
return
}
td := mtl.TextureDescriptor{
TextureType: mtl.TextureType2D,
PixelFormat: mtl.PixelFormatStencil8,
Width: graphics.InternalImageSize(i.width),
Height: graphics.InternalImageSize(i.height),
StorageMode: mtl.StorageModePrivate,
Usage: mtl.TextureUsageRenderTarget,
}
i.stencil = i.graphics.view.getMTLDevice().MakeTexture(td)
}

View File

@ -26,20 +26,25 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/shaderir/metal"
)
type shaderRpsKey struct {
compositeMode driver.CompositeMode
colorWriteMask bool
}
type Shader struct {
id driver.ShaderID
ir *shaderir.Program
fs mtl.Function
vs mtl.Function
rpss map[driver.CompositeMode]mtl.RenderPipelineState
rpss map[shaderRpsKey]mtl.RenderPipelineState
}
func newShader(device mtl.Device, id driver.ShaderID, program *shaderir.Program) (*Shader, error) {
s := &Shader{
id: id,
ir: program,
rpss: map[driver.CompositeMode]mtl.RenderPipelineState{},
rpss: map[shaderRpsKey]mtl.RenderPipelineState{},
}
if err := s.init(device); err != nil {
return nil, err
@ -83,31 +88,43 @@ func (s *Shader) init(device mtl.Device) error {
return nil
}
func (s *Shader) RenderPipelineState(device mtl.Device, c driver.CompositeMode) (mtl.RenderPipelineState, error) {
if rps, ok := s.rpss[c]; ok {
func (s *Shader) RenderPipelineState(device mtl.Device, compositeMode driver.CompositeMode, colorWriteMask bool) (mtl.RenderPipelineState, error) {
if rps, ok := s.rpss[shaderRpsKey{
compositeMode: compositeMode,
colorWriteMask: colorWriteMask,
}]; ok {
return rps, nil
}
rpld := mtl.RenderPipelineDescriptor{
VertexFunction: s.vs,
FragmentFunction: s.fs,
VertexFunction: s.vs,
FragmentFunction: s.fs,
StencilAttachmentPixelFormat: mtl.PixelFormatStencil8,
}
// TODO: For the precise pixel format, whether the render target is the screen or not must be considered.
rpld.ColorAttachments[0].PixelFormat = mtl.PixelFormatRGBA8UNorm
rpld.ColorAttachments[0].BlendingEnabled = true
src, dst := c.Operations()
src, dst := compositeMode.Operations()
rpld.ColorAttachments[0].DestinationAlphaBlendFactor = operationToBlendFactor(dst)
rpld.ColorAttachments[0].DestinationRGBBlendFactor = operationToBlendFactor(dst)
rpld.ColorAttachments[0].SourceAlphaBlendFactor = operationToBlendFactor(src)
rpld.ColorAttachments[0].SourceRGBBlendFactor = operationToBlendFactor(src)
if colorWriteMask {
rpld.ColorAttachments[0].WriteMask = mtl.ColorWriteMaskAll
} else {
rpld.ColorAttachments[0].WriteMask = mtl.ColorWriteMaskNone
}
rps, err := device.MakeRenderPipelineState(rpld)
if err != nil {
return mtl.RenderPipelineState{}, err
}
s.rpss[c] = rps
s.rpss[shaderRpsKey{
compositeMode: compositeMode,
colorWriteMask: colorWriteMask,
}] = rps
return rps, nil
}

View File

@ -49,6 +49,7 @@ type context struct {
screenFramebuffer framebufferNative // This might not be the default frame buffer '0' (e.g. iOS).
lastFramebuffer framebufferNative
lastTexture textureNative
lastRenderbuffer renderbufferNative
lastViewportWidth int
lastViewportHeight int
lastCompositeMode driver.CompositeMode
@ -68,6 +69,14 @@ func (c *context) bindTexture(t textureNative) {
c.lastTexture = t
}
func (c *context) bindRenderbuffer(r renderbufferNative) {
if c.lastRenderbuffer.equal(r) {
return
}
c.bindRenderbufferImpl(r)
c.lastRenderbuffer = r
}
func (c *context) bindFramebuffer(f framebufferNative) {
if c.lastFramebuffer.equal(f) {
return

View File

@ -29,17 +29,22 @@ import (
)
type (
textureNative uint32
framebufferNative uint32
shader uint32
program uint32
buffer uint32
textureNative uint32
renderbufferNative uint32
framebufferNative uint32
shader uint32
program uint32
buffer uint32
)
func (t textureNative) equal(rhs textureNative) bool {
return t == rhs
}
func (r renderbufferNative) equal(rhs renderbufferNative) bool {
return r == rhs
}
func (f framebufferNative) equal(rhs framebufferNative) bool {
return f == rhs
}
@ -198,6 +203,38 @@ func (c *context) isTexture(t textureNative) bool {
panic("opengl: isTexture is not implemented")
}
func (c *context) newRenderbuffer(width, height int) (renderbufferNative, error) {
var r uint32
gl.GenRenderbuffersEXT(1, &r)
if r <= 0 {
return 0, errors.New("opengl: creating renderbuffer failed")
}
renderbuffer := renderbufferNative(r)
c.bindRenderbuffer(renderbuffer)
// GL_STENCIL_INDEX8 might not be available with OpenGL 2.1.
// https://www.khronos.org/opengl/wiki/Image_Format
gl.RenderbufferStorageEXT(gl.RENDERBUFFER, gl.DEPTH24_STENCIL8, int32(width), int32(height))
return renderbuffer, nil
}
func (c *context) bindRenderbufferImpl(r renderbufferNative) {
gl.BindRenderbufferEXT(gl.RENDERBUFFER, uint32(r))
}
func (c *context) deleteRenderbuffer(r renderbufferNative) {
rr := uint32(r)
if !gl.IsRenderbufferEXT(rr) {
return
}
if c.lastRenderbuffer.equal(r) {
c.lastRenderbuffer = 0
}
gl.DeleteRenderbuffersEXT(1, &rr)
}
func (c *context) newFramebuffer(texture textureNative) (framebufferNative, error) {
var f uint32
gl.GenFramebuffersEXT(1, &f)
@ -220,6 +257,17 @@ func (c *context) newFramebuffer(texture textureNative) (framebufferNative, erro
return framebufferNative(f), nil
}
func (c *context) bindStencilBuffer(f framebufferNative, r renderbufferNative) error {
c.bindFramebuffer(f)
gl.FramebufferRenderbufferEXT(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, uint32(r))
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
}
func (c *context) setViewportImpl(width, height int) {
gl.Viewport(0, 0, int32(width), int32(height))
}
@ -492,3 +540,23 @@ func (c *context) getBufferSubData(buffer buffer, width, height int) []byte {
gl.BindBuffer(gl.PIXEL_UNPACK_BUFFER, 0)
return pixels
}
func (c *context) enableStencilTest() {
gl.Enable(gl.STENCIL_TEST)
}
func (c *context) disableStencilTest() {
gl.Disable(gl.STENCIL_TEST)
}
func (c *context) beginStencilWithEvenOddRule() {
gl.StencilFunc(gl.ALWAYS, 0x00, 0xff)
gl.StencilOp(gl.KEEP, gl.KEEP, gl.INVERT)
gl.ColorMask(false, false, false, false)
}
func (c *context) endStencilWithEvenOddRule() {
gl.StencilFunc(gl.NOTEQUAL, 0x00, 0xff)
gl.StencilOp(gl.ZERO, gl.ZERO, gl.ZERO)
gl.ColorMask(true, true, true, true)
}

View File

@ -26,11 +26,12 @@ import (
)
type (
textureNative js.Value
framebufferNative js.Value
shader js.Value
buffer js.Value
uniformLocation js.Value
textureNative js.Value
renderbufferNative js.Value
framebufferNative js.Value
shader js.Value
buffer js.Value
uniformLocation js.Value
attribLocation int
programID int
@ -44,6 +45,10 @@ func (t textureNative) equal(rhs textureNative) bool {
return js.Value(t).Equal(js.Value(rhs))
}
func (r renderbufferNative) equal(rhs renderbufferNative) bool {
return js.Value(r).Equal(js.Value(rhs))
}
func (f framebufferNative) equal(rhs framebufferNative) bool {
return js.Value(f).Equal(js.Value(rhs))
}
@ -100,6 +105,7 @@ func (c *context) initGL() {
attr := js.Global().Get("Object").New()
attr.Set("alpha", true)
attr.Set("premultipliedAlpha", true)
attr.Set("stencil", true)
if isWebGL2Available {
gl = canvas.Call("getContext", "webgl2", attr)
@ -165,7 +171,7 @@ func (c *context) newTexture(width, height int) (textureNative, error) {
gl := c.gl
t := gl.createTexture.Invoke()
if !t.Truthy() {
return textureNative(js.Null()), errors.New("opengl: glGenTexture failed")
return textureNative(js.Null()), errors.New("opengl: createTexture failed")
}
c.bindTexture(textureNative(t))
@ -240,6 +246,37 @@ func (c *context) isTexture(t textureNative) bool {
panic("opengl: isTexture is not implemented")
}
func (c *context) newRenderbuffer(width, height int) (renderbufferNative, error) {
gl := c.gl
r := gl.createRenderbuffer.Invoke()
if !r.Truthy() {
return renderbufferNative(js.Null()), errors.New("opengl: createRenderbuffer failed")
}
c.bindRenderbuffer(renderbufferNative(r))
// TODO: Is STENCIL_INDEX8 portable?
// https://stackoverflow.com/questions/11084961/binding-a-stencil-render-buffer-to-a-frame-buffer-in-opengl
gl.renderbufferStorage.Invoke(gles.RENDERBUFFER, gles.STENCIL_INDEX8, width, height)
return renderbufferNative(r), nil
}
func (c *context) bindRenderbufferImpl(r renderbufferNative) {
gl := c.gl
gl.bindRenderbuffer.Invoke(gles.RENDERBUFFER, js.Value(r))
}
func (c *context) deleteRenderbuffer(r renderbufferNative) {
gl := c.gl
if !gl.isRenderbuffer.Invoke(js.Value(r)).Bool() {
return
}
if c.lastRenderbuffer.equal(r) {
c.lastRenderbuffer = renderbufferNative(js.Null())
}
gl.deleteRenderbuffer.Invoke(js.Value(r))
}
func (c *context) newFramebuffer(t textureNative) (framebufferNative, error) {
gl := c.gl
f := gl.createFramebuffer.Invoke()
@ -253,6 +290,18 @@ func (c *context) newFramebuffer(t textureNative) (framebufferNative, error) {
return framebufferNative(f), nil
}
func (c *context) bindStencilBuffer(f framebufferNative, r renderbufferNative) error {
gl := c.gl
c.bindFramebuffer(f)
gl.framebufferRenderbuffer.Invoke(gles.FRAMEBUFFER, gles.STENCIL_ATTACHMENT, gles.RENDERBUFFER, js.Value(r))
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
}
func (c *context) setViewportImpl(width, height int) {
gl := c.gl
gl.viewport.Invoke(0, 0, width, height)
@ -598,3 +647,27 @@ func (c *context) getBufferSubData(buffer buffer, width, height int) []byte {
gl.bindBuffer.Invoke(gles.PIXEL_UNPACK_BUFFER, nil)
return jsutil.Uint8ArrayToSlice(arr, l)
}
func (c *context) enableStencilTest() {
gl := c.gl
gl.enable.Invoke(gles.STENCIL_TEST)
}
func (c *context) disableStencilTest() {
gl := c.gl
gl.disable.Invoke(gles.STENCIL_TEST)
}
func (c *context) beginStencilWithEvenOddRule() {
gl := c.gl
gl.stencilFunc.Invoke(gles.ALWAYS, 0x00, 0xff)
gl.stencilOp.Invoke(gles.KEEP, gles.KEEP, gles.INVERT)
gl.colorMask.Invoke(false, false, false, false)
}
func (c *context) endStencilWithEvenOddRule() {
gl := c.gl
gl.stencilFunc.Invoke(gles.NOTEQUAL, 0x00, 0xff)
gl.stencilOp.Invoke(gles.ZERO, gles.ZERO, gles.ZERO)
gl.colorMask.Invoke(true, true, true, true)
}

View File

@ -27,17 +27,22 @@ import (
)
type (
textureNative uint32
framebufferNative uint32
shader uint32
program uint32
buffer uint32
textureNative uint32
renderbufferNative uint32
framebufferNative uint32
shader uint32
program uint32
buffer uint32
)
func (t textureNative) equal(rhs textureNative) bool {
return t == rhs
}
func (r renderbufferNative) equal(rhs renderbufferNative) bool {
return r == rhs
}
func (f framebufferNative) equal(rhs framebufferNative) bool {
return f == rhs
}
@ -185,6 +190,34 @@ func (c *context) isTexture(t textureNative) bool {
return c.ctx.IsTexture(uint32(t))
}
func (c *context) newRenderbuffer(width, height int) (renderbufferNative, error) {
r := c.ctx.GenRenderbuffers(1)[0]
if r <= 0 {
return 0, errors.New("opengl: creating renderbuffer failed")
}
renderbuffer := renderbufferNative(r)
c.bindRenderbuffer(renderbuffer)
c.ctx.RenderbufferStorage(gles.RENDERBUFFER, gles.STENCIL_INDEX8, int32(width), int32(height))
return renderbuffer, nil
}
func (c *context) bindRenderbufferImpl(r renderbufferNative) {
c.ctx.BindRenderbuffer(gles.RENDERBUFFER, uint32(r))
}
func (c *context) deleteRenderbuffer(r renderbufferNative) {
if !c.ctx.IsRenderbuffer(uint32(r)) {
return
}
if c.lastRenderbuffer.equal(r) {
c.lastRenderbuffer = 0
}
c.ctx.DeleteRenderbuffers([]uint32{uint32(r)})
}
func (c *context) newFramebuffer(texture textureNative) (framebufferNative, error) {
f := c.ctx.GenFramebuffers(1)[0]
if f <= 0 {
@ -206,6 +239,17 @@ func (c *context) newFramebuffer(texture textureNative) (framebufferNative, erro
return framebufferNative(f), nil
}
func (c *context) bindStencilBuffer(f framebufferNative, r renderbufferNative) error {
c.bindFramebuffer(f)
c.ctx.FramebufferRenderbuffer(gles.FRAMEBUFFER, gles.STENCIL_ATTACHMENT, gles.RENDERBUFFER, uint32(r))
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
}
func (c *context) setViewportImpl(width, height int) {
c.ctx.Viewport(0, 0, int32(width), int32(height))
}
@ -457,3 +501,23 @@ func (c *context) getBufferSubData(buffer buffer, width, height int) []byte {
// As PBO is not used in mobiles, leave this unimplemented so far.
panic("opengl: getBufferSubData is not implemented for mobiles")
}
func (c *context) enableStencilTest() {
c.ctx.Enable(gles.STENCIL_TEST)
}
func (c *context) disableStencilTest() {
c.ctx.Disable(gles.STENCIL_TEST)
}
func (c *context) beginStencilWithEvenOddRule() {
c.ctx.StencilFunc(gles.ALWAYS, 0x00, 0xff)
c.ctx.StencilOp(gles.KEEP, gles.KEEP, gles.INVERT)
c.ctx.ColorMask(false, false, false, false)
}
func (c *context) endStencilWithEvenOddRule() {
c.ctx.StencilFunc(gles.NOTEQUAL, 0x00, 0xff)
c.ctx.StencilOp(gles.ZERO, gles.ZERO, gles.ZERO)
c.ctx.ColorMask(true, true, true, true)
}

View File

@ -142,7 +142,7 @@ func (g *Graphics) SetVertices(vertices []float32, indices []uint16) {
g.context.elementArrayBufferSubData(indices)
}
func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderImageNum]driver.ImageID, offsets [graphics.ShaderImageNum - 1][2]float32, shaderID driver.ShaderID, indexLen int, indexOffset int, mode driver.CompositeMode, colorM *affine.ColorM, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, uniforms []interface{}) error {
func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderImageNum]driver.ImageID, offsets [graphics.ShaderImageNum - 1][2]float32, shaderID driver.ShaderID, indexLen int, indexOffset int, mode driver.CompositeMode, colorM *affine.ColorM, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, uniforms []interface{}, evenOdd bool) error {
destination := g.images[dstID]
if !destination.pbo.equal(*new(buffer)) {
@ -308,7 +308,20 @@ func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderIm
return err
}
if evenOdd {
if err := destination.ensureStencilBuffer(); err != nil {
return err
}
g.context.enableStencilTest()
g.context.beginStencilWithEvenOddRule()
g.context.drawElements(indexLen, indexOffset*2)
g.context.endStencilWithEvenOddRule()
}
g.context.drawElements(indexLen, indexOffset*2) // 2 is uint16 size in bytes
if evenOdd {
g.context.disableStencilTest()
}
return nil
}

View File

@ -23,6 +23,7 @@ type Image struct {
id driver.ImageID
graphics *Graphics
texture textureNative
stencil renderbufferNative
framebuffer *framebuffer
pbo buffer
width int
@ -48,6 +49,9 @@ func (i *Image) Dispose() {
if !i.texture.equal(*new(textureNative)) {
i.graphics.context.deleteTexture(i.texture)
}
if !i.stencil.equal(*new(renderbufferNative)) {
i.graphics.context.deleteRenderbuffer(i.stencil)
}
i.graphics.removeImage(i)
}
@ -105,6 +109,27 @@ func (i *Image) ensureFramebuffer() error {
return nil
}
func (i *Image) ensureStencilBuffer() error {
if !i.stencil.equal(*new(renderbufferNative)) {
return nil
}
if err := i.ensureFramebuffer(); err != nil {
return err
}
r, err := i.graphics.context.newRenderbuffer(i.framebufferSize())
if err != nil {
return err
}
i.stencil = r
if err := i.graphics.context.bindStencilBuffer(i.framebuffer.native, i.stencil); err != nil {
return err
}
return nil
}
func (i *Image) ReplacePixels(args []*driver.ReplacePixelsArgs) {
if i.screen {
panic("opengl: ReplacePixels cannot be called on the screen, that doesn't have a texture")

View File

@ -85,7 +85,7 @@ func (m *Mipmap) Pixels(x, y, width, height int) ([]byte, error) {
return m.orig.Pixels(x, y, width, height)
}
func (m *Mipmap) DrawTriangles(srcs [graphics.ShaderImageNum]*Mipmap, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms []interface{}, canSkipMipmap bool) {
func (m *Mipmap) DrawTriangles(srcs [graphics.ShaderImageNum]*Mipmap, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms []interface{}, evenOdd bool, canSkipMipmap bool) {
if len(indices) == 0 {
return
}
@ -164,7 +164,7 @@ func (m *Mipmap) DrawTriangles(srcs [graphics.ShaderImageNum]*Mipmap, vertices [
imgs[i] = src.orig
}
m.orig.DrawTriangles(imgs, vertices, indices, colorm, mode, filter, address, dstRegion, srcRegion, subimageOffsets, s, uniforms)
m.orig.DrawTriangles(imgs, vertices, indices, colorm, mode, filter, address, dstRegion, srcRegion, subimageOffsets, s, uniforms, evenOdd)
m.disposeMipmaps()
}
@ -226,7 +226,7 @@ func (m *Mipmap) level(level int) *buffered.Image {
Width: float32(w2),
Height: float32(h2),
}
s.DrawTriangles([graphics.ShaderImageNum]*buffered.Image{src}, vs, is, nil, driver.CompositeModeCopy, filter, driver.AddressUnsafe, dstRegion, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil)
s.DrawTriangles([graphics.ShaderImageNum]*buffered.Image{src}, vs, is, nil, driver.CompositeModeCopy, filter, driver.AddressUnsafe, dstRegion, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
m.imgs[level] = s
return m.imgs[level]

View File

@ -76,6 +76,7 @@ type drawTrianglesHistoryItem struct {
srcRegion driver.Region
shader *Shader
uniforms []interface{}
evenOdd bool
}
// Image represents an image that can be restored when GL context is lost.
@ -186,7 +187,7 @@ func (i *Image) Extend(width, height int) *Image {
Width: float32(sw),
Height: float32(sh),
}
newImg.DrawTriangles(srcs, offsets, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil)
newImg.DrawTriangles(srcs, offsets, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
// Overwrite the history as if the image newImg is created only by ReplacePixels. Now drawTrianglesHistory
// and basePixels cannot be mixed.
@ -247,7 +248,7 @@ func clearImage(i *graphicscommand.Image) {
Width: float32(dw),
Height: float32(dh),
}
i.DrawTriangles(srcs, offsets, vs, is, nil, driver.CompositeModeClear, driver.FilterNearest, driver.AddressUnsafe, dstRegion, driver.Region{}, nil, nil)
i.DrawTriangles(srcs, offsets, vs, is, nil, driver.CompositeModeClear, driver.FilterNearest, driver.AddressUnsafe, dstRegion, driver.Region{}, nil, nil, false)
}
// BasePixelsForTesting returns the image's basePixels for testing.
@ -350,7 +351,7 @@ func (i *Image) ReplacePixels(pixels []byte, x, y, width, height int) {
// 5: Color G
// 6: Color B
// 7: Color Y
func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, uniforms []interface{}) {
func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, uniforms []interface{}, evenOdd bool) {
if i.priority {
panic("restorable: DrawTriangles cannot be called on a priority image")
}
@ -374,7 +375,7 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, offsets [gra
if srcstale || i.screen || !NeedsRestoring() || i.volatile {
i.makeStale()
} else {
i.appendDrawTrianglesHistory(srcs, offsets, vertices, indices, colorm, mode, filter, address, dstRegion, srcRegion, shader, uniforms)
i.appendDrawTrianglesHistory(srcs, offsets, vertices, indices, colorm, mode, filter, address, dstRegion, srcRegion, shader, uniforms, evenOdd)
}
var s *graphicscommand.Shader
@ -391,11 +392,11 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, offsets [gra
}
s = shader.shader
}
i.image.DrawTriangles(imgs, offsets, vertices, indices, colorm, mode, filter, address, dstRegion, srcRegion, s, uniforms)
i.image.DrawTriangles(imgs, offsets, vertices, indices, colorm, mode, filter, address, dstRegion, srcRegion, s, uniforms, evenOdd)
}
// appendDrawTrianglesHistory appends a draw-image history item to the image.
func (i *Image) appendDrawTrianglesHistory(srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, uniforms []interface{}) {
func (i *Image) appendDrawTrianglesHistory(srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, uniforms []interface{}, evenOdd bool) {
if i.stale || i.volatile || i.screen {
return
}
@ -427,6 +428,7 @@ func (i *Image) appendDrawTrianglesHistory(srcs [graphics.ShaderImageNum]*Image,
srcRegion: srcRegion,
shader: shader,
uniforms: uniforms,
evenOdd: evenOdd,
}
i.drawTrianglesHistory = append(i.drawTrianglesHistory, item)
}
@ -605,7 +607,7 @@ func (i *Image) restore() error {
}
imgs[i] = img.image
}
gimg.DrawTriangles(imgs, c.offsets, c.vertices, c.indices, c.colorm, c.mode, c.filter, c.address, c.dstRegion, c.srcRegion, s, c.uniforms)
gimg.DrawTriangles(imgs, c.offsets, c.vertices, c.indices, c.colorm, c.mode, c.filter, c.address, c.dstRegion, c.srcRegion, s, c.uniforms, c.evenOdd)
}
if len(i.drawTrianglesHistory) > 0 {

View File

@ -137,7 +137,7 @@ func TestRestoreChain(t *testing.T) {
Width: 1,
Height: 1,
}
imgs[i+1].DrawTriangles([graphics.ShaderImageNum]*Image{imgs[i]}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil)
imgs[i+1].DrawTriangles([graphics.ShaderImageNum]*Image{imgs[i]}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
}
if err := ResolveStaleImages(); err != nil {
t.Fatal(err)
@ -185,10 +185,10 @@ func TestRestoreChain2(t *testing.T) {
Width: w,
Height: h,
}
imgs[8].DrawTriangles([graphics.ShaderImageNum]*Image{imgs[7]}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil)
imgs[9].DrawTriangles([graphics.ShaderImageNum]*Image{imgs[8]}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil)
imgs[8].DrawTriangles([graphics.ShaderImageNum]*Image{imgs[7]}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
imgs[9].DrawTriangles([graphics.ShaderImageNum]*Image{imgs[8]}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
for i := 0; i < 7; i++ {
imgs[i+1].DrawTriangles([graphics.ShaderImageNum]*Image{imgs[i]}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil)
imgs[i+1].DrawTriangles([graphics.ShaderImageNum]*Image{imgs[i]}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
}
if err := ResolveStaleImages(); err != nil {
@ -234,10 +234,10 @@ func TestRestoreOverrideSource(t *testing.T) {
Width: w,
Height: h,
}
img2.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil)
img3.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil)
img2.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
img3.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
img0.ReplacePixels([]byte{clr1.R, clr1.G, clr1.B, clr1.A}, 0, 0, w, h)
img1.DrawTriangles([graphics.ShaderImageNum]*Image{img0}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil)
img1.DrawTriangles([graphics.ShaderImageNum]*Image{img0}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
if err := ResolveStaleImages(); err != nil {
t.Fatal(err)
}
@ -323,23 +323,23 @@ func TestRestoreComplexGraph(t *testing.T) {
Height: h,
}
var offsets [graphics.ShaderImageNum - 1][2]float32
img3.DrawTriangles([graphics.ShaderImageNum]*Image{img0}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil)
img3.DrawTriangles([graphics.ShaderImageNum]*Image{img0}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
vs = quadVertices(w, h, 1, 0)
img3.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil)
img3.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
vs = quadVertices(w, h, 1, 0)
img4.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil)
img4.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
vs = quadVertices(w, h, 2, 0)
img4.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil)
img4.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
vs = quadVertices(w, h, 0, 0)
img5.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil)
img5.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
vs = quadVertices(w, h, 0, 0)
img6.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil)
img6.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
vs = quadVertices(w, h, 1, 0)
img6.DrawTriangles([graphics.ShaderImageNum]*Image{img4}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil)
img6.DrawTriangles([graphics.ShaderImageNum]*Image{img4}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
vs = quadVertices(w, h, 0, 0)
img7.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil)
img7.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
vs = quadVertices(w, h, 2, 0)
img7.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil)
img7.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
if err := ResolveStaleImages(); err != nil {
t.Fatal(err)
}
@ -437,8 +437,8 @@ func TestRestoreRecursive(t *testing.T) {
Width: w,
Height: h,
}
img1.DrawTriangles([graphics.ShaderImageNum]*Image{img0}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 1, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil)
img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 1, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil)
img1.DrawTriangles([graphics.ShaderImageNum]*Image{img0}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 1, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 1, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
if err := ResolveStaleImages(); err != nil {
t.Fatal(err)
}
@ -538,7 +538,7 @@ func TestDrawTrianglesAndReplacePixels(t *testing.T) {
Width: 2,
Height: 1,
}
img1.DrawTriangles([graphics.ShaderImageNum]*Image{img0}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil)
img1.DrawTriangles([graphics.ShaderImageNum]*Image{img0}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
img1.ReplacePixels([]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 0, 0, 2, 1)
if err := ResolveStaleImages(); err != nil {
@ -581,8 +581,8 @@ func TestDispose(t *testing.T) {
Width: 1,
Height: 1,
}
img1.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(1, 1, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil)
img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(1, 1, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil)
img1.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(1, 1, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(1, 1, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
img1.Dispose()
if err := ResolveStaleImages(); err != nil {
@ -696,7 +696,7 @@ func TestReplacePixelsOnly(t *testing.T) {
Width: 1,
Height: 1,
}
img1.DrawTriangles([graphics.ShaderImageNum]*Image{img0}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil)
img1.DrawTriangles([graphics.ShaderImageNum]*Image{img0}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
img0.ReplacePixels([]byte{5, 6, 7, 8}, 0, 0, 1, 1)
// BasePixelsForTesting is available without GPU accessing.
@ -756,7 +756,7 @@ func TestReadPixelsFromVolatileImage(t *testing.T) {
Width: w,
Height: h,
}
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil)
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
// Read the pixels. If the implementation is correct, dst tries to read its pixels from GPU due to being
// stale.
@ -783,7 +783,7 @@ func TestAllowReplacePixelsAfterDrawTriangles(t *testing.T) {
Width: w,
Height: h,
}
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil)
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
dst.ReplacePixels(make([]byte, 4*w*h), 0, 0, w, h)
// ReplacePixels for a whole image doesn't panic.
}
@ -807,7 +807,7 @@ func TestDisallowReplacePixelsForPartAfterDrawTriangles(t *testing.T) {
Width: w,
Height: h,
}
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil)
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
dst.ReplacePixels(make([]byte, 4), 0, 0, 1, 1)
}
@ -884,7 +884,7 @@ func TestMutateSlices(t *testing.T) {
Width: w,
Height: h,
}
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil)
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
for i := range vs {
vs[i] = 0
}

View File

@ -48,7 +48,7 @@ func clearImage(img *Image, w, h int) {
Width: float32(w),
Height: float32(h),
}
img.DrawTriangles([graphics.ShaderImageNum]*Image{emptyImage}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeClear, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil)
img.DrawTriangles([graphics.ShaderImageNum]*Image{emptyImage}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeClear, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
}
func TestShader(t *testing.T) {
@ -63,7 +63,7 @@ func TestShader(t *testing.T) {
Width: 1,
Height: 1,
}
img.DrawTriangles([graphics.ShaderImageNum]*Image{}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, s, nil)
img.DrawTriangles([graphics.ShaderImageNum]*Image{}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, s, nil, false)
if err := ResolveStaleImages(); err != nil {
t.Fatal(err)
@ -99,7 +99,7 @@ func TestShaderChain(t *testing.T) {
Width: 1,
Height: 1,
}
imgs[i+1].DrawTriangles([graphics.ShaderImageNum]*Image{imgs[i]}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, s, nil)
imgs[i+1].DrawTriangles([graphics.ShaderImageNum]*Image{imgs[i]}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, s, nil, false)
}
if err := ResolveStaleImages(); err != nil {
@ -138,7 +138,7 @@ func TestShaderMultipleSources(t *testing.T) {
Width: 1,
Height: 1,
}
dst.DrawTriangles(srcs, offsets, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, s, nil)
dst.DrawTriangles(srcs, offsets, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, s, nil, false)
// Clear one of the sources after DrawTriangles. dst should not be affected.
clearImage(srcs[0], 1, 1)
@ -180,7 +180,7 @@ func TestShaderMultipleSourcesOnOneTexture(t *testing.T) {
Width: 1,
Height: 1,
}
dst.DrawTriangles(srcs, offsets, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, s, nil)
dst.DrawTriangles(srcs, offsets, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, s, nil, false)
// Clear one of the sources after DrawTriangles. dst should not be affected.
clearImage(srcs[0], 3, 1)
@ -211,7 +211,7 @@ func TestShaderDispose(t *testing.T) {
Width: 1,
Height: 1,
}
img.DrawTriangles([graphics.ShaderImageNum]*Image{}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, s, nil)
img.DrawTriangles([graphics.ShaderImageNum]*Image{}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, s, nil, false)
// Dispose the shader. This should invalidates all the images using this shader i.e., all the images become
// stale.

View File

@ -1,26 +0,0 @@
// Copyright 2019 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package triangulate
import (
"math"
)
var nan32 = float32(math.NaN())
type Point struct {
X float32
Y float32
}

View File

@ -1,154 +0,0 @@
// Copyright 2019 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package triangulate
import (
"fmt"
)
func cross(v0x, v0y, v1x, v1y float32) float32 {
return v0x*v1y - v0y*v1x
}
func triangleCross(pt0, pt1, pt2 Point) float32 {
return cross(pt1.X-pt0.X, pt1.Y-pt0.Y, pt2.X-pt1.X, pt2.Y-pt1.Y)
}
func adjacentIndices(indices []uint16, idx int) (uint16, uint16, uint16) {
return indices[(idx+len(indices)-1)%len(indices)], indices[idx], indices[(idx+1)%len(indices)]
}
func InTriangle(pt, pt0, pt1, pt2 Point) bool {
if pt.X <= pt0.X && pt.X <= pt1.X && pt.X <= pt2.X {
return false
}
if pt.X >= pt0.X && pt.X >= pt1.X && pt.X >= pt2.X {
return false
}
if pt.Y <= pt0.Y && pt.Y <= pt1.Y && pt.Y <= pt2.Y {
return false
}
if pt.Y >= pt0.Y && pt.Y >= pt1.Y && pt.Y >= pt2.Y {
return false
}
c0 := cross(pt.X-pt0.X, pt.Y-pt0.Y, pt1.X-pt0.X, pt1.Y-pt0.Y)
c1 := cross(pt.X-pt1.X, pt.Y-pt1.Y, pt2.X-pt1.X, pt2.Y-pt1.Y)
c2 := cross(pt.X-pt2.X, pt.Y-pt2.Y, pt0.X-pt2.X, pt0.Y-pt2.Y)
return (c0 <= 0 && c1 <= 0 && c2 <= 0) || (c0 >= 0 && c1 >= 0 && c2 >= 0)
}
// Triangulate triangulates the region surrounded by the points pts and returnes the point indices.
func Triangulate(pts []Point) []uint16 {
if len(pts) < 3 {
return nil
}
var currentIndices []uint16
// Split pts into the two point groups if there are the same points.
for i := range pts {
for j := 0; j < i; j++ {
if pts[i] == pts[j] {
is0 := Triangulate(pts[j:i])
for idx := range is0 {
is0[idx] += uint16(j)
}
is1 := Triangulate(append(pts[i:], pts[:j]...))
for idx := range is1 {
is1[idx] = uint16((int(is1[idx]) + i) % len(pts))
}
return append(is0, is1...)
}
}
currentIndices = append(currentIndices, uint16(i))
}
var indices []uint16
// Triangulation by Ear Clipping.
// https://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf
// TODO: Adopt a more efficient algorithm.
for len(currentIndices) >= 3 {
// Calculate cross-products and remove unneeded vertices.
cs := make([]float32, len(currentIndices))
idxToRemove := -1
// Determine the direction of the polygon from the upper-left point.
var upperLeft int
for i := range currentIndices {
i0, i1, i2 := adjacentIndices(currentIndices, i)
pt0 := pts[i0]
pt1 := pts[i1]
pt2 := pts[i2]
c := triangleCross(pt0, pt1, pt2)
if c == 0 {
idxToRemove = i
break
}
cs[i] = c
if pts[currentIndices[upperLeft]].X > pts[currentIndices[i]].X {
upperLeft = i
} else if pts[currentIndices[upperLeft]].X == pts[currentIndices[i]].X &&
pts[currentIndices[upperLeft]].Y > pts[currentIndices[i]].Y {
upperLeft = i
}
}
if idxToRemove != -1 {
currentIndices = append(currentIndices[:idxToRemove], currentIndices[idxToRemove+1:]...)
continue
}
clockwise := cs[upperLeft] < 0
idx := -1
index:
for i := range currentIndices {
c := cs[i]
if c == 0 {
panic("math: cross value must not be 0")
}
if c < 0 && !clockwise || c > 0 && clockwise {
// The angle is more than 180 degrees. This is not an ear.
continue
}
i0, i1, i2 := adjacentIndices(currentIndices, i)
pt0 := pts[i0]
pt1 := pts[i1]
pt2 := pts[i2]
for _, j := range currentIndices {
if j == i0 || j == i1 || j == i2 {
continue
}
if InTriangle(pts[j], pt0, pt1, pt2) {
// If the triangle includes another point, the triangle is not an ear.
continue index
}
}
// The angle is less than 180 degrees. This is an ear.
idx = i
break
}
if idx < 0 {
// TODO: This happens when there is self-crossing.
panic(fmt.Sprintf("math: there is no ear in the polygon: %v", pts))
}
i0, i1, i2 := adjacentIndices(currentIndices, idx)
indices = append(indices, i0, i1, i2)
currentIndices = append(currentIndices[:idx], currentIndices[idx+1:]...)
}
return indices
}

View File

@ -1,328 +0,0 @@
// Copyright 2019 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package triangulate_test
import (
"math"
"reflect"
"testing"
. "github.com/hajimehoshi/ebiten/v2/vector/internal/triangulate"
)
func TestIsInTriangle(t *testing.T) {
tests := []struct {
Tri []Point
Pt Point
Out bool
}{
{
Tri: []Point{
{0, 0},
{0, 10},
{10, 10},
},
Pt: Point{1, 9},
Out: true,
},
{
Tri: []Point{
{0, 0},
{0, 10},
{10, 10},
},
Pt: Point{8, 9},
Out: true,
},
{
Tri: []Point{
{0, 0},
{0, 10},
{10, 10},
},
Pt: Point{10, 9},
Out: false,
},
{
Tri: []Point{
{3, 5},
{2, 7},
{7, 7},
},
Pt: Point{3, 6},
Out: true,
},
{
Tri: []Point{
{3, 5},
{2, 7},
{7, 7},
},
Pt: Point{7, 6},
Out: false,
},
}
for _, tc := range tests {
got := InTriangle(tc.Pt, tc.Tri[0], tc.Tri[1], tc.Tri[2])
want := tc.Out
if got != want {
t.Errorf("InTriangle(%v, %v, %v, %v): got: %t, want: %t", tc.Pt, tc.Tri[0], tc.Tri[1], tc.Tri[2], got, want)
}
}
}
func TestTriangulate(t *testing.T) {
tests := []struct {
In []Point
Out []uint16
}{
{
In: []Point{},
Out: nil,
},
{
In: []Point{
{0, 0},
},
Out: nil,
},
{
In: []Point{
{0, 0},
{0, 1},
},
Out: nil,
},
{
In: []Point{
{0, 0},
{0, 0},
{1, 1},
},
Out: nil,
},
{
In: []Point{
{0, 0},
{0.5, 0.5},
{1, 1},
},
Out: nil,
},
{
In: []Point{
{0, 0},
{0.5, 0.5},
{1.5, 1.5},
{1, 1},
},
Out: nil,
},
{
In: []Point{
{0, 0},
{0, 1},
{1, 1},
},
Out: []uint16{2, 0, 1},
},
{
In: []Point{
{0, 0},
{1, 1},
{0, 1},
},
Out: []uint16{2, 0, 1},
},
{
In: []Point{
{0, 0},
{1, 1},
{0, 1},
{0, 0.5},
},
Out: []uint16{2, 0, 1},
},
{
In: []Point{
{0, 0},
{0, 1},
{1, 1},
{1, 0},
},
Out: []uint16{3, 0, 1, 3, 1, 2},
},
{
In: []Point{
{2, 2},
{2, 7},
{7, 7},
{7, 6},
{3, 6},
{3, 5},
},
Out: []uint16{5, 0, 1, 1, 2, 3, 1, 3, 4, 5, 1, 4},
},
{
In: []Point{
{2, 2},
{2, 7},
{7, 7},
{7, 6},
{3, 6},
{3, 5},
{7, 5},
{7, 4},
{3, 4},
{3, 3},
},
Out: []uint16{9, 0, 1, 1, 2, 3, 1, 3, 4, 9, 1, 4, 8, 5, 6, 8, 6, 7},
},
{
In: []Point{
{0, 0},
{0, 5},
{2, 5},
{3, 3},
{2, 2},
{3, 1},
{2, 0},
},
Out: []uint16{6, 0, 1, 6, 1, 2, 6, 2, 3, 4, 5, 6, 6, 3, 4},
},
{
In: []Point{
{0, 0},
{0, 5},
{2, 5},
{2, 5},
{3, 3},
{2, 2},
{2, 2},
{3, 1},
{2, 0},
},
Out: []uint16{6, 7, 8, 6, 8, 0, 4, 0, 1, 4, 1, 3},
},
{
In: []Point{
{0, 0},
{0, 1},
{1, 1},
{1, 0},
{2, 0},
},
Out: []uint16{3, 0, 1, 3, 1, 2},
},
{
In: []Point{
{2, 0},
{0, 0},
{1, 0},
{1, 1},
{2, 1},
},
Out: []uint16{4, 0, 2, 4, 2, 3},
},
{
In: []Point{
{2, 0},
{2, 1},
{1, 1},
{1, 0},
{0, 0},
},
Out: []uint16{3, 0, 1, 3, 1, 2},
},
{
// Butterfly
In: []Point{
{0, 2},
{1, 1},
{2, 2},
{2, 0},
{1, 1},
{0, 0},
},
Out: []uint16{3, 1, 2, 0, 4, 5},
},
{
In: []Point{
{0, 6},
{0, 9},
{6, 6},
{6, 3},
{9, 3},
{8, 0},
{6, 0},
{6, 3},
},
Out: []uint16{6, 3, 4, 6, 4, 5, 2, 7, 0, 2, 0, 1},
},
{
In: []Point{
{0, 4},
{0, 6},
{2, 6},
{2, 5},
{3, 5},
{3, 4},
{4, 4},
{4, 2},
{6, 2},
{6, 1},
{5, 1},
{5, 0},
{4, 0},
{4, 2},
{2, 2},
{2, 3},
{1, 3},
{1, 4},
},
Out: []uint16{7, 8, 9, 7, 9, 10, 12, 7, 10, 12, 10, 11, 6, 13, 14, 6, 14, 15, 6, 15, 16, 6, 16, 17, 5, 0, 1, 1, 2, 3, 5, 1, 3, 5, 3, 4},
},
}
for _, tc := range tests {
got := Triangulate(tc.In)
want := tc.Out
if !reflect.DeepEqual(got, want) {
t.Errorf("Triangulate(%v): got: %v, want: %v", tc.In, got, want)
}
}
}
var benchmarkPath []Point
func init() {
const (
w = 640
h = 480
)
var p []Point
for i := 0; i < w; i++ {
x := float32(i)
y := h/2 + 80*float32(math.Sin(2*math.Pi*float64(i)/40))
p = append(p, Point{X: x, Y: y})
}
p = append(p, Point{w, h}, Point{0, h})
benchmarkPath = p
}
func BenchmarkTriangulate(b *testing.B) {
for n := 0; n < b.N; n++ {
Triangulate(benchmarkPath)
}
}

View File

@ -18,33 +18,26 @@
package vector
import (
"image"
"image/color"
"math"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/vector/internal/triangulate"
)
var (
emptyImage = ebiten.NewImage(3, 3)
emptySubImage = emptyImage.SubImage(image.Rect(1, 1, 2, 2)).(*ebiten.Image)
)
func init() {
emptyImage.Fill(color.White)
type point struct {
x float32
y float32
}
// Path represents a collection of path segments.
type Path struct {
segs [][]triangulate.Point
cur triangulate.Point
segs [][]point
cur point
}
// MoveTo skips the current position of the path to the given position (x, y) without adding any strokes.
func (p *Path) MoveTo(x, y float32) {
p.cur = triangulate.Point{X: x, Y: y}
p.segs = append(p.segs, []triangulate.Point{p.cur})
p.cur = point{x: x, y: y}
p.segs = append(p.segs, []point{p.cur})
}
// LineTo adds a line segument to the path, which starts from the current position and ends to the given position (x, y).
@ -52,10 +45,10 @@ func (p *Path) MoveTo(x, y float32) {
// LineTo updates the current position to (x, y).
func (p *Path) LineTo(x, y float32) {
if len(p.segs) == 0 {
p.segs = append(p.segs, []triangulate.Point{p.cur})
p.segs = append(p.segs, []point{p.cur})
}
p.segs[len(p.segs)-1] = append(p.segs[len(p.segs)-1], triangulate.Point{X: x, Y: y})
p.cur = triangulate.Point{X: x, Y: y}
p.segs[len(p.segs)-1] = append(p.segs[len(p.segs)-1], point{x: x, y: y})
p.cur = point{x: x, y: y}
}
// nseg returns a number of segments based on the given two points (x0, y0) and (x1, y1).
@ -78,69 +71,61 @@ func nseg(x0, y0, x1, y1 float32) int {
// QuadTo adds a quadratic Bézier curve to the path.
func (p *Path) QuadTo(cpx, cpy, x, y float32) {
// TODO: Split more appropriate number of segments.
c := p.cur
num := nseg(c.X, c.Y, x, y)
num := nseg(c.x, c.y, x, y)
for t := float32(0.0); t <= 1; t += 1.0 / float32(num) {
xf := (1-t)*(1-t)*c.X + 2*t*(1-t)*cpx + t*t*x
yf := (1-t)*(1-t)*c.Y + 2*t*(1-t)*cpy + t*t*y
xf := (1-t)*(1-t)*c.x + 2*t*(1-t)*cpx + t*t*x
yf := (1-t)*(1-t)*c.y + 2*t*(1-t)*cpy + t*t*y
p.LineTo(xf, yf)
}
}
// CubicTo adds a cubic Bézier curve to the path.
func (p *Path) CubicTo(cp0x, cp0y, cp1x, cp1y, x, y float32) {
// TODO: Split more appropriate number of segments.
c := p.cur
num := nseg(c.X, c.Y, x, y)
num := nseg(c.x, c.y, x, y)
for t := float32(0.0); t <= 1; t += 1.0 / float32(num) {
xf := (1-t)*(1-t)*(1-t)*c.X + 3*(1-t)*(1-t)*t*cp0x + 3*(1-t)*t*t*cp1x + t*t*t*x
yf := (1-t)*(1-t)*(1-t)*c.Y + 3*(1-t)*(1-t)*t*cp0y + 3*(1-t)*t*t*cp1y + t*t*t*y
xf := (1-t)*(1-t)*(1-t)*c.x + 3*(1-t)*(1-t)*t*cp0x + 3*(1-t)*t*t*cp1x + t*t*t*x
yf := (1-t)*(1-t)*(1-t)*c.y + 3*(1-t)*(1-t)*t*cp0y + 3*(1-t)*t*t*cp1y + t*t*t*y
p.LineTo(xf, yf)
}
}
// FillOptions represents options to fill a path.
type FillOptions struct {
// Color is a color to fill with.
Color color.Color
}
// Fill fills the region of the path with the given options op.
func (p *Path) Fill(dst *ebiten.Image, op *FillOptions) {
var vertices []ebiten.Vertex
var indices []uint16
if op.Color == nil {
return
}
r, g, b, a := op.Color.RGBA()
var rf, gf, bf, af float32
if a == 0 {
return
}
rf = float32(r) / float32(a)
gf = float32(g) / float32(a)
bf = float32(b) / float32(a)
af = float32(a) / 0xffff
// AppendVerticesAndIndices appends vertices and indices for this path and returns them.
// AppendVerticesAndIndices works in a similar way to the built-in append function.
// If the arguments are nils, AppendVerticesAndIndices returns new slices.
//
// 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 EvenOdd option
// in order to render a complex polygon like a concave polygon, a polygon with holes, or a self-intersecting polygon.
func (p *Path) AppendVerticesAndIndices(vertices []ebiten.Vertex, indices []uint16) ([]ebiten.Vertex, []uint16) {
// TODO: Add tests.
var base uint16
for _, seg := range p.segs {
for _, pt := range seg {
if len(seg) < 3 {
continue
}
for i, pt := range seg {
vertices = append(vertices, ebiten.Vertex{
DstX: pt.X,
DstY: pt.Y,
DstX: pt.x,
DstY: pt.y,
SrcX: 0,
SrcY: 0,
ColorR: rf,
ColorG: gf,
ColorB: bf,
ColorA: af,
ColorR: 1,
ColorG: 1,
ColorB: 1,
ColorA: 1,
})
}
for _, idx := range triangulate.Triangulate(seg) {
indices = append(indices, idx+base)
if i < 2 {
continue
}
indices = append(indices, base, base+uint16(i-1), base+uint16(i))
}
base += uint16(len(seg))
}
dst.DrawTriangles(vertices, indices, emptySubImage, nil)
return vertices, indices
}