2019-06-26 15:17:45 +02:00
|
|
|
// Copyright 2018 The Ebiten 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 ebiten
|
|
|
|
|
|
|
|
import (
|
2019-07-30 06:20:41 +02:00
|
|
|
"fmt"
|
2019-06-26 15:17:45 +02:00
|
|
|
"image"
|
|
|
|
"math"
|
|
|
|
|
|
|
|
"github.com/hajimehoshi/ebiten/internal/driver"
|
|
|
|
"github.com/hajimehoshi/ebiten/internal/graphics"
|
|
|
|
"github.com/hajimehoshi/ebiten/internal/shareable"
|
|
|
|
)
|
|
|
|
|
2019-07-30 10:50:37 +02:00
|
|
|
type levelToImage map[int]*shareable.Image
|
|
|
|
|
2019-06-26 15:17:45 +02:00
|
|
|
type mipmap struct {
|
|
|
|
orig *shareable.Image
|
2019-07-30 10:50:37 +02:00
|
|
|
imgs map[image.Rectangle]levelToImage
|
2019-06-26 15:17:45 +02:00
|
|
|
}
|
|
|
|
|
2019-09-18 19:13:11 +02:00
|
|
|
func newMipmap(width, height int) *mipmap {
|
2019-06-26 15:17:45 +02:00
|
|
|
return &mipmap{
|
2019-09-18 19:13:11 +02:00
|
|
|
orig: shareable.NewImage(width, height),
|
|
|
|
imgs: map[image.Rectangle]levelToImage{},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func newScreenFramebufferMipmap(width, height int) *mipmap {
|
|
|
|
return &mipmap{
|
|
|
|
orig: shareable.NewScreenFramebufferImage(width, height),
|
2019-07-30 10:50:37 +02:00
|
|
|
imgs: map[image.Rectangle]levelToImage{},
|
2019-06-26 15:17:45 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mipmap) original() *shareable.Image {
|
|
|
|
return m.orig
|
|
|
|
}
|
|
|
|
|
2019-09-18 19:13:11 +02:00
|
|
|
func (m *mipmap) makeVolatile() {
|
|
|
|
m.orig.MakeVolatile()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mipmap) dump(name string) error {
|
|
|
|
return m.orig.Dump(name)
|
|
|
|
}
|
|
|
|
|
2019-06-26 15:17:45 +02:00
|
|
|
func (m *mipmap) level(r image.Rectangle, level int) *shareable.Image {
|
2019-07-30 06:20:41 +02:00
|
|
|
if level == 0 {
|
|
|
|
panic("ebiten: level must be non-zero at level")
|
2019-06-26 15:17:45 +02:00
|
|
|
}
|
|
|
|
|
2019-07-30 10:50:37 +02:00
|
|
|
if m.orig.IsVolatile() {
|
|
|
|
panic("ebiten: mipmap images for a volatile image is not implemented yet")
|
2019-06-26 15:17:45 +02:00
|
|
|
}
|
|
|
|
|
2019-07-30 10:50:37 +02:00
|
|
|
if _, ok := m.imgs[r]; !ok {
|
|
|
|
m.imgs[r] = levelToImage{}
|
2019-06-26 15:17:45 +02:00
|
|
|
}
|
2019-07-30 10:50:37 +02:00
|
|
|
imgs := m.imgs[r]
|
2019-06-26 15:17:45 +02:00
|
|
|
|
2019-07-30 10:50:37 +02:00
|
|
|
if img, ok := imgs[level]; ok {
|
|
|
|
return img
|
|
|
|
}
|
2019-06-26 15:17:45 +02:00
|
|
|
|
2019-07-30 10:50:37 +02:00
|
|
|
var src *shareable.Image
|
|
|
|
vs := vertexSlice(4)
|
2019-07-30 06:20:41 +02:00
|
|
|
var filter driver.Filter
|
|
|
|
switch {
|
|
|
|
case level == 1:
|
2019-07-30 10:50:37 +02:00
|
|
|
src = m.orig
|
|
|
|
graphics.PutQuadVertices(vs, src, r.Min.X, r.Min.Y, r.Max.X, r.Max.Y, 0.5, 0, 0, 0.5, 0, 0, 1, 1, 1, 1)
|
2019-07-30 06:20:41 +02:00
|
|
|
filter = driver.FilterLinear
|
|
|
|
case level > 1:
|
2019-07-30 10:50:37 +02:00
|
|
|
src = m.level(r, level-1)
|
|
|
|
if src == nil {
|
|
|
|
imgs[level] = nil
|
|
|
|
return nil
|
2019-06-26 15:17:45 +02:00
|
|
|
}
|
2019-07-31 02:20:24 +02:00
|
|
|
w, h := src.Size()
|
2019-07-30 10:50:37 +02:00
|
|
|
graphics.PutQuadVertices(vs, src, 0, 0, w, h, 0.5, 0, 0, 0.5, 0, 0, 1, 1, 1, 1)
|
2019-07-30 06:20:41 +02:00
|
|
|
filter = driver.FilterLinear
|
2019-07-31 02:20:24 +02:00
|
|
|
case level == -1:
|
2019-07-30 06:20:41 +02:00
|
|
|
src = m.orig
|
2019-07-31 02:20:24 +02:00
|
|
|
graphics.PutQuadVertices(vs, src, r.Min.X, r.Min.Y, r.Max.X, r.Max.Y, 2, 0, 0, 2, 0, 0, 1, 1, 1, 1)
|
|
|
|
filter = driver.FilterNearest
|
|
|
|
case level < -1:
|
|
|
|
src = m.level(r, level+1)
|
|
|
|
if src == nil {
|
|
|
|
imgs[level] = nil
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
w, h := src.Size()
|
|
|
|
graphics.PutQuadVertices(vs, src, 0, 0, w, h, 2, 0, 0, 2, 0, 0, 1, 1, 1, 1)
|
2019-07-30 06:20:41 +02:00
|
|
|
filter = driver.FilterNearest
|
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("ebiten: invalid level: %d", level))
|
2019-06-26 15:17:45 +02:00
|
|
|
}
|
2019-07-30 10:50:37 +02:00
|
|
|
is := graphics.QuadIndices()
|
2019-07-30 06:20:41 +02:00
|
|
|
|
2019-07-31 02:20:24 +02:00
|
|
|
size := r.Size()
|
|
|
|
w2, h2 := size.X, size.Y
|
|
|
|
if level > 0 {
|
|
|
|
for i := 0; i < level; i++ {
|
|
|
|
w2 /= 2
|
|
|
|
h2 /= 2
|
|
|
|
if w2 == 0 || h2 == 0 {
|
|
|
|
imgs[level] = nil
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for i := 0; i < -level; i++ {
|
|
|
|
w2 *= 2
|
|
|
|
h2 *= 2
|
|
|
|
}
|
|
|
|
}
|
2019-07-30 06:20:41 +02:00
|
|
|
s := shareable.NewImage(w2, h2)
|
|
|
|
s.DrawTriangles(src, vs, is, nil, driver.CompositeModeCopy, filter, driver.AddressClampToZero)
|
2019-07-30 10:50:37 +02:00
|
|
|
imgs[level] = s
|
2019-06-26 15:17:45 +02:00
|
|
|
|
2019-07-30 10:50:37 +02:00
|
|
|
return imgs[level]
|
2019-06-26 15:17:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mipmap) isDisposed() bool {
|
|
|
|
return m.orig == nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mipmap) dispose() {
|
|
|
|
m.disposeMipmaps()
|
|
|
|
m.orig.Dispose()
|
|
|
|
m.orig = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mipmap) disposeMipmaps() {
|
|
|
|
for _, a := range m.imgs {
|
|
|
|
for _, img := range a {
|
|
|
|
img.Dispose()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for k := range m.imgs {
|
|
|
|
delete(m.imgs, k)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-20 06:57:16 +02:00
|
|
|
func (m *mipmap) clearFramebuffer() {
|
|
|
|
m.orig.ClearFramebuffer()
|
|
|
|
}
|
|
|
|
|
2019-06-26 15:17:45 +02:00
|
|
|
// mipmapLevel returns an appropriate mipmap level for the given determinant of a geometry matrix.
|
|
|
|
//
|
2019-07-30 06:20:41 +02:00
|
|
|
// mipmapLevel panics if det is NaN or 0.
|
|
|
|
func (m *mipmap) mipmapLevel(geom *GeoM, width, height int, filter driver.Filter) int {
|
|
|
|
det := geom.det()
|
2019-06-26 15:17:45 +02:00
|
|
|
if math.IsNaN(float64(det)) {
|
2019-07-30 06:20:41 +02:00
|
|
|
panic("ebiten: det must be finite at mipmapLevel")
|
2019-06-26 15:17:45 +02:00
|
|
|
}
|
|
|
|
if det == 0 {
|
2019-07-30 06:20:41 +02:00
|
|
|
panic("ebiten: dst must be non zero at mipmapLevel")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Use 'negative' mipmap to render edges correctly (#611, #907).
|
2019-07-31 15:01:45 +02:00
|
|
|
// It looks like 128 is the enlargement factor that causes edge missings to pass the test TestImageStretch.
|
|
|
|
const tooBigScale = 128
|
2019-07-30 06:20:41 +02:00
|
|
|
if sx, sy := geomScaleSize(geom); sx >= tooBigScale || sy >= tooBigScale {
|
2019-07-31 18:07:19 +02:00
|
|
|
// If the filter is not nearest, the target needs to be rendered with graduation. Don't use mipmaps.
|
2019-07-30 06:20:41 +02:00
|
|
|
if filter != driver.FilterNearest {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
const mipmapMaxSize = 1024
|
|
|
|
w, h := width, height
|
|
|
|
if w >= mipmapMaxSize || h >= mipmapMaxSize {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
level := 0
|
|
|
|
for sx >= tooBigScale || sy >= tooBigScale {
|
|
|
|
level--
|
|
|
|
sx /= 2
|
|
|
|
sy /= 2
|
|
|
|
w *= 2
|
|
|
|
h *= 2
|
|
|
|
if w >= mipmapMaxSize || h >= mipmapMaxSize {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return level
|
2019-06-26 15:17:45 +02:00
|
|
|
}
|
|
|
|
|
2019-07-30 06:20:41 +02:00
|
|
|
if filter != driver.FilterLinear {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
if m.original().IsVolatile() {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// This is a separate function for testing.
|
|
|
|
return mipmapLevelForDownscale(det)
|
|
|
|
}
|
|
|
|
|
|
|
|
func mipmapLevelForDownscale(det float32) int {
|
|
|
|
if math.IsNaN(float64(det)) {
|
|
|
|
panic("ebiten: det must be finite at mipmapLevelForDownscale")
|
|
|
|
}
|
|
|
|
if det == 0 {
|
|
|
|
panic("ebiten: dst must be non zero at mipmapLevelForDownscale")
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Should this be determined by x/y scales instead of det?
|
2019-06-26 15:17:45 +02:00
|
|
|
d := math.Abs(float64(det))
|
|
|
|
level := 0
|
|
|
|
for d < 0.25 {
|
|
|
|
level++
|
|
|
|
d *= 4
|
|
|
|
}
|
|
|
|
return level
|
|
|
|
}
|
2019-07-30 06:20:41 +02:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
func maxf32(values ...float32) float32 {
|
|
|
|
max := float32(math.Inf(-1))
|
|
|
|
for _, v := range values {
|
|
|
|
if max < v {
|
|
|
|
max = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return max
|
|
|
|
}
|
|
|
|
|
|
|
|
func minf32(values ...float32) float32 {
|
|
|
|
min := float32(math.Inf(1))
|
|
|
|
for _, v := range values {
|
|
|
|
if min > v {
|
|
|
|
min = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return min
|
|
|
|
}
|
|
|
|
|
|
|
|
func geomScaleSize(geom *GeoM) (sx, sy float32) {
|
|
|
|
a, b, c, d, _, _ := geom.elements()
|
|
|
|
// (0, 1)
|
|
|
|
x0 := 0*a + 1*b
|
|
|
|
y0 := 0*c + 1*d
|
|
|
|
|
|
|
|
// (1, 0)
|
|
|
|
x1 := 1*a + 0*b
|
|
|
|
y1 := 1*c + 0*d
|
|
|
|
|
|
|
|
// (1, 1)
|
|
|
|
x2 := 1*a + 1*b
|
|
|
|
y2 := 1*c + 1*d
|
|
|
|
|
|
|
|
maxx := maxf32(0, x0, x1, x2)
|
|
|
|
maxy := maxf32(0, y0, y1, y2)
|
|
|
|
minx := minf32(0, x0, x1, x2)
|
|
|
|
miny := minf32(0, y0, y1, y2)
|
|
|
|
|
|
|
|
return maxx - minx, maxy - miny
|
|
|
|
}
|