Performance Tips

Make similar draw function calls successive

The less draw commands, the better the performance is.

One drawing function like DrawImage or Fill is usually treated as one (internal) draw command, but there is an exception. Successive drawing commands are treated as one draw command when all the below conditions are satisfied:

Even when all the above conditions are satisfied, multiple draw commands can be used in really rare cases. Ebiten images usually share an internal automatic texture atlas, but when you consume the atlas, or you create a huge image, those images cannot be on the same texture atlas. In this case, draw commands are separated. The texture atlas size is 4096x4096 so far. Another case is when you use an offscreen as a render source. An offscreen doesn't share the texture atlas with high probability.

examples/sprites is a good example to draw > 10000 sprites with one (or a few) draw command(s).

Know the actual drawing commands with ebitendebug build tag

To see actual drawing commands, you can use ebitendebug build tag. For example, if you execute blocks example, you will see the below logs:

go run -tags="example ebitendebug" github.com/hajimehoshi/ebiten/examples/blocks
--
draw-triangles: dst: 0xc0003a6640 <- src: 0xc0000b80c0, colorm: <nil>, mode 1, filter: 1, address: 0
draw-triangles: dst: 0xc0003a6640 <- src: 0xc0003a6940, colorm: <nil>, mode 0, filter: 1, address: 0
draw-triangles: dst: 0xc0003a66c0 <- src: 0xc0000b80c0, colorm: <nil>, mode 1, filter: 1, address: 0
draw-triangles: dst: 0xc0003a66c0 <- src: 0xc0003a6640, colorm: <nil>, mode 2, filter: 3, address: 0
--
draw-triangles: dst: 0xc0003a6640 <- src: 0xc0000b80c0, colorm: <nil>, mode 1, filter: 1, address: 0
draw-triangles: dst: 0xc0003a6640 <- src: 0xc0003a6940, colorm: <nil>, mode 0, filter: 1, address: 0
draw-triangles: dst: 0xc0003a66c0 <- src: 0xc0000b80c0, colorm: <nil>, mode 1, filter: 1, address: 0
draw-triangles: dst: 0xc0003a66c0 <- src: 0xc0003a6640, colorm: <nil>, mode 2, filter: 3, address: 0
--
draw-triangles: dst: 0xc0003a6640 <- src: 0xc0000b80c0, colorm: <nil>, mode 1, filter: 1, address: 0
draw-triangles: dst: 0xc0003a6640 <- src: 0xc0003a6940, colorm: <nil>, mode 0, filter: 1, address: 0
draw-triangles: dst: 0xc0003a66c0 <- src: 0xc0000b80c0, colorm: <nil>, mode 1, filter: 1, address: 0
draw-triangles: dst: 0xc0003a66c0 <- src: 0xc0003a6640, colorm: <nil>, mode 2, filter: 3, address: 0
--
draw-triangles: dst: 0xc0003a6640 <- src: 0xc0000b80c0, colorm: <nil>, mode 1, filter: 1, address: 0
draw-triangles: dst: 0xc0003a6640 <- src: 0xc0003a6940, colorm: <nil>, mode 0, filter: 1, address: 0
draw-triangles: dst: 0xc0003a66c0 <- src: 0xc0000b80c0, colorm: <nil>, mode 1, filter: 1, address: 0
draw-triangles: dst: 0xc0003a66c0 <- src: 0xc0003a6640, colorm: <nil>, mode 2, filter: 3, address: 0
...

Avoid changing render sources' pixels

Ebiten records almost all draw functions in order to restore when context lost happens. When a render source's pixel is changed after it is used as a render source, Ebiten tries a complicated calculation for restoring.

A.DrawImage(B, op) // B is a render source
B.DrawImage(C, op) // tries to change B's pixels. Avoid this if possible.

As well, cyclic drawing should also be avoided.

A.DrawImage(B, op)
B.DrawImage(A, op) // cyclic drawing! Avoid this if possible.

Avoid using the screen as a render source

The screen is a special image because the image is cleared at every frame. As explained above, Ebiten records a drawing function calls but using the screen as a render source makes the calculation complicated.

Don't call (*Image).ReplacePixels too much

ReplacePixels is a relatively heavy function that calls glTexSubImage2D internally.

Don't call (*Image).At too much

At is also heavy that tries to solve all the queued draw commands.