mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-25 03:08:54 +01:00
ebiten: add DrawTrianglesOptions.AntiAlias
and DrawTrianglesShaderOptions.AntiAlias
Closes #2385
This commit is contained in:
parent
8b16badb83
commit
9ec23ddeb4
@ -57,8 +57,6 @@ type Game struct {
|
|||||||
vertices []ebiten.Vertex
|
vertices []ebiten.Vertex
|
||||||
indices []uint16
|
indices []uint16
|
||||||
|
|
||||||
offscreen *ebiten.Image
|
|
||||||
|
|
||||||
aa bool
|
aa bool
|
||||||
showCenter bool
|
showCenter bool
|
||||||
}
|
}
|
||||||
@ -76,24 +74,6 @@ func (g *Game) Update() error {
|
|||||||
|
|
||||||
func (g *Game) Draw(screen *ebiten.Image) {
|
func (g *Game) Draw(screen *ebiten.Image) {
|
||||||
target := screen
|
target := screen
|
||||||
if g.aa {
|
|
||||||
// Prepare the double-sized offscreen.
|
|
||||||
// This is for anti-aliasing by a pseudo MSAA (multisample anti-aliasing).
|
|
||||||
if g.offscreen != nil {
|
|
||||||
sw, sh := screen.Size()
|
|
||||||
ow, oh := g.offscreen.Size()
|
|
||||||
if ow != sw*2 || oh != sh*2 {
|
|
||||||
g.offscreen.Dispose()
|
|
||||||
g.offscreen = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if g.offscreen == nil {
|
|
||||||
sw, sh := screen.Size()
|
|
||||||
g.offscreen = ebiten.NewImage(sw*2, sh*2)
|
|
||||||
}
|
|
||||||
g.offscreen.Clear()
|
|
||||||
target = g.offscreen
|
|
||||||
}
|
|
||||||
|
|
||||||
joins := []vector.LineJoin{
|
joins := []vector.LineJoin{
|
||||||
vector.LineJoinMiter,
|
vector.LineJoinMiter,
|
||||||
@ -123,14 +103,6 @@ func (g *Game) Draw(screen *ebiten.Image) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if g.aa {
|
|
||||||
// Render the offscreen to the screen.
|
|
||||||
op := &ebiten.DrawImageOptions{}
|
|
||||||
op.GeoM.Scale(0.5, 0.5)
|
|
||||||
op.Filter = ebiten.FilterLinear
|
|
||||||
screen.DrawImage(g.offscreen, op)
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := fmt.Sprintf(`FPS: %0.2f, TPS: %0.2f
|
msg := fmt.Sprintf(`FPS: %0.2f, TPS: %0.2f
|
||||||
Press A to switch anti-aliasing.
|
Press A to switch anti-aliasing.
|
||||||
Press C to switch to draw the center lines.`, ebiten.ActualFPS(), ebiten.ActualTPS())
|
Press C to switch to draw the center lines.`, ebiten.ActualFPS(), ebiten.ActualTPS())
|
||||||
@ -165,7 +137,9 @@ func (g *Game) drawLine(screen *ebiten.Image, region image.Rectangle, cap vector
|
|||||||
vs[i].SrcX = 1
|
vs[i].SrcX = 1
|
||||||
vs[i].SrcY = 1
|
vs[i].SrcY = 1
|
||||||
}
|
}
|
||||||
screen.DrawTriangles(vs, is, emptySubImage, nil)
|
screen.DrawTriangles(vs, is, emptySubImage, &ebiten.DrawTrianglesOptions{
|
||||||
|
AntiAlias: g.aa,
|
||||||
|
})
|
||||||
|
|
||||||
// Draw the center line in red.
|
// Draw the center line in red.
|
||||||
if g.showCenter {
|
if g.showCenter {
|
||||||
@ -180,7 +154,9 @@ func (g *Game) drawLine(screen *ebiten.Image, region image.Rectangle, cap vector
|
|||||||
vs[i].ColorG = 0
|
vs[i].ColorG = 0
|
||||||
vs[i].ColorB = 0
|
vs[i].ColorB = 0
|
||||||
}
|
}
|
||||||
screen.DrawTriangles(vs, is, emptySubImage, nil)
|
screen.DrawTriangles(vs, is, emptySubImage, &ebiten.DrawTrianglesOptions{
|
||||||
|
AntiAlias: g.aa,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ const (
|
|||||||
screenHeight = 480
|
screenHeight = 480
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawEbitenText(screen *ebiten.Image, x, y int, scale float32, line bool) {
|
func drawEbitenText(screen *ebiten.Image, x, y int, aa bool, line bool) {
|
||||||
var path vector.Path
|
var path vector.Path
|
||||||
|
|
||||||
// E
|
// E
|
||||||
@ -125,8 +125,8 @@ func drawEbitenText(screen *ebiten.Image, x, y int, scale float32, line bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i := range vs {
|
for i := range vs {
|
||||||
vs[i].DstX = (vs[i].DstX + float32(x)) * scale
|
vs[i].DstX = (vs[i].DstX + float32(x))
|
||||||
vs[i].DstY = (vs[i].DstY + float32(y)) * scale
|
vs[i].DstY = (vs[i].DstY + float32(y))
|
||||||
vs[i].SrcX = 1
|
vs[i].SrcX = 1
|
||||||
vs[i].SrcY = 1
|
vs[i].SrcY = 1
|
||||||
vs[i].ColorR = 0xdb / float32(0xff)
|
vs[i].ColorR = 0xdb / float32(0xff)
|
||||||
@ -135,13 +135,14 @@ func drawEbitenText(screen *ebiten.Image, x, y int, scale float32, line bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
op := &ebiten.DrawTrianglesOptions{}
|
op := &ebiten.DrawTrianglesOptions{}
|
||||||
|
op.AntiAlias = aa
|
||||||
if !line {
|
if !line {
|
||||||
op.FillRule = ebiten.EvenOdd
|
op.FillRule = ebiten.EvenOdd
|
||||||
}
|
}
|
||||||
screen.DrawTriangles(vs, is, emptySubImage, op)
|
screen.DrawTriangles(vs, is, emptySubImage, op)
|
||||||
}
|
}
|
||||||
|
|
||||||
func drawEbitenLogo(screen *ebiten.Image, x, y int, scale float32, line bool) {
|
func drawEbitenLogo(screen *ebiten.Image, x, y int, aa bool, line bool) {
|
||||||
const unit = 16
|
const unit = 16
|
||||||
|
|
||||||
var path vector.Path
|
var path vector.Path
|
||||||
@ -178,8 +179,8 @@ func drawEbitenLogo(screen *ebiten.Image, x, y int, scale float32, line bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i := range vs {
|
for i := range vs {
|
||||||
vs[i].DstX = (vs[i].DstX + float32(x)) * scale
|
vs[i].DstX = (vs[i].DstX + float32(x))
|
||||||
vs[i].DstY = (vs[i].DstY + float32(y)) * scale
|
vs[i].DstY = (vs[i].DstY + float32(y))
|
||||||
vs[i].SrcX = 1
|
vs[i].SrcX = 1
|
||||||
vs[i].SrcY = 1
|
vs[i].SrcY = 1
|
||||||
vs[i].ColorR = 0xdb / float32(0xff)
|
vs[i].ColorR = 0xdb / float32(0xff)
|
||||||
@ -188,13 +189,14 @@ func drawEbitenLogo(screen *ebiten.Image, x, y int, scale float32, line bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
op := &ebiten.DrawTrianglesOptions{}
|
op := &ebiten.DrawTrianglesOptions{}
|
||||||
|
op.AntiAlias = aa
|
||||||
if !line {
|
if !line {
|
||||||
op.FillRule = ebiten.EvenOdd
|
op.FillRule = ebiten.EvenOdd
|
||||||
}
|
}
|
||||||
screen.DrawTriangles(vs, is, emptySubImage, op)
|
screen.DrawTriangles(vs, is, emptySubImage, op)
|
||||||
}
|
}
|
||||||
|
|
||||||
func drawArc(screen *ebiten.Image, count int, scale float32, line bool) {
|
func drawArc(screen *ebiten.Image, count int, aa bool, line bool) {
|
||||||
var path vector.Path
|
var path vector.Path
|
||||||
|
|
||||||
path.MoveTo(350, 100)
|
path.MoveTo(350, 100)
|
||||||
@ -220,8 +222,6 @@ func drawArc(screen *ebiten.Image, count int, scale float32, line bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i := range vs {
|
for i := range vs {
|
||||||
vs[i].DstX *= scale
|
|
||||||
vs[i].DstY *= scale
|
|
||||||
vs[i].SrcX = 1
|
vs[i].SrcX = 1
|
||||||
vs[i].SrcY = 1
|
vs[i].SrcY = 1
|
||||||
vs[i].ColorR = 0x33 / float32(0xff)
|
vs[i].ColorR = 0x33 / float32(0xff)
|
||||||
@ -230,6 +230,7 @@ func drawArc(screen *ebiten.Image, count int, scale float32, line bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
op := &ebiten.DrawTrianglesOptions{}
|
op := &ebiten.DrawTrianglesOptions{}
|
||||||
|
op.AntiAlias = aa
|
||||||
if !line {
|
if !line {
|
||||||
op.FillRule = ebiten.EvenOdd
|
op.FillRule = ebiten.EvenOdd
|
||||||
}
|
}
|
||||||
@ -240,7 +241,7 @@ func maxCounter(index int) int {
|
|||||||
return 128 + (17*index+32)%64
|
return 128 + (17*index+32)%64
|
||||||
}
|
}
|
||||||
|
|
||||||
func drawWave(screen *ebiten.Image, counter int, scale float32, line bool) {
|
func drawWave(screen *ebiten.Image, counter int, aa bool, line bool) {
|
||||||
var path vector.Path
|
var path vector.Path
|
||||||
|
|
||||||
const npoints = 8
|
const npoints = 8
|
||||||
@ -277,8 +278,6 @@ func drawWave(screen *ebiten.Image, counter int, scale float32, line bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i := range vs {
|
for i := range vs {
|
||||||
vs[i].DstX *= scale
|
|
||||||
vs[i].DstY *= scale
|
|
||||||
vs[i].SrcX = 1
|
vs[i].SrcX = 1
|
||||||
vs[i].SrcY = 1
|
vs[i].SrcY = 1
|
||||||
vs[i].ColorR = 0x33 / float32(0xff)
|
vs[i].ColorR = 0x33 / float32(0xff)
|
||||||
@ -287,6 +286,7 @@ func drawWave(screen *ebiten.Image, counter int, scale float32, line bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
op := &ebiten.DrawTrianglesOptions{}
|
op := &ebiten.DrawTrianglesOptions{}
|
||||||
|
op.AntiAlias = aa
|
||||||
if !line {
|
if !line {
|
||||||
op.FillRule = ebiten.EvenOdd
|
op.FillRule = ebiten.EvenOdd
|
||||||
}
|
}
|
||||||
@ -296,9 +296,8 @@ func drawWave(screen *ebiten.Image, counter int, scale float32, line bool) {
|
|||||||
type Game struct {
|
type Game struct {
|
||||||
counter int
|
counter int
|
||||||
|
|
||||||
aa bool
|
aa bool
|
||||||
line bool
|
line bool
|
||||||
offscreen *ebiten.Image
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) Update() error {
|
func (g *Game) Update() error {
|
||||||
@ -318,37 +317,13 @@ func (g *Game) Update() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) Draw(screen *ebiten.Image) {
|
func (g *Game) Draw(screen *ebiten.Image) {
|
||||||
if g.offscreen != nil {
|
|
||||||
w, h := screen.Size()
|
|
||||||
if ow, oh := g.offscreen.Size(); ow != w*2 || oh != h*2 {
|
|
||||||
g.offscreen.Dispose()
|
|
||||||
g.offscreen = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if g.aa && g.offscreen == nil {
|
|
||||||
w, h := screen.Size()
|
|
||||||
g.offscreen = ebiten.NewImage(w*2, h*2)
|
|
||||||
}
|
|
||||||
|
|
||||||
scale := float32(1)
|
|
||||||
dst := screen
|
dst := screen
|
||||||
if g.aa {
|
|
||||||
scale = 2
|
|
||||||
dst = g.offscreen
|
|
||||||
}
|
|
||||||
|
|
||||||
dst.Fill(color.RGBA{0xe0, 0xe0, 0xe0, 0xff})
|
dst.Fill(color.RGBA{0xe0, 0xe0, 0xe0, 0xff})
|
||||||
drawEbitenText(dst, 0, 50, scale, g.line)
|
drawEbitenText(dst, 0, 50, g.aa, g.line)
|
||||||
drawEbitenLogo(dst, 20, 150, scale, g.line)
|
drawEbitenLogo(dst, 20, 150, g.aa, g.line)
|
||||||
drawArc(dst, g.counter, scale, g.line)
|
drawArc(dst, g.counter, g.aa, g.line)
|
||||||
drawWave(dst, g.counter, scale, g.line)
|
drawWave(dst, g.counter, g.aa, g.line)
|
||||||
|
|
||||||
if g.aa {
|
|
||||||
op := &ebiten.DrawImageOptions{}
|
|
||||||
op.GeoM.Scale(0.5, 0.5)
|
|
||||||
op.Filter = ebiten.FilterLinear
|
|
||||||
screen.DrawImage(g.offscreen, op)
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := fmt.Sprintf("TPS: %0.2f\nFPS: %0.2f", ebiten.ActualTPS(), ebiten.ActualFPS())
|
msg := fmt.Sprintf("TPS: %0.2f\nFPS: %0.2f", ebiten.ActualTPS(), ebiten.ActualFPS())
|
||||||
msg += "\nPress A to switch anti-alias."
|
msg += "\nPress A to switch anti-alias."
|
||||||
|
26
image.go
26
image.go
@ -257,7 +257,7 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
i.image.DrawTriangles(srcs, vs, is, blend, i.adjustedRegion(), graphicsdriver.Region{}, [graphics.ShaderImageCount - 1][2]float32{}, shader.shader, uniforms, false, canSkipMipmap(options.GeoM, filter))
|
i.image.DrawTriangles(srcs, vs, is, blend, i.adjustedRegion(), graphicsdriver.Region{}, [graphics.ShaderImageCount - 1][2]float32{}, shader.shader, uniforms, false, canSkipMipmap(options.GeoM, filter), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vertex represents a vertex passed to DrawTriangles.
|
// Vertex represents a vertex passed to DrawTriangles.
|
||||||
@ -365,6 +365,15 @@ type DrawTrianglesOptions struct {
|
|||||||
//
|
//
|
||||||
// The default (zero) value is FillAll.
|
// The default (zero) value is FillAll.
|
||||||
FillRule FillRule
|
FillRule FillRule
|
||||||
|
|
||||||
|
// AntiAlias indicates whether the rendering uses anti-alias or not.
|
||||||
|
// AntiAlias is useful especially when you pass vertices you get from the vector package.
|
||||||
|
//
|
||||||
|
// AntiAlias increases internal draw calls and might affect performance.
|
||||||
|
// Use `ebitenginedebug` to check the number of draw calls if you care.
|
||||||
|
//
|
||||||
|
// The default (zero) value is false.
|
||||||
|
AntiAlias bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// MaxIndicesCount is the maximum number of indices for DrawTriangles and DrawTrianglesShader.
|
// MaxIndicesCount is the maximum number of indices for DrawTriangles and DrawTrianglesShader.
|
||||||
@ -479,7 +488,7 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
i.image.DrawTriangles(srcs, vs, is, blend, i.adjustedRegion(), sr, [graphics.ShaderImageCount - 1][2]float32{}, shader.shader, uniforms, options.FillRule == EvenOdd, filter != builtinshader.FilterLinear)
|
i.image.DrawTriangles(srcs, vs, is, blend, i.adjustedRegion(), sr, [graphics.ShaderImageCount - 1][2]float32{}, shader.shader, uniforms, options.FillRule == EvenOdd, filter != builtinshader.FilterLinear, options.AntiAlias)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DrawTrianglesShaderOptions represents options for DrawTrianglesShader.
|
// DrawTrianglesShaderOptions represents options for DrawTrianglesShader.
|
||||||
@ -515,6 +524,15 @@ type DrawTrianglesShaderOptions struct {
|
|||||||
//
|
//
|
||||||
// The default (zero) value is FillAll.
|
// The default (zero) value is FillAll.
|
||||||
FillRule FillRule
|
FillRule FillRule
|
||||||
|
|
||||||
|
// AntiAlias indicates whether the rendering uses anti-alias or not.
|
||||||
|
// AntiAlias is useful especially when you pass vertices you get from the vector package.
|
||||||
|
//
|
||||||
|
// AntiAlias increases internal draw calls and might affect performance.
|
||||||
|
// Use `ebitenginedebug` to check the number of draw calls if you care.
|
||||||
|
//
|
||||||
|
// The default (zero) value is false.
|
||||||
|
AntiAlias bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -625,7 +643,7 @@ func (i *Image) DrawTrianglesShader(vertices []Vertex, indices []uint16, shader
|
|||||||
offsets[i][1] = float32(y - sy)
|
offsets[i][1] = float32(y - sy)
|
||||||
}
|
}
|
||||||
|
|
||||||
i.image.DrawTriangles(imgs, vs, is, blend, i.adjustedRegion(), sr, offsets, shader.shader, shader.convertUniforms(options.Uniforms), options.FillRule == EvenOdd, true)
|
i.image.DrawTriangles(imgs, vs, is, blend, i.adjustedRegion(), sr, offsets, shader.shader, shader.convertUniforms(options.Uniforms), options.FillRule == EvenOdd, true, options.AntiAlias)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DrawRectShaderOptions represents options for DrawRectShader.
|
// DrawRectShaderOptions represents options for DrawRectShader.
|
||||||
@ -738,7 +756,7 @@ func (i *Image) DrawRectShader(width, height int, shader *Shader, options *DrawR
|
|||||||
offsets[i][1] = float32(y - sy)
|
offsets[i][1] = float32(y - sy)
|
||||||
}
|
}
|
||||||
|
|
||||||
i.image.DrawTriangles(imgs, vs, is, blend, i.adjustedRegion(), sr, offsets, shader.shader, shader.convertUniforms(options.Uniforms), false, true)
|
i.image.DrawTriangles(imgs, vs, is, blend, i.adjustedRegion(), sr, offsets, shader.shader, shader.convertUniforms(options.Uniforms), false, true, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SubImage returns an image representing the portion of the image p visible through r.
|
// SubImage returns an image representing the portion of the image p visible through r.
|
||||||
|
@ -3864,3 +3864,80 @@ func TestImageBlendFactor(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestImageAntiAliasAndBlend(t *testing.T) {
|
||||||
|
const w, h = 16, 16
|
||||||
|
|
||||||
|
dst0 := ebiten.NewImage(w, h)
|
||||||
|
dst1 := ebiten.NewImage(w, h)
|
||||||
|
src := ebiten.NewImage(w, h)
|
||||||
|
|
||||||
|
for _, blend := range []ebiten.Blend{
|
||||||
|
{}, // Default
|
||||||
|
ebiten.BlendClear,
|
||||||
|
ebiten.BlendCopy,
|
||||||
|
ebiten.BlendSourceOver,
|
||||||
|
} {
|
||||||
|
dst0.Fill(color.RGBA{0x24, 0x3f, 0x6a, 0x88})
|
||||||
|
dst1.Fill(color.RGBA{0x24, 0x3f, 0x6a, 0x88})
|
||||||
|
src.Fill(color.RGBA{0x85, 0xa3, 0x08, 0xd3})
|
||||||
|
|
||||||
|
op0 := &ebiten.DrawTrianglesOptions{}
|
||||||
|
op0.Blend = blend
|
||||||
|
op0.AntiAlias = true
|
||||||
|
vs := []ebiten.Vertex{
|
||||||
|
{
|
||||||
|
DstX: 0,
|
||||||
|
DstY: 0,
|
||||||
|
SrcX: 0,
|
||||||
|
SrcY: 0,
|
||||||
|
ColorR: 1,
|
||||||
|
ColorG: 1,
|
||||||
|
ColorB: 1,
|
||||||
|
ColorA: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
DstX: w,
|
||||||
|
DstY: 0,
|
||||||
|
SrcX: w,
|
||||||
|
SrcY: 0,
|
||||||
|
ColorR: 1,
|
||||||
|
ColorG: 1,
|
||||||
|
ColorB: 1,
|
||||||
|
ColorA: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
DstX: 0,
|
||||||
|
DstY: h,
|
||||||
|
SrcX: 0,
|
||||||
|
SrcY: h,
|
||||||
|
ColorR: 1,
|
||||||
|
ColorG: 1,
|
||||||
|
ColorB: 1,
|
||||||
|
ColorA: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
DstX: w,
|
||||||
|
DstY: h,
|
||||||
|
SrcX: w,
|
||||||
|
SrcY: h,
|
||||||
|
ColorR: 1,
|
||||||
|
ColorG: 1,
|
||||||
|
ColorB: 1,
|
||||||
|
ColorA: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
is := []uint16{0, 1, 2, 1, 2, 3}
|
||||||
|
dst0.DrawTriangles(vs, is, src, op0)
|
||||||
|
got := dst0.At(0, 0).(color.RGBA)
|
||||||
|
|
||||||
|
op1 := &ebiten.DrawImageOptions{}
|
||||||
|
op1.Blend = blend
|
||||||
|
dst1.DrawImage(src, op1)
|
||||||
|
want := dst1.At(0, 0).(color.RGBA)
|
||||||
|
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("blend: %v, got: %v, want: %v", blend, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/hajimehoshi/ebiten/v2/internal/atlas"
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/buffered"
|
"github.com/hajimehoshi/ebiten/v2/internal/buffered"
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/clock"
|
"github.com/hajimehoshi/ebiten/v2/internal/clock"
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/debug"
|
"github.com/hajimehoshi/ebiten/v2/internal/debug"
|
||||||
@ -153,7 +154,7 @@ func (c *context) updateFrameImpl(graphicsDriver graphicsdriver.Graphics, update
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *context) drawGame(graphicsDriver graphicsdriver.Graphics) error {
|
func (c *context) drawGame(graphicsDriver graphicsdriver.Graphics) error {
|
||||||
if c.offscreen.volatile != theGlobalState.isScreenClearedEveryFrame() {
|
if (c.offscreen.imageType == atlas.ImageTypeVolatile) != theGlobalState.isScreenClearedEveryFrame() {
|
||||||
w, h := c.offscreen.width, c.offscreen.height
|
w, h := c.offscreen.width, c.offscreen.height
|
||||||
c.offscreen.MarkDisposed()
|
c.offscreen.MarkDisposed()
|
||||||
c.offscreen = c.game.NewOffscreenImage(w, h)
|
c.offscreen = c.game.NewOffscreenImage(w, h)
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/atlas"
|
"github.com/hajimehoshi/ebiten/v2/internal/atlas"
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
|
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
|
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
|
||||||
@ -30,20 +32,25 @@ func SetPanicOnErrorOnReadingPixelsForTesting(value bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Image struct {
|
type Image struct {
|
||||||
mipmap *mipmap.Mipmap
|
mipmap *mipmap.Mipmap
|
||||||
width int
|
width int
|
||||||
height int
|
height int
|
||||||
volatile bool
|
imageType atlas.ImageType
|
||||||
|
|
||||||
dotsBuffer map[[2]int][4]byte
|
dotsBuffer map[[2]int][4]byte
|
||||||
|
|
||||||
|
// bigOffscreenBuffer is a double-sized offscreen for anti-alias rendering.
|
||||||
|
bigOffscreenBuffer *Image
|
||||||
|
bigOffscreenBufferBlend graphicsdriver.Blend
|
||||||
|
bigOffscreenBufferDirty bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewImage(width, height int, imageType atlas.ImageType) *Image {
|
func NewImage(width, height int, imageType atlas.ImageType) *Image {
|
||||||
return &Image{
|
return &Image{
|
||||||
mipmap: mipmap.New(width, height, imageType),
|
mipmap: mipmap.New(width, height, imageType),
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
volatile: imageType == atlas.ImageTypeVolatile,
|
imageType: imageType,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,12 +58,72 @@ func (i *Image) MarkDisposed() {
|
|||||||
if i.mipmap == nil {
|
if i.mipmap == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if i.bigOffscreenBuffer != nil {
|
||||||
|
i.bigOffscreenBuffer.MarkDisposed()
|
||||||
|
i.bigOffscreenBuffer = nil
|
||||||
|
i.bigOffscreenBufferDirty = false
|
||||||
|
}
|
||||||
i.mipmap.MarkDisposed()
|
i.mipmap.MarkDisposed()
|
||||||
i.mipmap = nil
|
i.mipmap = nil
|
||||||
i.dotsBuffer = nil
|
i.dotsBuffer = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) DrawTriangles(srcs [graphics.ShaderImageCount]*Image, vertices []float32, indices []uint16, blend graphicsdriver.Blend, dstRegion, srcRegion graphicsdriver.Region, subimageOffsets [graphics.ShaderImageCount - 1][2]float32, shader *Shader, uniforms [][]float32, evenOdd bool, canSkipMipmap bool) {
|
func (i *Image) DrawTriangles(srcs [graphics.ShaderImageCount]*Image, vertices []float32, indices []uint16, blend graphicsdriver.Blend, dstRegion, srcRegion graphicsdriver.Region, subimageOffsets [graphics.ShaderImageCount - 1][2]float32, shader *Shader, uniforms [][]float32, evenOdd bool, canSkipMipmap bool, antialias bool) {
|
||||||
|
if antialias {
|
||||||
|
// Flush the other buffer to make the buffers exclusive.
|
||||||
|
i.flushDotsBufferIfNeeded()
|
||||||
|
|
||||||
|
if i.bigOffscreenBufferBlend != blend {
|
||||||
|
i.flushBigOffscreenBufferIfNeeded()
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.bigOffscreenBuffer == nil {
|
||||||
|
var imageType atlas.ImageType
|
||||||
|
switch i.imageType {
|
||||||
|
case atlas.ImageTypeRegular, atlas.ImageTypeUnmanaged:
|
||||||
|
imageType = atlas.ImageTypeUnmanaged
|
||||||
|
case atlas.ImageTypeScreen, atlas.ImageTypeVolatile:
|
||||||
|
imageType = atlas.ImageTypeVolatile
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("ui: unexpected image type: %d", imageType))
|
||||||
|
}
|
||||||
|
i.bigOffscreenBuffer = NewImage(i.width*2, i.height*2, imageType)
|
||||||
|
}
|
||||||
|
|
||||||
|
i.bigOffscreenBufferBlend = blend
|
||||||
|
|
||||||
|
// Copy the current rendering result to get the correct blending result.
|
||||||
|
if blend != graphicsdriver.BlendSourceOver && !i.bigOffscreenBufferDirty {
|
||||||
|
srcs := [graphics.ShaderImageCount]*Image{i}
|
||||||
|
vs := graphics.QuadVertices(
|
||||||
|
0, 0, float32(i.width), float32(i.height),
|
||||||
|
2, 0, 0, 2, 0, 0,
|
||||||
|
1, 1, 1, 1)
|
||||||
|
is := graphics.QuadIndices()
|
||||||
|
dstRegion := graphicsdriver.Region{
|
||||||
|
X: 0,
|
||||||
|
Y: 0,
|
||||||
|
Width: float32(i.width * 2),
|
||||||
|
Height: float32(i.height * 2),
|
||||||
|
}
|
||||||
|
i.bigOffscreenBuffer.DrawTriangles(srcs, vs, is, graphicsdriver.BlendCopy, dstRegion, graphicsdriver.Region{}, [graphics.ShaderImageCount - 1][2]float32{}, NearestFilterShader, nil, false, true, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(vertices); i += graphics.VertexFloatCount {
|
||||||
|
vertices[i] *= 2
|
||||||
|
vertices[i+1] *= 2
|
||||||
|
}
|
||||||
|
|
||||||
|
dstRegion.X *= 2
|
||||||
|
dstRegion.Y *= 2
|
||||||
|
dstRegion.Width *= 2
|
||||||
|
dstRegion.Height *= 2
|
||||||
|
|
||||||
|
i.bigOffscreenBuffer.DrawTriangles(srcs, vertices, indices, blend, dstRegion, srcRegion, subimageOffsets, shader, uniforms, evenOdd, canSkipMipmap, false)
|
||||||
|
i.bigOffscreenBufferDirty = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
i.flushBufferIfNeeded()
|
i.flushBufferIfNeeded()
|
||||||
|
|
||||||
var srcMipmaps [graphics.ShaderImageCount]*mipmap.Mipmap
|
var srcMipmaps [graphics.ShaderImageCount]*mipmap.Mipmap
|
||||||
@ -73,6 +140,9 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderImageCount]*Image, vertices [
|
|||||||
|
|
||||||
func (i *Image) WritePixels(pix []byte, x, y, width, height int) {
|
func (i *Image) WritePixels(pix []byte, x, y, width, height int) {
|
||||||
if width == 1 && height == 1 {
|
if width == 1 && height == 1 {
|
||||||
|
// Flush the other buffer to make the buffers exclusive.
|
||||||
|
i.flushBigOffscreenBufferIfNeeded()
|
||||||
|
|
||||||
if i.dotsBuffer == nil {
|
if i.dotsBuffer == nil {
|
||||||
i.dotsBuffer = map[[2]int][4]byte{}
|
i.dotsBuffer = map[[2]int][4]byte{}
|
||||||
}
|
}
|
||||||
@ -83,7 +153,7 @@ func (i *Image) WritePixels(pix []byte, x, y, width, height int) {
|
|||||||
|
|
||||||
// One square requires 6 indices (= 2 triangles).
|
// One square requires 6 indices (= 2 triangles).
|
||||||
if len(i.dotsBuffer) >= graphics.IndicesCount/6 {
|
if len(i.dotsBuffer) >= graphics.IndicesCount/6 {
|
||||||
i.flushBufferIfNeeded()
|
i.flushDotsBufferIfNeeded()
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -98,15 +168,17 @@ func (i *Image) ReadPixels(pixels []byte, x, y, width, height int) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i.flushBigOffscreenBufferIfNeeded()
|
||||||
|
|
||||||
if width == 1 && height == 1 {
|
if width == 1 && height == 1 {
|
||||||
if c, ok := i.dotsBuffer[[2]int{x, y}]; ok {
|
if c, ok := i.dotsBuffer[[2]int{x, y}]; ok {
|
||||||
copy(pixels, c[:])
|
copy(pixels, c[:])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Do not call flushBufferIfNeeded here. This would slow (image/draw).Draw.
|
// Do not call flushDotsBufferIfNeeded here. This would slow (image/draw).Draw.
|
||||||
// See ebiten.TestImageDrawOver.
|
// See ebiten.TestImageDrawOver.
|
||||||
} else {
|
} else {
|
||||||
i.flushBufferIfNeeded()
|
i.flushDotsBufferIfNeeded()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := theUI.readPixels(i.mipmap, pixels, x, y, width, height); err != nil {
|
if err := theUI.readPixels(i.mipmap, pixels, x, y, width, height); err != nil {
|
||||||
@ -122,6 +194,12 @@ func (i *Image) DumpScreenshot(name string, blackbg bool) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) flushBufferIfNeeded() {
|
func (i *Image) flushBufferIfNeeded() {
|
||||||
|
// The buffers are exclusive and the order should not matter.
|
||||||
|
i.flushDotsBufferIfNeeded()
|
||||||
|
i.flushBigOffscreenBufferIfNeeded()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Image) flushDotsBufferIfNeeded() {
|
||||||
if len(i.dotsBuffer) == 0 {
|
if len(i.dotsBuffer) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -193,6 +271,36 @@ func (i *Image) flushBufferIfNeeded() {
|
|||||||
i.mipmap.DrawTriangles(srcs, vs, is, graphicsdriver.BlendCopy, dr, graphicsdriver.Region{}, [graphics.ShaderImageCount - 1][2]float32{}, NearestFilterShader.shader, nil, false, true)
|
i.mipmap.DrawTriangles(srcs, vs, is, graphicsdriver.BlendCopy, dr, graphicsdriver.Region{}, [graphics.ShaderImageCount - 1][2]float32{}, NearestFilterShader.shader, nil, false, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *Image) flushBigOffscreenBufferIfNeeded() {
|
||||||
|
if !i.bigOffscreenBufferDirty {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark the offscreen clearn earlier to avoid recursive calls.
|
||||||
|
i.bigOffscreenBufferDirty = false
|
||||||
|
|
||||||
|
srcs := [graphics.ShaderImageCount]*Image{i.bigOffscreenBuffer}
|
||||||
|
vs := graphics.QuadVertices(
|
||||||
|
0, 0, float32(i.width*2), float32(i.height*2),
|
||||||
|
0.5, 0, 0, 0.5, 0, 0,
|
||||||
|
1, 1, 1, 1)
|
||||||
|
is := graphics.QuadIndices()
|
||||||
|
dstRegion := graphicsdriver.Region{
|
||||||
|
X: 0,
|
||||||
|
Y: 0,
|
||||||
|
Width: float32(i.width),
|
||||||
|
Height: float32(i.height),
|
||||||
|
}
|
||||||
|
blend := graphicsdriver.BlendSourceOver
|
||||||
|
if i.bigOffscreenBufferBlend != graphicsdriver.BlendSourceOver {
|
||||||
|
blend = graphicsdriver.BlendCopy
|
||||||
|
}
|
||||||
|
i.DrawTriangles(srcs, vs, is, blend, dstRegion, graphicsdriver.Region{}, [graphics.ShaderImageCount - 1][2]float32{}, LinearFilterShader, nil, false, true, false)
|
||||||
|
|
||||||
|
i.bigOffscreenBuffer.clear()
|
||||||
|
i.bigOffscreenBufferDirty = false
|
||||||
|
}
|
||||||
|
|
||||||
func DumpImages(dir string) (string, error) {
|
func DumpImages(dir string) (string, error) {
|
||||||
return theUI.dumpImages(dir)
|
return theUI.dumpImages(dir)
|
||||||
}
|
}
|
||||||
@ -230,5 +338,5 @@ func (i *Image) Fill(r, g, b, a float32, x, y, width, height int) {
|
|||||||
|
|
||||||
srcs := [graphics.ShaderImageCount]*Image{emptyImage}
|
srcs := [graphics.ShaderImageCount]*Image{emptyImage}
|
||||||
|
|
||||||
i.DrawTriangles(srcs, vs, is, graphicsdriver.BlendCopy, dstRegion, graphicsdriver.Region{}, [graphics.ShaderImageCount - 1][2]float32{}, NearestFilterShader, nil, false, true)
|
i.DrawTriangles(srcs, vs, is, graphicsdriver.BlendCopy, dstRegion, graphicsdriver.Region{}, [graphics.ShaderImageCount - 1][2]float32{}, NearestFilterShader, nil, false, true, false)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user