twitchapon-anim

Basic Twitchapon Receiver/Visuals
git clone git://bsandro.tech/twitchapon-anim
Log | Files | Refs | README | LICENSE

image.go (16042B)


      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 shareable
     16 
     17 import (
     18 	"fmt"
     19 	"image/color"
     20 	"runtime"
     21 	"sync"
     22 
     23 	"github.com/hajimehoshi/ebiten/v2/internal/affine"
     24 	"github.com/hajimehoshi/ebiten/v2/internal/driver"
     25 	"github.com/hajimehoshi/ebiten/v2/internal/graphics"
     26 	"github.com/hajimehoshi/ebiten/v2/internal/hooks"
     27 	"github.com/hajimehoshi/ebiten/v2/internal/packing"
     28 	"github.com/hajimehoshi/ebiten/v2/internal/restorable"
     29 )
     30 
     31 const (
     32 	// paddingSize represents the size of padding around an image.
     33 	// Every image or node except for a screen image has its padding.
     34 	paddingSize = 1
     35 )
     36 
     37 var graphicsDriver driver.Graphics
     38 
     39 func SetGraphicsDriver(graphics driver.Graphics) {
     40 	graphicsDriver = graphics
     41 }
     42 
     43 var (
     44 	minSize = 0
     45 	maxSize = 0
     46 )
     47 
     48 func max(a, b int) int {
     49 	if a > b {
     50 		return a
     51 	}
     52 	return b
     53 }
     54 
     55 func init() {
     56 	hooks.AppendHookOnBeforeUpdate(func() error {
     57 		backendsM.Lock()
     58 		defer backendsM.Unlock()
     59 
     60 		resolveDeferred()
     61 		return makeImagesShared()
     62 	})
     63 }
     64 
     65 func resolveDeferred() {
     66 	deferredM.Lock()
     67 	fs := deferred
     68 	deferred = nil
     69 	deferredM.Unlock()
     70 
     71 	for _, f := range fs {
     72 		f()
     73 	}
     74 }
     75 
     76 // MaxCountForShare represents the time duration when the image can become shared.
     77 //
     78 // This value is exported for testing.
     79 const MaxCountForShare = 10
     80 
     81 func makeImagesShared() error {
     82 	for i := range imagesToMakeShared {
     83 		i.nonUpdatedCount++
     84 		if i.nonUpdatedCount >= MaxCountForShare {
     85 			if err := i.makeShared(); err != nil {
     86 				return err
     87 			}
     88 		}
     89 		delete(imagesToMakeShared, i)
     90 	}
     91 	return nil
     92 }
     93 
     94 type backend struct {
     95 	restorable *restorable.Image
     96 
     97 	// If page is nil, the backend is not shared.
     98 	page *packing.Page
     99 }
    100 
    101 func (b *backend) TryAlloc(width, height int) (*packing.Node, bool) {
    102 	// If the region is allocated without any extension, that's fine.
    103 	if n := b.page.Alloc(width, height); n != nil {
    104 		return n, true
    105 	}
    106 
    107 	nExtended := 1
    108 	var n *packing.Node
    109 	for {
    110 		if !b.page.Extend(nExtended) {
    111 			// The page can't be extended any more. Return as failure.
    112 			return nil, false
    113 		}
    114 		nExtended++
    115 		n = b.page.Alloc(width, height)
    116 		if n != nil {
    117 			b.page.CommitExtension()
    118 			break
    119 		}
    120 		b.page.RollbackExtension()
    121 	}
    122 
    123 	s := b.page.Size()
    124 	b.restorable = b.restorable.Extend(s, s)
    125 
    126 	if n == nil {
    127 		panic("shareable: Alloc result must not be nil at TryAlloc")
    128 	}
    129 	return n, true
    130 }
    131 
    132 var (
    133 	// backendsM is a mutex for critical sections of the backend and packing.Node objects.
    134 	backendsM sync.Mutex
    135 
    136 	initOnce sync.Once
    137 
    138 	// theBackends is a set of actually shared images.
    139 	theBackends = []*backend{}
    140 
    141 	imagesToMakeShared = map[*Image]struct{}{}
    142 
    143 	deferred []func()
    144 
    145 	// deferredM is a mutext for the slice operations. This must not be used for other usages.
    146 	deferredM sync.Mutex
    147 )
    148 
    149 func init() {
    150 	// Lock the mutex before a frame begins.
    151 	//
    152 	// In each frame, restoring images and resolving images happen respectively:
    153 	//
    154 	//   [Restore -> Resolve] -> [Restore -> Resolve] -> ...
    155 	//
    156 	// Between each frame, any image operations are not permitted, or stale images would remain when restoring
    157 	// (#913).
    158 	backendsM.Lock()
    159 }
    160 
    161 type Image struct {
    162 	width    int
    163 	height   int
    164 	disposed bool
    165 	volatile bool
    166 	screen   bool
    167 
    168 	backend *backend
    169 
    170 	node *packing.Node
    171 
    172 	// nonUpdatedCount represents how long the image is kept not modified with DrawTriangles.
    173 	// In the current implementation, if an image is being modified by DrawTriangles, the image is separated from
    174 	// a shared (restorable) image by ensureNotShared.
    175 	//
    176 	// nonUpdatedCount is increased every frame if the image is not modified, or set to 0 if the image is
    177 	// modified.
    178 	//
    179 	// ReplacePixels doesn't affect this value since ReplacePixels can be done on shared images.
    180 	nonUpdatedCount int
    181 }
    182 
    183 func (i *Image) moveTo(dst *Image) {
    184 	dst.dispose(false)
    185 	*dst = *i
    186 
    187 	// i is no longer available but Dispose must not be called
    188 	// since i and dst have the same values like node.
    189 	runtime.SetFinalizer(i, nil)
    190 }
    191 
    192 func (i *Image) isShared() bool {
    193 	return i.node != nil
    194 }
    195 
    196 func (i *Image) ensureNotShared() {
    197 	if i.backend == nil {
    198 		i.allocate(false)
    199 		return
    200 	}
    201 
    202 	if !i.isShared() {
    203 		return
    204 	}
    205 
    206 	ox, oy, w, h := i.regionWithPadding()
    207 	dx0 := float32(0)
    208 	dy0 := float32(0)
    209 	dx1 := float32(w)
    210 	dy1 := float32(h)
    211 	sx0 := float32(ox)
    212 	sy0 := float32(oy)
    213 	sx1 := float32(ox + w)
    214 	sy1 := float32(oy + h)
    215 	newImg := restorable.NewImage(w, h)
    216 	newImg.SetVolatile(i.volatile)
    217 	vs := []float32{
    218 		dx0, dy0, sx0, sy0, 1, 1, 1, 1,
    219 		dx1, dy0, sx1, sy0, 1, 1, 1, 1,
    220 		dx0, dy1, sx0, sy1, 1, 1, 1, 1,
    221 		dx1, dy1, sx1, sy1, 1, 1, 1, 1,
    222 	}
    223 	is := graphics.QuadIndices()
    224 	srcs := [graphics.ShaderImageNum]*restorable.Image{i.backend.restorable}
    225 	var offsets [graphics.ShaderImageNum - 1][2]float32
    226 	newImg.DrawTriangles(srcs, offsets, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
    227 
    228 	i.dispose(false)
    229 	i.backend = &backend{
    230 		restorable: newImg,
    231 	}
    232 }
    233 
    234 func (i *Image) makeShared() error {
    235 	if i.backend == nil {
    236 		i.allocate(true)
    237 		return nil
    238 	}
    239 
    240 	if i.isShared() {
    241 		return nil
    242 	}
    243 
    244 	if !i.shareable() {
    245 		panic("shareable: makeShared cannot be called on a non-shareable image")
    246 	}
    247 
    248 	newI := NewImage(i.width, i.height)
    249 	newI.SetVolatile(i.volatile)
    250 	pixels := make([]byte, 4*i.width*i.height)
    251 	for y := 0; y < i.height; y++ {
    252 		for x := 0; x < i.width; x++ {
    253 			r, g, b, a, err := i.at(x+paddingSize, y+paddingSize)
    254 			if err != nil {
    255 				return err
    256 			}
    257 			pixels[4*(i.width*y+x)] = r
    258 			pixels[4*(i.width*y+x)+1] = g
    259 			pixels[4*(i.width*y+x)+2] = b
    260 			pixels[4*(i.width*y+x)+3] = a
    261 		}
    262 	}
    263 	newI.replacePixels(pixels)
    264 	newI.moveTo(i)
    265 	i.nonUpdatedCount = 0
    266 	return nil
    267 }
    268 
    269 func (i *Image) regionWithPadding() (x, y, width, height int) {
    270 	if i.backend == nil {
    271 		panic("shareable: backend must not be nil: not allocated yet?")
    272 	}
    273 	if !i.isShared() {
    274 		return 0, 0, i.width + 2*paddingSize, i.height + 2*paddingSize
    275 	}
    276 	return i.node.Region()
    277 }
    278 
    279 func (i *Image) processSrc(src *Image) {
    280 	if src == nil {
    281 		return
    282 	}
    283 	if src.disposed {
    284 		panic("shareable: the drawing source image must not be disposed (DrawTriangles)")
    285 	}
    286 	if src.backend == nil {
    287 		src.allocate(true)
    288 	}
    289 
    290 	// Compare i and source images after ensuring i is not shared, or
    291 	// i and a source image might share the same texture even though i != src.
    292 	if i.backend.restorable == src.backend.restorable {
    293 		panic("shareable: Image.DrawTriangles: source must be different from the receiver")
    294 	}
    295 }
    296 
    297 // DrawTriangles draws triangles with the given image.
    298 //
    299 // The vertex floats are:
    300 //
    301 //   0: Destination X in pixels
    302 //   1: Destination Y in pixels
    303 //   2: Source X in pixels (the upper-left is (0, 0))
    304 //   3: Source Y in pixels
    305 //   4: Color R [0.0-1.0]
    306 //   5: Color G
    307 //   6: Color B
    308 //   7: Color Y
    309 func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, 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{}) {
    310 	backendsM.Lock()
    311 	// Do not use defer for performance.
    312 
    313 	if i.disposed {
    314 		panic("shareable: the drawing target image must not be disposed (DrawTriangles)")
    315 	}
    316 	i.ensureNotShared()
    317 
    318 	for _, src := range srcs {
    319 		i.processSrc(src)
    320 	}
    321 
    322 	var dx, dy float32
    323 	// A screen image doesn't have its padding.
    324 	if !i.screen {
    325 		dx = paddingSize
    326 		dy = paddingSize
    327 	}
    328 
    329 	var oxf, oyf float32
    330 	if srcs[0] != nil {
    331 		ox, oy, _, _ := srcs[0].regionWithPadding()
    332 		ox += paddingSize
    333 		oy += paddingSize
    334 		oxf, oyf = float32(ox), float32(oy)
    335 		n := len(vertices) / graphics.VertexFloatNum
    336 		for i := 0; i < n; i++ {
    337 			vertices[i*graphics.VertexFloatNum+0] += dx
    338 			vertices[i*graphics.VertexFloatNum+1] += dy
    339 			vertices[i*graphics.VertexFloatNum+2] += oxf
    340 			vertices[i*graphics.VertexFloatNum+3] += oyf
    341 		}
    342 		// sourceRegion can be delibarately empty when this is not needed in order to avoid unexpected
    343 		// performance issue (#1293).
    344 		if sourceRegion.Width != 0 && sourceRegion.Height != 0 {
    345 			sourceRegion.X += oxf
    346 			sourceRegion.Y += oyf
    347 		}
    348 	} else {
    349 		n := len(vertices) / graphics.VertexFloatNum
    350 		for i := 0; i < n; i++ {
    351 			vertices[i*graphics.VertexFloatNum+0] += dx
    352 			vertices[i*graphics.VertexFloatNum+1] += dy
    353 		}
    354 	}
    355 
    356 	var offsets [graphics.ShaderImageNum - 1][2]float32
    357 	var s *restorable.Shader
    358 	var imgs [graphics.ShaderImageNum]*restorable.Image
    359 	if shader == nil {
    360 		// Fast path for rendering without a shader (#1355).
    361 		imgs[0] = srcs[0].backend.restorable
    362 	} else {
    363 		for i, subimageOffset := range subimageOffsets {
    364 			src := srcs[i+1]
    365 			if src == nil {
    366 				continue
    367 			}
    368 			ox, oy, _, _ := src.regionWithPadding()
    369 			offsets[i][0] = float32(ox) + paddingSize - oxf + subimageOffset[0]
    370 			offsets[i][1] = float32(oy) + paddingSize - oyf + subimageOffset[1]
    371 		}
    372 		s = shader.shader
    373 		for i, src := range srcs {
    374 			if src == nil {
    375 				continue
    376 			}
    377 			imgs[i] = src.backend.restorable
    378 		}
    379 	}
    380 
    381 	i.backend.restorable.DrawTriangles(imgs, offsets, vertices, indices, colorm, mode, filter, address, sourceRegion, s, uniforms)
    382 
    383 	i.nonUpdatedCount = 0
    384 	delete(imagesToMakeShared, i)
    385 
    386 	for _, src := range srcs {
    387 		if src == nil {
    388 			continue
    389 		}
    390 		if !src.isShared() && src.shareable() {
    391 			imagesToMakeShared[src] = struct{}{}
    392 		}
    393 	}
    394 
    395 	backendsM.Unlock()
    396 }
    397 
    398 func (i *Image) Fill(clr color.RGBA) {
    399 	backendsM.Lock()
    400 	defer backendsM.Unlock()
    401 
    402 	if i.disposed {
    403 		panic("shareable: the drawing target image must not be disposed (Fill)")
    404 	}
    405 	if i.backend == nil {
    406 		if _, _, _, a := clr.RGBA(); a == 0 {
    407 			return
    408 		}
    409 	}
    410 
    411 	i.ensureNotShared()
    412 
    413 	// As *restorable.Image is an independent image, it is fine to fill the entire image.
    414 	i.backend.restorable.Fill(clr)
    415 
    416 	i.nonUpdatedCount = 0
    417 	delete(imagesToMakeShared, i)
    418 }
    419 
    420 func (i *Image) ReplacePixels(pix []byte) {
    421 	backendsM.Lock()
    422 	defer backendsM.Unlock()
    423 	i.replacePixels(pix)
    424 }
    425 
    426 func (i *Image) replacePixels(pix []byte) {
    427 	if i.disposed {
    428 		panic("shareable: the image must not be disposed at replacePixels")
    429 	}
    430 	if i.backend == nil {
    431 		if pix == nil {
    432 			return
    433 		}
    434 		i.allocate(true)
    435 	}
    436 
    437 	x, y, w, h := i.regionWithPadding()
    438 	if pix == nil {
    439 		i.backend.restorable.ReplacePixels(nil, x, y, w, h)
    440 		return
    441 	}
    442 
    443 	ow, oh := w-2*paddingSize, h-2*paddingSize
    444 	if l := 4 * ow * oh; len(pix) != l {
    445 		panic(fmt.Sprintf("shareable: len(p) must be %d but %d", l, len(pix)))
    446 	}
    447 
    448 	// Add a padding around the image.
    449 	pixb := make([]byte, 4*w*h)
    450 	for j := 0; j < oh; j++ {
    451 		copy(pixb[4*((j+paddingSize)*w+paddingSize):], pix[4*j*ow:4*(j+1)*ow])
    452 	}
    453 
    454 	i.backend.restorable.ReplacePixels(pixb, x, y, w, h)
    455 }
    456 
    457 func (img *Image) Pixels(x, y, width, height int) ([]byte, error) {
    458 	backendsM.Lock()
    459 	defer backendsM.Unlock()
    460 
    461 	x += paddingSize
    462 	y += paddingSize
    463 
    464 	bs := make([]byte, 4*width*height)
    465 	idx := 0
    466 	for j := y; j < y+height; j++ {
    467 		for i := x; i < x+width; i++ {
    468 			r, g, b, a, err := img.at(i, j)
    469 			if err != nil {
    470 				return nil, err
    471 			}
    472 			bs[4*idx] = r
    473 			bs[4*idx+1] = g
    474 			bs[4*idx+2] = b
    475 			bs[4*idx+3] = a
    476 			idx++
    477 		}
    478 	}
    479 	return bs, nil
    480 }
    481 
    482 func (i *Image) at(x, y int) (byte, byte, byte, byte, error) {
    483 	if i.backend == nil {
    484 		return 0, 0, 0, 0, nil
    485 	}
    486 
    487 	ox, oy, w, h := i.regionWithPadding()
    488 	if x < 0 || y < 0 || x >= w || y >= h {
    489 		return 0, 0, 0, 0, nil
    490 	}
    491 
    492 	return i.backend.restorable.At(x+ox, y+oy)
    493 }
    494 
    495 // MarkDisposed marks the image as disposed. The actual operation is deferred.
    496 // MarkDisposed can be called from finalizers.
    497 //
    498 // A function from finalizer must not be blocked, but disposing operation can be blocked.
    499 // Defer this operation until it becomes safe. (#913)
    500 func (i *Image) MarkDisposed() {
    501 	deferredM.Lock()
    502 	deferred = append(deferred, func() {
    503 		i.dispose(true)
    504 	})
    505 	deferredM.Unlock()
    506 }
    507 
    508 func (i *Image) dispose(markDisposed bool) {
    509 	defer func() {
    510 		if markDisposed {
    511 			i.disposed = true
    512 		}
    513 		i.backend = nil
    514 		i.node = nil
    515 		if markDisposed {
    516 			runtime.SetFinalizer(i, nil)
    517 		}
    518 	}()
    519 
    520 	if i.disposed {
    521 		return
    522 	}
    523 
    524 	if i.backend == nil {
    525 		// Not allocated yet.
    526 		return
    527 	}
    528 
    529 	if !i.isShared() {
    530 		i.backend.restorable.Dispose()
    531 		return
    532 	}
    533 
    534 	i.backend.page.Free(i.node)
    535 	if !i.backend.page.IsEmpty() {
    536 		// As this part can be reused, this should be cleared explicitly.
    537 		i.backend.restorable.ClearPixels(i.regionWithPadding())
    538 		return
    539 	}
    540 
    541 	i.backend.restorable.Dispose()
    542 	index := -1
    543 	for idx, sh := range theBackends {
    544 		if sh == i.backend {
    545 			index = idx
    546 			break
    547 		}
    548 	}
    549 	if index == -1 {
    550 		panic("shareable: backend not found at an image being disposed")
    551 	}
    552 	theBackends = append(theBackends[:index], theBackends[index+1:]...)
    553 }
    554 
    555 func NewImage(width, height int) *Image {
    556 	// Actual allocation is done lazily, and the lock is not needed.
    557 	return &Image{
    558 		width:  width,
    559 		height: height,
    560 	}
    561 }
    562 
    563 func (i *Image) SetVolatile(volatile bool) {
    564 	i.volatile = volatile
    565 	if i.backend == nil {
    566 		return
    567 	}
    568 	if i.volatile {
    569 		i.ensureNotShared()
    570 	}
    571 	i.backend.restorable.SetVolatile(i.volatile)
    572 }
    573 
    574 func (i *Image) shareable() bool {
    575 	if minSize == 0 || maxSize == 0 {
    576 		panic("shareable: minSize or maxSize must be initialized")
    577 	}
    578 	if i.volatile {
    579 		return false
    580 	}
    581 	if i.screen {
    582 		return false
    583 	}
    584 	return i.width+2*paddingSize <= maxSize && i.height+2*paddingSize <= maxSize
    585 }
    586 
    587 func (i *Image) allocate(shareable bool) {
    588 	if i.backend != nil {
    589 		panic("shareable: the image is already allocated")
    590 	}
    591 
    592 	runtime.SetFinalizer(i, (*Image).MarkDisposed)
    593 
    594 	if i.screen {
    595 		// A screen image doesn't have a padding.
    596 		i.backend = &backend{
    597 			restorable: restorable.NewScreenFramebufferImage(i.width, i.height),
    598 		}
    599 		return
    600 	}
    601 
    602 	if !shareable || !i.shareable() {
    603 		i.backend = &backend{
    604 			restorable: restorable.NewImage(i.width+2*paddingSize, i.height+2*paddingSize),
    605 		}
    606 		i.backend.restorable.SetVolatile(i.volatile)
    607 		return
    608 	}
    609 
    610 	for _, b := range theBackends {
    611 		if n, ok := b.TryAlloc(i.width+2*paddingSize, i.height+2*paddingSize); ok {
    612 			i.backend = b
    613 			i.node = n
    614 			return
    615 		}
    616 	}
    617 	size := minSize
    618 	for i.width+2*paddingSize > size || i.height+2*paddingSize > size {
    619 		if size == maxSize {
    620 			panic(fmt.Sprintf("shareable: the image being shared is too big: width: %d, height: %d", i.width, i.height))
    621 		}
    622 		size *= 2
    623 	}
    624 
    625 	b := &backend{
    626 		restorable: restorable.NewImage(size, size),
    627 		page:       packing.NewPage(size, maxSize),
    628 	}
    629 	b.restorable.SetVolatile(i.volatile)
    630 	theBackends = append(theBackends, b)
    631 
    632 	n := b.page.Alloc(i.width+2*paddingSize, i.height+2*paddingSize)
    633 	if n == nil {
    634 		panic("shareable: Alloc result must not be nil at allocate")
    635 	}
    636 	i.backend = b
    637 	i.node = n
    638 }
    639 
    640 func (i *Image) Dump(path string, blackbg bool) error {
    641 	backendsM.Lock()
    642 	defer backendsM.Unlock()
    643 
    644 	return i.backend.restorable.Dump(path, blackbg)
    645 }
    646 
    647 func NewScreenFramebufferImage(width, height int) *Image {
    648 	// Actual allocation is done lazily.
    649 	i := &Image{
    650 		width:  width,
    651 		height: height,
    652 		screen: true,
    653 	}
    654 	return i
    655 }
    656 
    657 func EndFrame() error {
    658 	backendsM.Lock()
    659 
    660 	return restorable.ResolveStaleImages()
    661 }
    662 
    663 func BeginFrame() error {
    664 	defer backendsM.Unlock()
    665 
    666 	var err error
    667 	initOnce.Do(func() {
    668 		err = restorable.InitializeGraphicsDriverState()
    669 		if err != nil {
    670 			return
    671 		}
    672 		if len(theBackends) != 0 {
    673 			panic("shareable: all the images must be not-shared before the game starts")
    674 		}
    675 		minSize = 1024
    676 		maxSize = max(minSize, graphicsDriver.MaxImageSize())
    677 	})
    678 	if err != nil {
    679 		return err
    680 	}
    681 
    682 	return restorable.RestoreIfNeeded()
    683 }
    684 
    685 func DumpImages(dir string) error {
    686 	backendsM.Lock()
    687 	defer backendsM.Unlock()
    688 	return restorable.DumpImages(dir)
    689 }