mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-11-10 04:57:26 +01:00
Compare commits
23 Commits
59e45a523d
...
971c30f170
Author | SHA1 | Date | |
---|---|---|---|
|
971c30f170 | ||
|
6a51e5b003 | ||
|
af9bd6a282 | ||
|
73565034a9 | ||
|
bbe3cba110 | ||
|
f32648b144 | ||
|
aa8e112414 | ||
|
26f0479f16 | ||
|
9ef9ea0469 | ||
|
b36160d9e7 | ||
|
20f8df7fc1 | ||
|
167c3435f7 | ||
|
d84b030300 | ||
|
4824dc0360 | ||
|
30a2817ab5 | ||
|
29ef2c84ef | ||
|
1dd96726c4 | ||
|
30157b5dea | ||
|
b20692f523 | ||
|
2eebe55b90 | ||
|
ec06c68fa3 | ||
|
4601cffaba | ||
|
5e8d969034 |
41
image.go
41
image.go
@ -26,6 +26,7 @@ import (
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicscommand"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/restorable"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/ui"
|
||||
)
|
||||
@ -266,7 +267,32 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) {
|
||||
})
|
||||
}
|
||||
|
||||
i.image.DrawTriangles(srcs, vs, is, blend, i.adjustedBounds(), [graphics.ShaderSrcImageCount]image.Rectangle{img.adjustedBounds()}, shader.shader, i.tmpUniforms, graphicsdriver.FillRuleFillAll, canSkipMipmap(geoM, filter), false)
|
||||
dr := i.adjustedBounds()
|
||||
hint := restorable.HintNone
|
||||
if overwritesDstRegion(options.Blend, dr, geoM, sx0, sy0, sx1, sy1) {
|
||||
hint = restorable.HintOverwriteDstRegion
|
||||
}
|
||||
|
||||
i.image.DrawTriangles(srcs, vs, is, blend, dr, [graphics.ShaderSrcImageCount]image.Rectangle{img.adjustedBounds()}, shader.shader, i.tmpUniforms, graphicsdriver.FillRuleFillAll, canSkipMipmap(geoM, filter), false, hint)
|
||||
}
|
||||
|
||||
// overwritesDstRegion reports whether the given parameters overwrite the destination region completely.
|
||||
func overwritesDstRegion(blend Blend, dstRegion image.Rectangle, geoM GeoM, sx0, sy0, sx1, sy1 int) bool {
|
||||
// TODO: More precisely, BlendFactorDestinationRGB, BlendFactorDestinationAlpha, and operations should be checked.
|
||||
if blend != BlendCopy && blend != BlendClear {
|
||||
return false
|
||||
}
|
||||
// Check the result vertices is not a rotated rectangle.
|
||||
if geoM.b != 0 || geoM.c != 0 {
|
||||
return false
|
||||
}
|
||||
// Check the result vertices completely covers dstRegion.
|
||||
x0, y0 := geoM.Apply(float64(sx0), float64(sy0))
|
||||
x1, y1 := geoM.Apply(float64(sx1), float64(sy1))
|
||||
if float64(dstRegion.Min.X) < x0 || float64(dstRegion.Min.Y) < y0 || float64(dstRegion.Max.X) > x1 || float64(dstRegion.Max.Y) > y1 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Vertex represents a vertex passed to DrawTriangles.
|
||||
@ -550,7 +576,7 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o
|
||||
})
|
||||
}
|
||||
|
||||
i.image.DrawTriangles(srcs, vs, is, blend, i.adjustedBounds(), [graphics.ShaderSrcImageCount]image.Rectangle{img.adjustedBounds()}, shader.shader, i.tmpUniforms, graphicsdriver.FillRule(options.FillRule), filter != builtinshader.FilterLinear, options.AntiAlias)
|
||||
i.image.DrawTriangles(srcs, vs, is, blend, i.adjustedBounds(), [graphics.ShaderSrcImageCount]image.Rectangle{img.adjustedBounds()}, shader.shader, i.tmpUniforms, graphicsdriver.FillRule(options.FillRule), filter != builtinshader.FilterLinear, options.AntiAlias, restorable.HintNone)
|
||||
}
|
||||
|
||||
// DrawTrianglesShaderOptions represents options for DrawTrianglesShader.
|
||||
@ -722,7 +748,7 @@ func (i *Image) DrawTrianglesShader(vertices []Vertex, indices []uint16, shader
|
||||
i.tmpUniforms = i.tmpUniforms[:0]
|
||||
i.tmpUniforms = shader.appendUniforms(i.tmpUniforms, options.Uniforms)
|
||||
|
||||
i.image.DrawTriangles(imgs, vs, is, blend, i.adjustedBounds(), srcRegions, shader.shader, i.tmpUniforms, graphicsdriver.FillRule(options.FillRule), true, options.AntiAlias)
|
||||
i.image.DrawTriangles(imgs, vs, is, blend, i.adjustedBounds(), srcRegions, shader.shader, i.tmpUniforms, graphicsdriver.FillRule(options.FillRule), true, options.AntiAlias, restorable.HintNone)
|
||||
}
|
||||
|
||||
// DrawRectShaderOptions represents options for DrawRectShader.
|
||||
@ -855,7 +881,14 @@ func (i *Image) DrawRectShader(width, height int, shader *Shader, options *DrawR
|
||||
i.tmpUniforms = i.tmpUniforms[:0]
|
||||
i.tmpUniforms = shader.appendUniforms(i.tmpUniforms, options.Uniforms)
|
||||
|
||||
i.image.DrawTriangles(imgs, vs, is, blend, i.adjustedBounds(), srcRegions, shader.shader, i.tmpUniforms, graphicsdriver.FillRuleFillAll, true, false)
|
||||
dr := i.adjustedBounds()
|
||||
hint := restorable.HintNone
|
||||
// Do not use srcRegions[0].Dx() and srcRegions[0].Dy() as these might be empty.
|
||||
if overwritesDstRegion(options.Blend, dr, geoM, srcRegions[0].Min.X, srcRegions[0].Min.Y, srcRegions[0].Min.X+width, srcRegions[0].Min.Y+height) {
|
||||
hint = restorable.HintOverwriteDstRegion
|
||||
}
|
||||
|
||||
i.image.DrawTriangles(imgs, vs, is, blend, i.adjustedBounds(), srcRegions, shader.shader, i.tmpUniforms, graphicsdriver.FillRuleFillAll, true, false, hint)
|
||||
}
|
||||
|
||||
// SubImage returns an image representing the portion of the image p visible through r.
|
||||
|
@ -157,6 +157,7 @@ const (
|
||||
ImageTypeScreen
|
||||
|
||||
// ImageTypeVolatile is a volatile image that is cleared every frame.
|
||||
// A volatile image is also unmanaged.
|
||||
ImageTypeVolatile
|
||||
|
||||
// ImageTypeUnmanaged is an unmanaged image that is not on an atlas.
|
||||
@ -283,8 +284,9 @@ func (i *Image) ensureIsolatedFromSource(backends []*backend) {
|
||||
graphics.QuadVerticesFromDstAndSrc(vs, 0, 0, w, h, 0, 0, w, h, 1, 1, 1, 1)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, i.width, i.height)
|
||||
sr := image.Rect(0, 0, i.width, i.height)
|
||||
|
||||
newI.drawTriangles([graphics.ShaderSrcImageCount]*Image{i}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
newI.drawTriangles([graphics.ShaderSrcImageCount]*Image{i}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintOverwriteDstRegion)
|
||||
newI.moveTo(i)
|
||||
}
|
||||
|
||||
@ -314,7 +316,8 @@ func (i *Image) putOnSourceBackend() {
|
||||
graphics.QuadVerticesFromDstAndSrc(vs, 0, 0, w, h, 0, 0, w, h, 1, 1, 1, 1)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, i.width, i.height)
|
||||
newI.drawTriangles([graphics.ShaderSrcImageCount]*Image{i}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
sr := image.Rect(0, 0, i.width, i.height)
|
||||
newI.drawTriangles([graphics.ShaderSrcImageCount]*Image{i}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintOverwriteDstRegion)
|
||||
|
||||
newI.moveTo(i)
|
||||
i.usedAsSourceCount = 0
|
||||
@ -346,7 +349,7 @@ func (i *Image) regionWithPadding() image.Rectangle {
|
||||
// 5: Color G
|
||||
// 6: Color B
|
||||
// 7: Color Y
|
||||
func (i *Image) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule) {
|
||||
func (i *Image) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule, hint restorable.Hint) {
|
||||
backendsM.Lock()
|
||||
defer backendsM.Unlock()
|
||||
|
||||
@ -359,15 +362,15 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertice
|
||||
copy(us, uniforms)
|
||||
|
||||
appendDeferred(func() {
|
||||
i.drawTriangles(srcs, vs, is, blend, dstRegion, srcRegions, shader, us, fillRule)
|
||||
i.drawTriangles(srcs, vs, is, blend, dstRegion, srcRegions, shader, us, fillRule, hint)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
i.drawTriangles(srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule)
|
||||
i.drawTriangles(srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule, hint)
|
||||
}
|
||||
|
||||
func (i *Image) drawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule) {
|
||||
func (i *Image) drawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule, hint restorable.Hint) {
|
||||
backends := make([]*backend, 0, len(srcs))
|
||||
for _, src := range srcs {
|
||||
if src == nil {
|
||||
@ -432,6 +435,7 @@ func (i *Image) drawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertice
|
||||
|
||||
// A source region can be deliberately empty when this is not needed in order to avoid unexpected
|
||||
// performance issue (#1293).
|
||||
// TODO: This should no longer be needed but is kept just in case. Remove this later.
|
||||
if srcRegions[i].Empty() {
|
||||
continue
|
||||
}
|
||||
@ -448,7 +452,7 @@ func (i *Image) drawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertice
|
||||
imgs[i] = src.backend.restorable
|
||||
}
|
||||
|
||||
i.backend.restorable.DrawTriangles(imgs, vertices, indices, blend, dstRegion, srcRegions, shader.ensureShader(), uniforms, fillRule)
|
||||
i.backend.restorable.DrawTriangles(imgs, vertices, indices, blend, dstRegion, srcRegions, shader.ensureShader(), uniforms, fillRule, hint)
|
||||
|
||||
for _, src := range srcs {
|
||||
if src == nil {
|
||||
|
@ -25,6 +25,7 @@ import (
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/atlas"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/restorable"
|
||||
t "github.com/hajimehoshi/ebiten/v2/internal/testing"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/ui"
|
||||
)
|
||||
@ -102,7 +103,8 @@ func TestEnsureIsolatedFromSourceBackend(t *testing.T) {
|
||||
vs := quadVertices(size/2, size/2, size/4, size/4, 1)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, size, size)
|
||||
img4.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img3}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
sr := image.Rect(0, 0, size/2, size/2)
|
||||
img4.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img3}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
if got, want := img4.IsOnSourceBackendForTesting(), false; got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
@ -110,7 +112,8 @@ func TestEnsureIsolatedFromSourceBackend(t *testing.T) {
|
||||
// img5 is not allocated now, but is allocated at DrawTriangles.
|
||||
vs = quadVertices(0, 0, size/2, size/2, 1)
|
||||
dr = image.Rect(0, 0, size/2, size/2)
|
||||
img3.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img5}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
sr = image.Rect(0, 0, size/2, size/2)
|
||||
img3.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img5}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
if got, want := img3.IsOnSourceBackendForTesting(), false; got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
@ -144,7 +147,9 @@ func TestEnsureIsolatedFromSourceBackend(t *testing.T) {
|
||||
// Check further drawing doesn't cause panic.
|
||||
// This bug was fixed by 03dcd948.
|
||||
vs = quadVertices(0, 0, size/2, size/2, 1)
|
||||
img4.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img3}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
dr = image.Rect(0, 0, size, size)
|
||||
sr = image.Rect(0, 0, size/2, size/2)
|
||||
img4.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img3}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
}
|
||||
|
||||
func TestReputOnSourceBackend(t *testing.T) {
|
||||
@ -185,10 +190,11 @@ func TestReputOnSourceBackend(t *testing.T) {
|
||||
// Use img1 as a render target.
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, size, size)
|
||||
sr := image.Rect(0, 0, size, size)
|
||||
// Render onto img1. The count should not matter.
|
||||
for i := 0; i < 5; i++ {
|
||||
vs := quadVertices(size, size, 0, 0, 1)
|
||||
img1.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img2}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
img1.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img2}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
if got, want := img1.IsOnSourceBackendForTesting(), false; got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
@ -200,7 +206,7 @@ func TestReputOnSourceBackend(t *testing.T) {
|
||||
for i := 0; i < atlas.BaseCountToPutOnSourceBackend*2; i++ {
|
||||
atlas.PutImagesOnSourceBackendForTesting()
|
||||
vs := quadVertices(size, size, 0, 0, 1)
|
||||
img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
if got, want := img1.IsOnSourceBackendForTesting(), false; got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
@ -208,7 +214,7 @@ func TestReputOnSourceBackend(t *testing.T) {
|
||||
// Finally, img1 is on a source backend.
|
||||
atlas.PutImagesOnSourceBackendForTesting()
|
||||
vs := quadVertices(size, size, 0, 0, 1)
|
||||
img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
if got, want := img1.IsOnSourceBackendForTesting(), true; got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
@ -237,7 +243,7 @@ func TestReputOnSourceBackend(t *testing.T) {
|
||||
}
|
||||
|
||||
vs = quadVertices(size, size, 0, 0, 1)
|
||||
img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
if got, want := img1.IsOnSourceBackendForTesting(), true; got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
@ -267,7 +273,7 @@ func TestReputOnSourceBackend(t *testing.T) {
|
||||
// Use img1 as a render target again. The count should not matter.
|
||||
for i := 0; i < 5; i++ {
|
||||
vs := quadVertices(size, size, 0, 0, 1)
|
||||
img1.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img2}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
img1.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img2}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
if got, want := img1.IsOnSourceBackendForTesting(), false; got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
@ -279,7 +285,7 @@ func TestReputOnSourceBackend(t *testing.T) {
|
||||
atlas.PutImagesOnSourceBackendForTesting()
|
||||
img1.WritePixels(make([]byte, 4*size*size), image.Rect(0, 0, size, size))
|
||||
vs := quadVertices(size, size, 0, 0, 1)
|
||||
img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
if got, want := img1.IsOnSourceBackendForTesting(), false; got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
@ -288,7 +294,7 @@ func TestReputOnSourceBackend(t *testing.T) {
|
||||
|
||||
// img1 is not on an atlas due to WritePixels.
|
||||
vs = quadVertices(size, size, 0, 0, 1)
|
||||
img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
if got, want := img1.IsOnSourceBackendForTesting(), false; got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
@ -297,7 +303,7 @@ func TestReputOnSourceBackend(t *testing.T) {
|
||||
for i := 0; i < atlas.BaseCountToPutOnSourceBackend*2; i++ {
|
||||
atlas.PutImagesOnSourceBackendForTesting()
|
||||
vs := quadVertices(size, size, 0, 0, 1)
|
||||
img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img3}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img3}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
if got, want := img3.IsOnSourceBackendForTesting(), false; got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
@ -400,7 +406,8 @@ func TestWritePixelsAfterDrawTriangles(t *testing.T) {
|
||||
vs := quadVertices(w, h, 0, 0, 1)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, w, h)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
sr := image.Rect(0, 0, w, h)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
dst.WritePixels(pix, image.Rect(0, 0, w, h))
|
||||
|
||||
pix = make([]byte, 4*w*h)
|
||||
@ -447,7 +454,8 @@ func TestSmallImages(t *testing.T) {
|
||||
vs := quadVertices(w, h, 0, 0, 1)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, w, h)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
sr := image.Rect(0, 0, w, h)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
|
||||
pix = make([]byte, 4*w*h)
|
||||
ok, err := dst.ReadPixels(ui.Get().GraphicsDriverForTesting(), pix, image.Rect(0, 0, w, h))
|
||||
@ -494,7 +502,8 @@ func TestLongImages(t *testing.T) {
|
||||
vs := quadVertices(w, h, 0, 0, scale)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, dstW, dstH)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
sr := image.Rect(0, 0, w, h)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
|
||||
pix = make([]byte, 4*dstW*dstH)
|
||||
ok, err := dst.ReadPixels(ui.Get().GraphicsDriverForTesting(), pix, image.Rect(0, 0, dstW, dstH))
|
||||
@ -610,7 +619,8 @@ func TestDeallocatedAndReputOnSourceBackend(t *testing.T) {
|
||||
vs := quadVertices(size, size, 0, 0, 1)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, size, size)
|
||||
src.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src2}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
sr := image.Rect(0, 0, size, size)
|
||||
src.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src2}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
if got, want := src.IsOnSourceBackendForTesting(), false; got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
@ -619,7 +629,7 @@ func TestDeallocatedAndReputOnSourceBackend(t *testing.T) {
|
||||
for i := 0; i < atlas.BaseCountToPutOnSourceBackend/2; i++ {
|
||||
atlas.PutImagesOnSourceBackendForTesting()
|
||||
vs := quadVertices(size, size, 0, 0, 1)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
if got, want := src.IsOnSourceBackendForTesting(), false; got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
@ -647,13 +657,14 @@ func TestImageIsNotReputOnSourceBackendWithoutUsingAsSource(t *testing.T) {
|
||||
vs := quadVertices(size, size, 0, 0, 1)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, size, size)
|
||||
sr := image.Rect(0, 0, size, size)
|
||||
|
||||
// Use src2 as a rendering target, and make src2 on a destination backend.
|
||||
//
|
||||
// Call DrawTriangles multiple times.
|
||||
// The number of DrawTriangles doesn't matter as long as these are called in one frame.
|
||||
for i := 0; i < 2; i++ {
|
||||
src2.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
src2.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
}
|
||||
if got, want := src2.IsOnSourceBackendForTesting(), false; got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
@ -672,7 +683,7 @@ func TestImageIsNotReputOnSourceBackendWithoutUsingAsSource(t *testing.T) {
|
||||
for i := 0; i < atlas.BaseCountToPutOnSourceBackend; i++ {
|
||||
atlas.PutImagesOnSourceBackendForTesting()
|
||||
vs := quadVertices(size, size, 0, 0, 1)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src2}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src2}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
if got, want := src2.IsOnSourceBackendForTesting(), false; got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
@ -795,17 +806,18 @@ func TestDestinationCountOverflow(t *testing.T) {
|
||||
vs := quadVertices(w, h, 0, 0, 1)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, w, h)
|
||||
sr := image.Rect(0, 0, w, h)
|
||||
|
||||
// Use dst0 as a destination for a while.
|
||||
for i := 0; i < 31; i++ {
|
||||
dst0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
dst0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
atlas.PutImagesOnSourceBackendForTesting()
|
||||
}
|
||||
|
||||
// Use dst0 as a source for a while.
|
||||
// As dst0 is used as a destination too many times (31 is a maximum), dst0's backend should never be a source backend.
|
||||
for i := 0; i < 100; i++ {
|
||||
dst1.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{dst0}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
dst1.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{dst0}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
atlas.PutImagesOnSourceBackendForTesting()
|
||||
if dst0.IsOnSourceBackendForTesting() {
|
||||
t.Errorf("dst0 cannot be on a source backend: %d", i)
|
||||
@ -830,8 +842,9 @@ func TestIteratingImagesToPutOnSourceBackend(t *testing.T) {
|
||||
vs := quadVertices(w, h, 0, 0, 1)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, w, h)
|
||||
sr := image.Rect(0, 0, w, h)
|
||||
for _, img := range srcs {
|
||||
img.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
img.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
}
|
||||
atlas.PutImagesOnSourceBackendForTesting()
|
||||
|
||||
@ -839,7 +852,7 @@ func TestIteratingImagesToPutOnSourceBackend(t *testing.T) {
|
||||
// Check iterating the registered image works correctly.
|
||||
for i := 0; i < 100; i++ {
|
||||
for _, src := range srcs {
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
}
|
||||
atlas.PutImagesOnSourceBackendForTesting()
|
||||
}
|
||||
@ -887,7 +900,8 @@ func TestDallocateUnmanagedImageBackends(t *testing.T) {
|
||||
vs := quadVertices(w, h, 0, 0, 1)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, w, h)
|
||||
img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
sr := image.Rect(0, 0, w, h)
|
||||
img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
|
||||
// Get the difference of the number of backends before and after the images are deallocated.
|
||||
c := atlas.BackendCountForTesting()
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/atlas"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/restorable"
|
||||
etesting "github.com/hajimehoshi/ebiten/v2/internal/testing"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/ui"
|
||||
)
|
||||
@ -37,12 +38,12 @@ func TestShaderFillTwice(t *testing.T) {
|
||||
dr := image.Rect(0, 0, w, h)
|
||||
g := ui.Get().GraphicsDriverForTesting()
|
||||
s0 := atlas.NewShader(etesting.ShaderProgramFill(0xff, 0xff, 0xff, 0xff), "")
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s0, nil, graphicsdriver.FillRuleFillAll)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s0, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
|
||||
// Vertices must be recreated (#1755)
|
||||
vs = quadVertices(w, h, 0, 0, 1)
|
||||
s1 := atlas.NewShader(etesting.ShaderProgramFill(0x80, 0x80, 0x80, 0xff), "")
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s1, nil, graphicsdriver.FillRuleFillAll)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s1, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
|
||||
pix := make([]byte, 4*w*h)
|
||||
ok, err := dst.ReadPixels(g, pix, image.Rect(0, 0, w, h))
|
||||
@ -69,11 +70,12 @@ func TestImageDrawTwice(t *testing.T) {
|
||||
vs := quadVertices(w, h, 0, 0, 1)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, w, h)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src0}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
sr := image.Rect(0, 0, w, h)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src0}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
|
||||
// Vertices must be recreated (#1755)
|
||||
vs = quadVertices(w, h, 0, 0, 1)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
|
||||
pix := make([]byte, 4*w*h)
|
||||
ok, err := dst.ReadPixels(ui.Get().GraphicsDriverForTesting(), pix, image.Rect(0, 0, w, h))
|
||||
@ -97,7 +99,7 @@ func TestGCShader(t *testing.T) {
|
||||
vs := quadVertices(w, h, 0, 0, 1)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, w, h)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s, nil, graphicsdriver.FillRuleFillAll)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
|
||||
// Ensure other objects are GCed, as GC appends deferred functions for collected objects.
|
||||
ensureGC()
|
||||
|
@ -196,7 +196,7 @@ func (i *Image) WritePixels(pix []byte, region image.Rectangle) {
|
||||
// DrawTriangles draws the src image with the given vertices.
|
||||
//
|
||||
// Copying vertices and indices is the caller's responsibility.
|
||||
func (i *Image) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *atlas.Shader, uniforms []uint32, fillRule graphicsdriver.FillRule) {
|
||||
func (i *Image) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *atlas.Shader, uniforms []uint32, fillRule graphicsdriver.FillRule, hint restorable.Hint) {
|
||||
for _, src := range srcs {
|
||||
if i == src {
|
||||
panic("buffered: Image.DrawTriangles: source images must be different from the receiver")
|
||||
@ -218,7 +218,7 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertice
|
||||
imgs[i] = img.img
|
||||
}
|
||||
|
||||
i.img.DrawTriangles(imgs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule)
|
||||
i.img.DrawTriangles(imgs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule, hint)
|
||||
|
||||
// After rendering, the pixel cache is no longer valid.
|
||||
i.pixels = nil
|
||||
@ -310,8 +310,9 @@ func (i *Image) syncPixelsIfNeeded() {
|
||||
|
||||
srcs := [graphics.ShaderSrcImageCount]*atlas.Image{whiteImage.img}
|
||||
dr := image.Rect(0, 0, i.width, i.height)
|
||||
sr := image.Rect(0, 0, whiteImage.width, whiteImage.height)
|
||||
blend := graphicsdriver.BlendCopy
|
||||
i.img.DrawTriangles(srcs, vs, is, blend, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
i.img.DrawTriangles(srcs, vs, is, blend, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
|
||||
// TODO: Use clear if Go 1.21 is available.
|
||||
for pos := range i.dotsBuffer {
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/buffered"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/restorable"
|
||||
t "github.com/hajimehoshi/ebiten/v2/internal/testing"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/ui"
|
||||
)
|
||||
@ -56,7 +57,7 @@ func TestUnsyncedPixels(t *testing.T) {
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, 16, 16)
|
||||
sr := [graphics.ShaderSrcImageCount]image.Rectangle{image.Rect(0, 0, 16, 16)}
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*buffered.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, sr, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*buffered.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, sr, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
|
||||
// Check the result is correct.
|
||||
var got [4]byte
|
||||
|
@ -56,7 +56,8 @@ func TestClear(t *testing.T) {
|
||||
vs := quadVertices(w/2, h/2)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, w, h)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*graphicscommand.Image{src}, vs, is, graphicsdriver.BlendClear, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, nearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
sr := image.Rect(0, 0, w/2, h/2)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*graphicscommand.Image{src}, vs, is, graphicsdriver.BlendClear, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, nearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
|
||||
pix := make([]byte, 4*w*h)
|
||||
if err := dst.ReadPixels(ui.Get().GraphicsDriverForTesting(), []graphicsdriver.PixelsArgs{
|
||||
@ -87,8 +88,10 @@ func TestWritePixelsPartAfterDrawTriangles(t *testing.T) {
|
||||
vs := quadVertices(w/2, h/2)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, w, h)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*graphicscommand.Image{clr}, vs, is, graphicsdriver.BlendClear, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, nearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*graphicscommand.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, nearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
sr0 := image.Rect(0, 0, w, h)
|
||||
sr1 := image.Rect(0, 0, w/2, h/2)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*graphicscommand.Image{clr}, vs, is, graphicsdriver.BlendClear, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr0}, nearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*graphicscommand.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr1}, nearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
bs := graphics.NewManagedBytes(4, func(bs []byte) {
|
||||
for i := range bs {
|
||||
bs[i] = 0
|
||||
@ -106,7 +109,8 @@ func TestShader(t *testing.T) {
|
||||
vs := quadVertices(w, h)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, w, h)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*graphicscommand.Image{clr}, vs, is, graphicsdriver.BlendClear, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, nearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
sr := image.Rect(0, 0, w, h)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*graphicscommand.Image{clr}, vs, is, graphicsdriver.BlendClear, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, nearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
|
||||
g := ui.Get().GraphicsDriverForTesting()
|
||||
s := graphicscommand.NewShader(etesting.ShaderProgramFill(0xff, 0, 0, 0xff), "")
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/buffered"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/restorable"
|
||||
)
|
||||
|
||||
func canUseMipmap(imageType atlas.ImageType) bool {
|
||||
@ -65,7 +66,7 @@ func (m *Mipmap) ReadPixels(graphicsDriver graphicsdriver.Graphics, pixels []byt
|
||||
return m.orig.ReadPixels(graphicsDriver, pixels, region)
|
||||
}
|
||||
|
||||
func (m *Mipmap) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Mipmap, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *atlas.Shader, uniforms []uint32, fillRule graphicsdriver.FillRule, canSkipMipmap bool) {
|
||||
func (m *Mipmap) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Mipmap, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *atlas.Shader, uniforms []uint32, fillRule graphicsdriver.FillRule, canSkipMipmap bool, hint restorable.Hint) {
|
||||
if len(indices) == 0 {
|
||||
return
|
||||
}
|
||||
@ -123,7 +124,7 @@ func (m *Mipmap) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Mipmap, verti
|
||||
imgs[i] = src.orig
|
||||
}
|
||||
|
||||
m.orig.DrawTriangles(imgs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule)
|
||||
m.orig.DrawTriangles(imgs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule, hint)
|
||||
m.deallocateMipmaps()
|
||||
}
|
||||
|
||||
@ -184,7 +185,10 @@ func (m *Mipmap) level(level int) *buffered.Image {
|
||||
s := buffered.NewImage(w2, h2, m.imageType)
|
||||
|
||||
dstRegion := image.Rect(0, 0, w2, h2)
|
||||
s.DrawTriangles([graphics.ShaderSrcImageCount]*buffered.Image{src}, vs, is, graphicsdriver.BlendCopy, dstRegion, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.LinearFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
w := sizeForLevel(m.width, level-1)
|
||||
h := sizeForLevel(m.height, level-1)
|
||||
srcRegion := image.Rect(0, 0, w, h)
|
||||
s.DrawTriangles([graphics.ShaderSrcImageCount]*buffered.Image{src}, vs, is, graphicsdriver.BlendCopy, dstRegion, [graphics.ShaderSrcImageCount]image.Rectangle{srcRegion}, atlas.LinearFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintOverwriteDstRegion)
|
||||
m.setImg(level, s)
|
||||
|
||||
return m.imgs[level]
|
||||
|
@ -80,7 +80,7 @@ func (p *Pixels) Dispose() {
|
||||
|
||||
// drawTrianglesHistoryItem is an item for history of draw-image commands.
|
||||
type drawTrianglesHistoryItem struct {
|
||||
images [graphics.ShaderSrcImageCount]*Image
|
||||
srcImages [graphics.ShaderSrcImageCount]*Image
|
||||
vertices []float32
|
||||
indices []uint32
|
||||
blend graphicsdriver.Blend
|
||||
@ -109,6 +109,18 @@ const (
|
||||
ImageTypeVolatile
|
||||
)
|
||||
|
||||
// Hint is a hint to optimize the info to restore the image.
|
||||
type Hint int
|
||||
|
||||
const (
|
||||
// HintNone indicates that there is no hint.
|
||||
HintNone Hint = iota
|
||||
|
||||
// HintOverwriteDstRegion indicates that the destination region is overwritten.
|
||||
// HintOverwriteDstRegion helps to reduce the size of the draw-image history.
|
||||
HintOverwriteDstRegion
|
||||
)
|
||||
|
||||
// Image represents an image that can be restored when GL context is lost.
|
||||
type Image struct {
|
||||
image *graphicscommand.Image
|
||||
@ -194,7 +206,7 @@ func (i *Image) Extend(width, height int) *Image {
|
||||
graphics.QuadVerticesFromDstAndSrc(vs, 0, 0, float32(sw), float32(sh), 0, 0, float32(sw), float32(sh), 1, 1, 1, 1)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, sw, sh)
|
||||
newImg.DrawTriangles(srcs, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
newImg.DrawTriangles(srcs, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, HintOverwriteDstRegion)
|
||||
i.Dispose()
|
||||
|
||||
return newImg
|
||||
@ -221,6 +233,10 @@ func (i *Image) makeStale(rect image.Rectangle) {
|
||||
return
|
||||
}
|
||||
|
||||
if !i.needsRestoration() {
|
||||
return
|
||||
}
|
||||
|
||||
origSize := len(i.staleRegions)
|
||||
i.staleRegions = i.appendRegionsForDrawTriangles(i.staleRegions)
|
||||
if !rect.Empty() {
|
||||
@ -261,9 +277,7 @@ func (i *Image) WritePixels(pixels *graphics.ManagedBytes, region image.Rectangl
|
||||
panic(fmt.Sprintf("restorable: out of range %v", region))
|
||||
}
|
||||
|
||||
// TODO: Avoid making other images stale if possible. (#514)
|
||||
// For this purpose, images should remember which part of that is used for DrawTriangles.
|
||||
theImages.makeStaleIfDependingOn(i)
|
||||
theImages.makeStaleIfDependingOnAtRegion(i, region)
|
||||
|
||||
if pixels != nil {
|
||||
i.image.WritePixels(pixels, region)
|
||||
@ -271,8 +285,7 @@ func (i *Image) WritePixels(pixels *graphics.ManagedBytes, region image.Rectangl
|
||||
clearImage(i.image, region)
|
||||
}
|
||||
|
||||
// Even if the image is already stale, call makeStale to extend the stale region.
|
||||
if !needsRestoration() || !i.needsRestoration() || i.stale {
|
||||
if !needsRestoration() || !i.needsRestoration() {
|
||||
i.makeStale(region)
|
||||
return
|
||||
}
|
||||
@ -280,9 +293,9 @@ func (i *Image) WritePixels(pixels *graphics.ManagedBytes, region image.Rectangl
|
||||
if region.Eq(image.Rect(0, 0, w, h)) {
|
||||
if pixels != nil {
|
||||
// Clone a ManagedBytes as the package graphicscommand has a different lifetime management.
|
||||
i.basePixels.AddOrReplace(pixels.Clone(), image.Rect(0, 0, w, h))
|
||||
i.basePixels.AddOrReplace(pixels.Clone(), region)
|
||||
} else {
|
||||
i.basePixels.Clear(image.Rect(0, 0, w, h))
|
||||
i.basePixels.Clear(region)
|
||||
}
|
||||
i.clearDrawTrianglesHistory()
|
||||
i.stale = false
|
||||
@ -290,6 +303,14 @@ func (i *Image) WritePixels(pixels *graphics.ManagedBytes, region image.Rectangl
|
||||
return
|
||||
}
|
||||
|
||||
if i.stale {
|
||||
// Even if the image is already stale, call makeStale to extend the stale region.
|
||||
i.makeStale(region)
|
||||
return
|
||||
}
|
||||
|
||||
i.removeDrawTrianglesHistoryItems(region)
|
||||
|
||||
// Records for DrawTriangles cannot come before records for WritePixels.
|
||||
if len(i.drawTrianglesHistory) > 0 {
|
||||
i.makeStale(region)
|
||||
@ -316,10 +337,13 @@ func (i *Image) WritePixels(pixels *graphics.ManagedBytes, region image.Rectangl
|
||||
// 5: Color G
|
||||
// 6: Color B
|
||||
// 7: Color Y
|
||||
func (i *Image) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule) {
|
||||
func (i *Image) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule, hint Hint) {
|
||||
if len(vertices) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// makeStaleIfDependingOnAtRegion is not available here.
|
||||
// This might create cyclic dependency.
|
||||
theImages.makeStaleIfDependingOn(i)
|
||||
|
||||
// TODO: Add tests to confirm this logic.
|
||||
@ -335,10 +359,26 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertice
|
||||
}
|
||||
|
||||
// Even if the image is already stale, call makeStale to extend the stale region.
|
||||
if srcstale || !needsRestoration() || !i.needsRestoration() || i.stale {
|
||||
if srcstale || !needsRestoration() || !i.needsRestoration() {
|
||||
i.makeStale(dstRegion)
|
||||
} else {
|
||||
i.appendDrawTrianglesHistory(srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule)
|
||||
} else if i.stale {
|
||||
var overwrite bool
|
||||
if hint == HintOverwriteDstRegion {
|
||||
overwrite = i.areStaleRegionsIncludedIn(dstRegion)
|
||||
}
|
||||
if overwrite {
|
||||
i.basePixels.Clear(dstRegion)
|
||||
i.clearDrawTrianglesHistory()
|
||||
i.stale = false
|
||||
i.staleRegions = i.staleRegions[:0]
|
||||
} else {
|
||||
// Even if the image is already stale, call makeStale to extend the stale region.
|
||||
i.makeStale(dstRegion)
|
||||
}
|
||||
}
|
||||
|
||||
if !i.stale {
|
||||
i.appendDrawTrianglesHistory(srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule, hint)
|
||||
}
|
||||
|
||||
var imgs [graphics.ShaderSrcImageCount]*graphicscommand.Image
|
||||
@ -351,8 +391,39 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertice
|
||||
i.image.DrawTriangles(imgs, vertices, indices, blend, dstRegion, srcRegions, shader.shader, uniforms, fillRule)
|
||||
}
|
||||
|
||||
func (i *Image) areStaleRegionsIncludedIn(r image.Rectangle) bool {
|
||||
if !i.stale {
|
||||
return false
|
||||
}
|
||||
for _, sr := range i.staleRegions {
|
||||
if !sr.In(r) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// removeDrawTrianglesHistoryItems removes draw-image history items whose destination regions are in the given region.
|
||||
// This is useful when the destination region is overwritten soon later.
|
||||
func (i *Image) removeDrawTrianglesHistoryItems(region image.Rectangle) {
|
||||
for idx, c := range i.drawTrianglesHistory {
|
||||
if c.dstRegion.In(region) {
|
||||
i.drawTrianglesHistory[idx] = nil
|
||||
}
|
||||
}
|
||||
var n int
|
||||
for _, c := range i.drawTrianglesHistory {
|
||||
if c == nil {
|
||||
continue
|
||||
}
|
||||
i.drawTrianglesHistory[n] = c
|
||||
n++
|
||||
}
|
||||
i.drawTrianglesHistory = i.drawTrianglesHistory[:n]
|
||||
}
|
||||
|
||||
// appendDrawTrianglesHistory appends a draw-image history item to the image.
|
||||
func (i *Image) appendDrawTrianglesHistory(srcs [graphics.ShaderSrcImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule) {
|
||||
func (i *Image) appendDrawTrianglesHistory(srcs [graphics.ShaderSrcImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule, hint Hint) {
|
||||
if i.stale || !i.needsRestoration() {
|
||||
panic("restorable: an image must not be stale or need restoration at appendDrawTrianglesHistory")
|
||||
}
|
||||
@ -360,6 +431,11 @@ func (i *Image) appendDrawTrianglesHistory(srcs [graphics.ShaderSrcImageCount]*I
|
||||
panic("restorable: appendDrawTrianglesHistory must not be called when AlwaysReadPixelsFromGPU() returns true")
|
||||
}
|
||||
|
||||
// If the command overwrites the destination region, remove the history items that are in the region.
|
||||
if hint == HintOverwriteDstRegion {
|
||||
i.removeDrawTrianglesHistoryItems(dstRegion)
|
||||
}
|
||||
|
||||
// TODO: Would it be possible to merge draw image history items?
|
||||
const maxDrawTrianglesHistoryCount = 1024
|
||||
if len(i.drawTrianglesHistory)+1 > maxDrawTrianglesHistoryCount {
|
||||
@ -379,7 +455,7 @@ func (i *Image) appendDrawTrianglesHistory(srcs [graphics.ShaderSrcImageCount]*I
|
||||
copy(us, uniforms)
|
||||
|
||||
item := &drawTrianglesHistoryItem{
|
||||
images: srcs,
|
||||
srcImages: srcs,
|
||||
vertices: vs,
|
||||
indices: is,
|
||||
blend: blend,
|
||||
@ -402,7 +478,7 @@ func (i *Image) readPixelsFromGPUIfNeeded(graphicsDriver graphicsdriver.Graphics
|
||||
}
|
||||
|
||||
func (i *Image) ReadPixels(graphicsDriver graphicsdriver.Graphics, pixels []byte, region image.Rectangle) error {
|
||||
if AlwaysReadPixelsFromGPU() {
|
||||
if AlwaysReadPixelsFromGPU() || !i.needsRestoration() {
|
||||
if err := i.image.ReadPixels(graphicsDriver, []graphicsdriver.PixelsArgs{
|
||||
{
|
||||
Pixels: pixels,
|
||||
@ -424,12 +500,23 @@ func (i *Image) ReadPixels(graphicsDriver graphicsdriver.Graphics, pixels []byte
|
||||
return nil
|
||||
}
|
||||
|
||||
// makeStaleIfDependingOn makes the image stale if the image depends on target.
|
||||
func (i *Image) makeStaleIfDependingOn(target *Image) {
|
||||
// makeStaleIfDependingOn makes the image stale if the image depends on src.
|
||||
func (i *Image) makeStaleIfDependingOn(src *Image) {
|
||||
if i.stale {
|
||||
return
|
||||
}
|
||||
if i.dependsOn(target) {
|
||||
if i.dependsOn(src) {
|
||||
// There is no new region to make stale.
|
||||
i.makeStale(image.Rectangle{})
|
||||
}
|
||||
}
|
||||
|
||||
// makeStaleIfDependingOnAtRegion makes the image stale if the image depends on src at srcRegion.
|
||||
func (i *Image) makeStaleIfDependingOnAtRegion(src *Image, srcRegion image.Rectangle) {
|
||||
if i.stale {
|
||||
return
|
||||
}
|
||||
if i.dependsOnAtRegion(src, srcRegion) {
|
||||
// There is no new region to make stale.
|
||||
i.makeStale(image.Rectangle{})
|
||||
}
|
||||
@ -515,14 +602,27 @@ func (i *Image) resolveStale(graphicsDriver graphicsdriver.Graphics) error {
|
||||
return i.readPixelsFromGPU(graphicsDriver)
|
||||
}
|
||||
|
||||
// dependsOn reports whether the image depends on target.
|
||||
func (i *Image) dependsOn(target *Image) bool {
|
||||
// dependsOn reports whether the image depends on src.
|
||||
func (i *Image) dependsOn(src *Image) bool {
|
||||
for _, c := range i.drawTrianglesHistory {
|
||||
for _, img := range c.images {
|
||||
if img == nil {
|
||||
for _, img := range c.srcImages {
|
||||
if img != src {
|
||||
continue
|
||||
}
|
||||
if img == target {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// dependsOnAtRegion reports whether the image depends on src at srcRegion.
|
||||
func (i *Image) dependsOnAtRegion(src *Image, srcRegion image.Rectangle) bool {
|
||||
for _, c := range i.drawTrianglesHistory {
|
||||
for i, img := range c.srcImages {
|
||||
if img != src {
|
||||
continue
|
||||
}
|
||||
if c.srcRegions[i].Overlaps(srcRegion) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -544,7 +644,7 @@ func (i *Image) dependsOnShader(shader *Shader) bool {
|
||||
func (i *Image) dependingImages() map[*Image]struct{} {
|
||||
r := map[*Image]struct{}{}
|
||||
for _, c := range i.drawTrianglesHistory {
|
||||
for _, img := range c.images {
|
||||
for _, img := range c.srcImages {
|
||||
if img == nil {
|
||||
continue
|
||||
}
|
||||
@ -598,7 +698,7 @@ func (i *Image) restore(graphicsDriver graphicsdriver.Graphics) error {
|
||||
|
||||
for _, c := range i.drawTrianglesHistory {
|
||||
var imgs [graphics.ShaderSrcImageCount]*graphicscommand.Image
|
||||
for i, img := range c.images {
|
||||
for i, img := range c.srcImages {
|
||||
if img == nil {
|
||||
continue
|
||||
}
|
||||
|
@ -62,7 +62,6 @@ func AlwaysReadPixelsFromGPU() bool {
|
||||
type images struct {
|
||||
images map[*Image]struct{}
|
||||
shaders map[*Shader]struct{}
|
||||
lastTarget *Image
|
||||
contextLost atomic.Bool
|
||||
}
|
||||
|
||||
@ -91,7 +90,7 @@ func resolveStaleImages(graphicsDriver graphicsdriver.Graphics, endFrame bool) e
|
||||
if disabled.Load() {
|
||||
disabledOnce.Do(func() {
|
||||
for img := range theImages.images {
|
||||
img.makeStale(image.Rectangle{})
|
||||
img.makeStale(image.Rect(0, 0, img.width, img.height))
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -157,7 +156,6 @@ func (i *images) removeShader(shader *Shader) {
|
||||
|
||||
// resolveStaleImages resolves stale images.
|
||||
func (i *images) resolveStaleImages(graphicsDriver graphicsdriver.Graphics) error {
|
||||
i.lastTarget = nil
|
||||
for img := range i.images {
|
||||
if err := img.resolveStale(graphicsDriver); err != nil {
|
||||
return err
|
||||
@ -166,20 +164,29 @@ func (i *images) resolveStaleImages(graphicsDriver graphicsdriver.Graphics) erro
|
||||
return nil
|
||||
}
|
||||
|
||||
// makeStaleIfDependingOn makes all the images stale that depend on target.
|
||||
// makeStaleIfDependingOn makes all the images stale that depend on src.
|
||||
//
|
||||
// When target is modified, all images depending on target can't be restored with target.
|
||||
// When src is modified, all images depending on src can't be restored with src.
|
||||
// makeStaleIfDependingOn is called in such situation.
|
||||
func (i *images) makeStaleIfDependingOn(target *Image) {
|
||||
if target == nil {
|
||||
panic("restorable: target must not be nil at makeStaleIfDependingOn")
|
||||
func (i *images) makeStaleIfDependingOn(src *Image) {
|
||||
if src == nil {
|
||||
panic("restorable: src must not be nil at makeStaleIfDependingOn")
|
||||
}
|
||||
if i.lastTarget == target {
|
||||
return
|
||||
}
|
||||
i.lastTarget = target
|
||||
for img := range i.images {
|
||||
img.makeStaleIfDependingOn(target)
|
||||
img.makeStaleIfDependingOn(src)
|
||||
}
|
||||
}
|
||||
|
||||
// makeStaleIfDependingOnAtRegion makes all the images stale that depend on src at srcRegion.
|
||||
//
|
||||
// When src is modified, all images depending on src can't be restored with src at srcRegion.
|
||||
// makeStaleIfDependingOnAtRegion is called in such situation.
|
||||
func (i *images) makeStaleIfDependingOnAtRegion(src *Image, srcRegion image.Rectangle) {
|
||||
if src == nil {
|
||||
panic("restorable: src must not be nil at makeStaleIfDependingOnAtRegion")
|
||||
}
|
||||
for img := range i.images {
|
||||
img.makeStaleIfDependingOnAtRegion(src, srcRegion)
|
||||
}
|
||||
}
|
||||
|
||||
@ -242,9 +249,7 @@ func (i *images) restore(graphicsDriver graphicsdriver.Graphics) error {
|
||||
current[i] = struct{}{}
|
||||
}
|
||||
for e := range edges {
|
||||
if _, ok := current[e.target]; ok {
|
||||
delete(current, e.target)
|
||||
}
|
||||
delete(current, e.target)
|
||||
}
|
||||
for i := range current {
|
||||
delete(images, i)
|
||||
|
@ -140,7 +140,8 @@ func TestRestoreChain(t *testing.T) {
|
||||
vs := quadVertices(1, 1, 0, 0)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, 1, 1)
|
||||
imgs[i+1].DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{imgs[i]}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
sr := image.Rect(0, 0, 1, 1)
|
||||
imgs[i+1].DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{imgs[i]}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
}
|
||||
if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil {
|
||||
t.Fatal(err)
|
||||
@ -183,10 +184,11 @@ func TestRestoreChain2(t *testing.T) {
|
||||
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, w, h)
|
||||
imgs[8].DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{imgs[7]}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
imgs[9].DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{imgs[8]}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
sr := image.Rect(0, 0, w, h)
|
||||
imgs[8].DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{imgs[7]}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
imgs[9].DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{imgs[8]}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
for i := 0; i < 7; i++ {
|
||||
imgs[i+1].DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{imgs[i]}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
imgs[i+1].DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{imgs[i]}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
}
|
||||
|
||||
if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil {
|
||||
@ -227,10 +229,11 @@ func TestRestoreOverrideSource(t *testing.T) {
|
||||
img1.WritePixels(bytesToManagedBytes([]byte{clr0.R, clr0.G, clr0.B, clr0.A}), image.Rect(0, 0, w, h))
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, w, h)
|
||||
img2.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img1}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
img3.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img2}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
sr := image.Rect(0, 0, w, h)
|
||||
img2.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img1}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
img3.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img2}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
img0.WritePixels(bytesToManagedBytes([]byte{clr1.R, clr1.G, clr1.B, clr1.A}), image.Rect(0, 0, w, h))
|
||||
img1.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img0}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
img1.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img0}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -309,24 +312,25 @@ func TestRestoreComplexGraph(t *testing.T) {
|
||||
}()
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, w, h)
|
||||
sr := image.Rect(0, 0, w, h)
|
||||
vs := quadVertices(w, h, 0, 0)
|
||||
img3.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img0}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
img3.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img0}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
vs = quadVertices(w, h, 1, 0)
|
||||
img3.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img1}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
img3.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img1}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
vs = quadVertices(w, h, 1, 0)
|
||||
img4.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img1}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
img4.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img1}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
vs = quadVertices(w, h, 2, 0)
|
||||
img4.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img2}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
img4.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img2}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
vs = quadVertices(w, h, 0, 0)
|
||||
img5.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img3}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
img5.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img3}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
vs = quadVertices(w, h, 0, 0)
|
||||
img6.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img3}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
img6.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img3}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
vs = quadVertices(w, h, 1, 0)
|
||||
img6.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img4}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
img6.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img4}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
vs = quadVertices(w, h, 0, 0)
|
||||
img7.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img2}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
img7.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img2}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
vs = quadVertices(w, h, 2, 0)
|
||||
img7.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img3}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
img7.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img3}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -419,8 +423,9 @@ func TestRestoreRecursive(t *testing.T) {
|
||||
}()
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, w, h)
|
||||
img1.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img0}, quadVertices(w, h, 1, 0), is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
img0.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img1}, quadVertices(w, h, 1, 0), is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
sr := image.Rect(0, 0, w, h)
|
||||
img1.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img0}, quadVertices(w, h, 1, 0), is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
img0.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img1}, quadVertices(w, h, 1, 0), is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -519,7 +524,8 @@ func TestDrawTrianglesAndWritePixels(t *testing.T) {
|
||||
vs := quadVertices(1, 1, 0, 0)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, 2, 1)
|
||||
img1.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img0}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
sr := image.Rect(0, 0, 1, 1)
|
||||
img1.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img0}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
img1.WritePixels(bytesToManagedBytes([]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}), image.Rect(0, 0, 2, 1))
|
||||
|
||||
if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil {
|
||||
@ -557,8 +563,9 @@ func TestDispose(t *testing.T) {
|
||||
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, 1, 1)
|
||||
img1.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img2}, quadVertices(1, 1, 0, 0), is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
img0.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img1}, quadVertices(1, 1, 0, 0), is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
sr := image.Rect(0, 0, 1, 1)
|
||||
img1.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img2}, quadVertices(1, 1, 0, 0), is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
img0.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img1}, quadVertices(1, 1, 0, 0), is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
img1.Dispose()
|
||||
|
||||
if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil {
|
||||
@ -667,7 +674,8 @@ func TestWritePixelsOnly(t *testing.T) {
|
||||
vs := quadVertices(1, 1, 0, 0)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, 1, 1)
|
||||
img1.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img0}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
sr := image.Rect(0, 0, w, h)
|
||||
img1.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img0}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
img0.WritePixels(bytesToManagedBytes([]byte{5, 6, 7, 8}), image.Rect(0, 0, 1, 1))
|
||||
|
||||
// BasePixelsForTesting is available without GPU accessing.
|
||||
@ -721,7 +729,8 @@ func TestReadPixelsFromVolatileImage(t *testing.T) {
|
||||
vs := quadVertices(1, 1, 0, 0)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, w, h)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
sr := image.Rect(0, 0, w, h)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
|
||||
// Read the pixels. If the implementation is correct, dst tries to read its pixels from GPU due to being
|
||||
// stale.
|
||||
@ -745,7 +754,8 @@ func TestAllowWritePixelsAfterDrawTriangles(t *testing.T) {
|
||||
vs := quadVertices(w, h, 0, 0)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, w, h)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
sr := image.Rect(0, 0, w, h)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
dst.WritePixels(bytesToManagedBytes(make([]byte, 4*w*h)), image.Rect(0, 0, w, h))
|
||||
// WritePixels for a whole image doesn't panic.
|
||||
}
|
||||
@ -764,7 +774,8 @@ func TestAllowWritePixelsForPartAfterDrawTriangles(t *testing.T) {
|
||||
vs := quadVertices(w, h, 0, 0)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, w, h)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
sr := image.Rect(0, 0, w, h)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
dst.WritePixels(bytesToManagedBytes(make([]byte, 4*2*2)), image.Rect(0, 0, 2, 2))
|
||||
// WritePixels for a part of image doesn't panic.
|
||||
|
||||
@ -858,7 +869,8 @@ func TestDrawTrianglesAndExtend(t *testing.T) {
|
||||
vs := quadVertices(w, h, 0, 0)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, w, h)
|
||||
orig.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
sr := image.Rect(0, 0, w, h)
|
||||
orig.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
extended := orig.Extend(w*2, h*2) // After this, orig is already disposed.
|
||||
|
||||
result := make([]byte, 4*(w*2)*(h*2))
|
||||
@ -908,7 +920,8 @@ func TestMutateSlices(t *testing.T) {
|
||||
is := make([]uint32, len(graphics.QuadIndices()))
|
||||
copy(is, graphics.QuadIndices())
|
||||
dr := image.Rect(0, 0, w, h)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
sr := image.Rect(0, 0, w, h)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
for i := range vs {
|
||||
vs[i] = 0
|
||||
}
|
||||
@ -1100,7 +1113,8 @@ func TestDrawTrianglesAndReadPixels(t *testing.T) {
|
||||
vs := quadVertices(w, h, 0, 0)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, w, h)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
sr := image.Rect(0, 0, w, h)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
|
||||
pix := make([]byte, 4*w*h)
|
||||
if err := dst.ReadPixels(ui.Get().GraphicsDriverForTesting(), pix, image.Rect(0, 0, w, h)); err != nil {
|
||||
@ -1124,7 +1138,8 @@ func TestWritePixelsAndDrawTriangles(t *testing.T) {
|
||||
vs := quadVertices(1, 1, 1, 0)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(1, 0, 2, 1)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
sr := image.Rect(0, 0, 1, 1)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
|
||||
// Get the pixels.
|
||||
pix := make([]byte, 4*2*1)
|
||||
@ -1138,3 +1153,37 @@ func TestWritePixelsAndDrawTriangles(t *testing.T) {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOverwriteDstRegion(t *testing.T) {
|
||||
const w, h = 1, 1
|
||||
src0 := restorable.NewImage(w, h, restorable.ImageTypeRegular)
|
||||
src1 := restorable.NewImage(w, h, restorable.ImageTypeRegular)
|
||||
dst := restorable.NewImage(w, h, restorable.ImageTypeRegular)
|
||||
|
||||
src0.WritePixels(bytesToManagedBytes([]byte{0x40, 0x40, 0x40, 0x40}), image.Rect(0, 0, 1, 1))
|
||||
src1.WritePixels(bytesToManagedBytes([]byte{0x80, 0x80, 0x80, 0x80}), image.Rect(0, 0, 1, 1))
|
||||
|
||||
vs := quadVertices(w, h, 0, 0)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, w, h)
|
||||
sr := image.Rect(0, 0, w, h)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{src0}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
// This tests that HintOverwriteDstRegion removes the previous DrawTriangles.
|
||||
// In practice, BlendCopy should be used instead of BlendSourceOver in this case.
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{src1}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintOverwriteDstRegion)
|
||||
|
||||
if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := restorable.RestoreIfNeeded(ui.Get().GraphicsDriverForTesting()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
pix := make([]byte, 4*w*h)
|
||||
if err := dst.ReadPixels(ui.Get().GraphicsDriverForTesting(), pix, image.Rect(0, 0, w, h)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, want := (color.RGBA{R: pix[0], G: pix[1], B: pix[2], A: pix[3]}), (color.RGBA{R: 0x80, G: 0x80, B: 0x80, A: 0x80}); !sameColors(got, want, 1) {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,8 @@ func clearImage(img *restorable.Image, w, h int) {
|
||||
}
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, w, h)
|
||||
img.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{emptyImage}, vs, is, graphicsdriver.BlendClear, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
sr := image.Rect(0, 0, 3, 3)
|
||||
img.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{emptyImage}, vs, is, graphicsdriver.BlendClear, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
}
|
||||
|
||||
func TestShader(t *testing.T) {
|
||||
@ -55,7 +56,7 @@ func TestShader(t *testing.T) {
|
||||
|
||||
s := restorable.NewShader(etesting.ShaderProgramFill(0xff, 0, 0, 0xff), "")
|
||||
dr := image.Rect(0, 0, 1, 1)
|
||||
img.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{}, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s, nil, graphicsdriver.FillRuleFillAll)
|
||||
img.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{}, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
|
||||
if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil {
|
||||
t.Fatal(err)
|
||||
@ -85,7 +86,8 @@ func TestShaderChain(t *testing.T) {
|
||||
s := restorable.NewShader(etesting.ShaderProgramImages(1), "")
|
||||
for i := 0; i < num-1; i++ {
|
||||
dr := image.Rect(0, 0, 1, 1)
|
||||
imgs[i+1].DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{imgs[i]}, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s, nil, graphicsdriver.FillRuleFillAll)
|
||||
sr := image.Rect(0, 0, 1, 1)
|
||||
imgs[i+1].DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{imgs[i]}, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, s, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
}
|
||||
|
||||
if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil {
|
||||
@ -117,7 +119,12 @@ func TestShaderMultipleSources(t *testing.T) {
|
||||
|
||||
s := restorable.NewShader(etesting.ShaderProgramImages(3), "")
|
||||
dr := image.Rect(0, 0, 1, 1)
|
||||
dst.DrawTriangles(srcs, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s, nil, graphicsdriver.FillRuleFillAll)
|
||||
srs := [graphics.ShaderSrcImageCount]image.Rectangle{
|
||||
image.Rect(0, 0, 1, 1),
|
||||
image.Rect(0, 0, 1, 1),
|
||||
image.Rect(0, 0, 1, 1),
|
||||
}
|
||||
dst.DrawTriangles(srcs, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), graphicsdriver.BlendCopy, dr, srs, s, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
|
||||
// Clear one of the sources after DrawTriangles. dst should not be affected.
|
||||
clearImage(srcs[0], 1, 1)
|
||||
@ -154,7 +161,7 @@ func TestShaderMultipleSourcesOnOneTexture(t *testing.T) {
|
||||
image.Rect(1, 0, 2, 1),
|
||||
image.Rect(2, 0, 3, 1),
|
||||
}
|
||||
dst.DrawTriangles(srcs, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), graphicsdriver.BlendCopy, dr, srcRegions, s, nil, graphicsdriver.FillRuleFillAll)
|
||||
dst.DrawTriangles(srcs, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), graphicsdriver.BlendCopy, dr, srcRegions, s, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
|
||||
// Clear one of the sources after DrawTriangles. dst should not be affected.
|
||||
clearImage(srcs[0], 3, 1)
|
||||
@ -179,7 +186,7 @@ func TestShaderDispose(t *testing.T) {
|
||||
|
||||
s := restorable.NewShader(etesting.ShaderProgramFill(0xff, 0, 0, 0xff), "")
|
||||
dr := image.Rect(0, 0, 1, 1)
|
||||
img.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{}, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s, nil, graphicsdriver.FillRuleFillAll)
|
||||
img.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{}, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
|
||||
// Dispose the shader. This should invalidate all the images using this shader i.e., all the images become
|
||||
// stale.
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/mipmap"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/restorable"
|
||||
)
|
||||
|
||||
// panicOnErrorOnReadingPixels indicates whether reading pixels panics on an error or not.
|
||||
@ -77,7 +78,7 @@ func (i *Image) Deallocate() {
|
||||
i.mipmap.Deallocate()
|
||||
}
|
||||
|
||||
func (i *Image) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule, canSkipMipmap bool, antialias bool) {
|
||||
func (i *Image) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule, canSkipMipmap bool, antialias bool, hint restorable.Hint) {
|
||||
if i.modifyCallback != nil {
|
||||
i.modifyCallback()
|
||||
}
|
||||
@ -113,7 +114,7 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertice
|
||||
srcMipmaps[i] = src.mipmap
|
||||
}
|
||||
|
||||
i.mipmap.DrawTriangles(srcMipmaps, vertices, indices, blend, dstRegion, srcRegions, shader.shader, uniforms, fillRule, canSkipMipmap)
|
||||
i.mipmap.DrawTriangles(srcMipmaps, vertices, indices, blend, dstRegion, srcRegions, shader.shader, uniforms, fillRule, canSkipMipmap, hint)
|
||||
}
|
||||
|
||||
func (i *Image) WritePixels(pix []byte, region image.Rectangle) {
|
||||
@ -182,8 +183,9 @@ func (i *Image) Fill(r, g, b, a float32, region image.Rectangle) {
|
||||
if a == 1 && i.lastBlend == graphicsdriver.BlendSourceOver {
|
||||
blend = graphicsdriver.BlendSourceOver
|
||||
}
|
||||
sr := image.Rect(0, 0, i.ui.whiteImage.width, i.ui.whiteImage.height)
|
||||
// i.lastBlend is updated in DrawTriangles.
|
||||
i.DrawTriangles(srcs, i.tmpVerticesForFill, is, blend, region, [graphics.ShaderSrcImageCount]image.Rectangle{}, NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, true, false)
|
||||
i.DrawTriangles(srcs, i.tmpVerticesForFill, is, blend, region, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, true, false, restorable.HintOverwriteDstRegion)
|
||||
}
|
||||
|
||||
type bigOffscreenImage struct {
|
||||
@ -252,7 +254,8 @@ func (i *bigOffscreenImage) drawTriangles(srcs [graphics.ShaderSrcImageCount]*Im
|
||||
1, 1, 1, 1)
|
||||
is := graphics.QuadIndices()
|
||||
dstRegion := image.Rect(0, 0, i.region.Dx()*bigOffscreenScale, i.region.Dy()*bigOffscreenScale)
|
||||
i.image.DrawTriangles(srcs, i.tmpVerticesForCopying, is, graphicsdriver.BlendCopy, dstRegion, [graphics.ShaderSrcImageCount]image.Rectangle{}, NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, true, false)
|
||||
srcRegion := i.region
|
||||
i.image.DrawTriangles(srcs, i.tmpVerticesForCopying, is, graphicsdriver.BlendCopy, dstRegion, [graphics.ShaderSrcImageCount]image.Rectangle{srcRegion}, NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, true, false, restorable.HintOverwriteDstRegion)
|
||||
}
|
||||
|
||||
for idx := 0; idx < len(vertices); idx += graphics.VertexFloatCount {
|
||||
@ -268,7 +271,7 @@ func (i *bigOffscreenImage) drawTriangles(srcs [graphics.ShaderSrcImageCount]*Im
|
||||
dstRegion.Max.X *= bigOffscreenScale
|
||||
dstRegion.Max.Y *= bigOffscreenScale
|
||||
|
||||
i.image.DrawTriangles(srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule, canSkipMipmap, false)
|
||||
i.image.DrawTriangles(srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule, canSkipMipmap, false, restorable.HintNone)
|
||||
i.dirty = true
|
||||
}
|
||||
|
||||
@ -296,11 +299,14 @@ func (i *bigOffscreenImage) flush() {
|
||||
1, 1, 1, 1)
|
||||
is := graphics.QuadIndices()
|
||||
dstRegion := i.region
|
||||
srcRegion := image.Rect(0, 0, i.region.Dx()*bigOffscreenScale, i.region.Dy()*bigOffscreenScale)
|
||||
blend := graphicsdriver.BlendSourceOver
|
||||
hint := restorable.HintNone
|
||||
if i.blend != graphicsdriver.BlendSourceOver {
|
||||
blend = graphicsdriver.BlendCopy
|
||||
hint = restorable.HintOverwriteDstRegion
|
||||
}
|
||||
i.orig.DrawTriangles(srcs, i.tmpVerticesForFlushing, is, blend, dstRegion, [graphics.ShaderSrcImageCount]image.Rectangle{}, LinearFilterShader, nil, graphicsdriver.FillRuleFillAll, true, false)
|
||||
i.orig.DrawTriangles(srcs, i.tmpVerticesForFlushing, is, blend, dstRegion, [graphics.ShaderSrcImageCount]image.Rectangle{srcRegion}, LinearFilterShader, nil, graphicsdriver.FillRuleFillAll, true, false, hint)
|
||||
|
||||
i.image.clear()
|
||||
i.dirty = false
|
||||
|
297
text/v2/atlas.go
Normal file
297
text/v2/atlas.go
Normal file
@ -0,0 +1,297 @@
|
||||
// Copyright 2024 The Ebitengine 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 text
|
||||
|
||||
import (
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/packing"
|
||||
)
|
||||
|
||||
type glyphAtlas struct {
|
||||
page *packing.Page
|
||||
image *ebiten.Image
|
||||
}
|
||||
|
||||
type glyphImage struct {
|
||||
atlas *glyphAtlas
|
||||
node *packing.Node
|
||||
img *ebiten.Image
|
||||
}
|
||||
|
||||
func (i *glyphImage) Image() *ebiten.Image {
|
||||
return i.img
|
||||
}
|
||||
|
||||
func newGlyphAtlas() *glyphAtlas {
|
||||
return &glyphAtlas{
|
||||
// Note: 128x128 is arbitrary, maybe a better value can be inferred
|
||||
// from the font size or something
|
||||
page: packing.NewPage(128, 128, 1024), // TODO: not 1024
|
||||
image: ebiten.NewImage(128, 128),
|
||||
}
|
||||
}
|
||||
|
||||
func (g *glyphAtlas) NewImage(w, h int) *glyphImage {
|
||||
n := g.page.Alloc(w, h)
|
||||
pw, ph := g.page.Size()
|
||||
if pw > g.image.Bounds().Dx() || ph > g.image.Bounds().Dy() {
|
||||
newImage := ebiten.NewImage(pw, ph)
|
||||
newImage.DrawImage(g.image, nil)
|
||||
g.image = newImage
|
||||
}
|
||||
|
||||
return &glyphImage{
|
||||
atlas: g,
|
||||
node: n,
|
||||
img: g.image.SubImage(n.Region()).(*ebiten.Image),
|
||||
}
|
||||
}
|
||||
|
||||
func (g *glyphAtlas) Free(img *glyphImage) {
|
||||
g.page.Free(img.node)
|
||||
}
|
||||
|
||||
type drawRange struct {
|
||||
atlas *glyphAtlas
|
||||
end int
|
||||
}
|
||||
|
||||
// drawList stores triangle versions of DrawImage calls when
|
||||
// all images are sub-images of an atlas.
|
||||
// Temporary vertices and indices can be re-used after calling
|
||||
// Flush, so it is more efficient to keep a reference to a drawList
|
||||
// instead of creating a new one every frame.
|
||||
type drawList struct {
|
||||
ranges []drawRange
|
||||
vx []ebiten.Vertex
|
||||
ix []uint16
|
||||
}
|
||||
|
||||
// drawCommand is the equivalent of the regular DrawImageOptions
|
||||
// but only including options that will not break batching.
|
||||
// Filter, Address, Blend and AntiAlias are determined at Flush()
|
||||
type drawCommand struct {
|
||||
Image *glyphImage
|
||||
|
||||
ColorScale ebiten.ColorScale
|
||||
GeoM ebiten.GeoM
|
||||
}
|
||||
|
||||
var rectIndices = [6]uint16{0, 1, 2, 1, 2, 3}
|
||||
|
||||
type point struct {
|
||||
X, Y float32
|
||||
}
|
||||
|
||||
func pt(x, y float64) point {
|
||||
return point{
|
||||
X: float32(x),
|
||||
Y: float32(y),
|
||||
}
|
||||
}
|
||||
|
||||
type rectOpts struct {
|
||||
Dsts [4]point
|
||||
SrcX0, SrcY0 float32
|
||||
SrcX1, SrcY1 float32
|
||||
R, G, B, A float32
|
||||
}
|
||||
|
||||
// adjustDestinationPixel is the original ebitengine implementation found here:
|
||||
// https://github.com/hajimehoshi/ebiten/blob/v2.8.0-alpha.1/internal/graphics/vertex.go#L102-L126
|
||||
func adjustDestinationPixel(x float32) float32 {
|
||||
// Avoid the center of the pixel, which is problematic (#929, #1171).
|
||||
// Instead, align the vertices with about 1/3 pixels.
|
||||
//
|
||||
// The intention here is roughly this code:
|
||||
//
|
||||
// float32(math.Floor((float64(x)+1.0/6.0)*3) / 3)
|
||||
//
|
||||
// The actual implementation is more optimized than the above implementation.
|
||||
ix := float32(int(x))
|
||||
if x < 0 && x != ix {
|
||||
ix -= 1
|
||||
}
|
||||
frac := x - ix
|
||||
switch {
|
||||
case frac < 3.0/16.0:
|
||||
return ix
|
||||
case frac < 8.0/16.0:
|
||||
return ix + 5.0/16.0
|
||||
case frac < 13.0/16.0:
|
||||
return ix + 11.0/16.0
|
||||
default:
|
||||
return ix + 16.0/16.0
|
||||
}
|
||||
}
|
||||
|
||||
func appendRectVerticesIndices(vertices []ebiten.Vertex, indices []uint16, index int, opts *rectOpts) ([]ebiten.Vertex, []uint16) {
|
||||
sx0, sy0, sx1, sy1 := opts.SrcX0, opts.SrcY0, opts.SrcX1, opts.SrcY1
|
||||
r, g, b, a := opts.R, opts.G, opts.B, opts.A
|
||||
vertices = append(vertices,
|
||||
ebiten.Vertex{
|
||||
DstX: adjustDestinationPixel(opts.Dsts[0].X),
|
||||
DstY: adjustDestinationPixel(opts.Dsts[0].Y),
|
||||
SrcX: sx0,
|
||||
SrcY: sy0,
|
||||
ColorR: r,
|
||||
ColorG: g,
|
||||
ColorB: b,
|
||||
ColorA: a,
|
||||
},
|
||||
ebiten.Vertex{
|
||||
DstX: adjustDestinationPixel(opts.Dsts[1].X),
|
||||
DstY: adjustDestinationPixel(opts.Dsts[1].Y),
|
||||
SrcX: sx1,
|
||||
SrcY: sy0,
|
||||
ColorR: r,
|
||||
ColorG: g,
|
||||
ColorB: b,
|
||||
ColorA: a,
|
||||
},
|
||||
ebiten.Vertex{
|
||||
DstX: adjustDestinationPixel(opts.Dsts[2].X),
|
||||
DstY: adjustDestinationPixel(opts.Dsts[2].Y),
|
||||
SrcX: sx0,
|
||||
SrcY: sy1,
|
||||
ColorR: r,
|
||||
ColorG: g,
|
||||
ColorB: b,
|
||||
ColorA: a,
|
||||
},
|
||||
ebiten.Vertex{
|
||||
DstX: adjustDestinationPixel(opts.Dsts[3].X),
|
||||
DstY: adjustDestinationPixel(opts.Dsts[3].Y),
|
||||
SrcX: sx1,
|
||||
SrcY: sy1,
|
||||
ColorR: r,
|
||||
ColorG: g,
|
||||
ColorB: b,
|
||||
ColorA: a,
|
||||
},
|
||||
)
|
||||
|
||||
indiceCursor := uint16(index * 4)
|
||||
indices = append(indices,
|
||||
rectIndices[0]+indiceCursor,
|
||||
rectIndices[1]+indiceCursor,
|
||||
rectIndices[2]+indiceCursor,
|
||||
rectIndices[3]+indiceCursor,
|
||||
rectIndices[4]+indiceCursor,
|
||||
rectIndices[5]+indiceCursor,
|
||||
)
|
||||
|
||||
return vertices, indices
|
||||
}
|
||||
|
||||
// Add adds DrawImage commands to the DrawList, images from multiple
|
||||
// atlases can be added but they will break the previous batch bound to
|
||||
// a different atlas, requiring an additional draw call internally.
|
||||
// So, it is better to have the maximum of consecutive DrawCommand images
|
||||
// sharing the same atlas.
|
||||
func (dl *drawList) Add(commands ...*drawCommand) {
|
||||
if len(commands) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var batch *drawRange
|
||||
|
||||
if len(dl.ranges) > 0 {
|
||||
batch = &dl.ranges[len(dl.ranges)-1]
|
||||
} else {
|
||||
dl.ranges = append(dl.ranges, drawRange{
|
||||
atlas: commands[0].Image.atlas,
|
||||
})
|
||||
batch = &dl.ranges[0]
|
||||
}
|
||||
// Add vertices and indices
|
||||
opts := &rectOpts{}
|
||||
for _, cmd := range commands {
|
||||
if cmd.Image.atlas != batch.atlas {
|
||||
dl.ranges = append(dl.ranges, drawRange{
|
||||
atlas: cmd.Image.atlas,
|
||||
})
|
||||
batch = &dl.ranges[len(dl.ranges)-1]
|
||||
}
|
||||
|
||||
// Dst attributes
|
||||
bounds := cmd.Image.node.Region()
|
||||
opts.Dsts[0] = pt(cmd.GeoM.Apply(0, 0))
|
||||
opts.Dsts[1] = pt(cmd.GeoM.Apply(
|
||||
float64(bounds.Dx()), 0,
|
||||
))
|
||||
opts.Dsts[2] = pt(cmd.GeoM.Apply(
|
||||
0, float64(bounds.Dy()),
|
||||
))
|
||||
opts.Dsts[3] = pt(cmd.GeoM.Apply(
|
||||
float64(bounds.Dx()), float64(bounds.Dy()),
|
||||
))
|
||||
|
||||
// Color and source attributes
|
||||
opts.R = cmd.ColorScale.R()
|
||||
opts.G = cmd.ColorScale.G()
|
||||
opts.B = cmd.ColorScale.B()
|
||||
opts.A = cmd.ColorScale.A()
|
||||
opts.SrcX0 = float32(bounds.Min.X)
|
||||
opts.SrcY0 = float32(bounds.Min.Y)
|
||||
opts.SrcX1 = float32(bounds.Max.X)
|
||||
opts.SrcY1 = float32(bounds.Max.Y)
|
||||
|
||||
dl.vx, dl.ix = appendRectVerticesIndices(
|
||||
dl.vx, dl.ix, batch.end, opts,
|
||||
)
|
||||
|
||||
batch.end++
|
||||
}
|
||||
}
|
||||
|
||||
// DrawOptions are additional options that will be applied to
|
||||
// all draw commands from the draw list when calling Flush().
|
||||
type drawOptions struct {
|
||||
ColorScaleMode ebiten.ColorScaleMode
|
||||
Blend ebiten.Blend
|
||||
Filter ebiten.Filter
|
||||
Address ebiten.Address
|
||||
AntiAlias bool
|
||||
}
|
||||
|
||||
// Flush executes all the draw commands as the smallest possible
|
||||
// amount of draw calls, and then clears the list for next uses.
|
||||
func (dl *drawList) Flush(dst *ebiten.Image, opts *drawOptions) {
|
||||
var topts *ebiten.DrawTrianglesOptions
|
||||
if opts != nil {
|
||||
topts = &ebiten.DrawTrianglesOptions{
|
||||
ColorScaleMode: opts.ColorScaleMode,
|
||||
Blend: opts.Blend,
|
||||
Filter: opts.Filter,
|
||||
Address: opts.Address,
|
||||
AntiAlias: opts.AntiAlias,
|
||||
}
|
||||
}
|
||||
index := 0
|
||||
for _, r := range dl.ranges {
|
||||
dst.DrawTriangles(
|
||||
dl.vx[index*4:(index+r.end)*4],
|
||||
dl.ix[index*6:(index+r.end)*6],
|
||||
r.atlas.image,
|
||||
topts,
|
||||
)
|
||||
index += r.end
|
||||
}
|
||||
// Clear buffers
|
||||
dl.ranges = dl.ranges[:0]
|
||||
dl.vx = dl.vx[:0]
|
||||
dl.ix = dl.ix[:0]
|
||||
}
|
@ -18,7 +18,6 @@ import (
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/hook"
|
||||
)
|
||||
|
||||
@ -38,17 +37,18 @@ func init() {
|
||||
}
|
||||
|
||||
type glyphImageCacheEntry struct {
|
||||
image *ebiten.Image
|
||||
image *glyphImage
|
||||
atime int64
|
||||
}
|
||||
|
||||
type glyphImageCache[Key comparable] struct {
|
||||
atlas *glyphAtlas
|
||||
cache map[Key]*glyphImageCacheEntry
|
||||
atime int64
|
||||
m sync.Mutex
|
||||
}
|
||||
|
||||
func (g *glyphImageCache[Key]) getOrCreate(face Face, key Key, create func() *ebiten.Image) *ebiten.Image {
|
||||
func (g *glyphImageCache[Key]) getOrCreate(face Face, key Key, create func(a *glyphAtlas) *glyphImage) *glyphImage {
|
||||
g.m.Lock()
|
||||
defer g.m.Unlock()
|
||||
|
||||
@ -61,10 +61,11 @@ func (g *glyphImageCache[Key]) getOrCreate(face Face, key Key, create func() *eb
|
||||
}
|
||||
|
||||
if g.cache == nil {
|
||||
g.atlas = newGlyphAtlas()
|
||||
g.cache = map[Key]*glyphImageCacheEntry{}
|
||||
}
|
||||
|
||||
img := create()
|
||||
img := create(g.atlas)
|
||||
e = &glyphImageCacheEntry{
|
||||
image: img,
|
||||
}
|
||||
@ -91,6 +92,7 @@ func (g *glyphImageCache[Key]) getOrCreate(face Face, key Key, create func() *eb
|
||||
continue
|
||||
}
|
||||
delete(g.cache, key)
|
||||
g.atlas.Free(e.image)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -311,11 +311,16 @@ func (g *GoTextFace) appendGlyphsForLine(glyphs []Glyph, line string, indexOffse
|
||||
img, imgX, imgY := g.glyphImage(glyph, o)
|
||||
// Append a glyph even if img is nil.
|
||||
// This is necessary to return index information for control characters.
|
||||
var ebitenImage *ebiten.Image
|
||||
if img != nil {
|
||||
ebitenImage = img.Image()
|
||||
}
|
||||
glyphs = append(glyphs, Glyph{
|
||||
img: img,
|
||||
StartIndexInBytes: indexOffset + glyph.startIndex,
|
||||
EndIndexInBytes: indexOffset + glyph.endIndex,
|
||||
GID: uint32(glyph.shapingGlyph.GlyphID),
|
||||
Image: img,
|
||||
Image: ebitenImage,
|
||||
X: float64(imgX),
|
||||
Y: float64(imgY),
|
||||
OriginX: fixed26_6ToFloat64(origin.X),
|
||||
@ -332,7 +337,7 @@ func (g *GoTextFace) appendGlyphsForLine(glyphs []Glyph, line string, indexOffse
|
||||
return glyphs
|
||||
}
|
||||
|
||||
func (g *GoTextFace) glyphImage(glyph glyph, origin fixed.Point26_6) (*ebiten.Image, int, int) {
|
||||
func (g *GoTextFace) glyphImage(glyph glyph, origin fixed.Point26_6) (*glyphImage, int, int) {
|
||||
if g.direction().isHorizontal() {
|
||||
origin.X = adjustGranularity(origin.X, g)
|
||||
origin.Y &^= ((1 << 6) - 1)
|
||||
@ -352,8 +357,8 @@ func (g *GoTextFace) glyphImage(glyph glyph, origin fixed.Point26_6) (*ebiten.Im
|
||||
yoffset: subpixelOffset.Y,
|
||||
variations: g.ensureVariationsString(),
|
||||
}
|
||||
img := g.Source.getOrCreateGlyphImage(g, key, func() *ebiten.Image {
|
||||
return segmentsToImage(glyph.scaledSegments, subpixelOffset, b)
|
||||
img := g.Source.getOrCreateGlyphImage(g, key, func(a *glyphAtlas) *glyphImage {
|
||||
return segmentsToImage(a, glyph.scaledSegments, subpixelOffset, b)
|
||||
})
|
||||
|
||||
imgX := (origin.X + b.Min.X).Floor()
|
||||
|
@ -26,8 +26,6 @@ import (
|
||||
"github.com/go-text/typesetting/opentype/loader"
|
||||
"github.com/go-text/typesetting/shaping"
|
||||
"golang.org/x/image/math/fixed"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
type goTextOutputCacheKey struct {
|
||||
@ -282,7 +280,7 @@ func (g *GoTextFaceSource) scale(size float64) float64 {
|
||||
return size / float64(g.f.Upem())
|
||||
}
|
||||
|
||||
func (g *GoTextFaceSource) getOrCreateGlyphImage(goTextFace *GoTextFace, key goTextGlyphImageCacheKey, create func() *ebiten.Image) *ebiten.Image {
|
||||
func (g *GoTextFaceSource) getOrCreateGlyphImage(goTextFace *GoTextFace, key goTextGlyphImageCacheKey, create func(a *glyphAtlas) *glyphImage) *glyphImage {
|
||||
if g.glyphImageCache == nil {
|
||||
g.glyphImageCache = map[float64]*glyphImageCache[goTextGlyphImageCacheKey]{}
|
||||
}
|
||||
|
@ -19,11 +19,11 @@ import (
|
||||
"image/draw"
|
||||
"math"
|
||||
|
||||
"github.com/go-text/typesetting/opentype/api"
|
||||
"golang.org/x/image/math/fixed"
|
||||
gvector "golang.org/x/image/vector"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/go-text/typesetting/opentype/api"
|
||||
"golang.org/x/image/math/fixed"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/vector"
|
||||
)
|
||||
|
||||
@ -75,7 +75,7 @@ func segmentsToBounds(segs []api.Segment) fixed.Rectangle26_6 {
|
||||
}
|
||||
}
|
||||
|
||||
func segmentsToImage(segs []api.Segment, subpixelOffset fixed.Point26_6, glyphBounds fixed.Rectangle26_6) *ebiten.Image {
|
||||
func segmentsToImage(a *glyphAtlas, segs []api.Segment, subpixelOffset fixed.Point26_6, glyphBounds fixed.Rectangle26_6) *glyphImage {
|
||||
if len(segs) == 0 {
|
||||
return nil
|
||||
}
|
||||
@ -122,7 +122,10 @@ func segmentsToImage(segs []api.Segment, subpixelOffset fixed.Point26_6, glyphBo
|
||||
|
||||
dst := image.NewRGBA(image.Rect(0, 0, w, h))
|
||||
rast.Draw(dst, dst.Bounds(), image.Opaque, image.Point{})
|
||||
return ebiten.NewImageFromImage(dst)
|
||||
img := a.NewImage(w, h)
|
||||
img.Image().WritePixels(dst.Pix)
|
||||
|
||||
return img
|
||||
}
|
||||
|
||||
func appendVectorPathFromSegments(path *vector.Path, segs []api.Segment, x, y float32) {
|
||||
|
@ -21,7 +21,6 @@ import (
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/math/fixed"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/vector"
|
||||
)
|
||||
|
||||
@ -119,9 +118,10 @@ func (s *GoXFace) appendGlyphsForLine(glyphs []Glyph, line string, indexOffset i
|
||||
// Append a glyph even if img is nil.
|
||||
// This is necessary to return index information for control characters.
|
||||
glyphs = append(glyphs, Glyph{
|
||||
img: img,
|
||||
StartIndexInBytes: indexOffset + i,
|
||||
EndIndexInBytes: indexOffset + i + size,
|
||||
Image: img,
|
||||
Image: img.Image(),
|
||||
X: float64(imgX),
|
||||
Y: float64(imgY),
|
||||
OriginX: fixed26_6ToFloat64(origin.X),
|
||||
@ -136,7 +136,7 @@ func (s *GoXFace) appendGlyphsForLine(glyphs []Glyph, line string, indexOffset i
|
||||
return glyphs
|
||||
}
|
||||
|
||||
func (s *GoXFace) glyphImage(r rune, origin fixed.Point26_6) (*ebiten.Image, int, int, fixed.Int26_6) {
|
||||
func (s *GoXFace) glyphImage(r rune, origin fixed.Point26_6) (*glyphImage, int, int, fixed.Int26_6) {
|
||||
// Assume that GoXFace's direction is always horizontal.
|
||||
origin.X = adjustGranularity(origin.X, s)
|
||||
origin.Y &^= ((1 << 6) - 1)
|
||||
@ -150,15 +150,15 @@ func (s *GoXFace) glyphImage(r rune, origin fixed.Point26_6) (*ebiten.Image, int
|
||||
rune: r,
|
||||
xoffset: subpixelOffset.X,
|
||||
}
|
||||
img := s.glyphImageCache.getOrCreate(s, key, func() *ebiten.Image {
|
||||
return s.glyphImageImpl(r, subpixelOffset, b)
|
||||
img := s.glyphImageCache.getOrCreate(s, key, func(a *glyphAtlas) *glyphImage {
|
||||
return s.glyphImageImpl(a, r, subpixelOffset, b)
|
||||
})
|
||||
imgX := (origin.X + b.Min.X).Floor()
|
||||
imgY := (origin.Y + b.Min.Y).Floor()
|
||||
return img, imgX, imgY, a
|
||||
}
|
||||
|
||||
func (s *GoXFace) glyphImageImpl(r rune, subpixelOffset fixed.Point26_6, glyphBounds fixed.Rectangle26_6) *ebiten.Image {
|
||||
func (s *GoXFace) glyphImageImpl(a *glyphAtlas, r rune, subpixelOffset fixed.Point26_6, glyphBounds fixed.Rectangle26_6) *glyphImage {
|
||||
w, h := (glyphBounds.Max.X - glyphBounds.Min.X).Ceil(), (glyphBounds.Max.Y - glyphBounds.Min.Y).Ceil()
|
||||
if w == 0 || h == 0 {
|
||||
return nil
|
||||
@ -182,7 +182,10 @@ func (s *GoXFace) glyphImageImpl(r rune, subpixelOffset fixed.Point26_6, glyphBo
|
||||
}
|
||||
d.DrawString(string(r))
|
||||
|
||||
return ebiten.NewImageFromImage(rgba)
|
||||
img := a.NewImage(w, h)
|
||||
img.Image().WritePixels(rgba.Pix)
|
||||
|
||||
return img
|
||||
}
|
||||
|
||||
// direction implements Face.
|
||||
|
@ -111,15 +111,24 @@ func Draw(dst *ebiten.Image, text string, face Face, options *DrawOptions) {
|
||||
|
||||
geoM := drawOp.GeoM
|
||||
|
||||
dl := &drawList{}
|
||||
dc := &drawCommand{}
|
||||
for _, g := range AppendGlyphs(nil, text, face, &layoutOp) {
|
||||
if g.Image == nil {
|
||||
continue
|
||||
}
|
||||
drawOp.GeoM.Reset()
|
||||
drawOp.GeoM.Translate(g.X, g.Y)
|
||||
drawOp.GeoM.Concat(geoM)
|
||||
dst.DrawImage(g.Image, &drawOp)
|
||||
dc.GeoM.Reset()
|
||||
dc.GeoM.Translate(g.X, g.Y)
|
||||
dc.GeoM.Concat(geoM)
|
||||
dc.ColorScale = drawOp.ColorScale
|
||||
dc.Image = g.img
|
||||
dl.Add(dc)
|
||||
}
|
||||
dl.Flush(dst, &drawOptions{
|
||||
Blend: drawOp.Blend,
|
||||
Filter: drawOp.Filter,
|
||||
ColorScaleMode: ebiten.ColorScaleModePremultipliedAlpha,
|
||||
})
|
||||
}
|
||||
|
||||
// AppendGlyphs appends glyphs to the given slice and returns a slice.
|
||||
|
@ -115,6 +115,11 @@ func adjustGranularity(x fixed.Int26_6, face Face) fixed.Int26_6 {
|
||||
|
||||
// Glyph represents one glyph to render.
|
||||
type Glyph struct {
|
||||
// Image is a rasterized glyph image.
|
||||
// Image is a grayscale image i.e. RGBA values are the same.
|
||||
// Image should be used as a render source and should not be modified.
|
||||
img *glyphImage
|
||||
|
||||
// StartIndexInBytes is the start index in bytes for the given string at AppendGlyphs.
|
||||
StartIndexInBytes int
|
||||
|
||||
|
@ -15,6 +15,7 @@
|
||||
package text_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"image"
|
||||
"image/color"
|
||||
"regexp"
|
||||
@ -23,6 +24,7 @@ import (
|
||||
|
||||
"github.com/hajimehoshi/bitmapfont/v3"
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/font/gofont/goregular"
|
||||
"golang.org/x/image/math/fixed"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
@ -371,3 +373,23 @@ func TestDrawOptionsNotModified(t *testing.T) {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDrawText(b *testing.B) {
|
||||
var txt string
|
||||
for i := 0; i < 32; i++ {
|
||||
txt += "The quick brown fox jumps over the lazy dog.\n"
|
||||
}
|
||||
screen := ebiten.NewImage(16, 16)
|
||||
source, err := text.NewGoTextFaceSource(bytes.NewReader(goregular.TTF))
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
f := &text.GoTextFace{
|
||||
Source: source,
|
||||
Size: 10,
|
||||
}
|
||||
op := &text.DrawOptions{}
|
||||
for i := 0; i < b.N; i++ {
|
||||
text.Draw(screen, txt, f, op)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user