mipmap.go (10253B)
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 "image/color" 20 "math" 21 22 "github.com/hajimehoshi/ebiten/v2/internal/affine" 23 "github.com/hajimehoshi/ebiten/v2/internal/buffered" 24 "github.com/hajimehoshi/ebiten/v2/internal/driver" 25 "github.com/hajimehoshi/ebiten/v2/internal/graphics" 26 "github.com/hajimehoshi/ebiten/v2/internal/shaderir" 27 ) 28 29 var graphicsDriver driver.Graphics 30 31 func SetGraphicsDriver(graphics driver.Graphics) { 32 graphicsDriver = graphics 33 } 34 35 func BeginFrame() error { 36 return buffered.BeginFrame() 37 } 38 39 func EndFrame() error { 40 return buffered.EndFrame() 41 } 42 43 // Mipmap is a set of buffered.Image sorted by the order of mipmap level. 44 // The level 0 image is a regular image and higher-level images are used for mipmap. 45 type Mipmap struct { 46 width int 47 height int 48 volatile bool 49 orig *buffered.Image 50 imgs map[int]*buffered.Image 51 } 52 53 func New(width, height int) *Mipmap { 54 return &Mipmap{ 55 width: width, 56 height: height, 57 orig: buffered.NewImage(width, height), 58 imgs: map[int]*buffered.Image{}, 59 } 60 } 61 62 func NewScreenFramebufferMipmap(width, height int) *Mipmap { 63 return &Mipmap{ 64 width: width, 65 height: height, 66 orig: buffered.NewScreenFramebufferImage(width, height), 67 imgs: map[int]*buffered.Image{}, 68 } 69 } 70 71 func (m *Mipmap) SetVolatile(volatile bool) { 72 m.volatile = volatile 73 if m.volatile { 74 m.disposeMipmaps() 75 } 76 m.orig.SetVolatile(volatile) 77 } 78 79 func (m *Mipmap) Dump(name string, blackbg bool) error { 80 return m.orig.Dump(name, blackbg) 81 } 82 83 func (m *Mipmap) Fill(clr color.RGBA) { 84 m.orig.Fill(clr) 85 m.disposeMipmaps() 86 } 87 88 func (m *Mipmap) ReplacePixels(pix []byte, x, y, width, height int) error { 89 if err := m.orig.ReplacePixels(pix, x, y, width, height); err != nil { 90 return err 91 } 92 m.disposeMipmaps() 93 return nil 94 } 95 96 func (m *Mipmap) Pixels(x, y, width, height int) ([]byte, error) { 97 return m.orig.Pixels(x, y, width, height) 98 } 99 100 func (m *Mipmap) DrawTriangles(srcs [graphics.ShaderImageNum]*Mipmap, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms []interface{}, canSkipMipmap bool) { 101 if len(indices) == 0 { 102 return 103 } 104 105 level := 0 106 // TODO: Do we need to check all the sources' states of being volatile? 107 if !canSkipMipmap && srcs[0] != nil && !srcs[0].volatile && filter != driver.FilterScreen { 108 level = math.MaxInt32 109 for i := 0; i < len(indices)/3; i++ { 110 const n = graphics.VertexFloatNum 111 dx0 := vertices[n*indices[3*i]+0] 112 dy0 := vertices[n*indices[3*i]+1] 113 sx0 := vertices[n*indices[3*i]+2] 114 sy0 := vertices[n*indices[3*i]+3] 115 dx1 := vertices[n*indices[3*i+1]+0] 116 dy1 := vertices[n*indices[3*i+1]+1] 117 sx1 := vertices[n*indices[3*i+1]+2] 118 sy1 := vertices[n*indices[3*i+1]+3] 119 dx2 := vertices[n*indices[3*i+2]+0] 120 dy2 := vertices[n*indices[3*i+2]+1] 121 sx2 := vertices[n*indices[3*i+2]+2] 122 sy2 := vertices[n*indices[3*i+2]+3] 123 if l := mipmapLevelFromDistance(dx0, dy0, dx1, dy1, sx0, sy0, sx1, sy1, filter); level > l { 124 level = l 125 } 126 if l := mipmapLevelFromDistance(dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, filter); level > l { 127 level = l 128 } 129 if l := mipmapLevelFromDistance(dx2, dy2, dx0, dy0, sx2, sy2, sx0, sy0, filter); level > l { 130 level = l 131 } 132 } 133 if level == math.MaxInt32 { 134 panic("mipmap: level must be calculated at least once but not") 135 } 136 } 137 138 if colorm != nil && colorm.ScaleOnly() { 139 body, _ := colorm.UnsafeElements() 140 cr := body[0] 141 cg := body[5] 142 cb := body[10] 143 ca := body[15] 144 colorm = nil 145 const n = graphics.VertexFloatNum 146 for i := 0; i < len(vertices)/n; i++ { 147 vertices[i*n+4] *= cr 148 vertices[i*n+5] *= cg 149 vertices[i*n+6] *= cb 150 vertices[i*n+7] *= ca 151 } 152 } 153 154 var s *buffered.Shader 155 if shader != nil { 156 s = shader.shader 157 } 158 159 var imgs [graphics.ShaderImageNum]*buffered.Image 160 for i, src := range srcs { 161 if src == nil { 162 continue 163 } 164 if level != 0 { 165 if img := src.level(level); img != nil { 166 const n = graphics.VertexFloatNum 167 s := float32(pow2(level)) 168 for i := 0; i < len(vertices)/n; i++ { 169 vertices[i*n+2] /= s 170 vertices[i*n+3] /= s 171 } 172 imgs[i] = img 173 continue 174 } 175 } 176 imgs[i] = src.orig 177 } 178 179 m.orig.DrawTriangles(imgs, vertices, indices, colorm, mode, filter, address, sourceRegion, subimageOffsets, s, uniforms) 180 m.disposeMipmaps() 181 } 182 183 func (m *Mipmap) level(level int) *buffered.Image { 184 if level == 0 { 185 panic("ebiten: level must be non-zero at level") 186 } 187 188 if m.volatile { 189 panic("ebiten: mipmap images for a volatile image is not implemented yet") 190 } 191 192 if img, ok := m.imgs[level]; ok { 193 return img 194 } 195 196 var src *buffered.Image 197 var vs []float32 198 var filter driver.Filter 199 switch { 200 case level == 1: 201 src = m.orig 202 vs = graphics.QuadVertices(0, 0, float32(m.width), float32(m.height), 0.5, 0, 0, 0.5, 0, 0, 1, 1, 1, 1, false) 203 filter = driver.FilterLinear 204 case level > 1: 205 src = m.level(level - 1) 206 if src == nil { 207 m.imgs[level] = nil 208 return nil 209 } 210 w := sizeForLevel(m.width, level-1) 211 h := sizeForLevel(m.height, level-1) 212 vs = graphics.QuadVertices(0, 0, float32(w), float32(h), 0.5, 0, 0, 0.5, 0, 0, 1, 1, 1, 1, false) 213 filter = driver.FilterLinear 214 case level == -1: 215 src = m.orig 216 vs = graphics.QuadVertices(0, 0, float32(m.width), float32(m.height), 2, 0, 0, 2, 0, 0, 1, 1, 1, 1, false) 217 filter = driver.FilterNearest 218 case level < -1: 219 src = m.level(level + 1) 220 if src == nil { 221 m.imgs[level] = nil 222 return nil 223 } 224 w := sizeForLevel(m.width, level-1) 225 h := sizeForLevel(m.height, level-1) 226 vs = graphics.QuadVertices(0, 0, float32(w), float32(h), 2, 0, 0, 2, 0, 0, 1, 1, 1, 1, false) 227 filter = driver.FilterNearest 228 default: 229 panic(fmt.Sprintf("ebiten: invalid level: %d", level)) 230 } 231 is := graphics.QuadIndices() 232 233 w2 := sizeForLevel(m.width, level-1) 234 h2 := sizeForLevel(m.height, level-1) 235 if w2 == 0 || h2 == 0 { 236 m.imgs[level] = nil 237 return nil 238 } 239 // buffered.NewImage panics with a too big size when actual allocation happens. 240 // 4096 should be a safe size in most environments (#1399). 241 // Unfortunately a precise max image size cannot be obtained here since this requires GPU access. 242 if w2 > 4096 || h2 > 4096 { 243 m.imgs[level] = nil 244 return nil 245 } 246 s := buffered.NewImage(w2, h2) 247 s.SetVolatile(m.volatile) 248 s.DrawTriangles([graphics.ShaderImageNum]*buffered.Image{src}, vs, is, nil, driver.CompositeModeCopy, filter, driver.AddressUnsafe, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil) 249 m.imgs[level] = s 250 251 return m.imgs[level] 252 } 253 254 func sizeForLevel(x int, level int) int { 255 if level > 0 { 256 for i := 0; i < level; i++ { 257 x /= 2 258 if x == 0 { 259 return 0 260 } 261 } 262 } else { 263 for i := 0; i < -level; i++ { 264 x *= 2 265 } 266 } 267 return x 268 } 269 270 func (m *Mipmap) MarkDisposed() { 271 m.disposeMipmaps() 272 m.orig.MarkDisposed() 273 m.orig = nil 274 } 275 276 func (m *Mipmap) disposeMipmaps() { 277 for _, img := range m.imgs { 278 if img != nil { 279 img.MarkDisposed() 280 } 281 } 282 for k := range m.imgs { 283 delete(m.imgs, k) 284 } 285 } 286 287 // mipmapLevel returns an appropriate mipmap level for the given distance. 288 func mipmapLevelFromDistance(dx0, dy0, dx1, dy1, sx0, sy0, sx1, sy1 float32, filter driver.Filter) int { 289 const maxLevel = 6 290 291 if filter == driver.FilterScreen { 292 return 0 293 } 294 295 d := (dx1-dx0)*(dx1-dx0) + (dy1-dy0)*(dy1-dy0) 296 s := (sx1-sx0)*(sx1-sx0) + (sy1-sy0)*(sy1-sy0) 297 if s == 0 { 298 return 0 299 } 300 scale := d / s 301 302 // Scale can be infinite when the specified scale is extremely big (#1398). 303 if math.IsInf(float64(scale), 0) { 304 if filter == driver.FilterNearest { 305 return -maxLevel 306 } 307 return 0 308 } 309 310 // Scale can be zero when the specified scale is extremely small (#1398). 311 if scale == 0 { 312 return 0 313 } 314 315 // Use 'negative' mipmap to render edges correctly (#611, #907). 316 // It looks like 128 is the enlargement factor that causes edge missings to pass the test TestImageStretch, 317 // but we use 32 here for environments where the float precision is low (#1044, #1270). 318 var tooBigScale float32 = 32 319 320 if scale >= tooBigScale*tooBigScale { 321 // If the filter is not nearest, the target needs to be rendered with graduation. Don't use mipmaps. 322 if filter != driver.FilterNearest { 323 return 0 324 } 325 326 const mipmapMaxSize = 1024 327 w, h := sx1-sx0, sy1-sy0 328 if w >= mipmapMaxSize || h >= mipmapMaxSize { 329 return 0 330 } 331 332 level := 0 333 for scale >= tooBigScale*tooBigScale { 334 level-- 335 scale /= 4 336 w *= 2 337 h *= 2 338 if w >= mipmapMaxSize || h >= mipmapMaxSize { 339 break 340 } 341 } 342 343 // If tooBigScale is 32, level -6 means that the maximum scale is 32 * 2^6 = 2048. This should be 344 // enough. 345 if level < -maxLevel { 346 level = -maxLevel 347 } 348 return level 349 } 350 351 if filter != driver.FilterLinear { 352 return 0 353 } 354 355 level := 0 356 for scale < 0.25 { 357 level++ 358 scale *= 4 359 } 360 361 if level > 0 { 362 // If the image can be scaled into 0 size, adjust the level. (#839) 363 w, h := int(sx1-sx0), int(sy1-sy0) 364 for level >= 0 { 365 s := 1 << uint(level) 366 if (w > 0 && w/s == 0) || (h > 0 && h/s == 0) { 367 level-- 368 continue 369 } 370 break 371 } 372 373 if level < 0 { 374 // As the render source is too small, nothing is rendered. 375 return 0 376 } 377 } 378 379 if level > maxLevel { 380 level = maxLevel 381 } 382 383 return level 384 } 385 386 func pow2(power int) float32 { 387 if power >= 0 { 388 x := 1 389 return float32(x << uint(power)) 390 } 391 392 x := float32(1) 393 for i := 0; i < -power; i++ { 394 x /= 2 395 } 396 return x 397 } 398 399 type Shader struct { 400 shader *buffered.Shader 401 } 402 403 func NewShader(program *shaderir.Program) *Shader { 404 return &Shader{ 405 shader: buffered.NewShader(program), 406 } 407 } 408 409 func (s *Shader) MarkDisposed() { 410 s.shader.MarkDisposed() 411 s.shader = nil 412 }