mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-25 03:08:54 +01:00
mipmap: Stop using negative mipmaps
Negative mipmaps tend to allocate extremely big images. Instead, encourage to use images with explicit padding when enlarging the image. Fixes #1400
This commit is contained in:
parent
19d6f8d20a
commit
fa53160e18
@ -15,6 +15,7 @@
|
||||
package ebitenutil
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"math"
|
||||
|
||||
@ -23,7 +24,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
emptyImage = ebiten.NewImage(1, 1)
|
||||
emptyImage = ebiten.NewImage(3, 3)
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -36,17 +37,16 @@ func init() {
|
||||
//
|
||||
// DrawLine is not concurrent-safe.
|
||||
func DrawLine(dst *ebiten.Image, x1, y1, x2, y2 float64, clr color.Color) {
|
||||
ew, eh := emptyImage.Size()
|
||||
length := math.Hypot(x2-x1, y2-y1)
|
||||
|
||||
op := &ebiten.DrawImageOptions{}
|
||||
op.GeoM.Scale(length/float64(ew), 1/float64(eh))
|
||||
op.GeoM.Scale(length, 1)
|
||||
op.GeoM.Rotate(math.Atan2(y2-y1, x2-x1))
|
||||
op.GeoM.Translate(x1, y1)
|
||||
op.ColorM = colormcache.ColorToColorM(clr)
|
||||
// Filter must be 'nearest' filter (default).
|
||||
// Linear filtering would make edges blurred.
|
||||
dst.DrawImage(emptyImage, op)
|
||||
dst.DrawImage(emptyImage.SubImage(image.Rect(1, 1, 2, 2)).(*ebiten.Image), op)
|
||||
}
|
||||
|
||||
// DrawRect draws a rectangle on the given destination dst.
|
||||
@ -55,13 +55,11 @@ func DrawLine(dst *ebiten.Image, x1, y1, x2, y2 float64, clr color.Color) {
|
||||
//
|
||||
// DrawRect is not concurrent-safe.
|
||||
func DrawRect(dst *ebiten.Image, x, y, width, height float64, clr color.Color) {
|
||||
ew, eh := emptyImage.Size()
|
||||
|
||||
op := &ebiten.DrawImageOptions{}
|
||||
op.GeoM.Scale(width/float64(ew), height/float64(eh))
|
||||
op.GeoM.Scale(width, height)
|
||||
op.GeoM.Translate(x, y)
|
||||
op.ColorM = colormcache.ColorToColorM(clr)
|
||||
// Filter must be 'nearest' filter (default).
|
||||
// Linear filtering would make edges blurred.
|
||||
dst.DrawImage(emptyImage, op)
|
||||
dst.DrawImage(emptyImage.SubImage(image.Rect(1, 1, 2, 2)).(*ebiten.Image), op)
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"log"
|
||||
"math"
|
||||
@ -33,7 +34,7 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
emptyImage = ebiten.NewImage(16, 16)
|
||||
emptyImage = ebiten.NewImage(3, 3)
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -126,7 +127,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
|
||||
for i := 0; i < g.ngon; i++ {
|
||||
indices = append(indices, uint16(i), uint16(i+1)%uint16(g.ngon), uint16(g.ngon))
|
||||
}
|
||||
screen.DrawTriangles(g.vertices, indices, emptyImage, op)
|
||||
screen.DrawTriangles(g.vertices, indices, emptyImage.SubImage(image.Rect(1, 1, 2, 2)).(*ebiten.Image), op)
|
||||
|
||||
msg := fmt.Sprintf("TPS: %0.2f\n%d-gon\nPress <- or -> to change the number of the vertices", ebiten.CurrentTPS(), g.ngon)
|
||||
ebitenutil.DebugPrint(screen, msg)
|
||||
|
@ -18,6 +18,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"log"
|
||||
"math"
|
||||
@ -32,7 +33,7 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
emptyImage = ebiten.NewImage(16, 16)
|
||||
emptyImage = ebiten.NewImage(3, 3)
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -161,18 +162,20 @@ func (g *Game) Update() error {
|
||||
}
|
||||
|
||||
func (g *Game) Draw(screen *ebiten.Image) {
|
||||
src := emptyImage.SubImage(image.Rect(1, 1, 2, 2)).(*ebiten.Image)
|
||||
|
||||
cf := float64(g.count)
|
||||
v, i := line(100, 100, 300, 100, color.RGBA{0xff, 0xff, 0xff, 0xff})
|
||||
screen.DrawTriangles(v, i, emptyImage, nil)
|
||||
screen.DrawTriangles(v, i, src, nil)
|
||||
v, i = line(50, 150, 50, 350, color.RGBA{0xff, 0xff, 0x00, 0xff})
|
||||
screen.DrawTriangles(v, i, emptyImage, nil)
|
||||
screen.DrawTriangles(v, i, src, nil)
|
||||
v, i = line(50, 100+float32(cf), 200+float32(cf), 250, color.RGBA{0x00, 0xff, 0xff, 0xff})
|
||||
screen.DrawTriangles(v, i, emptyImage, nil)
|
||||
screen.DrawTriangles(v, i, src, nil)
|
||||
|
||||
v, i = rect(50+float32(cf), 50+float32(cf), 100+float32(cf), 100+float32(cf), color.RGBA{0x80, 0x80, 0x80, 0x80})
|
||||
screen.DrawTriangles(v, i, emptyImage, nil)
|
||||
screen.DrawTriangles(v, i, src, nil)
|
||||
v, i = rect(300-float32(cf), 50, 120, 120, color.RGBA{0x00, 0x80, 0x00, 0x80})
|
||||
screen.DrawTriangles(v, i, emptyImage, nil)
|
||||
screen.DrawTriangles(v, i, src, nil)
|
||||
|
||||
ebitenutil.DebugPrint(screen, fmt.Sprintf("TPS: %0.2f", ebiten.CurrentTPS()))
|
||||
}
|
||||
|
@ -798,10 +798,10 @@ func TestImageStretch(t *testing.T) {
|
||||
dst := NewImage(w, 4096)
|
||||
loop:
|
||||
for h := 1; h <= 32; h++ {
|
||||
src := NewImage(w, h)
|
||||
src := NewImage(w+2, h+2)
|
||||
|
||||
pix := make([]byte, 4*w*h)
|
||||
for i := 0; i < w*h; i++ {
|
||||
pix := make([]byte, 4*(w+2)*(h+2))
|
||||
for i := 0; i < (w+2)*(h+2); i++ {
|
||||
pix[4*i] = 0xff
|
||||
pix[4*i+3] = 0xff
|
||||
}
|
||||
@ -812,7 +812,7 @@ loop:
|
||||
dst.Clear()
|
||||
op := &DrawImageOptions{}
|
||||
op.GeoM.Scale(1, float64(i)/float64(h))
|
||||
dst.DrawImage(src, op)
|
||||
dst.DrawImage(src.SubImage(image.Rect(1, 1, w+1, h+1)).(*Image), op)
|
||||
for j := -1; j <= 1; j++ {
|
||||
if i+j < 0 {
|
||||
continue
|
||||
|
@ -205,20 +205,6 @@ func (m *Mipmap) level(level int) *buffered.Image {
|
||||
h := sizeForLevel(m.height, level-1)
|
||||
vs = graphics.QuadVertices(0, 0, float32(w), float32(h), 0.5, 0, 0, 0.5, 0, 0, 1, 1, 1, 1, false)
|
||||
filter = driver.FilterLinear
|
||||
case level == -1:
|
||||
src = m.orig
|
||||
vs = graphics.QuadVertices(0, 0, float32(m.width), float32(m.height), 2, 0, 0, 2, 0, 0, 1, 1, 1, 1, false)
|
||||
filter = driver.FilterNearest
|
||||
case level < -1:
|
||||
src = m.level(level + 1)
|
||||
if src == nil {
|
||||
m.imgs[level] = nil
|
||||
return nil
|
||||
}
|
||||
w := sizeForLevel(m.width, level-1)
|
||||
h := sizeForLevel(m.height, level-1)
|
||||
vs = graphics.QuadVertices(0, 0, float32(w), float32(h), 2, 0, 0, 2, 0, 0, 1, 1, 1, 1, false)
|
||||
filter = driver.FilterNearest
|
||||
default:
|
||||
panic(fmt.Sprintf("ebiten: invalid level: %d", level))
|
||||
}
|
||||
@ -246,16 +232,10 @@ func (m *Mipmap) level(level int) *buffered.Image {
|
||||
}
|
||||
|
||||
func sizeForLevel(x int, level int) int {
|
||||
if level > 0 {
|
||||
for i := 0; i < level; i++ {
|
||||
x /= 2
|
||||
if x == 0 {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < -level; i++ {
|
||||
x *= 2
|
||||
for i := 0; i < level; i++ {
|
||||
x /= 2
|
||||
if x == 0 {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
return x
|
||||
@ -295,9 +275,6 @@ func mipmapLevelFromDistance(dx0, dy0, dx1, dy1, sx0, sy0, sx1, sy1 float32, fil
|
||||
|
||||
// Scale can be infinite when the specified scale is extremely big (#1398).
|
||||
if math.IsInf(float64(scale), 0) {
|
||||
if filter == driver.FilterNearest {
|
||||
return -maxLevel
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
@ -306,42 +283,6 @@ func mipmapLevelFromDistance(dx0, dy0, dx1, dy1, sx0, sy0, sx1, sy1 float32, fil
|
||||
return 0
|
||||
}
|
||||
|
||||
// Use 'negative' mipmap to render edges correctly (#611, #907).
|
||||
// It looks like 128 is the enlargement factor that causes edge missings to pass the test TestImageStretch,
|
||||
// but we use 32 here for environments where the float precision is low (#1044, #1270).
|
||||
var tooBigScale float32 = 32
|
||||
|
||||
if scale >= tooBigScale*tooBigScale {
|
||||
// If the filter is not nearest, the target needs to be rendered with graduation. Don't use mipmaps.
|
||||
if filter != driver.FilterNearest {
|
||||
return 0
|
||||
}
|
||||
|
||||
const mipmapMaxSize = 1024
|
||||
w, h := sx1-sx0, sy1-sy0
|
||||
if w >= mipmapMaxSize || h >= mipmapMaxSize {
|
||||
return 0
|
||||
}
|
||||
|
||||
level := 0
|
||||
for scale >= tooBigScale*tooBigScale {
|
||||
level--
|
||||
scale /= 4
|
||||
w *= 2
|
||||
h *= 2
|
||||
if w >= mipmapMaxSize || h >= mipmapMaxSize {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// If tooBigScale is 32, level -6 means that the maximum scale is 32 * 2^6 = 2048. This should be
|
||||
// enough.
|
||||
if level < -maxLevel {
|
||||
level = -maxLevel
|
||||
}
|
||||
return level
|
||||
}
|
||||
|
||||
if filter != driver.FilterLinear {
|
||||
return 0
|
||||
}
|
||||
@ -378,16 +319,8 @@ func mipmapLevelFromDistance(dx0, dy0, dx1, dy1, sx0, sy0, sx1, sy1 float32, fil
|
||||
}
|
||||
|
||||
func pow2(power int) float32 {
|
||||
if power >= 0 {
|
||||
x := 1
|
||||
return float32(x << uint(power))
|
||||
}
|
||||
|
||||
x := float32(1)
|
||||
for i := 0; i < -power; i++ {
|
||||
x /= 2
|
||||
}
|
||||
return x
|
||||
x := 1
|
||||
return float32(x << uint(power))
|
||||
}
|
||||
|
||||
type Shader struct {
|
||||
|
@ -113,6 +113,7 @@ var emptyImage *Image
|
||||
func init() {
|
||||
// Use a big-enough image as an rendering source. By enlarging with x128, this can reach to 16384.
|
||||
// See #907 for details.
|
||||
// TODO: This doesn't have to be 128 due to the 1px padding. 3x3 should be enough.
|
||||
const w, h = 128, 128
|
||||
emptyImage = &Image{
|
||||
image: graphicscommand.NewImage(w, h),
|
||||
|
@ -18,6 +18,7 @@
|
||||
package vector
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"math"
|
||||
|
||||
@ -25,7 +26,7 @@ import (
|
||||
"github.com/hajimehoshi/ebiten/v2/vector/internal/triangulate"
|
||||
)
|
||||
|
||||
var emptyImage = ebiten.NewImage(1, 1)
|
||||
var emptyImage = ebiten.NewImage(3, 3)
|
||||
|
||||
func init() {
|
||||
emptyImage.Fill(color.White)
|
||||
@ -138,5 +139,5 @@ func (p *Path) Fill(dst *ebiten.Image, op *FillOptions) {
|
||||
}
|
||||
base += uint16(len(seg))
|
||||
}
|
||||
dst.DrawTriangles(vertices, indices, emptyImage, nil)
|
||||
dst.DrawTriangles(vertices, indices, emptyImage.SubImage(image.Rect(1, 1, 2, 2)).(*ebiten.Image), nil)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user