command.go (19046B)
1 // Copyright 2016 Hajime Hoshi 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 graphicscommand 16 17 import ( 18 "fmt" 19 "math" 20 "strings" 21 22 "github.com/hajimehoshi/ebiten/v2/internal/affine" 23 "github.com/hajimehoshi/ebiten/v2/internal/debug" 24 "github.com/hajimehoshi/ebiten/v2/internal/driver" 25 "github.com/hajimehoshi/ebiten/v2/internal/graphics" 26 "github.com/hajimehoshi/ebiten/v2/internal/shaderir" 27 ) 28 29 var theGraphicsDriver driver.Graphics 30 31 func SetGraphicsDriver(driver driver.Graphics) { 32 theGraphicsDriver = driver 33 } 34 35 func NeedsRestoring() bool { 36 return theGraphicsDriver.NeedsRestoring() 37 } 38 39 // command represents a drawing command. 40 // 41 // A command for drawing that is created when Image functions are called like DrawTriangles, 42 // or Fill. 43 // A command is not immediately executed after created. Instaed, it is queued after created, 44 // and executed only when necessary. 45 type command interface { 46 fmt.Stringer 47 48 Exec(indexOffset int) error 49 } 50 51 type size struct { 52 width float32 53 height float32 54 } 55 56 // commandQueue is a command queue for drawing commands. 57 type commandQueue struct { 58 // commands is a queue of drawing commands. 59 commands []command 60 61 // vertices represents a vertices data in OpenGL's array buffer. 62 vertices []float32 63 64 // nvertices represents the current length of vertices. 65 // nvertices must <= len(vertices). 66 // vertices is never shrunk since re-extending a vertices buffer is heavy. 67 // 68 // TODO: This is a number of float32 values, not a number of vertices. 69 // Rename or fix the program. 70 nvertices int 71 72 srcSizes []size 73 74 indices []uint16 75 nindices int 76 77 tmpNumIndices int 78 nextIndex int 79 80 err error 81 } 82 83 // theCommandQueue is the command queue for the current process. 84 var theCommandQueue = &commandQueue{} 85 86 // appendVertices appends vertices to the queue. 87 func (q *commandQueue) appendVertices(vertices []float32, src *Image) { 88 if len(q.vertices) < q.nvertices+len(vertices) { 89 n := q.nvertices + len(vertices) - len(q.vertices) 90 q.vertices = append(q.vertices, make([]float32, n)...) 91 q.srcSizes = append(q.srcSizes, make([]size, n/graphics.VertexFloatNum)...) 92 } 93 copy(q.vertices[q.nvertices:], vertices) 94 95 n := len(vertices) / graphics.VertexFloatNum 96 base := q.nvertices / graphics.VertexFloatNum 97 98 width := float32(1) 99 height := float32(1) 100 // src is nil when a shader is used and there are no specified images. 101 if src != nil { 102 w, h := src.InternalSize() 103 width = float32(w) 104 height = float32(h) 105 } 106 for i := 0; i < n; i++ { 107 idx := base + i 108 q.srcSizes[idx].width = width 109 q.srcSizes[idx].height = height 110 } 111 q.nvertices += len(vertices) 112 } 113 114 func (q *commandQueue) appendIndices(indices []uint16, offset uint16) { 115 if len(q.indices) < q.nindices+len(indices) { 116 n := q.nindices + len(indices) - len(q.indices) 117 q.indices = append(q.indices, make([]uint16, n)...) 118 } 119 for i := range indices { 120 q.indices[q.nindices+i] = indices[i] + offset 121 } 122 q.nindices += len(indices) 123 } 124 125 // EnqueueDrawTrianglesCommand enqueues a drawing-image command. 126 func (q *commandQueue) EnqueueDrawTrianglesCommand(dst *Image, srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, color affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, uniforms []interface{}, evenOdd bool) { 127 if len(indices) > graphics.IndicesNum { 128 panic(fmt.Sprintf("graphicscommand: len(indices) must be <= graphics.IndicesNum but not at EnqueueDrawTrianglesCommand: len(indices): %d, graphics.IndicesNum: %d", len(indices), graphics.IndicesNum)) 129 } 130 131 split := false 132 if q.tmpNumIndices+len(indices) > graphics.IndicesNum { 133 q.tmpNumIndices = 0 134 q.nextIndex = 0 135 split = true 136 } 137 138 // Assume that all the image sizes are same. 139 // Assume that the images are packed from the front in the slice srcs. 140 q.appendVertices(vertices, srcs[0]) 141 q.appendIndices(indices, uint16(q.nextIndex)) 142 q.nextIndex += len(vertices) / graphics.VertexFloatNum 143 q.tmpNumIndices += len(indices) 144 145 if srcs[0] != nil { 146 w, h := srcs[0].InternalSize() 147 srcRegion.X /= float32(w) 148 srcRegion.Y /= float32(h) 149 srcRegion.Width /= float32(w) 150 srcRegion.Height /= float32(h) 151 for i := range offsets { 152 offsets[i][0] /= float32(w) 153 offsets[i][1] /= float32(h) 154 } 155 } 156 157 // TODO: If dst is the screen, reorder the command to be the last. 158 if !split && 0 < len(q.commands) { 159 // TODO: Pass offsets and uniforms when merging considers the shader. 160 if last, ok := q.commands[len(q.commands)-1].(*drawTrianglesCommand); ok { 161 if last.CanMergeWithDrawTrianglesCommand(dst, srcs, vertices, color, mode, filter, address, dstRegion, srcRegion, shader, evenOdd) { 162 last.setVertices(q.lastVertices(len(vertices) + last.numVertices())) 163 last.addNumIndices(len(indices)) 164 return 165 } 166 } 167 } 168 169 c := &drawTrianglesCommand{ 170 dst: dst, 171 srcs: srcs, 172 offsets: offsets, 173 vertices: q.lastVertices(len(vertices)), 174 nindices: len(indices), 175 color: color, 176 mode: mode, 177 filter: filter, 178 address: address, 179 dstRegion: dstRegion, 180 srcRegion: srcRegion, 181 shader: shader, 182 uniforms: uniforms, 183 evenOdd: evenOdd, 184 } 185 q.commands = append(q.commands, c) 186 } 187 188 func (q *commandQueue) lastVertices(n int) []float32 { 189 return q.vertices[q.nvertices-n : q.nvertices] 190 } 191 192 // Enqueue enqueues a drawing command other than a draw-triangles command. 193 // 194 // For a draw-triangles command, use EnqueueDrawTrianglesCommand. 195 func (q *commandQueue) Enqueue(command command) { 196 // TODO: If dst is the screen, reorder the command to be the last. 197 q.commands = append(q.commands, command) 198 } 199 200 // Flush flushes the command queue. 201 func (q *commandQueue) Flush() error { 202 return runOnMainThread(func() error { 203 return q.flush() 204 }) 205 } 206 207 // flush must be called the main thread. 208 func (q *commandQueue) flush() error { 209 if len(q.commands) == 0 { 210 return nil 211 } 212 213 es := q.indices 214 vs := q.vertices 215 debug.Logf("Graphics commands:\n") 216 217 if theGraphicsDriver.HasHighPrecisionFloat() { 218 n := q.nvertices / graphics.VertexFloatNum 219 for i := 0; i < n; i++ { 220 s := q.srcSizes[i] 221 222 idx := i * graphics.VertexFloatNum 223 224 // Convert pixels to texels. 225 vs[idx+2] /= s.width 226 vs[idx+3] /= s.height 227 228 // Avoid the center of the pixel, which is problematic (#929, #1171). 229 // Instead, align the vertices with about 1/3 pixels. 230 x := vs[idx] 231 y := vs[idx+1] 232 ix := float32(math.Floor(float64(x))) 233 iy := float32(math.Floor(float64(y))) 234 fracx := x - ix 235 fracy := y - iy 236 switch { 237 case fracx < 3.0/16.0: 238 vs[idx] = ix 239 case fracx < 8.0/16.0: 240 vs[idx] = ix + 5.0/16.0 241 case fracx < 13.0/16.0: 242 vs[idx] = ix + 11.0/16.0 243 default: 244 vs[idx] = ix + 16.0/16.0 245 } 246 switch { 247 case fracy < 3.0/16.0: 248 vs[idx+1] = iy 249 case fracy < 8.0/16.0: 250 vs[idx+1] = iy + 5.0/16.0 251 case fracy < 13.0/16.0: 252 vs[idx+1] = iy + 11.0/16.0 253 default: 254 vs[idx+1] = iy + 16.0/16.0 255 } 256 } 257 } else { 258 n := q.nvertices / graphics.VertexFloatNum 259 for i := 0; i < n; i++ { 260 s := q.srcSizes[i] 261 262 // Convert pixels to texels. 263 vs[i*graphics.VertexFloatNum+2] /= s.width 264 vs[i*graphics.VertexFloatNum+3] /= s.height 265 } 266 } 267 268 theGraphicsDriver.Begin() 269 cs := q.commands 270 for len(cs) > 0 { 271 nv := 0 272 ne := 0 273 nc := 0 274 for _, c := range cs { 275 if dtc, ok := c.(*drawTrianglesCommand); ok { 276 if dtc.numIndices() > graphics.IndicesNum { 277 panic(fmt.Sprintf("graphicscommand: dtc.NumIndices() must be <= graphics.IndicesNum but not at Flush: dtc.NumIndices(): %d, graphics.IndicesNum: %d", dtc.numIndices(), graphics.IndicesNum)) 278 } 279 if ne+dtc.numIndices() > graphics.IndicesNum { 280 break 281 } 282 nv += dtc.numVertices() 283 ne += dtc.numIndices() 284 } 285 nc++ 286 } 287 if 0 < ne { 288 theGraphicsDriver.SetVertices(vs[:nv], es[:ne]) 289 es = es[ne:] 290 vs = vs[nv:] 291 } 292 indexOffset := 0 293 for _, c := range cs[:nc] { 294 if err := c.Exec(indexOffset); err != nil { 295 return err 296 } 297 debug.Logf(" %s\n", c) 298 // TODO: indexOffset should be reset if the command type is different 299 // from the previous one. This fix is needed when another drawing command is 300 // introduced than drawTrianglesCommand. 301 if dtc, ok := c.(*drawTrianglesCommand); ok { 302 indexOffset += dtc.numIndices() 303 } 304 } 305 cs = cs[nc:] 306 } 307 theGraphicsDriver.End() 308 309 // Release the commands explicitly (#1803). 310 // Apparently, the part of a slice between len and cap-1 still holds references. 311 // Then, resetting the length by [:0] doesn't release the references. 312 for i := range q.commands { 313 q.commands[i] = nil 314 } 315 q.commands = q.commands[:0] 316 q.nvertices = 0 317 q.nindices = 0 318 q.tmpNumIndices = 0 319 q.nextIndex = 0 320 return nil 321 } 322 323 // FlushCommands flushes the command queue. 324 func FlushCommands() error { 325 return theCommandQueue.Flush() 326 } 327 328 // drawTrianglesCommand represents a drawing command to draw an image on another image. 329 type drawTrianglesCommand struct { 330 dst *Image 331 srcs [graphics.ShaderImageNum]*Image 332 offsets [graphics.ShaderImageNum - 1][2]float32 333 vertices []float32 334 nindices int 335 color affine.ColorM 336 mode driver.CompositeMode 337 filter driver.Filter 338 address driver.Address 339 dstRegion driver.Region 340 srcRegion driver.Region 341 shader *Shader 342 uniforms []interface{} 343 evenOdd bool 344 } 345 346 func (c *drawTrianglesCommand) String() string { 347 mode := "" 348 switch c.mode { 349 case driver.CompositeModeSourceOver: 350 mode = "source-over" 351 case driver.CompositeModeClear: 352 mode = "clear" 353 case driver.CompositeModeCopy: 354 mode = "copy" 355 case driver.CompositeModeDestination: 356 mode = "destination" 357 case driver.CompositeModeDestinationOver: 358 mode = "destination-over" 359 case driver.CompositeModeSourceIn: 360 mode = "source-in" 361 case driver.CompositeModeDestinationIn: 362 mode = "destination-in" 363 case driver.CompositeModeSourceOut: 364 mode = "source-out" 365 case driver.CompositeModeDestinationOut: 366 mode = "destination-out" 367 case driver.CompositeModeSourceAtop: 368 mode = "source-atop" 369 case driver.CompositeModeDestinationAtop: 370 mode = "destination-atop" 371 case driver.CompositeModeXor: 372 mode = "xor" 373 case driver.CompositeModeLighter: 374 mode = "lighter" 375 case driver.CompositeModeMultiply: 376 mode = "multiply" 377 default: 378 panic(fmt.Sprintf("graphicscommand: invalid composite mode: %d", c.mode)) 379 } 380 381 dst := fmt.Sprintf("%d", c.dst.id) 382 if c.dst.screen { 383 dst += " (screen)" 384 } 385 386 if c.shader != nil { 387 return fmt.Sprintf("draw-triangles: dst: %s, shader, num of indices: %d, mode %s", dst, c.nindices, mode) 388 } 389 390 filter := "" 391 switch c.filter { 392 case driver.FilterNearest: 393 filter = "nearest" 394 case driver.FilterLinear: 395 filter = "linear" 396 case driver.FilterScreen: 397 filter = "screen" 398 default: 399 panic(fmt.Sprintf("graphicscommand: invalid filter: %d", c.filter)) 400 } 401 402 address := "" 403 switch c.address { 404 case driver.AddressClampToZero: 405 address = "clamp_to_zero" 406 case driver.AddressRepeat: 407 address = "repeat" 408 case driver.AddressUnsafe: 409 address = "unsafe" 410 default: 411 panic(fmt.Sprintf("graphicscommand: invalid address: %d", c.address)) 412 } 413 414 var srcstrs [graphics.ShaderImageNum]string 415 for i, src := range c.srcs { 416 if src == nil { 417 srcstrs[i] = "(nil)" 418 continue 419 } 420 srcstrs[i] = fmt.Sprintf("%d", src.id) 421 if src.screen { 422 srcstrs[i] += " (screen)" 423 } 424 } 425 426 r := fmt.Sprintf("(x:%d, y:%d, width:%d, height:%d)", 427 int(c.dstRegion.X), int(c.dstRegion.Y), int(c.dstRegion.Width), int(c.dstRegion.Height)) 428 return fmt.Sprintf("draw-triangles: dst: %s <- src: [%s], dst region: %s, num of indices: %d, colorm: %v, mode: %s, filter: %s, address: %s, even-odd: %t", dst, strings.Join(srcstrs[:], ", "), r, c.nindices, c.color, mode, filter, address, c.evenOdd) 429 } 430 431 // Exec executes the drawTrianglesCommand. 432 func (c *drawTrianglesCommand) Exec(indexOffset int) error { 433 // TODO: Is it ok not to bind any framebuffer here? 434 if c.nindices == 0 { 435 return nil 436 } 437 438 var shaderID driver.ShaderID = driver.InvalidShaderID 439 var imgs [graphics.ShaderImageNum]driver.ImageID 440 if c.shader != nil { 441 shaderID = c.shader.shader.ID() 442 for i, src := range c.srcs { 443 if src == nil { 444 imgs[i] = driver.InvalidImageID 445 continue 446 } 447 imgs[i] = src.image.ID() 448 } 449 } else { 450 imgs[0] = c.srcs[0].image.ID() 451 } 452 453 return theGraphicsDriver.DrawTriangles(c.dst.image.ID(), imgs, c.offsets, shaderID, c.nindices, indexOffset, c.mode, c.color, c.filter, c.address, c.dstRegion, c.srcRegion, c.uniforms, c.evenOdd) 454 } 455 456 func (c *drawTrianglesCommand) numVertices() int { 457 return len(c.vertices) 458 } 459 460 func (c *drawTrianglesCommand) numIndices() int { 461 return c.nindices 462 } 463 464 func (c *drawTrianglesCommand) setVertices(vertices []float32) { 465 c.vertices = vertices 466 } 467 468 func (c *drawTrianglesCommand) addNumIndices(n int) { 469 c.nindices += n 470 } 471 472 // CanMergeWithDrawTrianglesCommand returns a boolean value indicating whether the other drawTrianglesCommand can be merged 473 // with the drawTrianglesCommand c. 474 func (c *drawTrianglesCommand) CanMergeWithDrawTrianglesCommand(dst *Image, srcs [graphics.ShaderImageNum]*Image, vertices []float32, color affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, evenOdd bool) bool { 475 // If a shader is used, commands are not merged. 476 // 477 // TODO: Merge shader commands considering uniform variables. 478 if c.shader != nil || shader != nil { 479 return false 480 } 481 if c.dst != dst { 482 return false 483 } 484 if c.srcs != srcs { 485 return false 486 } 487 if !c.color.Equals(color) { 488 return false 489 } 490 if c.mode != mode { 491 return false 492 } 493 if c.filter != filter { 494 return false 495 } 496 if c.address != address { 497 return false 498 } 499 if c.dstRegion != dstRegion { 500 return false 501 } 502 if c.srcRegion != srcRegion { 503 return false 504 } 505 if c.evenOdd || evenOdd { 506 if c.evenOdd && evenOdd { 507 return !mightOverlapDstRegions(c.vertices, vertices) 508 } 509 return false 510 } 511 return true 512 } 513 514 var ( 515 posInf32 = float32(math.Inf(1)) 516 negInf32 = float32(math.Inf(-1)) 517 ) 518 519 func dstRegionFromVertices(vertices []float32) (minX, minY, maxX, maxY float32) { 520 minX = posInf32 521 minY = posInf32 522 maxX = negInf32 523 maxY = negInf32 524 525 for i := 0; i < len(vertices)/graphics.VertexFloatNum; i++ { 526 x := vertices[graphics.VertexFloatNum*i] 527 y := vertices[graphics.VertexFloatNum*i+1] 528 if x < minX { 529 minX = x 530 } 531 if y < minY { 532 minY = y 533 } 534 if maxX < x { 535 maxX = x 536 } 537 if maxY < y { 538 maxY = y 539 } 540 } 541 return 542 } 543 544 func mightOverlapDstRegions(vertices1, vertices2 []float32) bool { 545 minX1, minY1, maxX1, maxY1 := dstRegionFromVertices(vertices1) 546 minX2, minY2, maxX2, maxY2 := dstRegionFromVertices(vertices2) 547 const mergin = 1 548 return minX1 < maxX2+mergin && minX2 < maxX1+mergin && minY1 < maxY2+mergin && minY2 < maxY1+mergin 549 } 550 551 // replacePixelsCommand represents a command to replace pixels of an image. 552 type replacePixelsCommand struct { 553 dst *Image 554 args []*driver.ReplacePixelsArgs 555 } 556 557 func (c *replacePixelsCommand) String() string { 558 return fmt.Sprintf("replace-pixels: dst: %d, len(args): %d", c.dst.id, len(c.args)) 559 } 560 561 // Exec executes the replacePixelsCommand. 562 func (c *replacePixelsCommand) Exec(indexOffset int) error { 563 c.dst.image.ReplacePixels(c.args) 564 return nil 565 } 566 567 type pixelsCommand struct { 568 result []byte 569 img *Image 570 } 571 572 // Exec executes a pixelsCommand. 573 func (c *pixelsCommand) Exec(indexOffset int) error { 574 p, err := c.img.image.Pixels() 575 if err != nil { 576 return err 577 } 578 c.result = p 579 return nil 580 } 581 582 func (c *pixelsCommand) String() string { 583 return fmt.Sprintf("pixels: image: %d", c.img.id) 584 } 585 586 // disposeImageCommand represents a command to dispose an image. 587 type disposeImageCommand struct { 588 target *Image 589 } 590 591 func (c *disposeImageCommand) String() string { 592 return fmt.Sprintf("dispose-image: target: %d", c.target.id) 593 } 594 595 // Exec executes the disposeImageCommand. 596 func (c *disposeImageCommand) Exec(indexOffset int) error { 597 c.target.image.Dispose() 598 return nil 599 } 600 601 // disposeShaderCommand represents a command to dispose a shader. 602 type disposeShaderCommand struct { 603 target *Shader 604 } 605 606 func (c *disposeShaderCommand) String() string { 607 return fmt.Sprintf("dispose-shader: target") 608 } 609 610 // Exec executes the disposeShaderCommand. 611 func (c *disposeShaderCommand) Exec(indexOffset int) error { 612 c.target.shader.Dispose() 613 return nil 614 } 615 616 // newImageCommand represents a command to create an empty image with given width and height. 617 type newImageCommand struct { 618 result *Image 619 width int 620 height int 621 } 622 623 func (c *newImageCommand) String() string { 624 return fmt.Sprintf("new-image: result: %d, width: %d, height: %d", c.result.id, c.width, c.height) 625 } 626 627 // Exec executes a newImageCommand. 628 func (c *newImageCommand) Exec(indexOffset int) error { 629 i, err := theGraphicsDriver.NewImage(c.width, c.height) 630 if err != nil { 631 return err 632 } 633 c.result.image = i 634 return nil 635 } 636 637 // newScreenFramebufferImageCommand is a command to create a special image for the screen. 638 type newScreenFramebufferImageCommand struct { 639 result *Image 640 width int 641 height int 642 } 643 644 func (c *newScreenFramebufferImageCommand) String() string { 645 return fmt.Sprintf("new-screen-framebuffer-image: result: %d, width: %d, height: %d", c.result.id, c.width, c.height) 646 } 647 648 // Exec executes a newScreenFramebufferImageCommand. 649 func (c *newScreenFramebufferImageCommand) Exec(indexOffset int) error { 650 var err error 651 c.result.image, err = theGraphicsDriver.NewScreenFramebufferImage(c.width, c.height) 652 return err 653 } 654 655 // newShaderCommand is a command to create a shader. 656 type newShaderCommand struct { 657 result *Shader 658 ir *shaderir.Program 659 } 660 661 func (c *newShaderCommand) String() string { 662 return fmt.Sprintf("new-shader") 663 } 664 665 // Exec executes a newShaderCommand. 666 func (c *newShaderCommand) Exec(indexOffset int) error { 667 var err error 668 c.result.shader, err = theGraphicsDriver.NewShader(c.ir) 669 return err 670 } 671 672 // InitializeGraphicsDriverState initialize the current graphics driver state. 673 func InitializeGraphicsDriverState() error { 674 return runOnMainThread(func() error { 675 return theGraphicsDriver.Initialize() 676 }) 677 } 678 679 // ResetGraphicsDriverState resets the current graphics driver state. 680 // If the graphics driver doesn't have an API to reset, ResetGraphicsDriverState does nothing. 681 func ResetGraphicsDriverState() error { 682 if r, ok := theGraphicsDriver.(interface{ Reset() error }); ok { 683 return runOnMainThread(func() error { 684 return r.Reset() 685 }) 686 } 687 return nil 688 } 689 690 // MaxImageSize returns the maximum size of an image. 691 func MaxImageSize() int { 692 var size int 693 _ = runOnMainThread(func() error { 694 size = theGraphicsDriver.MaxImageSize() 695 return nil 696 }) 697 return size 698 }