mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-26 03:38:55 +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),
|
float32(r.Min.X), float32(r.Min.Y), float32(r.Max.X), float32(r.Max.Y),
|
||||||
v.ColorR, v.ColorG, v.ColorB, v.ColorA)
|
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()
|
i.disposeMipmaps()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,6 +53,7 @@ type command interface {
|
|||||||
AddNumVertices(n int)
|
AddNumVertices(n int)
|
||||||
AddNumIndices(n int)
|
AddNumIndices(n int)
|
||||||
CanMerge(dst, src *Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address) bool
|
CanMerge(dst, src *Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address) bool
|
||||||
|
Dst() *Image
|
||||||
}
|
}
|
||||||
|
|
||||||
type size struct {
|
type size struct {
|
||||||
@ -412,6 +413,10 @@ func (c *drawTrianglesCommand) CanMerge(dst, src *Image, color *affine.ColorM, m
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *drawTrianglesCommand) Dst() *Image {
|
||||||
|
return c.dst
|
||||||
|
}
|
||||||
|
|
||||||
// replacePixelsCommand represents a command to replace pixels of an image.
|
// replacePixelsCommand represents a command to replace pixels of an image.
|
||||||
type replacePixelsCommand struct {
|
type replacePixelsCommand struct {
|
||||||
dst *Image
|
dst *Image
|
||||||
@ -450,6 +455,10 @@ func (c *replacePixelsCommand) CanMerge(dst, src *Image, color *affine.ColorM, m
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *replacePixelsCommand) Dst() *Image {
|
||||||
|
return c.dst
|
||||||
|
}
|
||||||
|
|
||||||
type pixelsCommand struct {
|
type pixelsCommand struct {
|
||||||
result []byte
|
result []byte
|
||||||
img *Image
|
img *Image
|
||||||
@ -487,6 +496,10 @@ func (c *pixelsCommand) CanMerge(dst, src *Image, color *affine.ColorM, mode dri
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *pixelsCommand) Dst() *Image {
|
||||||
|
return c.img
|
||||||
|
}
|
||||||
|
|
||||||
// disposeCommand represents a command to dispose an image.
|
// disposeCommand represents a command to dispose an image.
|
||||||
type disposeCommand struct {
|
type disposeCommand struct {
|
||||||
target *Image
|
target *Image
|
||||||
@ -520,6 +533,10 @@ func (c *disposeCommand) CanMerge(dst, src *Image, color *affine.ColorM, mode dr
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *disposeCommand) Dst() *Image {
|
||||||
|
return c.target
|
||||||
|
}
|
||||||
|
|
||||||
// newImageCommand represents a command to create an empty image with given width and height.
|
// newImageCommand represents a command to create an empty image with given width and height.
|
||||||
type newImageCommand struct {
|
type newImageCommand struct {
|
||||||
result *Image
|
result *Image
|
||||||
@ -559,6 +576,10 @@ func (c *newImageCommand) CanMerge(dst, src *Image, color *affine.ColorM, mode d
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *newImageCommand) Dst() *Image {
|
||||||
|
return c.result
|
||||||
|
}
|
||||||
|
|
||||||
// newScreenFramebufferImageCommand is a command to create a special image for the screen.
|
// newScreenFramebufferImageCommand is a command to create a special image for the screen.
|
||||||
type newScreenFramebufferImageCommand struct {
|
type newScreenFramebufferImageCommand struct {
|
||||||
result *Image
|
result *Image
|
||||||
@ -595,6 +616,10 @@ func (c *newScreenFramebufferImageCommand) CanMerge(dst, src *Image, color *affi
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *newScreenFramebufferImageCommand) Dst() *Image {
|
||||||
|
return c.result
|
||||||
|
}
|
||||||
|
|
||||||
// ResetGraphicsDriverState resets or initializes the current graphics driver state.
|
// ResetGraphicsDriverState resets or initializes the current graphics driver state.
|
||||||
func ResetGraphicsDriverState() error {
|
func ResetGraphicsDriverState() error {
|
||||||
return theGraphicsDriver.Reset()
|
return theGraphicsDriver.Reset()
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/internal/affine"
|
"github.com/hajimehoshi/ebiten/internal/affine"
|
||||||
@ -56,6 +57,79 @@ func genNextID() int {
|
|||||||
return id
|
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.
|
// NewImage returns a new image.
|
||||||
//
|
//
|
||||||
// Note that the image is not initialized yet.
|
// Note that the image is not initialized yet.
|
||||||
@ -72,7 +146,7 @@ func NewImage(width, height int) *Image {
|
|||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
}
|
}
|
||||||
theCommandQueue.Enqueue(c)
|
enqueueDelayedCommand(c)
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,15 +164,16 @@ func NewScreenFramebufferImage(width, height int) *Image {
|
|||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
}
|
}
|
||||||
theCommandQueue.Enqueue(c)
|
enqueueDelayedCommand(c)
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) Dispose() {
|
func (i *Image) Dispose() {
|
||||||
|
// TODO: We can remove unnecessary commands from delayedCommand.
|
||||||
c := &disposeCommand{
|
c := &disposeCommand{
|
||||||
target: i,
|
target: i,
|
||||||
}
|
}
|
||||||
theCommandQueue.Enqueue(c)
|
enqueueDelayedCommand(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) InternalSize() (int, int) {
|
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 {
|
if i.lastCommand == lastCommandNone && !i.screen {
|
||||||
i.lastCommand = lastCommandClear
|
i.lastCommand = lastCommandClear
|
||||||
@ -128,6 +216,7 @@ func (i *Image) DrawTriangles(src *Image, vertices []float32, indices []uint16,
|
|||||||
// Pixels returns the image's pixels.
|
// Pixels returns the image's pixels.
|
||||||
// Pixels might return nil when OpenGL error happens.
|
// Pixels might return nil when OpenGL error happens.
|
||||||
func (i *Image) Pixels() []byte {
|
func (i *Image) Pixels() []byte {
|
||||||
|
flushDelayedCommandsFor(i.id)
|
||||||
c := &pixelsCommand{
|
c := &pixelsCommand{
|
||||||
result: nil,
|
result: nil,
|
||||||
img: i,
|
img: i,
|
||||||
@ -154,7 +243,7 @@ func (i *Image) ReplacePixels(p []byte, x, y, width, height int) {
|
|||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
}
|
}
|
||||||
theCommandQueue.Enqueue(c)
|
enqueueDelayedCommand(c)
|
||||||
i.lastCommand = lastCommandReplacePixels
|
i.lastCommand = lastCommandReplacePixels
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user