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 ( import (
"fmt" "fmt"
"image"
"image/color" "image/color"
"log" "log"
"math" "math"
@ -28,6 +29,15 @@ import (
"github.com/hajimehoshi/ebiten/v2/vector" "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 ( const (
screenWidth = 640 screenWidth = 640
screenHeight = 480 screenHeight = 480
@ -99,10 +109,18 @@ func drawEbitenText(screen *ebiten.Image) {
path.LineTo(320, 55) path.LineTo(320, 55)
path.LineTo(290, 20) path.LineTo(290, 20)
op := &vector.FillOptions{ op := &ebiten.DrawTrianglesOptions{
Color: color.RGBA{0xdb, 0x56, 0x20, 0xff}, 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) { 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+3*unit)
path.LineTo(xf+unit, yf+4*unit) path.LineTo(xf+unit, yf+4*unit)
op := &vector.FillOptions{ op := &ebiten.DrawTrianglesOptions{
Color: color.RGBA{0xdb, 0x56, 0x20, 0xff}, 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 { func maxCounter(index int) int {
@ -166,10 +192,18 @@ func drawWave(screen *ebiten.Image, counter int) {
path.LineTo(screenWidth, screenHeight) path.LineTo(screenWidth, screenHeight)
path.LineTo(0, screenHeight) path.LineTo(0, screenHeight)
op := &vector.FillOptions{ op := &ebiten.DrawTrianglesOptions{
Color: color.RGBA{0x33, 0x66, 0xff, 0xff}, 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 { type Game struct {

View File

@ -213,7 +213,7 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) {
is := graphics.QuadIndices() is := graphics.QuadIndices()
srcs := [graphics.ShaderImageNum]*mipmap.Mipmap{img.mipmap} 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. // Vertex represents a vertex passed to DrawTriangles.
@ -271,6 +271,19 @@ type DrawTrianglesOptions struct {
// Address is a sampler address mode. // Address is a sampler address mode.
// The default (zero) value is AddressUnsafe. // The default (zero) value is AddressUnsafe.
Address Address 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. // 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} 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. // DrawTrianglesShaderOptions represents options for DrawTrianglesShader.
@ -371,6 +384,19 @@ type DrawTrianglesShaderOptions struct {
// Images is a set of the source images. // Images is a set of the source images.
// All the image must be the same size. // All the image must be the same size.
Images [4]*Image 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() { func init() {
@ -485,7 +511,7 @@ func (i *Image) DrawTrianglesShader(vertices []Vertex, indices []uint16, shader
} }
us := shader.convertUniforms(options.Uniforms) 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. // 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) 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. // 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), Width: float32(w - 2*paddingSize),
Height: float32(h - 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.dispose(false)
i.backend = &backend{ i.backend = &backend{
@ -354,7 +354,7 @@ func (i *Image) putOnAtlas() error {
Width: w, Width: w,
Height: h, 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) newI.moveTo(i)
@ -402,13 +402,13 @@ func (i *Image) processSrc(src *Image) {
// 5: Color G // 5: Color G
// 6: Color B // 6: Color B
// 7: Color Y // 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() backendsM.Lock()
defer backendsM.Unlock() 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 { if i.disposed {
panic("atlas: the drawing target image must not be disposed (DrawTriangles)") 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 { for _, src := range srcs {
if src == nil { if src == nil {

View File

@ -102,7 +102,7 @@ func TestEnsureIsolated(t *testing.T) {
Width: size, Width: size,
Height: 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 want := false
if got := img4.IsOnAtlasForTesting(); got != want { if got := img4.IsOnAtlasForTesting(); got != want {
t.Errorf("got: %v, want: %v", 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. // Check further drawing doesn't cause panic.
// This bug was fixed by 03dcd948. // 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) { func TestReputOnAtlas(t *testing.T) {
@ -179,7 +179,7 @@ func TestReputOnAtlas(t *testing.T) {
Width: size, Width: size,
Height: 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 { if got, want := img1.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
@ -191,7 +191,7 @@ func TestReputOnAtlas(t *testing.T) {
if err := PutImagesOnAtlasForTesting(); err != nil { if err := PutImagesOnAtlasForTesting(); err != nil {
t.Fatal(err) 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 { if got, want := img1.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", 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. // 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 { if got, want := img1.IsOnAtlasForTesting(), true; got != want {
t.Errorf("got: %v, want: %v", 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. // 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 { if got, want := img1.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
@ -255,7 +255,7 @@ func TestReputOnAtlas(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
img1.ReplacePixels(make([]byte, 4*size*size)) 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 { if got, want := img1.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", 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. // 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 { if got, want := img1.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
@ -275,7 +275,7 @@ func TestReputOnAtlas(t *testing.T) {
if err := PutImagesOnAtlasForTesting(); err != nil { if err := PutImagesOnAtlasForTesting(); err != nil {
t.Fatal(err) 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 { if got, want := img3.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
@ -375,7 +375,7 @@ func TestReplacePixelsAfterDrawTriangles(t *testing.T) {
Width: w, Width: w,
Height: h, 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) dst.ReplacePixels(pix)
pix, err := dst.Pixels(0, 0, w, h) pix, err := dst.Pixels(0, 0, w, h)
@ -423,7 +423,7 @@ func TestSmallImages(t *testing.T) {
Width: w, Width: w,
Height: h, 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) pix, err := dst.Pixels(0, 0, w, h)
if err != nil { if err != nil {
@ -471,7 +471,7 @@ func TestLongImages(t *testing.T) {
Width: dstW, Width: dstW,
Height: dstH, 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) pix, err := dst.Pixels(0, 0, dstW, dstH)
if err != nil { if err != nil {
@ -559,7 +559,7 @@ func TestDisposedAndReputOnAtlas(t *testing.T) {
Width: size, Width: size,
Height: 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 { if got, want := src.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
@ -569,7 +569,7 @@ func TestDisposedAndReputOnAtlas(t *testing.T) {
if err := PutImagesOnAtlasForTesting(); err != nil { if err := PutImagesOnAtlasForTesting(); err != nil {
t.Fatal(err) 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 { if got, want := src.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", 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. // 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 { if got, want := src2.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
@ -630,7 +630,7 @@ func TestImageIsNotReputOnAtlasWithoutUsingAsSource(t *testing.T) {
if err := PutImagesOnAtlasForTesting(); err != nil { if err := PutImagesOnAtlasForTesting(); err != nil {
t.Fatal(err) 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 { if got, want := src2.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", 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. // DrawTriangles draws the src image with the given vertices.
// //
// Copying vertices and indices is the caller's responsibility. // 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 { for _, src := range srcs {
if i == src { if i == src {
panic("buffered: Image.DrawTriangles: source images must be different from the receiver") 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 maybeCanAddDelayedCommand() {
if tryAddDelayedCommand(func() error { if tryAddDelayedCommand(func() error {
// Arguments are not copied. Copying is the caller's responsibility. // 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 nil
}) { }) {
return return
@ -238,7 +238,7 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []f
} }
i.resolvePendingPixels(false) 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() i.invalidatePendingPixels()
} }

View File

@ -57,7 +57,7 @@ type Graphics interface {
// //
// * float32 // * float32
// * []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. // 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 NumIndices() int
AddNumVertices(n int) AddNumVertices(n int)
AddNumIndices(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 { type size struct {
@ -133,7 +133,7 @@ func (q *commandQueue) appendIndices(indices []uint16, offset uint16) {
} }
// EnqueueDrawTrianglesCommand enqueues a drawing-image command. // 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 { 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)) 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. // TODO: If dst is the screen, reorder the command to be the last.
if !split && 0 < len(q.commands) { if !split && 0 < len(q.commands) {
// TODO: Pass offsets and uniforms when merging considers the shader. // 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.AddNumVertices(len(vertices))
last.AddNumIndices(len(indices)) last.AddNumIndices(len(indices))
return return
@ -188,6 +188,7 @@ func (q *commandQueue) EnqueueDrawTrianglesCommand(dst *Image, srcs [graphics.Sh
srcRegion: srcRegion, srcRegion: srcRegion,
shader: shader, shader: shader,
uniforms: uniforms, uniforms: uniforms,
evenOdd: evenOdd,
} }
q.commands = append(q.commands, c) q.commands = append(q.commands, c)
} }
@ -319,6 +320,7 @@ type drawTrianglesCommand struct {
srcRegion driver.Region srcRegion driver.Region
shader *Shader shader *Shader
uniforms []interface{} uniforms []interface{}
evenOdd bool
} }
func (c *drawTrianglesCommand) String() string { func (c *drawTrianglesCommand) String() string {
@ -428,7 +430,7 @@ func (c *drawTrianglesCommand) Exec(indexOffset int) error {
imgs[0] = c.srcs[0].image.ID() 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 { 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 // CanMergeWithDrawTrianglesCommand returns a boolean value indicating whether the other drawTrianglesCommand can be merged
// with the drawTrianglesCommand c. // 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. // If a shader is used, commands are not merged.
// //
// TODO: Merge shader commands considering uniform variables. // TODO: Merge shader commands considering uniform variables.
@ -480,6 +482,9 @@ func (c *drawTrianglesCommand) CanMergeWithDrawTrianglesCommand(dst *Image, srcs
if c.srcRegion != srcRegion { if c.srcRegion != srcRegion {
return false return false
} }
if c.evenOdd != evenOdd {
return false
}
return true return true
} }
@ -513,7 +518,7 @@ func (c *replacePixelsCommand) AddNumVertices(n int) {
func (c *replacePixelsCommand) AddNumIndices(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 return false
} }
@ -550,7 +555,7 @@ func (c *pixelsCommand) AddNumVertices(n int) {
func (c *pixelsCommand) AddNumIndices(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 return false
} }
@ -583,7 +588,7 @@ func (c *disposeImageCommand) AddNumVertices(n int) {
func (c *disposeImageCommand) AddNumIndices(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 return false
} }
@ -616,7 +621,7 @@ func (c *disposeShaderCommand) AddNumVertices(n int) {
func (c *disposeShaderCommand) AddNumIndices(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 return false
} }
@ -655,7 +660,7 @@ func (c *newImageCommand) AddNumVertices(n int) {
func (c *newImageCommand) AddNumIndices(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 return false
} }
@ -691,7 +696,7 @@ func (c *newScreenFramebufferImageCommand) AddNumVertices(n int) {
func (c *newScreenFramebufferImageCommand) AddNumIndices(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 return false
} }
@ -726,7 +731,7 @@ func (c *newShaderCommand) AddNumVertices(n int) {
func (c *newShaderCommand) AddNumIndices(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 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 // 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. // 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 { if shader == nil {
// Fast path for rendering without a shader (#1355). // Fast path for rendering without a shader (#1355).
img := srcs[0] img := srcs[0]
@ -160,7 +160,7 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, offsets [gra
} }
i.resolveBufferedReplacePixels() 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. // Pixels returns the image's pixels.

View File

@ -50,7 +50,7 @@ func TestClear(t *testing.T) {
Width: w, Width: w,
Height: h, 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() pix, err := dst.Pixels()
if err != nil { if err != nil {
@ -81,8 +81,8 @@ func TestReplacePixelsPartAfterDrawTriangles(t *testing.T) {
Width: w, Width: w,
Height: h, 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)
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) dst.ReplacePixels(make([]byte, 4), 0, 0, 1, 1)
// TODO: Check the result. // TODO: Check the result.
@ -100,11 +100,11 @@ func TestShader(t *testing.T) {
Width: w, Width: w,
Height: h, 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) ir := etesting.ShaderProgramFill(0xff, 0, 0, 0xff)
s := NewShader(&ir) 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() pix, err := dst.Pixels()
if err != nil { if err != nil {

View File

@ -296,11 +296,12 @@ FragmentShaderFunc(0, FILTER_SCREEN, ADDRESS_UNSAFE)
` `
type rpsKey struct { type rpsKey struct {
useColorM bool useColorM bool
filter driver.Filter filter driver.Filter
address driver.Address address driver.Address
compositeMode driver.CompositeMode compositeMode driver.CompositeMode
screen bool colorWriteMask bool
screen bool
} }
type Graphics struct { type Graphics struct {
@ -313,7 +314,9 @@ type Graphics struct {
rce mtl.RenderCommandEncoder rce mtl.RenderCommandEncoder
screenDrawable ca.MetalDrawable screenDrawable ca.MetalDrawable
lastDstTexture mtl.Texture lastDstTexture mtl.Texture
lastStencilUse bool
vb mtl.Buffer vb mtl.Buffer
ib mtl.Buffer ib mtl.Buffer
@ -334,6 +337,18 @@ type Graphics struct {
pool unsafe.Pointer pool unsafe.Pointer
} }
type stencilMode int
const (
prepareStencil stencilMode = iota
drawWithStencil
noStencil
)
func (s stencilMode) useStencil() bool {
return s != noStencil
}
var theGraphics Graphics var theGraphics Graphics
func Get() *Graphics { func Get() *Graphics {
@ -539,8 +554,9 @@ func (g *Graphics) Reset() error {
return err return err
} }
rpld := mtl.RenderPipelineDescriptor{ rpld := mtl.RenderPipelineDescriptor{
VertexFunction: vs, VertexFunction: vs,
FragmentFunction: fs, FragmentFunction: fs,
StencilAttachmentPixelFormat: mtl.PixelFormatStencil8,
} }
rpld.ColorAttachments[0].PixelFormat = g.view.colorPixelFormat() rpld.ColorAttachments[0].PixelFormat = g.view.colorPixelFormat()
rpld.ColorAttachments[0].BlendingEnabled = true rpld.ColorAttachments[0].BlendingEnabled = true
@ -548,6 +564,7 @@ func (g *Graphics) Reset() error {
rpld.ColorAttachments[0].DestinationRGBBlendFactor = mtl.BlendFactorZero rpld.ColorAttachments[0].DestinationRGBBlendFactor = mtl.BlendFactorZero
rpld.ColorAttachments[0].SourceAlphaBlendFactor = mtl.BlendFactorOne rpld.ColorAttachments[0].SourceAlphaBlendFactor = mtl.BlendFactorOne
rpld.ColorAttachments[0].SourceRGBBlendFactor = mtl.BlendFactorOne rpld.ColorAttachments[0].SourceRGBBlendFactor = mtl.BlendFactorOne
rpld.ColorAttachments[0].WriteMask = mtl.ColorWriteMaskAll
rps, err := g.view.getMTLDevice().MakeRenderPipelineState(rpld) rps, err := g.view.getMTLDevice().MakeRenderPipelineState(rpld)
if err != nil { if err != nil {
return err return err
@ -566,42 +583,51 @@ func (g *Graphics) Reset() error {
driver.FilterLinear, driver.FilterLinear,
} { } {
for c := driver.CompositeModeSourceOver; c <= driver.CompositeModeMax; c++ { for c := driver.CompositeModeSourceOver; c <= driver.CompositeModeMax; c++ {
cmi := 0 for _, cwm := range []bool{false, true} {
if cm { cmi := 0
cmi = 1 if cm {
} cmi = 1
fs, err := lib.MakeFunction(fmt.Sprintf("FragmentShader_%d_%d_%d", cmi, f, a)) }
if err != nil { fs, err := lib.MakeFunction(fmt.Sprintf("FragmentShader_%d_%d_%d", cmi, f, a))
return err if err != nil {
} return err
rpld := mtl.RenderPipelineDescriptor{ }
VertexFunction: vs, rpld := mtl.RenderPipelineDescriptor{
FragmentFunction: fs, VertexFunction: vs,
} FragmentFunction: fs,
StencilAttachmentPixelFormat: mtl.PixelFormatStencil8,
}
pix := mtl.PixelFormatRGBA8UNorm pix := mtl.PixelFormatRGBA8UNorm
if screen { if screen {
pix = g.view.colorPixelFormat() pix = g.view.colorPixelFormat()
} }
rpld.ColorAttachments[0].PixelFormat = pix rpld.ColorAttachments[0].PixelFormat = pix
rpld.ColorAttachments[0].BlendingEnabled = true rpld.ColorAttachments[0].BlendingEnabled = true
src, dst := c.Operations() src, dst := c.Operations()
rpld.ColorAttachments[0].DestinationAlphaBlendFactor = operationToBlendFactor(dst) rpld.ColorAttachments[0].DestinationAlphaBlendFactor = operationToBlendFactor(dst)
rpld.ColorAttachments[0].DestinationRGBBlendFactor = operationToBlendFactor(dst) rpld.ColorAttachments[0].DestinationRGBBlendFactor = operationToBlendFactor(dst)
rpld.ColorAttachments[0].SourceAlphaBlendFactor = operationToBlendFactor(src) rpld.ColorAttachments[0].SourceAlphaBlendFactor = operationToBlendFactor(src)
rpld.ColorAttachments[0].SourceRGBBlendFactor = operationToBlendFactor(src) rpld.ColorAttachments[0].SourceRGBBlendFactor = operationToBlendFactor(src)
rps, err := g.view.getMTLDevice().MakeRenderPipelineState(rpld) if cwm {
if err != nil { rpld.ColorAttachments[0].WriteMask = mtl.ColorWriteMaskAll
return err } 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.EndEncoding()
g.rce = mtl.RenderCommandEncoder{} g.rce = mtl.RenderCommandEncoder{}
g.lastDstTexture = mtl.Texture{} 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 { 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() { if g.lastDstTexture != dst.mtlTexture() || g.lastStencilUse != stencilMode.useStencil() {
g.flushRenderCommandEncoderIfNeeded() g.flushRenderCommandEncoderIfNeeded()
} }
if g.rce == (mtl.RenderCommandEncoder{}) { if g.rce == (mtl.RenderCommandEncoder{}) {
rpd := mtl.RenderPassDescriptor{} rpd := mtl.RenderPassDescriptor{}
// Even though the destination pixels are not used, mtl.LoadActionDontCare might cause glitches // 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].Texture = t
rpd.ColorAttachments[0].ClearColor = mtl.ClearColor{} 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{}) { if g.cb == (mtl.CommandBuffer{}) {
g.cb = g.cq.MakeCommandBuffer() g.cb = g.cq.MakeCommandBuffer()
} }
g.rce = g.cb.MakeRenderCommandEncoder(rpd) g.rce = g.cb.MakeRenderCommandEncoder(rpd)
} }
g.lastStencilUse = stencilMode.useStencil()
g.rce.SetRenderPipelineState(rps) 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) 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) g.rce.DrawIndexedPrimitives(mtl.PrimitiveTypeTriangle, indexLen, mtl.IndexTypeUInt16, g.ib, indexOffset*2)
return nil 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] dst := g.images[dstID]
if dst.screen { if dst.screen {
@ -703,18 +779,28 @@ func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderIm
srcs[i] = g.images[srcID] srcs[i] = g.images[srcID]
} }
var rps mtl.RenderPipelineState var rpss [2]mtl.RenderPipelineState
var uniformVars []interface{} var uniformVars []interface{}
if shaderID == driver.InvalidShaderID { if shaderID == driver.InvalidShaderID {
if dst.screen && filter == driver.FilterScreen { if dst.screen && filter == driver.FilterScreen {
rps = g.screenRPS rpss[1] = g.screenRPS
} else { } else {
rps = g.rpss[rpsKey{ useColorM := colorM != nil
screen: dst.screen, rpss[0] = g.rpss[rpsKey{
useColorM: colorM != nil, screen: dst.screen,
filter: filter, useColorM: useColorM,
address: address, filter: filter,
compositeMode: mode, 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 { } else {
var err error 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 { if err != nil {
return err 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 { if evenOdd {
return err 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 return nil
@ -889,6 +988,7 @@ type Image struct {
height int height int
screen bool screen bool
texture mtl.Texture texture mtl.Texture
stencil mtl.Texture
} }
func (i *Image) ID() driver.ImageID { func (i *Image) ID() driver.ImageID {
@ -903,6 +1003,10 @@ func (i *Image) internalSize() (int, int) {
} }
func (i *Image) Dispose() { func (i *Image) Dispose() {
if i.stencil != (mtl.Texture{}) {
i.stencil.Release()
i.stencil = mtl.Texture{}
}
if i.texture != (mtl.Texture{}) { if i.texture != (mtl.Texture{}) {
i.texture.Release() i.texture.Release()
i.texture = mtl.Texture{} i.texture = mtl.Texture{}
@ -1022,3 +1126,19 @@ func (i *Image) mtlTexture() mtl.Texture {
} }
return i.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" "github.com/hajimehoshi/ebiten/v2/internal/shaderir/metal"
) )
type shaderRpsKey struct {
compositeMode driver.CompositeMode
colorWriteMask bool
}
type Shader struct { type Shader struct {
id driver.ShaderID id driver.ShaderID
ir *shaderir.Program ir *shaderir.Program
fs mtl.Function fs mtl.Function
vs 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) { func newShader(device mtl.Device, id driver.ShaderID, program *shaderir.Program) (*Shader, error) {
s := &Shader{ s := &Shader{
id: id, id: id,
ir: program, ir: program,
rpss: map[driver.CompositeMode]mtl.RenderPipelineState{}, rpss: map[shaderRpsKey]mtl.RenderPipelineState{},
} }
if err := s.init(device); err != nil { if err := s.init(device); err != nil {
return nil, err return nil, err
@ -83,31 +88,43 @@ func (s *Shader) init(device mtl.Device) error {
return nil return nil
} }
func (s *Shader) RenderPipelineState(device mtl.Device, c driver.CompositeMode) (mtl.RenderPipelineState, error) { func (s *Shader) RenderPipelineState(device mtl.Device, compositeMode driver.CompositeMode, colorWriteMask bool) (mtl.RenderPipelineState, error) {
if rps, ok := s.rpss[c]; ok { if rps, ok := s.rpss[shaderRpsKey{
compositeMode: compositeMode,
colorWriteMask: colorWriteMask,
}]; ok {
return rps, nil return rps, nil
} }
rpld := mtl.RenderPipelineDescriptor{ rpld := mtl.RenderPipelineDescriptor{
VertexFunction: s.vs, VertexFunction: s.vs,
FragmentFunction: s.fs, FragmentFunction: s.fs,
StencilAttachmentPixelFormat: mtl.PixelFormatStencil8,
} }
// TODO: For the precise pixel format, whether the render target is the screen or not must be considered. // 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].PixelFormat = mtl.PixelFormatRGBA8UNorm
rpld.ColorAttachments[0].BlendingEnabled = true rpld.ColorAttachments[0].BlendingEnabled = true
src, dst := c.Operations() src, dst := compositeMode.Operations()
rpld.ColorAttachments[0].DestinationAlphaBlendFactor = operationToBlendFactor(dst) rpld.ColorAttachments[0].DestinationAlphaBlendFactor = operationToBlendFactor(dst)
rpld.ColorAttachments[0].DestinationRGBBlendFactor = operationToBlendFactor(dst) rpld.ColorAttachments[0].DestinationRGBBlendFactor = operationToBlendFactor(dst)
rpld.ColorAttachments[0].SourceAlphaBlendFactor = operationToBlendFactor(src) rpld.ColorAttachments[0].SourceAlphaBlendFactor = operationToBlendFactor(src)
rpld.ColorAttachments[0].SourceRGBBlendFactor = 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) rps, err := device.MakeRenderPipelineState(rpld)
if err != nil { if err != nil {
return mtl.RenderPipelineState{}, err return mtl.RenderPipelineState{}, err
} }
s.rpss[c] = rps s.rpss[shaderRpsKey{
compositeMode: compositeMode,
colorWriteMask: colorWriteMask,
}] = rps
return rps, nil 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). screenFramebuffer framebufferNative // This might not be the default frame buffer '0' (e.g. iOS).
lastFramebuffer framebufferNative lastFramebuffer framebufferNative
lastTexture textureNative lastTexture textureNative
lastRenderbuffer renderbufferNative
lastViewportWidth int lastViewportWidth int
lastViewportHeight int lastViewportHeight int
lastCompositeMode driver.CompositeMode lastCompositeMode driver.CompositeMode
@ -68,6 +69,14 @@ func (c *context) bindTexture(t textureNative) {
c.lastTexture = t 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) { func (c *context) bindFramebuffer(f framebufferNative) {
if c.lastFramebuffer.equal(f) { if c.lastFramebuffer.equal(f) {
return return

View File

@ -29,17 +29,22 @@ import (
) )
type ( type (
textureNative uint32 textureNative uint32
framebufferNative uint32 renderbufferNative uint32
shader uint32 framebufferNative uint32
program uint32 shader uint32
buffer uint32 program uint32
buffer uint32
) )
func (t textureNative) equal(rhs textureNative) bool { func (t textureNative) equal(rhs textureNative) bool {
return t == rhs return t == rhs
} }
func (r renderbufferNative) equal(rhs renderbufferNative) bool {
return r == rhs
}
func (f framebufferNative) equal(rhs framebufferNative) bool { func (f framebufferNative) equal(rhs framebufferNative) bool {
return f == rhs return f == rhs
} }
@ -198,6 +203,38 @@ func (c *context) isTexture(t textureNative) bool {
panic("opengl: isTexture is not implemented") 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) { func (c *context) newFramebuffer(texture textureNative) (framebufferNative, error) {
var f uint32 var f uint32
gl.GenFramebuffersEXT(1, &f) gl.GenFramebuffersEXT(1, &f)
@ -220,6 +257,17 @@ func (c *context) newFramebuffer(texture textureNative) (framebufferNative, erro
return framebufferNative(f), nil 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) { func (c *context) setViewportImpl(width, height int) {
gl.Viewport(0, 0, int32(width), int32(height)) 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) gl.BindBuffer(gl.PIXEL_UNPACK_BUFFER, 0)
return pixels 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 ( type (
textureNative js.Value textureNative js.Value
framebufferNative js.Value renderbufferNative js.Value
shader js.Value framebufferNative js.Value
buffer js.Value shader js.Value
uniformLocation js.Value buffer js.Value
uniformLocation js.Value
attribLocation int attribLocation int
programID int programID int
@ -44,6 +45,10 @@ func (t textureNative) equal(rhs textureNative) bool {
return js.Value(t).Equal(js.Value(rhs)) 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 { func (f framebufferNative) equal(rhs framebufferNative) bool {
return js.Value(f).Equal(js.Value(rhs)) return js.Value(f).Equal(js.Value(rhs))
} }
@ -100,6 +105,7 @@ func (c *context) initGL() {
attr := js.Global().Get("Object").New() attr := js.Global().Get("Object").New()
attr.Set("alpha", true) attr.Set("alpha", true)
attr.Set("premultipliedAlpha", true) attr.Set("premultipliedAlpha", true)
attr.Set("stencil", true)
if isWebGL2Available { if isWebGL2Available {
gl = canvas.Call("getContext", "webgl2", attr) gl = canvas.Call("getContext", "webgl2", attr)
@ -165,7 +171,7 @@ func (c *context) newTexture(width, height int) (textureNative, error) {
gl := c.gl gl := c.gl
t := gl.createTexture.Invoke() t := gl.createTexture.Invoke()
if !t.Truthy() { 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)) c.bindTexture(textureNative(t))
@ -240,6 +246,37 @@ func (c *context) isTexture(t textureNative) bool {
panic("opengl: isTexture is not implemented") 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) { func (c *context) newFramebuffer(t textureNative) (framebufferNative, error) {
gl := c.gl gl := c.gl
f := gl.createFramebuffer.Invoke() f := gl.createFramebuffer.Invoke()
@ -253,6 +290,18 @@ func (c *context) newFramebuffer(t textureNative) (framebufferNative, error) {
return framebufferNative(f), nil 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) { func (c *context) setViewportImpl(width, height int) {
gl := c.gl gl := c.gl
gl.viewport.Invoke(0, 0, width, height) 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) gl.bindBuffer.Invoke(gles.PIXEL_UNPACK_BUFFER, nil)
return jsutil.Uint8ArrayToSlice(arr, l) 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 ( type (
textureNative uint32 textureNative uint32
framebufferNative uint32 renderbufferNative uint32
shader uint32 framebufferNative uint32
program uint32 shader uint32
buffer uint32 program uint32
buffer uint32
) )
func (t textureNative) equal(rhs textureNative) bool { func (t textureNative) equal(rhs textureNative) bool {
return t == rhs return t == rhs
} }
func (r renderbufferNative) equal(rhs renderbufferNative) bool {
return r == rhs
}
func (f framebufferNative) equal(rhs framebufferNative) bool { func (f framebufferNative) equal(rhs framebufferNative) bool {
return f == rhs return f == rhs
} }
@ -185,6 +190,34 @@ func (c *context) isTexture(t textureNative) bool {
return c.ctx.IsTexture(uint32(t)) 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) { func (c *context) newFramebuffer(texture textureNative) (framebufferNative, error) {
f := c.ctx.GenFramebuffers(1)[0] f := c.ctx.GenFramebuffers(1)[0]
if f <= 0 { if f <= 0 {
@ -206,6 +239,17 @@ func (c *context) newFramebuffer(texture textureNative) (framebufferNative, erro
return framebufferNative(f), nil 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) { func (c *context) setViewportImpl(width, height int) {
c.ctx.Viewport(0, 0, int32(width), int32(height)) 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. // As PBO is not used in mobiles, leave this unimplemented so far.
panic("opengl: getBufferSubData is not implemented for mobiles") 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) 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] destination := g.images[dstID]
if !destination.pbo.equal(*new(buffer)) { if !destination.pbo.equal(*new(buffer)) {
@ -308,7 +308,20 @@ func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderIm
return err 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 g.context.drawElements(indexLen, indexOffset*2) // 2 is uint16 size in bytes
if evenOdd {
g.context.disableStencilTest()
}
return nil return nil
} }

View File

@ -23,6 +23,7 @@ type Image struct {
id driver.ImageID id driver.ImageID
graphics *Graphics graphics *Graphics
texture textureNative texture textureNative
stencil renderbufferNative
framebuffer *framebuffer framebuffer *framebuffer
pbo buffer pbo buffer
width int width int
@ -48,6 +49,9 @@ func (i *Image) Dispose() {
if !i.texture.equal(*new(textureNative)) { if !i.texture.equal(*new(textureNative)) {
i.graphics.context.deleteTexture(i.texture) i.graphics.context.deleteTexture(i.texture)
} }
if !i.stencil.equal(*new(renderbufferNative)) {
i.graphics.context.deleteRenderbuffer(i.stencil)
}
i.graphics.removeImage(i) i.graphics.removeImage(i)
} }
@ -105,6 +109,27 @@ func (i *Image) ensureFramebuffer() error {
return nil 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) { func (i *Image) ReplacePixels(args []*driver.ReplacePixelsArgs) {
if i.screen { if i.screen {
panic("opengl: ReplacePixels cannot be called on the screen, that doesn't have a texture") 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) 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 { if len(indices) == 0 {
return return
} }
@ -164,7 +164,7 @@ func (m *Mipmap) DrawTriangles(srcs [graphics.ShaderImageNum]*Mipmap, vertices [
imgs[i] = src.orig 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() m.disposeMipmaps()
} }
@ -226,7 +226,7 @@ func (m *Mipmap) level(level int) *buffered.Image {
Width: float32(w2), Width: float32(w2),
Height: float32(h2), 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 m.imgs[level] = s
return m.imgs[level] return m.imgs[level]

View File

@ -76,6 +76,7 @@ type drawTrianglesHistoryItem struct {
srcRegion driver.Region srcRegion driver.Region
shader *Shader shader *Shader
uniforms []interface{} uniforms []interface{}
evenOdd bool
} }
// Image represents an image that can be restored when GL context is lost. // 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), Width: float32(sw),
Height: float32(sh), 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 // Overwrite the history as if the image newImg is created only by ReplacePixels. Now drawTrianglesHistory
// and basePixels cannot be mixed. // and basePixels cannot be mixed.
@ -247,7 +248,7 @@ func clearImage(i *graphicscommand.Image) {
Width: float32(dw), Width: float32(dw),
Height: float32(dh), 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. // 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 // 5: Color G
// 6: Color B // 6: Color B
// 7: Color Y // 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 { if i.priority {
panic("restorable: DrawTriangles cannot be called on a priority image") 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 { if srcstale || i.screen || !NeedsRestoring() || i.volatile {
i.makeStale() i.makeStale()
} else { } 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 var s *graphicscommand.Shader
@ -391,11 +392,11 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, offsets [gra
} }
s = shader.shader 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. // 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 { if i.stale || i.volatile || i.screen {
return return
} }
@ -427,6 +428,7 @@ func (i *Image) appendDrawTrianglesHistory(srcs [graphics.ShaderImageNum]*Image,
srcRegion: srcRegion, srcRegion: srcRegion,
shader: shader, shader: shader,
uniforms: uniforms, uniforms: uniforms,
evenOdd: evenOdd,
} }
i.drawTrianglesHistory = append(i.drawTrianglesHistory, item) i.drawTrianglesHistory = append(i.drawTrianglesHistory, item)
} }
@ -605,7 +607,7 @@ func (i *Image) restore() error {
} }
imgs[i] = img.image 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 { if len(i.drawTrianglesHistory) > 0 {

View File

@ -137,7 +137,7 @@ func TestRestoreChain(t *testing.T) {
Width: 1, Width: 1,
Height: 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 { if err := ResolveStaleImages(); err != nil {
t.Fatal(err) t.Fatal(err)
@ -185,10 +185,10 @@ func TestRestoreChain2(t *testing.T) {
Width: w, Width: w,
Height: h, 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[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) 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++ { 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 { if err := ResolveStaleImages(); err != nil {
@ -234,10 +234,10 @@ func TestRestoreOverrideSource(t *testing.T) {
Width: w, Width: w,
Height: h, 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) 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) 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) 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 { if err := ResolveStaleImages(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -323,23 +323,23 @@ func TestRestoreComplexGraph(t *testing.T) {
Height: h, Height: h,
} }
var offsets [graphics.ShaderImageNum - 1][2]float32 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) 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) 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) 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) 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) 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) 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) 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) 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 { if err := ResolveStaleImages(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -437,8 +437,8 @@ func TestRestoreRecursive(t *testing.T) {
Width: w, Width: w,
Height: h, 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) 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) 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 { if err := ResolveStaleImages(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -538,7 +538,7 @@ func TestDrawTrianglesAndReplacePixels(t *testing.T) {
Width: 2, Width: 2,
Height: 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)
img1.ReplacePixels([]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 0, 0, 2, 1) img1.ReplacePixels([]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 0, 0, 2, 1)
if err := ResolveStaleImages(); err != nil { if err := ResolveStaleImages(); err != nil {
@ -581,8 +581,8 @@ func TestDispose(t *testing.T) {
Width: 1, Width: 1,
Height: 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) 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) 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() img1.Dispose()
if err := ResolveStaleImages(); err != nil { if err := ResolveStaleImages(); err != nil {
@ -696,7 +696,7 @@ func TestReplacePixelsOnly(t *testing.T) {
Width: 1, Width: 1,
Height: 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) img0.ReplacePixels([]byte{5, 6, 7, 8}, 0, 0, 1, 1)
// BasePixelsForTesting is available without GPU accessing. // BasePixelsForTesting is available without GPU accessing.
@ -756,7 +756,7 @@ func TestReadPixelsFromVolatileImage(t *testing.T) {
Width: w, Width: w,
Height: h, 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 // Read the pixels. If the implementation is correct, dst tries to read its pixels from GPU due to being
// stale. // stale.
@ -783,7 +783,7 @@ func TestAllowReplacePixelsAfterDrawTriangles(t *testing.T) {
Width: w, Width: w,
Height: h, 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) dst.ReplacePixels(make([]byte, 4*w*h), 0, 0, w, h)
// ReplacePixels for a whole image doesn't panic. // ReplacePixels for a whole image doesn't panic.
} }
@ -807,7 +807,7 @@ func TestDisallowReplacePixelsForPartAfterDrawTriangles(t *testing.T) {
Width: w, Width: w,
Height: h, 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) dst.ReplacePixels(make([]byte, 4), 0, 0, 1, 1)
} }
@ -884,7 +884,7 @@ func TestMutateSlices(t *testing.T) {
Width: w, Width: w,
Height: h, 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 { for i := range vs {
vs[i] = 0 vs[i] = 0
} }

View File

@ -48,7 +48,7 @@ func clearImage(img *Image, w, h int) {
Width: float32(w), Width: float32(w),
Height: float32(h), 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) { func TestShader(t *testing.T) {
@ -63,7 +63,7 @@ func TestShader(t *testing.T) {
Width: 1, Width: 1,
Height: 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 { if err := ResolveStaleImages(); err != nil {
t.Fatal(err) t.Fatal(err)
@ -99,7 +99,7 @@ func TestShaderChain(t *testing.T) {
Width: 1, Width: 1,
Height: 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 { if err := ResolveStaleImages(); err != nil {
@ -138,7 +138,7 @@ func TestShaderMultipleSources(t *testing.T) {
Width: 1, Width: 1,
Height: 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. // Clear one of the sources after DrawTriangles. dst should not be affected.
clearImage(srcs[0], 1, 1) clearImage(srcs[0], 1, 1)
@ -180,7 +180,7 @@ func TestShaderMultipleSourcesOnOneTexture(t *testing.T) {
Width: 1, Width: 1,
Height: 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. // Clear one of the sources after DrawTriangles. dst should not be affected.
clearImage(srcs[0], 3, 1) clearImage(srcs[0], 3, 1)
@ -211,7 +211,7 @@ func TestShaderDispose(t *testing.T) {
Width: 1, Width: 1,
Height: 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 // Dispose the shader. This should invalidates all the images using this shader i.e., all the images become
// stale. // 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 package vector
import ( import (
"image"
"image/color"
"math" "math"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/vector/internal/triangulate"
) )
var ( type point struct {
emptyImage = ebiten.NewImage(3, 3) x float32
emptySubImage = emptyImage.SubImage(image.Rect(1, 1, 2, 2)).(*ebiten.Image) y float32
)
func init() {
emptyImage.Fill(color.White)
} }
// Path represents a collection of path segments. // Path represents a collection of path segments.
type Path struct { type Path struct {
segs [][]triangulate.Point segs [][]point
cur triangulate.Point cur point
} }
// MoveTo skips the current position of the path to the given position (x, y) without adding any strokes. // 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) { func (p *Path) MoveTo(x, y float32) {
p.cur = triangulate.Point{X: x, Y: y} p.cur = point{x: x, y: y}
p.segs = append(p.segs, []triangulate.Point{p.cur}) 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). // 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). // LineTo updates the current position to (x, y).
func (p *Path) LineTo(x, y float32) { func (p *Path) LineTo(x, y float32) {
if len(p.segs) == 0 { 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.segs[len(p.segs)-1] = append(p.segs[len(p.segs)-1], point{x: x, y: y})
p.cur = triangulate.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). // 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. // QuadTo adds a quadratic Bézier curve to the path.
func (p *Path) QuadTo(cpx, cpy, x, y float32) { func (p *Path) QuadTo(cpx, cpy, x, y float32) {
// TODO: Split more appropriate number of segments.
c := p.cur 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) { 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 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 yf := (1-t)*(1-t)*c.y + 2*t*(1-t)*cpy + t*t*y
p.LineTo(xf, yf) p.LineTo(xf, yf)
} }
} }
// CubicTo adds a cubic Bézier curve to the path. // CubicTo adds a cubic Bézier curve to the path.
func (p *Path) CubicTo(cp0x, cp0y, cp1x, cp1y, x, y float32) { func (p *Path) CubicTo(cp0x, cp0y, cp1x, cp1y, x, y float32) {
// TODO: Split more appropriate number of segments.
c := p.cur 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) { 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 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 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) p.LineTo(xf, yf)
} }
} }
// FillOptions represents options to fill a path. // AppendVerticesAndIndices appends vertices and indices for this path and returns them.
type FillOptions struct { // AppendVerticesAndIndices works in a similar way to the built-in append function.
// Color is a color to fill with. // If the arguments are nils, AppendVerticesAndIndices returns new slices.
Color color.Color //
} // The returned vertice's SrcX and SrcY are 0, and ColorR, ColorG, ColorB, and ColorA are 1.
//
// Fill fills the region of the path with the given options op. // The returned values are intended to be passed to DrawTriangles or DrawTrianglesShader with EvenOdd option
func (p *Path) Fill(dst *ebiten.Image, op *FillOptions) { // in order to render a complex polygon like a concave polygon, a polygon with holes, or a self-intersecting polygon.
var vertices []ebiten.Vertex func (p *Path) AppendVerticesAndIndices(vertices []ebiten.Vertex, indices []uint16) ([]ebiten.Vertex, []uint16) {
var indices []uint16 // TODO: Add tests.
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
var base uint16 var base uint16
for _, seg := range p.segs { for _, seg := range p.segs {
for _, pt := range seg { if len(seg) < 3 {
continue
}
for i, pt := range seg {
vertices = append(vertices, ebiten.Vertex{ vertices = append(vertices, ebiten.Vertex{
DstX: pt.X, DstX: pt.x,
DstY: pt.Y, DstY: pt.y,
SrcX: 0, SrcX: 0,
SrcY: 0, SrcY: 0,
ColorR: rf, ColorR: 1,
ColorG: gf, ColorG: 1,
ColorB: bf, ColorB: 1,
ColorA: af, ColorA: 1,
}) })
} if i < 2 {
for _, idx := range triangulate.Triangulate(seg) { continue
indices = append(indices, idx+base) }
indices = append(indices, base, base+uint16(i-1), base+uint16(i))
} }
base += uint16(len(seg)) base += uint16(len(seg))
} }
dst.DrawTriangles(vertices, indices, emptySubImage, nil) return vertices, indices
} }