image.go (7504B)
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 "image/color" 21 22 "github.com/hajimehoshi/ebiten/v2/internal/affine" 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 "github.com/hajimehoshi/ebiten/v2/internal/shareable" 27 ) 28 29 type Image struct { 30 img *shareable.Image 31 width int 32 height int 33 34 hasFill bool 35 fillColor color.RGBA 36 37 pixels []byte 38 needsToResolvePixels bool 39 } 40 41 func BeginFrame() error { 42 if err := shareable.BeginFrame(); err != nil { 43 return err 44 } 45 return flushDelayedCommands() 46 } 47 48 func EndFrame() error { 49 return shareable.EndFrame() 50 } 51 52 func NewImage(width, height int) *Image { 53 i := &Image{} 54 i.initialize(width, height) 55 return i 56 } 57 58 func (i *Image) initialize(width, height int) { 59 if maybeCanAddDelayedCommand() { 60 if tryAddDelayedCommand(func() error { 61 i.initialize(width, height) 62 return nil 63 }) { 64 return 65 } 66 } 67 i.img = shareable.NewImage(width, height) 68 i.width = width 69 i.height = height 70 } 71 72 func (i *Image) SetVolatile(volatile bool) { 73 if maybeCanAddDelayedCommand() { 74 if tryAddDelayedCommand(func() error { 75 i.SetVolatile(volatile) 76 return nil 77 }) { 78 return 79 } 80 } 81 i.img.SetVolatile(volatile) 82 } 83 84 func NewScreenFramebufferImage(width, height int) *Image { 85 i := &Image{} 86 i.initializeAsScreenFramebuffer(width, height) 87 return i 88 } 89 90 func (i *Image) initializeAsScreenFramebuffer(width, height int) { 91 if maybeCanAddDelayedCommand() { 92 if tryAddDelayedCommand(func() error { 93 i.initializeAsScreenFramebuffer(width, height) 94 return nil 95 }) { 96 return 97 } 98 } 99 100 i.img = shareable.NewScreenFramebufferImage(width, height) 101 i.width = width 102 i.height = height 103 } 104 105 func (i *Image) invalidatePendingPixels() { 106 i.pixels = nil 107 i.needsToResolvePixels = false 108 i.hasFill = false 109 } 110 111 func (i *Image) resolvePendingPixels(keepPendingPixels bool) { 112 if i.needsToResolvePixels && i.hasFill { 113 panic("buffered: needsToResolvePixels and hasFill must not be true at the same time") 114 } 115 if i.needsToResolvePixels { 116 i.img.ReplacePixels(i.pixels) 117 if !keepPendingPixels { 118 i.pixels = nil 119 } 120 i.needsToResolvePixels = false 121 } 122 i.resolvePendingFill() 123 } 124 125 func (i *Image) resolvePendingFill() { 126 if !i.hasFill { 127 return 128 } 129 i.img.Fill(i.fillColor) 130 i.hasFill = false 131 } 132 133 func (i *Image) MarkDisposed() { 134 if maybeCanAddDelayedCommand() { 135 if tryAddDelayedCommand(func() error { 136 i.MarkDisposed() 137 return nil 138 }) { 139 return 140 } 141 } 142 i.invalidatePendingPixels() 143 i.img.MarkDisposed() 144 } 145 146 func (img *Image) Pixels(x, y, width, height int) (pix []byte, err error) { 147 checkDelayedCommandsFlushed("Pixels") 148 149 if !image.Rect(x, y, x+width, y+height).In(image.Rect(0, 0, img.width, img.height)) { 150 return nil, fmt.Errorf("buffered: out of range") 151 } 152 153 pix = make([]byte, 4*width*height) 154 155 // If there are pixels or pending fillling that needs to be resolved, use this rather than resolving. 156 // Resolving them needs to access GPU and is expensive (#1137). 157 if img.hasFill { 158 for i := 0; i < len(pix)/4; i++ { 159 pix[4*i] = img.fillColor.R 160 pix[4*i+1] = img.fillColor.G 161 pix[4*i+2] = img.fillColor.B 162 pix[4*i+3] = img.fillColor.A 163 } 164 return pix, nil 165 } 166 167 if img.pixels == nil { 168 pix, err := img.img.Pixels(0, 0, img.width, img.height) 169 if err != nil { 170 return nil, err 171 } 172 img.pixels = pix 173 } 174 175 for j := 0; j < height; j++ { 176 copy(pix[4*j*width:4*(j+1)*width], img.pixels[4*((j+y)*img.width+x):]) 177 } 178 return pix, nil 179 } 180 181 func (i *Image) Dump(name string, blackbg bool) error { 182 checkDelayedCommandsFlushed("Dump") 183 return i.img.Dump(name, blackbg) 184 } 185 186 func (i *Image) Fill(clr color.RGBA) { 187 if maybeCanAddDelayedCommand() { 188 if tryAddDelayedCommand(func() error { 189 i.Fill(clr) 190 return nil 191 }) { 192 return 193 } 194 } 195 196 // Defer filling the image so that successive fillings will be merged into one (#1134). 197 i.invalidatePendingPixels() 198 i.fillColor = clr 199 i.hasFill = true 200 } 201 202 func (i *Image) ReplacePixels(pix []byte, x, y, width, height int) error { 203 if l := 4 * width * height; len(pix) != l { 204 panic(fmt.Sprintf("buffered: len(pix) was %d but must be %d", len(pix), l)) 205 } 206 207 if maybeCanAddDelayedCommand() { 208 copied := make([]byte, len(pix)) 209 copy(copied, pix) 210 if tryAddDelayedCommand(func() error { 211 i.ReplacePixels(copied, x, y, width, height) 212 return nil 213 }) { 214 return nil 215 } 216 } 217 218 if x == 0 && y == 0 && width == i.width && height == i.height { 219 i.invalidatePendingPixels() 220 221 // Call ReplacePixels immediately. If a lot of new images are created but they are used at different 222 // timings, pixels are sent to GPU at different timings, which is very inefficient. 223 i.img.ReplacePixels(pix) 224 return nil 225 } 226 227 i.resolvePendingFill() 228 229 // TODO: Can we use (*restorable.Image).ReplacePixels? 230 if i.pixels == nil { 231 pix, err := i.img.Pixels(0, 0, i.width, i.height) 232 if err != nil { 233 return err 234 } 235 i.pixels = pix 236 } 237 i.replacePendingPixels(pix, x, y, width, height) 238 return nil 239 } 240 241 func (i *Image) replacePendingPixels(pix []byte, x, y, width, height int) { 242 for j := 0; j < height; j++ { 243 copy(i.pixels[4*((j+y)*i.width+x):], pix[4*j*width:4*(j+1)*width]) 244 } 245 i.needsToResolvePixels = true 246 } 247 248 // DrawTriangles draws the src image with the given vertices. 249 // 250 // Copying vertices and indices is the caller's responsibility. 251 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{}) { 252 for _, src := range srcs { 253 if i == src { 254 panic("buffered: Image.DrawTriangles: source images must be different from the receiver") 255 } 256 } 257 258 if maybeCanAddDelayedCommand() { 259 if tryAddDelayedCommand(func() error { 260 // Arguments are not copied. Copying is the caller's responsibility. 261 i.DrawTriangles(srcs, vertices, indices, colorm, mode, filter, address, sourceRegion, subimageOffsets, shader, uniforms) 262 return nil 263 }) { 264 return 265 } 266 } 267 268 var s *shareable.Shader 269 var imgs [graphics.ShaderImageNum]*shareable.Image 270 if shader == nil { 271 // Fast path for rendering without a shader (#1355). 272 img := srcs[0] 273 img.resolvePendingPixels(true) 274 imgs[0] = img.img 275 } else { 276 for i, img := range srcs { 277 if img == nil { 278 continue 279 } 280 img.resolvePendingPixels(true) 281 imgs[i] = img.img 282 } 283 s = shader.shader 284 } 285 i.resolvePendingPixels(false) 286 287 i.img.DrawTriangles(imgs, vertices, indices, colorm, mode, filter, address, sourceRegion, subimageOffsets, s, uniforms) 288 i.invalidatePendingPixels() 289 } 290 291 type Shader struct { 292 shader *shareable.Shader 293 } 294 295 func NewShader(program *shaderir.Program) *Shader { 296 return &Shader{ 297 shader: shareable.NewShader(program), 298 } 299 } 300 301 func (s *Shader) MarkDisposed() { 302 s.shader.MarkDisposed() 303 s.shader = nil 304 }