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 }