zorldo

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

image.go (6501B)


      1 // Copyright 2019 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 buffered
     16 
     17 import (
     18 	"fmt"
     19 	"image"
     20 
     21 	"github.com/hajimehoshi/ebiten/v2/internal/affine"
     22 	"github.com/hajimehoshi/ebiten/v2/internal/atlas"
     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 type Image struct {
     29 	img    *atlas.Image
     30 	width  int
     31 	height int
     32 
     33 	pixels               []byte
     34 	needsToResolvePixels bool
     35 }
     36 
     37 func BeginFrame() error {
     38 	if err := atlas.BeginFrame(); err != nil {
     39 		return err
     40 	}
     41 	return flushDelayedCommands()
     42 }
     43 
     44 func EndFrame() error {
     45 	return atlas.EndFrame()
     46 }
     47 
     48 func NewImage(width, height int) *Image {
     49 	i := &Image{}
     50 	i.initialize(width, height)
     51 	return i
     52 }
     53 
     54 func (i *Image) initialize(width, height int) {
     55 	if maybeCanAddDelayedCommand() {
     56 		if tryAddDelayedCommand(func() error {
     57 			i.initialize(width, height)
     58 			return nil
     59 		}) {
     60 			return
     61 		}
     62 	}
     63 	i.img = atlas.NewImage(width, height)
     64 	i.width = width
     65 	i.height = height
     66 }
     67 
     68 func (i *Image) SetVolatile(volatile bool) {
     69 	if maybeCanAddDelayedCommand() {
     70 		if tryAddDelayedCommand(func() error {
     71 			i.SetVolatile(volatile)
     72 			return nil
     73 		}) {
     74 			return
     75 		}
     76 	}
     77 	i.img.SetVolatile(volatile)
     78 }
     79 
     80 func NewScreenFramebufferImage(width, height int) *Image {
     81 	i := &Image{}
     82 	i.initializeAsScreenFramebuffer(width, height)
     83 	return i
     84 }
     85 
     86 func (i *Image) initializeAsScreenFramebuffer(width, height int) {
     87 	if maybeCanAddDelayedCommand() {
     88 		if tryAddDelayedCommand(func() error {
     89 			i.initializeAsScreenFramebuffer(width, height)
     90 			return nil
     91 		}) {
     92 			return
     93 		}
     94 	}
     95 
     96 	i.img = atlas.NewScreenFramebufferImage(width, height)
     97 	i.width = width
     98 	i.height = height
     99 }
    100 
    101 func (i *Image) invalidatePendingPixels() {
    102 	i.pixels = nil
    103 	i.needsToResolvePixels = false
    104 }
    105 
    106 func (i *Image) resolvePendingPixels(keepPendingPixels bool) {
    107 	if i.needsToResolvePixels {
    108 		i.img.ReplacePixels(i.pixels)
    109 		if !keepPendingPixels {
    110 			i.pixels = nil
    111 		}
    112 		i.needsToResolvePixels = false
    113 	}
    114 }
    115 
    116 func (i *Image) MarkDisposed() {
    117 	if maybeCanAddDelayedCommand() {
    118 		if tryAddDelayedCommand(func() error {
    119 			i.MarkDisposed()
    120 			return nil
    121 		}) {
    122 			return
    123 		}
    124 	}
    125 	i.invalidatePendingPixels()
    126 	i.img.MarkDisposed()
    127 }
    128 
    129 func (img *Image) Pixels(x, y, width, height int) (pix []byte, err error) {
    130 	checkDelayedCommandsFlushed("Pixels")
    131 
    132 	if !image.Rect(x, y, x+width, y+height).In(image.Rect(0, 0, img.width, img.height)) {
    133 		return nil, fmt.Errorf("buffered: out of range")
    134 	}
    135 
    136 	pix = make([]byte, 4*width*height)
    137 
    138 	if img.pixels == nil {
    139 		pix, err := img.img.Pixels(0, 0, img.width, img.height)
    140 		if err != nil {
    141 			return nil, err
    142 		}
    143 		img.pixels = pix
    144 	}
    145 
    146 	for j := 0; j < height; j++ {
    147 		copy(pix[4*j*width:4*(j+1)*width], img.pixels[4*((j+y)*img.width+x):])
    148 	}
    149 	return pix, nil
    150 }
    151 
    152 func (i *Image) DumpScreenshot(name string, blackbg bool) error {
    153 	checkDelayedCommandsFlushed("Dump")
    154 	return i.img.DumpScreenshot(name, blackbg)
    155 }
    156 
    157 func (i *Image) ReplacePixels(pix []byte, x, y, width, height int) error {
    158 	if l := 4 * width * height; len(pix) != l {
    159 		panic(fmt.Sprintf("buffered: len(pix) was %d but must be %d", len(pix), l))
    160 	}
    161 
    162 	if maybeCanAddDelayedCommand() {
    163 		copied := make([]byte, len(pix))
    164 		copy(copied, pix)
    165 		if tryAddDelayedCommand(func() error {
    166 			i.ReplacePixels(copied, x, y, width, height)
    167 			return nil
    168 		}) {
    169 			return nil
    170 		}
    171 	}
    172 
    173 	if x == 0 && y == 0 && width == i.width && height == i.height {
    174 		i.invalidatePendingPixels()
    175 
    176 		// Call ReplacePixels immediately. Do not buffer the command.
    177 		// If a lot of new images are created but they are used at different timings,
    178 		// pixels are sent to GPU at different timings, which is very inefficient.
    179 		i.img.ReplacePixels(pix)
    180 		return nil
    181 	}
    182 
    183 	// TODO: Can we use (*restorable.Image).ReplacePixels?
    184 	if i.pixels == nil {
    185 		pix, err := i.img.Pixels(0, 0, i.width, i.height)
    186 		if err != nil {
    187 			return err
    188 		}
    189 		i.pixels = pix
    190 	}
    191 	i.replacePendingPixels(pix, x, y, width, height)
    192 	return nil
    193 }
    194 
    195 func (i *Image) replacePendingPixels(pix []byte, x, y, width, height int) {
    196 	for j := 0; j < height; j++ {
    197 		copy(i.pixels[4*((j+y)*i.width+x):], pix[4*j*width:4*(j+1)*width])
    198 	}
    199 	i.needsToResolvePixels = true
    200 }
    201 
    202 // DrawTriangles draws the src image with the given vertices.
    203 //
    204 // Copying vertices and indices is the caller's responsibility.
    205 func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, 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) {
    206 	for _, src := range srcs {
    207 		if i == src {
    208 			panic("buffered: Image.DrawTriangles: source images must be different from the receiver")
    209 		}
    210 	}
    211 
    212 	if maybeCanAddDelayedCommand() {
    213 		if tryAddDelayedCommand(func() error {
    214 			// Arguments are not copied. Copying is the caller's responsibility.
    215 			i.DrawTriangles(srcs, vertices, indices, colorm, mode, filter, address, dstRegion, srcRegion, subimageOffsets, shader, uniforms, evenOdd)
    216 			return nil
    217 		}) {
    218 			return
    219 		}
    220 	}
    221 
    222 	var s *atlas.Shader
    223 	var imgs [graphics.ShaderImageNum]*atlas.Image
    224 	if shader == nil {
    225 		// Fast path for rendering without a shader (#1355).
    226 		img := srcs[0]
    227 		img.resolvePendingPixels(true)
    228 		imgs[0] = img.img
    229 	} else {
    230 		for i, img := range srcs {
    231 			if img == nil {
    232 				continue
    233 			}
    234 			img.resolvePendingPixels(true)
    235 			imgs[i] = img.img
    236 		}
    237 		s = shader.shader
    238 	}
    239 	i.resolvePendingPixels(false)
    240 
    241 	i.img.DrawTriangles(imgs, vertices, indices, colorm, mode, filter, address, dstRegion, srcRegion, subimageOffsets, s, uniforms, evenOdd)
    242 	i.invalidatePendingPixels()
    243 }
    244 
    245 type Shader struct {
    246 	shader *atlas.Shader
    247 }
    248 
    249 func NewShader(program *shaderir.Program) *Shader {
    250 	return &Shader{
    251 		shader: atlas.NewShader(program),
    252 	}
    253 }
    254 
    255 func (s *Shader) MarkDisposed() {
    256 	s.shader.MarkDisposed()
    257 	s.shader = nil
    258 }