mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-25 11:18:54 +01:00
graphics: Delay draw commands and execute them only when needed
This change introduces a queue for delayed graphics commands. When an image's pixels are retrieved or the screen is rendered, Ebiten calculates the set of the necessary draw commands and execute them. This reduces the number of draw calls especially for the launching phase. Fixes #921
This commit is contained in:
parent
2621681a2a
commit
0c70823f27
4
image.go
4
image.go
@ -488,7 +488,9 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o
|
||||
float32(r.Min.X), float32(r.Min.Y), float32(r.Max.X), float32(r.Max.Y),
|
||||
v.ColorR, v.ColorG, v.ColorB, v.ColorA)
|
||||
}
|
||||
i.mipmap.original().DrawTriangles(src, vs, indices, options.ColorM.impl, mode, filter, driver.Address(options.Address))
|
||||
is := make([]uint16, len(indices))
|
||||
copy(is, indices)
|
||||
i.mipmap.original().DrawTriangles(src, vs, is, options.ColorM.impl, mode, filter, driver.Address(options.Address))
|
||||
i.disposeMipmaps()
|
||||
}
|
||||
|
||||
|
@ -53,6 +53,7 @@ type command interface {
|
||||
AddNumVertices(n int)
|
||||
AddNumIndices(n int)
|
||||
CanMerge(dst, src *Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address) bool
|
||||
Dst() *Image
|
||||
}
|
||||
|
||||
type size struct {
|
||||
@ -412,6 +413,10 @@ func (c *drawTrianglesCommand) CanMerge(dst, src *Image, color *affine.ColorM, m
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *drawTrianglesCommand) Dst() *Image {
|
||||
return c.dst
|
||||
}
|
||||
|
||||
// replacePixelsCommand represents a command to replace pixels of an image.
|
||||
type replacePixelsCommand struct {
|
||||
dst *Image
|
||||
@ -450,6 +455,10 @@ func (c *replacePixelsCommand) CanMerge(dst, src *Image, color *affine.ColorM, m
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *replacePixelsCommand) Dst() *Image {
|
||||
return c.dst
|
||||
}
|
||||
|
||||
type pixelsCommand struct {
|
||||
result []byte
|
||||
img *Image
|
||||
@ -487,6 +496,10 @@ func (c *pixelsCommand) CanMerge(dst, src *Image, color *affine.ColorM, mode dri
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *pixelsCommand) Dst() *Image {
|
||||
return c.img
|
||||
}
|
||||
|
||||
// disposeCommand represents a command to dispose an image.
|
||||
type disposeCommand struct {
|
||||
target *Image
|
||||
@ -520,6 +533,10 @@ func (c *disposeCommand) CanMerge(dst, src *Image, color *affine.ColorM, mode dr
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *disposeCommand) Dst() *Image {
|
||||
return c.target
|
||||
}
|
||||
|
||||
// newImageCommand represents a command to create an empty image with given width and height.
|
||||
type newImageCommand struct {
|
||||
result *Image
|
||||
@ -559,6 +576,10 @@ func (c *newImageCommand) CanMerge(dst, src *Image, color *affine.ColorM, mode d
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *newImageCommand) Dst() *Image {
|
||||
return c.result
|
||||
}
|
||||
|
||||
// newScreenFramebufferImageCommand is a command to create a special image for the screen.
|
||||
type newScreenFramebufferImageCommand struct {
|
||||
result *Image
|
||||
@ -595,6 +616,10 @@ func (c *newScreenFramebufferImageCommand) CanMerge(dst, src *Image, color *affi
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *newScreenFramebufferImageCommand) Dst() *Image {
|
||||
return c.result
|
||||
}
|
||||
|
||||
// ResetGraphicsDriverState resets or initializes the current graphics driver state.
|
||||
func ResetGraphicsDriverState() error {
|
||||
return theGraphicsDriver.Reset()
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"fmt"
|
||||
"image"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/internal/affine"
|
||||
@ -56,6 +57,79 @@ func genNextID() int {
|
||||
return id
|
||||
}
|
||||
|
||||
var delayedCommands []interface{}
|
||||
|
||||
type drawTrianglesCommandArgs struct {
|
||||
dst *Image
|
||||
src *Image
|
||||
vertices []float32
|
||||
indices []uint16
|
||||
color *affine.ColorM
|
||||
mode driver.CompositeMode
|
||||
filter driver.Filter
|
||||
address driver.Address
|
||||
}
|
||||
|
||||
func enqueueDelayedCommand(c interface{}) {
|
||||
delayedCommands = append(delayedCommands, c)
|
||||
}
|
||||
|
||||
func flushDelayedCommandsFor(dstID int) {
|
||||
// Choose commands that are needed to solve an image that ID is dstID.
|
||||
indices := map[int]struct{}{}
|
||||
ids := map[int]struct{}{dstID: {}}
|
||||
for len(ids) > 0 {
|
||||
newIDs := map[int]struct{}{}
|
||||
for i, c := range delayedCommands {
|
||||
if _, ok := indices[i]; ok {
|
||||
continue
|
||||
}
|
||||
switch c := c.(type) {
|
||||
case *drawTrianglesCommandArgs:
|
||||
if _, ok := ids[c.dst.id]; ok {
|
||||
indices[i] = struct{}{}
|
||||
newIDs[c.src.id] = struct{}{}
|
||||
}
|
||||
case command:
|
||||
if _, ok := ids[c.Dst().id]; ok {
|
||||
indices[i] = struct{}{}
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("graphicscommands: unexpected command: %v", c))
|
||||
}
|
||||
}
|
||||
ids = newIDs
|
||||
}
|
||||
|
||||
// Enqueue the chosen commands. Replace the used command with nil.
|
||||
var sortedIndices []int
|
||||
for i := range indices {
|
||||
sortedIndices = append(sortedIndices, i)
|
||||
}
|
||||
sort.Ints(sortedIndices)
|
||||
for _, i := range sortedIndices {
|
||||
switch c := delayedCommands[i].(type) {
|
||||
case *drawTrianglesCommandArgs:
|
||||
theCommandQueue.EnqueueDrawTrianglesCommand(c.dst, c.src, c.vertices, c.indices, c.color, c.mode, c.filter, c.address)
|
||||
case command:
|
||||
theCommandQueue.Enqueue(c)
|
||||
default:
|
||||
panic(fmt.Sprintf("graphicscommands: unexpected command: %v", c))
|
||||
}
|
||||
delayedCommands[i] = nil
|
||||
}
|
||||
|
||||
// Remove the used commands from delayedCommands.
|
||||
var newDelayedCommands []interface{}
|
||||
for _, c := range delayedCommands {
|
||||
if c == nil {
|
||||
continue
|
||||
}
|
||||
newDelayedCommands = append(newDelayedCommands, c)
|
||||
}
|
||||
delayedCommands = newDelayedCommands
|
||||
}
|
||||
|
||||
// NewImage returns a new image.
|
||||
//
|
||||
// Note that the image is not initialized yet.
|
||||
@ -72,7 +146,7 @@ func NewImage(width, height int) *Image {
|
||||
width: width,
|
||||
height: height,
|
||||
}
|
||||
theCommandQueue.Enqueue(c)
|
||||
enqueueDelayedCommand(c)
|
||||
return i
|
||||
}
|
||||
|
||||
@ -90,15 +164,16 @@ func NewScreenFramebufferImage(width, height int) *Image {
|
||||
width: width,
|
||||
height: height,
|
||||
}
|
||||
theCommandQueue.Enqueue(c)
|
||||
enqueueDelayedCommand(c)
|
||||
return i
|
||||
}
|
||||
|
||||
func (i *Image) Dispose() {
|
||||
// TODO: We can remove unnecessary commands from delayedCommand.
|
||||
c := &disposeCommand{
|
||||
target: i,
|
||||
}
|
||||
theCommandQueue.Enqueue(c)
|
||||
enqueueDelayedCommand(c)
|
||||
}
|
||||
|
||||
func (i *Image) InternalSize() (int, int) {
|
||||
@ -116,7 +191,20 @@ func (i *Image) DrawTriangles(src *Image, vertices []float32, indices []uint16,
|
||||
}
|
||||
}
|
||||
|
||||
theCommandQueue.EnqueueDrawTrianglesCommand(i, src, vertices, indices, clr, mode, filter, address)
|
||||
// vertices and indices are created internally and it is fine to assume they are immutable.
|
||||
enqueueDelayedCommand(&drawTrianglesCommandArgs{
|
||||
dst: i,
|
||||
src: src,
|
||||
vertices: vertices,
|
||||
indices: indices,
|
||||
color: clr,
|
||||
mode: mode,
|
||||
filter: filter,
|
||||
address: address,
|
||||
})
|
||||
if i.screen {
|
||||
flushDelayedCommandsFor(i.id)
|
||||
}
|
||||
|
||||
if i.lastCommand == lastCommandNone && !i.screen {
|
||||
i.lastCommand = lastCommandClear
|
||||
@ -128,6 +216,7 @@ func (i *Image) DrawTriangles(src *Image, vertices []float32, indices []uint16,
|
||||
// Pixels returns the image's pixels.
|
||||
// Pixels might return nil when OpenGL error happens.
|
||||
func (i *Image) Pixels() []byte {
|
||||
flushDelayedCommandsFor(i.id)
|
||||
c := &pixelsCommand{
|
||||
result: nil,
|
||||
img: i,
|
||||
@ -154,7 +243,7 @@ func (i *Image) ReplacePixels(p []byte, x, y, width, height int) {
|
||||
width: width,
|
||||
height: height,
|
||||
}
|
||||
theCommandQueue.Enqueue(c)
|
||||
enqueueDelayedCommand(c)
|
||||
i.lastCommand = lastCommandReplacePixels
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user