mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-26 03:38:55 +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
|
package ebitenutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
@ -23,7 +24,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
emptyImage = ebiten.NewImage(1, 1)
|
emptyImage = ebiten.NewImage(3, 3)
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -36,17 +37,16 @@ func init() {
|
|||||||
//
|
//
|
||||||
// DrawLine is not concurrent-safe.
|
// DrawLine is not concurrent-safe.
|
||||||
func DrawLine(dst *ebiten.Image, x1, y1, x2, y2 float64, clr color.Color) {
|
func DrawLine(dst *ebiten.Image, x1, y1, x2, y2 float64, clr color.Color) {
|
||||||
ew, eh := emptyImage.Size()
|
|
||||||
length := math.Hypot(x2-x1, y2-y1)
|
length := math.Hypot(x2-x1, y2-y1)
|
||||||
|
|
||||||
op := &ebiten.DrawImageOptions{}
|
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.Rotate(math.Atan2(y2-y1, x2-x1))
|
||||||
op.GeoM.Translate(x1, y1)
|
op.GeoM.Translate(x1, y1)
|
||||||
op.ColorM = colormcache.ColorToColorM(clr)
|
op.ColorM = colormcache.ColorToColorM(clr)
|
||||||
// Filter must be 'nearest' filter (default).
|
// Filter must be 'nearest' filter (default).
|
||||||
// Linear filtering would make edges blurred.
|
// 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.
|
// 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.
|
// DrawRect is not concurrent-safe.
|
||||||
func DrawRect(dst *ebiten.Image, x, y, width, height float64, clr color.Color) {
|
func DrawRect(dst *ebiten.Image, x, y, width, height float64, clr color.Color) {
|
||||||
ew, eh := emptyImage.Size()
|
|
||||||
|
|
||||||
op := &ebiten.DrawImageOptions{}
|
op := &ebiten.DrawImageOptions{}
|
||||||
op.GeoM.Scale(width/float64(ew), height/float64(eh))
|
op.GeoM.Scale(width, height)
|
||||||
op.GeoM.Translate(x, y)
|
op.GeoM.Translate(x, y)
|
||||||
op.ColorM = colormcache.ColorToColorM(clr)
|
op.ColorM = colormcache.ColorToColorM(clr)
|
||||||
// Filter must be 'nearest' filter (default).
|
// Filter must be 'nearest' filter (default).
|
||||||
// Linear filtering would make edges blurred.
|
// 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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
"log"
|
"log"
|
||||||
"math"
|
"math"
|
||||||
@ -33,7 +34,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
emptyImage = ebiten.NewImage(16, 16)
|
emptyImage = ebiten.NewImage(3, 3)
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -126,7 +127,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
|
|||||||
for i := 0; i < g.ngon; i++ {
|
for i := 0; i < g.ngon; i++ {
|
||||||
indices = append(indices, uint16(i), uint16(i+1)%uint16(g.ngon), uint16(g.ngon))
|
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)
|
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)
|
ebitenutil.DebugPrint(screen, msg)
|
||||||
|
@ -18,6 +18,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
"log"
|
"log"
|
||||||
"math"
|
"math"
|
||||||
@ -32,7 +33,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
emptyImage = ebiten.NewImage(16, 16)
|
emptyImage = ebiten.NewImage(3, 3)
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -161,18 +162,20 @@ func (g *Game) Update() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) Draw(screen *ebiten.Image) {
|
func (g *Game) Draw(screen *ebiten.Image) {
|
||||||
|
src := emptyImage.SubImage(image.Rect(1, 1, 2, 2)).(*ebiten.Image)
|
||||||
|
|
||||||
cf := float64(g.count)
|
cf := float64(g.count)
|
||||||
v, i := line(100, 100, 300, 100, color.RGBA{0xff, 0xff, 0xff, 0xff})
|
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})
|
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})
|
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})
|
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})
|
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()))
|
ebitenutil.DebugPrint(screen, fmt.Sprintf("TPS: %0.2f", ebiten.CurrentTPS()))
|
||||||
}
|
}
|
||||||
|
@ -798,10 +798,10 @@ func TestImageStretch(t *testing.T) {
|
|||||||
dst := NewImage(w, 4096)
|
dst := NewImage(w, 4096)
|
||||||
loop:
|
loop:
|
||||||
for h := 1; h <= 32; h++ {
|
for h := 1; h <= 32; h++ {
|
||||||
src := NewImage(w, h)
|
src := NewImage(w+2, h+2)
|
||||||
|
|
||||||
pix := make([]byte, 4*w*h)
|
pix := make([]byte, 4*(w+2)*(h+2))
|
||||||
for i := 0; i < w*h; i++ {
|
for i := 0; i < (w+2)*(h+2); i++ {
|
||||||
pix[4*i] = 0xff
|
pix[4*i] = 0xff
|
||||||
pix[4*i+3] = 0xff
|
pix[4*i+3] = 0xff
|
||||||
}
|
}
|
||||||
@ -812,7 +812,7 @@ loop:
|
|||||||
dst.Clear()
|
dst.Clear()
|
||||||
op := &DrawImageOptions{}
|
op := &DrawImageOptions{}
|
||||||
op.GeoM.Scale(1, float64(i)/float64(h))
|
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++ {
|
for j := -1; j <= 1; j++ {
|
||||||
if i+j < 0 {
|
if i+j < 0 {
|
||||||
continue
|
continue
|
||||||
|
@ -205,20 +205,6 @@ func (m *Mipmap) level(level int) *buffered.Image {
|
|||||||
h := sizeForLevel(m.height, level-1)
|
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)
|
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
|
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:
|
default:
|
||||||
panic(fmt.Sprintf("ebiten: invalid level: %d", level))
|
panic(fmt.Sprintf("ebiten: invalid level: %d", level))
|
||||||
}
|
}
|
||||||
@ -246,18 +232,12 @@ func (m *Mipmap) level(level int) *buffered.Image {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func sizeForLevel(x int, level int) int {
|
func sizeForLevel(x int, level int) int {
|
||||||
if level > 0 {
|
|
||||||
for i := 0; i < level; i++ {
|
for i := 0; i < level; i++ {
|
||||||
x /= 2
|
x /= 2
|
||||||
if x == 0 {
|
if x == 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
for i := 0; i < -level; i++ {
|
|
||||||
x *= 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return x
|
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).
|
// Scale can be infinite when the specified scale is extremely big (#1398).
|
||||||
if math.IsInf(float64(scale), 0) {
|
if math.IsInf(float64(scale), 0) {
|
||||||
if filter == driver.FilterNearest {
|
|
||||||
return -maxLevel
|
|
||||||
}
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -306,42 +283,6 @@ func mipmapLevelFromDistance(dx0, dy0, dx1, dy1, sx0, sy0, sx1, sy1 float32, fil
|
|||||||
return 0
|
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 {
|
if filter != driver.FilterLinear {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@ -378,18 +319,10 @@ func mipmapLevelFromDistance(dx0, dy0, dx1, dy1, sx0, sy0, sx1, sy1 float32, fil
|
|||||||
}
|
}
|
||||||
|
|
||||||
func pow2(power int) float32 {
|
func pow2(power int) float32 {
|
||||||
if power >= 0 {
|
|
||||||
x := 1
|
x := 1
|
||||||
return float32(x << uint(power))
|
return float32(x << uint(power))
|
||||||
}
|
}
|
||||||
|
|
||||||
x := float32(1)
|
|
||||||
for i := 0; i < -power; i++ {
|
|
||||||
x /= 2
|
|
||||||
}
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
|
|
||||||
type Shader struct {
|
type Shader struct {
|
||||||
shader *buffered.Shader
|
shader *buffered.Shader
|
||||||
}
|
}
|
||||||
|
@ -113,6 +113,7 @@ var emptyImage *Image
|
|||||||
func init() {
|
func init() {
|
||||||
// Use a big-enough image as an rendering source. By enlarging with x128, this can reach to 16384.
|
// Use a big-enough image as an rendering source. By enlarging with x128, this can reach to 16384.
|
||||||
// See #907 for details.
|
// 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
|
const w, h = 128, 128
|
||||||
emptyImage = &Image{
|
emptyImage = &Image{
|
||||||
image: graphicscommand.NewImage(w, h),
|
image: graphicscommand.NewImage(w, h),
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
package vector
|
package vector
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
@ -25,7 +26,7 @@ import (
|
|||||||
"github.com/hajimehoshi/ebiten/v2/vector/internal/triangulate"
|
"github.com/hajimehoshi/ebiten/v2/vector/internal/triangulate"
|
||||||
)
|
)
|
||||||
|
|
||||||
var emptyImage = ebiten.NewImage(1, 1)
|
var emptyImage = ebiten.NewImage(3, 3)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
emptyImage.Fill(color.White)
|
emptyImage.Fill(color.White)
|
||||||
@ -138,5 +139,5 @@ func (p *Path) Fill(dst *ebiten.Image, op *FillOptions) {
|
|||||||
}
|
}
|
||||||
base += uint16(len(seg))
|
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