zorldo

Goofing around with Ebiten
git clone git://bsandro.tech/zorldo
Log | Files | Refs | README

mipmap.go (8271B)


      1 // Copyright 2018 The Ebiten Authors
      2 //
      3 // Licensed under the Apache License, Version 2.0 (the "License");
      4 // you may not use this file except in compliance with the License.
      5 // You may obtain a copy of the License at
      6 //
      7 //     http://www.apache.org/licenses/LICENSE-2.0
      8 //
      9 // Unless required by applicable law or agreed to in writing, software
     10 // distributed under the License is distributed on an "AS IS" BASIS,
     11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 // See the License for the specific language governing permissions and
     13 // limitations under the License.
     14 
     15 package mipmap
     16 
     17 import (
     18 	"fmt"
     19 	"math"
     20 
     21 	"github.com/hajimehoshi/ebiten/v2/internal/affine"
     22 	"github.com/hajimehoshi/ebiten/v2/internal/buffered"
     23 	"github.com/hajimehoshi/ebiten/v2/internal/driver"
     24 	"github.com/hajimehoshi/ebiten/v2/internal/graphics"
     25 	"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
     26 )
     27 
     28 func BeginFrame() error {
     29 	return buffered.BeginFrame()
     30 }
     31 
     32 func EndFrame() error {
     33 	return buffered.EndFrame()
     34 }
     35 
     36 // Mipmap is a set of buffered.Image sorted by the order of mipmap level.
     37 // The level 0 image is a regular image and higher-level images are used for mipmap.
     38 type Mipmap struct {
     39 	width    int
     40 	height   int
     41 	volatile bool
     42 	orig     *buffered.Image
     43 	imgs     map[int]*buffered.Image
     44 }
     45 
     46 func New(width, height int) *Mipmap {
     47 	return &Mipmap{
     48 		width:  width,
     49 		height: height,
     50 		orig:   buffered.NewImage(width, height),
     51 	}
     52 }
     53 
     54 func NewScreenFramebufferMipmap(width, height int) *Mipmap {
     55 	return &Mipmap{
     56 		width:  width,
     57 		height: height,
     58 		orig:   buffered.NewScreenFramebufferImage(width, height),
     59 	}
     60 }
     61 
     62 func (m *Mipmap) SetVolatile(volatile bool) {
     63 	m.volatile = volatile
     64 	if m.volatile {
     65 		m.disposeMipmaps()
     66 	}
     67 	m.orig.SetVolatile(volatile)
     68 }
     69 
     70 func (m *Mipmap) DumpScreenshot(name string, blackbg bool) error {
     71 	return m.orig.DumpScreenshot(name, blackbg)
     72 }
     73 
     74 func (m *Mipmap) ReplacePixels(pix []byte, x, y, width, height int) error {
     75 	if err := m.orig.ReplacePixels(pix, x, y, width, height); err != nil {
     76 		return err
     77 	}
     78 	m.disposeMipmaps()
     79 	return nil
     80 }
     81 
     82 func (m *Mipmap) Pixels(x, y, width, height int) ([]byte, error) {
     83 	return m.orig.Pixels(x, y, width, height)
     84 }
     85 
     86 func (m *Mipmap) DrawTriangles(srcs [graphics.ShaderImageNum]*Mipmap, vertices []float32, indices []uint16, colorm affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms []interface{}, evenOdd bool, canSkipMipmap bool) {
     87 	if len(indices) == 0 {
     88 		return
     89 	}
     90 
     91 	level := 0
     92 	// TODO: Do we need to check all the sources' states of being volatile?
     93 	if !canSkipMipmap && srcs[0] != nil && !srcs[0].volatile && filter != driver.FilterScreen {
     94 		level = math.MaxInt32
     95 		for i := 0; i < len(indices)/3; i++ {
     96 			const n = graphics.VertexFloatNum
     97 			dx0 := vertices[n*indices[3*i]+0]
     98 			dy0 := vertices[n*indices[3*i]+1]
     99 			sx0 := vertices[n*indices[3*i]+2]
    100 			sy0 := vertices[n*indices[3*i]+3]
    101 			dx1 := vertices[n*indices[3*i+1]+0]
    102 			dy1 := vertices[n*indices[3*i+1]+1]
    103 			sx1 := vertices[n*indices[3*i+1]+2]
    104 			sy1 := vertices[n*indices[3*i+1]+3]
    105 			dx2 := vertices[n*indices[3*i+2]+0]
    106 			dy2 := vertices[n*indices[3*i+2]+1]
    107 			sx2 := vertices[n*indices[3*i+2]+2]
    108 			sy2 := vertices[n*indices[3*i+2]+3]
    109 			if l := mipmapLevelFromDistance(dx0, dy0, dx1, dy1, sx0, sy0, sx1, sy1, filter); level > l {
    110 				level = l
    111 			}
    112 			if l := mipmapLevelFromDistance(dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, filter); level > l {
    113 				level = l
    114 			}
    115 			if l := mipmapLevelFromDistance(dx2, dy2, dx0, dy0, sx2, sy2, sx0, sy0, filter); level > l {
    116 				level = l
    117 			}
    118 		}
    119 		if level == math.MaxInt32 {
    120 			panic("mipmap: level must be calculated at least once but not")
    121 		}
    122 	}
    123 
    124 	var s *buffered.Shader
    125 	if shader != nil {
    126 		s = shader.shader
    127 	}
    128 
    129 	var imgs [graphics.ShaderImageNum]*buffered.Image
    130 	for i, src := range srcs {
    131 		if src == nil {
    132 			continue
    133 		}
    134 		if level != 0 {
    135 			if img := src.level(level); img != nil {
    136 				const n = graphics.VertexFloatNum
    137 				s := float32(pow2(level))
    138 				for i := 0; i < len(vertices)/n; i++ {
    139 					vertices[i*n+2] /= s
    140 					vertices[i*n+3] /= s
    141 				}
    142 				imgs[i] = img
    143 				continue
    144 			}
    145 		}
    146 		imgs[i] = src.orig
    147 	}
    148 
    149 	m.orig.DrawTriangles(imgs, vertices, indices, colorm, mode, filter, address, dstRegion, srcRegion, subimageOffsets, s, uniforms, evenOdd)
    150 	m.disposeMipmaps()
    151 }
    152 
    153 func (m *Mipmap) setImg(level int, img *buffered.Image) {
    154 	if m.imgs == nil {
    155 		m.imgs = map[int]*buffered.Image{}
    156 	}
    157 	m.imgs[level] = img
    158 }
    159 
    160 func (m *Mipmap) level(level int) *buffered.Image {
    161 	if level == 0 {
    162 		panic("ebiten: level must be non-zero at level")
    163 	}
    164 
    165 	if m.volatile {
    166 		panic("ebiten: mipmap images for a volatile image is not implemented yet")
    167 	}
    168 
    169 	if img, ok := m.imgs[level]; ok {
    170 		return img
    171 	}
    172 
    173 	var src *buffered.Image
    174 	var vs []float32
    175 	var filter driver.Filter
    176 	switch {
    177 	case level == 1:
    178 		src = m.orig
    179 		vs = graphics.QuadVertices(0, 0, float32(m.width), float32(m.height), 0.5, 0, 0, 0.5, 0, 0, 1, 1, 1, 1)
    180 		filter = driver.FilterLinear
    181 	case level > 1:
    182 		src = m.level(level - 1)
    183 		if src == nil {
    184 			m.setImg(level, nil)
    185 			return nil
    186 		}
    187 		w := sizeForLevel(m.width, level-1)
    188 		h := sizeForLevel(m.height, level-1)
    189 		vs = graphics.QuadVertices(0, 0, float32(w), float32(h), 0.5, 0, 0, 0.5, 0, 0, 1, 1, 1, 1)
    190 		filter = driver.FilterLinear
    191 	default:
    192 		panic(fmt.Sprintf("ebiten: invalid level: %d", level))
    193 	}
    194 	is := graphics.QuadIndices()
    195 
    196 	w2 := sizeForLevel(m.width, level-1)
    197 	h2 := sizeForLevel(m.height, level-1)
    198 	if w2 == 0 || h2 == 0 {
    199 		m.setImg(level, nil)
    200 		return nil
    201 	}
    202 	// buffered.NewImage panics with a too big size when actual allocation happens.
    203 	// 4096 should be a safe size in most environments (#1399).
    204 	// Unfortunately a precise max image size cannot be obtained here since this requires GPU access.
    205 	if w2 > 4096 || h2 > 4096 {
    206 		m.setImg(level, nil)
    207 		return nil
    208 	}
    209 	s := buffered.NewImage(w2, h2)
    210 	s.SetVolatile(m.volatile)
    211 
    212 	dstRegion := driver.Region{
    213 		X:      0,
    214 		Y:      0,
    215 		Width:  float32(w2),
    216 		Height: float32(h2),
    217 	}
    218 	s.DrawTriangles([graphics.ShaderImageNum]*buffered.Image{src}, vs, is, affine.ColorMIdentity{}, driver.CompositeModeCopy, filter, driver.AddressUnsafe, dstRegion, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
    219 	m.setImg(level, s)
    220 
    221 	return m.imgs[level]
    222 }
    223 
    224 func sizeForLevel(x int, level int) int {
    225 	for i := 0; i < level; i++ {
    226 		x /= 2
    227 		if x == 0 {
    228 			return 0
    229 		}
    230 	}
    231 	return x
    232 }
    233 
    234 func (m *Mipmap) MarkDisposed() {
    235 	m.disposeMipmaps()
    236 	m.orig.MarkDisposed()
    237 	m.orig = nil
    238 }
    239 
    240 func (m *Mipmap) disposeMipmaps() {
    241 	for _, img := range m.imgs {
    242 		if img != nil {
    243 			img.MarkDisposed()
    244 		}
    245 	}
    246 	for k := range m.imgs {
    247 		delete(m.imgs, k)
    248 	}
    249 }
    250 
    251 // mipmapLevel returns an appropriate mipmap level for the given distance.
    252 func mipmapLevelFromDistance(dx0, dy0, dx1, dy1, sx0, sy0, sx1, sy1 float32, filter driver.Filter) int {
    253 	const maxLevel = 6
    254 
    255 	if filter == driver.FilterScreen {
    256 		return 0
    257 	}
    258 
    259 	d := (dx1-dx0)*(dx1-dx0) + (dy1-dy0)*(dy1-dy0)
    260 	s := (sx1-sx0)*(sx1-sx0) + (sy1-sy0)*(sy1-sy0)
    261 	if s == 0 {
    262 		return 0
    263 	}
    264 	scale := d / s
    265 
    266 	// Scale can be infinite when the specified scale is extremely big (#1398).
    267 	if math.IsInf(float64(scale), 0) {
    268 		return 0
    269 	}
    270 
    271 	// Scale can be zero when the specified scale is extremely small (#1398).
    272 	if scale == 0 {
    273 		return 0
    274 	}
    275 
    276 	if filter != driver.FilterLinear {
    277 		return 0
    278 	}
    279 
    280 	level := 0
    281 	for scale < 0.25 {
    282 		level++
    283 		scale *= 4
    284 	}
    285 
    286 	if level > 0 {
    287 		// If the image can be scaled into 0 size, adjust the level. (#839)
    288 		w, h := int(sx1-sx0), int(sy1-sy0)
    289 		for level >= 0 {
    290 			s := 1 << uint(level)
    291 			if (w > 0 && w/s == 0) || (h > 0 && h/s == 0) {
    292 				level--
    293 				continue
    294 			}
    295 			break
    296 		}
    297 
    298 		if level < 0 {
    299 			// As the render source is too small, nothing is rendered.
    300 			return 0
    301 		}
    302 	}
    303 
    304 	if level > maxLevel {
    305 		level = maxLevel
    306 	}
    307 
    308 	return level
    309 }
    310 
    311 func pow2(power int) float32 {
    312 	x := 1
    313 	return float32(x << uint(power))
    314 }
    315 
    316 type Shader struct {
    317 	shader *buffered.Shader
    318 }
    319 
    320 func NewShader(program *shaderir.Program) *Shader {
    321 	return &Shader{
    322 		shader: buffered.NewShader(program),
    323 	}
    324 }
    325 
    326 func (s *Shader) MarkDisposed() {
    327 	s.shader.MarkDisposed()
    328 	s.shader = nil
    329 }