ebiten: add Draw{Image,Triangles}Options.DisableMipmaps

Mipmaps could be unexpectedly expensive even when we don't need mipmaps.
In order to improve performance, let's add an option to disable mipmaps.

Closes #3095
This commit is contained in:
Hajime Hoshi 2024-09-12 22:17:02 +09:00
parent 355dd453bd
commit 4b1ae72f59
2 changed files with 57 additions and 12 deletions

View File

@ -32,7 +32,7 @@ import (
) )
const ( const (
screenWidth = 800 screenWidth = 1000
screenHeight = 480 screenHeight = 480
) )
@ -44,20 +44,28 @@ type Game struct {
rotate bool rotate bool
clip bool clip bool
counter int counter int
pause bool
} }
func (g *Game) Update() error { func (g *Game) Update() error {
g.counter++
if g.counter == 480 {
g.counter = 0
}
if inpututil.IsKeyJustPressed(ebiten.KeyR) { if inpututil.IsKeyJustPressed(ebiten.KeyR) {
g.rotate = !g.rotate g.rotate = !g.rotate
} }
if inpututil.IsKeyJustPressed(ebiten.KeyC) { if inpututil.IsKeyJustPressed(ebiten.KeyC) {
g.clip = !g.clip g.clip = !g.clip
} }
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
g.pause = !g.pause
}
if g.pause {
return nil
}
g.counter++
if g.counter == 480 {
g.counter = 0
}
return nil return nil
} }
@ -65,7 +73,8 @@ func (g *Game) Draw(screen *ebiten.Image) {
s := 1.5 / math.Pow(1.01, float64(g.counter)) s := 1.5 / math.Pow(1.01, float64(g.counter))
clippedGophersImage := gophersImage.SubImage(image.Rect(100, 100, 200, 200)).(*ebiten.Image) clippedGophersImage := gophersImage.SubImage(image.Rect(100, 100, 200, 200)).(*ebiten.Image)
for i, f := range []ebiten.Filter{ebiten.FilterNearest, ebiten.FilterLinear} { for i := range 3 {
//for i, f := range []ebiten.Filter{ebiten.FilterNearest, ebiten.FilterLinear} {
w, h := gophersImage.Bounds().Dx(), gophersImage.Bounds().Dy() w, h := gophersImage.Bounds().Dx(), gophersImage.Bounds().Dy()
op := &ebiten.DrawImageOptions{} op := &ebiten.DrawImageOptions{}
@ -75,8 +84,15 @@ func (g *Game) Draw(screen *ebiten.Image) {
op.GeoM.Translate(float64(w)/2, float64(h)/2) op.GeoM.Translate(float64(w)/2, float64(h)/2)
} }
op.GeoM.Scale(s, s) op.GeoM.Scale(s, s)
op.GeoM.Translate(32+float64(i*w)*s+float64(i*4), 64) op.GeoM.Translate(32+float64(i*w)*s+float64(i*4), 100)
op.Filter = f if i == 0 {
op.Filter = ebiten.FilterNearest
} else {
op.Filter = ebiten.FilterLinear
}
if i == 2 {
op.DisableMipmaps = true
}
if g.clip { if g.clip {
screen.DrawImage(clippedGophersImage, op) screen.DrawImage(clippedGophersImage, op)
} else { } else {
@ -84,9 +100,10 @@ func (g *Game) Draw(screen *ebiten.Image) {
} }
} }
msg := fmt.Sprintf(`Minifying images (Nearest filter vs Linear filter): msg := fmt.Sprintf(`Minifying images (Nearest filter, Linear filter (w/ mipmaps), and Linear Filter (w/o mipmaps)):
Press R to rotate the images. Press R to rotate the images.
Press C to clip the images. Press C to clip the images.
Click to pause and resume.
Scale: %0.2f`, s) Scale: %0.2f`, s)
ebitenutil.DebugPrint(screen, msg) ebitenutil.DebugPrint(screen, msg)
} }

View File

@ -144,6 +144,16 @@ type DrawImageOptions struct {
// Filter is a type of texture filter. // Filter is a type of texture filter.
// The default (zero) value is FilterNearest. // The default (zero) value is FilterNearest.
Filter Filter Filter Filter
// DisableMipmaps disables mipmaps.
// When Filter is FilterLinear and GeoM shrinks the image, mipmaps are used by default.
// Mipmap is useful to render a shrunk image with high quality.
// However, mipmaps can be expensive, especially on mobiles.
// When DisableMipmaps is true, mipmap is not used.
// When Filter is not FilterLinear, DisableMipmaps is ignored.
//
// The default (zero) value is false.
DisableMipmaps bool
} }
// adjustPosition converts the position in the *ebiten.Image coordinate to the *ui.Image coordinate. // adjustPosition converts the position in the *ebiten.Image coordinate to the *ui.Image coordinate.
@ -273,7 +283,11 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) {
hint = restorable.HintOverwriteDstRegion 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) skipMipmap := options.DisableMipmaps
if !skipMipmap {
skipMipmap = canSkipMipmap(geoM, filter)
}
i.image.DrawTriangles(srcs, vs, is, blend, dr, [graphics.ShaderSrcImageCount]image.Rectangle{img.adjustedBounds()}, shader.shader, i.tmpUniforms, graphicsdriver.FillRuleFillAll, skipMipmap, false, hint)
} }
// overwritesDstRegion reports whether the given parameters overwrite the destination region completely. // overwritesDstRegion reports whether the given parameters overwrite the destination region completely.
@ -447,6 +461,16 @@ type DrawTrianglesOptions struct {
// //
// The default (zero) value is false. // The default (zero) value is false.
AntiAlias bool AntiAlias bool
// DisableMipmaps disables mipmaps.
// When Filter is FilterLinear and GeoM shrinks the image, mipmaps are used by default.
// Mipmap is useful to render a shrunk image with high quality.
// However, mipmaps can be expensive, especially on mobiles.
// When DisableMipmaps is true, mipmap is not used.
// When Filter is not FilterLinear, DisableMipmaps is ignored.
//
// The default (zero) value is false.
DisableMipmaps bool
} }
// MaxIndicesCount is the maximum number of indices for DrawTriangles and DrawTrianglesShader. // MaxIndicesCount is the maximum number of indices for DrawTriangles and DrawTrianglesShader.
@ -576,7 +600,11 @@ 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, restorable.HintNone) skipMipmap := options.DisableMipmaps
if !skipMipmap {
skipMipmap = filter != builtinshader.FilterLinear
}
i.image.DrawTriangles(srcs, vs, is, blend, i.adjustedBounds(), [graphics.ShaderSrcImageCount]image.Rectangle{img.adjustedBounds()}, shader.shader, i.tmpUniforms, graphicsdriver.FillRule(options.FillRule), skipMipmap, options.AntiAlias, restorable.HintNone)
} }
// DrawTrianglesShaderOptions represents options for DrawTrianglesShader. // DrawTrianglesShaderOptions represents options for DrawTrianglesShader.