80. Efficient Rendering¶
Learn how to write efficient rendering programs in Siv3D.
80.1 Rendering Load Indicators¶
- For simple 2D rendering, inefficient programs rarely affect performance, but when performing large-scale rendering in complex games, you need to be mindful of rendering load
- To reduce CPU and GPU rendering load in drawing processes, aim to minimize the following three indicators
80.1.1 Draw Call Count¶
- The number of drawing commands issued by the Siv3D engine to the GPU
- Note that this is different from the number of
.draw()calls - When the program calls consecutive
.draw()methods, if they use common render settings or textures, they are grouped into a single draw call - The draw call count from the previous frame can be obtained with
Profiler::GetStat().drawCalls - Even for complex games, aim to keep draw calls below a few hundred
80.1.2 Triangle Count¶
- The number of triangles drawn by the GPU
- For example,
Rectdraws 2 triangles,Fonttext rendering draws 2 triangles per character, andCircledraws 10-200 triangles depending on size - The triangle count from the previous frame can be obtained with
Profiler::GetStat().triangleCount - If the draw count exceeds tens of thousands, consider whether there are more efficient drawing methods
80.1.3 Cumulative Pixels Drawn¶
- The cumulative number of pixels painted on screen by each
.draw()call - Off-screen areas are not included. Background fills by
Scene::SetBackground()are not counted - For example, the following drawing paints 400 x 600 = 240,000 pixels (excluding off-screen areas)
- The following drawing ultimately shows only the last rectangle drawn, but since it paints over itself, it totals 400 x 600 x 4 = 960,000 pixels
- There is no way to get the cumulative pixel count painted by the program
Sample Code¶
# include <Siv3D.hpp>
void Main()
{
while (System::Update())
{
ClearPrint();
Print << U"Draw call count: " << Profiler::GetStat().drawCalls;
Print << U"Triangle count: " << Profiler::GetStat().triangleCount;
}
}
80.2 Tips for Reducing Draw Calls¶
- Shape drawing and texture (text) drawing use different render settings, so alternating
.draw()calls between them inhibits draw call grouping - Draw calls can be reduced by grouping shapes together and textures together
Method 1. Alternating Shape and Text Drawing¶
- Two draw calls are issued per cell, which is inefficient
| Indicator | Value |
|---|---|
| Draw calls | ❌ 202 |
| Triangles | 461 |

# include <Siv3D.hpp>
void Main()
{
Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });
const Font font{ FontMethod::MSDF, 48, Typeface::Heavy };
while (System::Update())
{
ClearPrint();
Print << U"Draw call count: " << Profiler::GetStat().drawCalls;
Print << U"Triangle count: " << Profiler::GetStat().triangleCount;
for (int32 y = 0; y < 10; ++y)
{
for (int32 x = 0; x < 10; ++x)
{
Rect{ (x * 40), (y * 40), 38 }.draw();
font(U"0").drawAt(20, Vec2{ (x * 40) + 20, (y * 40) + 20 }, ColorF{ 0.8 });
}
}
}
}
Method 2. Grouping Shape and Text Drawing Separately¶
- All
Rectdraws are combined into one draw call - All text draws are combined into one draw call
| Indicator | Value |
|---|---|
| Draw calls | ✅ 4 |
| Triangles | 457 |

# include <Siv3D.hpp>
void Main()
{
Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });
const Font font{ FontMethod::MSDF, 48, Typeface::Heavy };
while (System::Update())
{
ClearPrint();
Print << U"Draw call count: " << Profiler::GetStat().drawCalls;
Print << U"Triangle count: " << Profiler::GetStat().triangleCount;
for (int32 y = 0; y < 10; ++y)
{
for (int32 x = 0; x < 10; ++x)
{
Rect{ (x * 40), (y * 40), 38 }.draw();
}
}
for (int32 y = 0; y < 10; ++y)
{
for (int32 x = 0; x < 10; ++x)
{
font(U"0").drawAt(20, Vec2{ (x * 40) + 20, (y * 40) + 20 }, ColorF{ 0.8 });
}
}
}
}
80.3 Tips for Reducing Triangle Count¶
- Using grid drawing as an example, let's consider ways to reduce triangle count through drawing method improvements
Method 1. Using Rect::drawFrame() for All Cells¶
Rect::drawFrame()draws 8 triangles per call
| Indicator | Value |
|---|---|
| Draw calls | 3 |
| Triangles | ❌❌ 859 |

# include <Siv3D.hpp>
void Main()
{
Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });
while (System::Update())
{
ClearPrint();
Print << U"Draw call count: " << Profiler::GetStat().drawCalls;
Print << U"Triangle count: " << Profiler::GetStat().triangleCount;
Rect{ 400, 400 }.draw();
for (int32 y = 0; y < 10; ++y)
{
for (int32 x = 0; x < 10; ++x)
{
Rect{ (x * 40), (y * 40), 40 }.drawFrame(1, 0, ColorF{ 0.0 });
}
}
}
}
Method 2. Using Rect::draw() with Gaps¶
Rect::draw()draws 2 triangles- Triangle count is reduced compared to method 1, but pixel fill count doubles
| Indicator | Value |
|---|---|
| Draw calls | 3 |
| Triangles | ❌ 259 |

# include <Siv3D.hpp>
void Main()
{
Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });
while (System::Update())
{
ClearPrint();
Print << U"Draw call count: " << Profiler::GetStat().drawCalls;
Print << U"Triangle count: " << Profiler::GetStat().triangleCount;
Rect{ 400, 400 }.draw(ColorF{ 0.0 });
for (int32 y = 0; y < 10; ++y)
{
for (int32 x = 0; x < 10; ++x)
{
Rect{ (x * 40), (y * 40), 40 }.stretched(-1).draw();
}
}
}
}
Method 3. Drawing Only Vertical and Horizontal Lines¶
- By drawing only the background and vertical/horizontal lines, triangle count and pixel fill count are minimized
| Indicator | Value |
|---|---|
| Draw calls | 3 |
| Triangles | ✅ 103 |

# include <Siv3D.hpp>
void Main()
{
Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });
while (System::Update())
{
ClearPrint();
Print << U"Draw call count: " << Profiler::GetStat().drawCalls;
Print << U"Triangle count: " << Profiler::GetStat().triangleCount;
Rect{ 400, 400 }.draw();
for (int32 i = 0; i <= 10; ++i)
{
Rect{ -1, (-1 + (i * 40)), 402, 2 }.draw(ColorF{ 0.0 });
Rect{ (-1 + (i * 40)), -1, 2, 402 }.draw(ColorF{ 0.0 });
}
}
}
80.4 Drawing Colored and Numbered Grids Efficiently¶
- When drawing many colored
Rectobjects in a grid, consider usingImage+Texture - All cells can be drawn with just one draw call and 2 triangles
- For numbers 0 to N, drawing performance can be saved by skipping rendering when the value is 0
| Indicator | Value |
|---|---|
| Draw calls | 5 |
| Triangles | 255 |

# include <Siv3D.hpp>
Color ToColor(int32 n)
{
static const std::array<Color, 4> palettes = { Colormap01(0), Colormap01(0.33), Colormap01(0.67), Colormap01(1.0) };
return palettes[n];
}
void UpdateImageFromGrid(const Grid<int32>& grid, Image& image)
{
assert(grid.size() == image.size());
for (int32 y = 0; y < grid.height(); ++y)
{
for (int32 x = 0; x < grid.width(); ++x)
{
image[y][x] = ToColor(grid[y][x]);
}
}
}
void Main()
{
Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });
const Font font{ FontMethod::MSDF, 48, Typeface::Heavy };
const TextStyle textStyle = TextStyle::OutlineShadow(0.2, ColorF{ 0.1 }, Vec2{ 2, 2 }, ColorF{ 0.1 });
Grid<int32> grid(10, 10, 0);
for (size_t y = 0; y < grid.height(); ++y)
{
for (size_t x = 0; x < grid.width(); ++x)
{
grid[y][x] = Random(0, 3);
}
}
Image image{ grid.size() };
UpdateImageFromGrid(grid, image);
DynamicTexture texture{ image };
while (System::Update())
{
ClearPrint();
Print << U"Draw call count: " << Profiler::GetStat().drawCalls;
Print << U"Triangle count: " << Profiler::GetStat().triangleCount;
/*
if (grid was updated)
{
UpdateImageFromGrid(grid, image);
texture.fill(image);
}
*/
{
const ScopedRenderStates2D sampler{ SamplerState::ClampNearest };
texture.resized(grid.size() * 40).draw();
}
for (size_t i = 0; i <= grid.width(); ++i)
{
Rect{ -1, (-1 + (i * 40)), (grid.width() * 40 + 2), 2}.draw();
Rect{ (-1 + (i * 40)), -1, 2, (grid.height() * 40 + 2) }.draw();
}
for (int32 y = 0; y < grid.height(); ++y)
{
for (int32 x = 0; x < grid.width(); ++x)
{
if (const int32 n = grid[y][x])
{
const Vec2 pos{ (x * 40 + 20), (y * 40 + 20) };
font(n).drawAt(textStyle, 25, pos);
}
}
}
}
}
80.5 Drawing a 256x256 Grid¶
- Based on 80.4, this uses
Transformer2Dto apply appropriate scaling while drawing a 256 x 256 cell grid to the screen - Text is omitted since cells are small
- This achieves very lightweight rendering for the amount of information displayed
| Indicator | Value |
|---|---|
| Draw calls | 4 |
| Triangles | 1089 |

# include <Siv3D.hpp>
Color ToColor(int32 n)
{
static const std::array<Color, 4> palettes = { Colormap01(0), Colormap01(0.33), Colormap01(0.67), Colormap01(1.0) };
return palettes[n];
}
void UpdateImageFromGrid(const Grid<int32>& grid, Image& image)
{
assert(grid.size() == image.size());
for (int32 y = 0; y < grid.height(); ++y)
{
for (int32 x = 0; x < grid.width(); ++x)
{
image[y][x] = ToColor(grid[y][x]);
}
}
}
void Main()
{
Window::Resize(1280, 720);
Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });
Grid<int32> grid(256, 256, 0);
for (size_t y = 0; y < grid.height(); ++y)
{
for (size_t x = 0; x < grid.width(); ++x)
{
grid[y][x] = Random(0, 3);
}
}
Image image{ grid.size() };
UpdateImageFromGrid(grid, image);
DynamicTexture texture{ image };
while (System::Update())
{
ClearPrint();
Print << U"Draw call count: " << Profiler::GetStat().drawCalls;
Print << U"Triangle count: " << Profiler::GetStat().triangleCount;
/*
if (grid was updated)
{
UpdateImageFromGrid(grid, image);
texture.fill(image);
}
*/
{
// Apply 0.07x scaling to drawing coordinates
const Transformer2D tr{ Mat3x2::Scale(0.07) };
{
const ScopedRenderStates2D sampler{ SamplerState::ClampNearest };
texture.resized(grid.size() * 40).draw();
}
for (size_t i = 0; i <= grid.width(); ++i)
{
Rect{ -1, (-1 + (i * 40)), (grid.width() * 40 + 2), 2 }.draw();
Rect{ (-1 + (i * 40)), -1, 2, (grid.height() * 40 + 2) }.draw();
}
}
}
}