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