graphics.go (10021B)
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 opengl 16 17 import ( 18 "fmt" 19 20 "github.com/hajimehoshi/ebiten/v2/internal/affine" 21 "github.com/hajimehoshi/ebiten/v2/internal/driver" 22 "github.com/hajimehoshi/ebiten/v2/internal/graphics" 23 "github.com/hajimehoshi/ebiten/v2/internal/shaderir" 24 "github.com/hajimehoshi/ebiten/v2/internal/thread" 25 ) 26 27 var theGraphics Graphics 28 29 func Get() *Graphics { 30 return &theGraphics 31 } 32 33 type Graphics struct { 34 state openGLState 35 context context 36 37 nextImageID driver.ImageID 38 images map[driver.ImageID]*Image 39 40 nextShaderID driver.ShaderID 41 shaders map[driver.ShaderID]*Shader 42 43 // drawCalled is true just after Draw is called. This holds true until ReplacePixels is called. 44 drawCalled bool 45 } 46 47 func (g *Graphics) SetThread(thread *thread.Thread) { 48 g.context.t = thread 49 } 50 51 func (g *Graphics) Begin() { 52 // Do nothing. 53 } 54 55 func (g *Graphics) End() { 56 // Call glFlush to prevent black flicking (especially on Android (#226) and iOS). 57 // TODO: examples/sprites worked without this. Is this really needed? 58 g.context.flush() 59 } 60 61 func (g *Graphics) SetTransparent(transparent bool) { 62 // Do nothings. 63 } 64 65 func (g *Graphics) checkSize(width, height int) { 66 if width < 1 { 67 panic(fmt.Sprintf("opengl: width (%d) must be equal or more than %d", width, 1)) 68 } 69 if height < 1 { 70 panic(fmt.Sprintf("opengl: height (%d) must be equal or more than %d", height, 1)) 71 } 72 m := g.context.getMaxTextureSize() 73 if width > m { 74 panic(fmt.Sprintf("opengl: width (%d) must be less than or equal to %d", width, m)) 75 } 76 if height > m { 77 panic(fmt.Sprintf("opengl: height (%d) must be less than or equal to %d", height, m)) 78 } 79 } 80 81 func (g *Graphics) genNextImageID() driver.ImageID { 82 id := g.nextImageID 83 g.nextImageID++ 84 return id 85 } 86 87 func (g *Graphics) InvalidImageID() driver.ImageID { 88 return -1 89 } 90 91 func (g *Graphics) genNextShaderID() driver.ShaderID { 92 id := g.nextShaderID 93 g.nextShaderID++ 94 return id 95 } 96 97 func (g *Graphics) NewImage(width, height int) (driver.Image, error) { 98 i := &Image{ 99 id: g.genNextImageID(), 100 graphics: g, 101 width: width, 102 height: height, 103 } 104 w := graphics.InternalImageSize(width) 105 h := graphics.InternalImageSize(height) 106 g.checkSize(w, h) 107 t, err := g.context.newTexture(w, h) 108 if err != nil { 109 return nil, err 110 } 111 i.textureNative = t 112 g.addImage(i) 113 return i, nil 114 } 115 116 func (g *Graphics) NewScreenFramebufferImage(width, height int) (driver.Image, error) { 117 g.checkSize(width, height) 118 i := &Image{ 119 id: g.genNextImageID(), 120 graphics: g, 121 width: width, 122 height: height, 123 screen: true, 124 } 125 g.addImage(i) 126 return i, nil 127 } 128 129 func (g *Graphics) addImage(img *Image) { 130 if g.images == nil { 131 g.images = map[driver.ImageID]*Image{} 132 } 133 if _, ok := g.images[img.id]; ok { 134 panic(fmt.Sprintf("opengl: image ID %d was already registered", img.id)) 135 } 136 g.images[img.id] = img 137 } 138 139 func (g *Graphics) removeImage(img *Image) { 140 delete(g.images, img.id) 141 } 142 143 // Reset resets or initializes the current OpenGL state. 144 func (g *Graphics) Reset() error { 145 return g.state.reset(&g.context) 146 } 147 148 func (g *Graphics) SetVertices(vertices []float32, indices []uint16) { 149 // Note that the vertices passed to BufferSubData is not under GC management 150 // in opengl package due to unsafe-way. 151 // See BufferSubData in context_mobile.go. 152 g.context.arrayBufferSubData(vertices) 153 g.context.elementArrayBufferSubData(indices) 154 } 155 156 func (g *Graphics) Draw(dst, src driver.ImageID, indexLen int, indexOffset int, mode driver.CompositeMode, colorM *affine.ColorM, filter driver.Filter, address driver.Address, sourceRegion driver.Region) error { 157 destination := g.images[dst] 158 source := g.images[src] 159 160 g.drawCalled = true 161 162 if err := destination.setViewport(); err != nil { 163 return err 164 } 165 g.context.blendFunc(mode) 166 167 program := g.state.programs[programKey{ 168 useColorM: colorM != nil, 169 filter: filter, 170 address: address, 171 }] 172 173 uniforms := []uniformVariable{} 174 175 vw := destination.framebuffer.width 176 vh := destination.framebuffer.height 177 uniforms = append(uniforms, uniformVariable{ 178 name: "viewport_size", 179 value: []float32{float32(vw), float32(vh)}, 180 typ: shaderir.Type{Main: shaderir.Vec2}, 181 }, uniformVariable{ 182 name: "source_region", 183 value: []float32{ 184 sourceRegion.X, 185 sourceRegion.Y, 186 sourceRegion.X + sourceRegion.Width, 187 sourceRegion.Y + sourceRegion.Height, 188 }, 189 typ: shaderir.Type{Main: shaderir.Vec4}, 190 }) 191 192 if colorM != nil { 193 // ColorM's elements are immutable. It's OK to hold the reference without copying. 194 esBody, esTranslate := colorM.UnsafeElements() 195 uniforms = append(uniforms, uniformVariable{ 196 name: "color_matrix_body", 197 value: esBody, 198 typ: shaderir.Type{Main: shaderir.Mat4}, 199 }, uniformVariable{ 200 name: "color_matrix_translation", 201 value: esTranslate, 202 typ: shaderir.Type{Main: shaderir.Vec4}, 203 }) 204 } 205 206 if filter != driver.FilterNearest { 207 sw, sh := source.framebufferSize() 208 uniforms = append(uniforms, uniformVariable{ 209 name: "source_size", 210 value: []float32{float32(sw), float32(sh)}, 211 typ: shaderir.Type{Main: shaderir.Vec2}, 212 }) 213 } 214 215 if filter == driver.FilterScreen { 216 scale := float32(destination.width) / float32(source.width) 217 uniforms = append(uniforms, uniformVariable{ 218 name: "scale", 219 value: scale, 220 typ: shaderir.Type{Main: shaderir.Float}, 221 }) 222 } 223 224 var imgs [graphics.ShaderImageNum]textureVariable 225 for i := range imgs { 226 if i == 0 { 227 imgs[i].valid = true 228 imgs[i].native = source.textureNative 229 } 230 } 231 232 if err := g.useProgram(program, uniforms, imgs); err != nil { 233 return err 234 } 235 236 g.context.drawElements(indexLen, indexOffset*2) // 2 is uint16 size in bytes 237 238 // glFlush() might be necessary at least on MacBook Pro (a smilar problem at #419), 239 // but basically this pass the tests (esp. TestImageTooManyFill). 240 // As glFlush() causes performance problems, this should be avoided as much as possible. 241 // Let's wait and see, and file a new issue when this problem is newly foung. 242 return nil 243 } 244 245 func (g *Graphics) SetVsyncEnabled(enabled bool) { 246 // Do nothing 247 } 248 249 func (g *Graphics) FramebufferYDirection() driver.YDirection { 250 return driver.Upward 251 } 252 253 func (g *Graphics) NeedsRestoring() bool { 254 return g.context.needsRestoring() 255 } 256 257 func (g *Graphics) IsGL() bool { 258 return true 259 } 260 261 func (g *Graphics) HasHighPrecisionFloat() bool { 262 return g.context.hasHighPrecisionFloat() 263 } 264 265 func (g *Graphics) MaxImageSize() int { 266 return g.context.getMaxTextureSize() 267 } 268 269 func (g *Graphics) NewShader(program *shaderir.Program) (driver.Shader, error) { 270 s, err := newShader(g.genNextShaderID(), g, program) 271 if err != nil { 272 return nil, err 273 } 274 g.addShader(s) 275 return s, nil 276 } 277 278 func (g *Graphics) addShader(shader *Shader) { 279 if g.shaders == nil { 280 g.shaders = map[driver.ShaderID]*Shader{} 281 } 282 if _, ok := g.shaders[shader.id]; ok { 283 panic(fmt.Sprintf("opengl: shader ID %d was already registered", shader.id)) 284 } 285 g.shaders[shader.id] = shader 286 } 287 288 func (g *Graphics) removeShader(shader *Shader) { 289 delete(g.shaders, shader.id) 290 } 291 292 func (g *Graphics) DrawShader(dst driver.ImageID, srcs [graphics.ShaderImageNum]driver.ImageID, offsets [graphics.ShaderImageNum - 1][2]float32, shader driver.ShaderID, indexLen int, indexOffset int, sourceRegion driver.Region, mode driver.CompositeMode, uniforms []interface{}) error { 293 d := g.images[dst] 294 s := g.shaders[shader] 295 296 g.drawCalled = true 297 298 if err := d.setViewport(); err != nil { 299 return err 300 } 301 g.context.blendFunc(mode) 302 303 us := make([]uniformVariable, graphics.PreservedUniformVariablesNum+len(uniforms)) 304 305 { 306 const idx = graphics.DestinationTextureSizeUniformVariableIndex 307 w, h := d.framebufferSize() 308 us[idx].name = "U0" 309 us[idx].value = []float32{float32(w), float32(h)} 310 us[idx].typ = s.ir.Uniforms[0] 311 } 312 { 313 sizes := make([]float32, 2*len(srcs)) 314 for i, src := range srcs { 315 if img := g.images[src]; img != nil { 316 w, h := img.framebufferSize() 317 sizes[2*i] = float32(w) 318 sizes[2*i+1] = float32(h) 319 } 320 321 } 322 const idx = graphics.TextureSizesUniformVariableIndex 323 us[idx].name = fmt.Sprintf("U%d", idx) 324 us[idx].value = sizes 325 us[idx].typ = s.ir.Uniforms[idx] 326 } 327 { 328 voffsets := make([]float32, 2*len(offsets)) 329 for i, o := range offsets { 330 voffsets[2*i] = o[0] 331 voffsets[2*i+1] = o[1] 332 } 333 const idx = graphics.TextureSourceOffsetsUniformVariableIndex 334 us[idx].name = fmt.Sprintf("U%d", idx) 335 us[idx].value = voffsets 336 us[idx].typ = s.ir.Uniforms[idx] 337 } 338 { 339 origin := []float32{float32(sourceRegion.X), float32(sourceRegion.Y)} 340 const idx = graphics.TextureSourceRegionOriginUniformVariableIndex 341 us[idx].name = fmt.Sprintf("U%d", idx) 342 us[idx].value = origin 343 us[idx].typ = s.ir.Uniforms[idx] 344 } 345 { 346 size := []float32{float32(sourceRegion.Width), float32(sourceRegion.Height)} 347 const idx = graphics.TextureSourceRegionSizeUniformVariableIndex 348 us[idx].name = fmt.Sprintf("U%d", idx) 349 us[idx].value = size 350 us[idx].typ = s.ir.Uniforms[idx] 351 } 352 353 for i, v := range uniforms { 354 const offset = graphics.PreservedUniformVariablesNum 355 us[i+offset].name = fmt.Sprintf("U%d", i+offset) 356 us[i+offset].value = v 357 us[i+offset].typ = s.ir.Uniforms[i+offset] 358 } 359 360 var ts [graphics.ShaderImageNum]textureVariable 361 for i, src := range srcs { 362 if src == g.InvalidImageID() { 363 continue 364 } 365 ts[i].valid = true 366 ts[i].native = g.images[src].textureNative 367 } 368 369 if err := g.useProgram(s.p, us, ts); err != nil { 370 return err 371 } 372 g.context.drawElements(indexLen, indexOffset*2) // 2 is uint16 size in bytes 373 374 return nil 375 }