Add texture blitting optimization
This commit is contained in:
parent
f12e67346d
commit
24c27d7836
4 changed files with 79 additions and 11 deletions
12
TODO.md
12
TODO.md
|
|
@ -29,9 +29,15 @@ findings (AMD Radeon test):
|
|||
|
||||
based on phase 2 results:
|
||||
|
||||
- [ ] batch rendering, instancing (render-bound confirmed)
|
||||
- [ ] ~~if cpu-bound: SIMD, struct-of-arrays, multithreading~~ (not needed)
|
||||
- [ ] re-test after each change
|
||||
- [x] batch rendering via texture blitting (10x improvement)
|
||||
- [x] ~~if cpu-bound: SIMD, struct-of-arrays, multithreading~~ (not needed)
|
||||
- [x] re-test after each change
|
||||
|
||||
findings:
|
||||
- texture blitting: pre-render circle to texture, drawTexture() per entity
|
||||
- baseline: 60fps @ ~5k entities
|
||||
- optimized: 60fps @ ~50k entities, 30fps @ 100k entities
|
||||
- see journal.txt for detailed benchmarks
|
||||
|
||||
## phase 4: add collision
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
.{
|
||||
.name = .lofivor,
|
||||
.version = "0.0.1",
|
||||
.fingerprint = 0x9de74c19d031cdc9,
|
||||
.fingerprint = 0x74960d9e2a8050e2,
|
||||
.dependencies = .{
|
||||
.raylib_zig = .{
|
||||
.url = "git+https://github.com/raylib-zig/raylib-zig?ref=devel#a4d18b2d1cf8fdddec68b5b084535fca0475f466",
|
||||
|
|
|
|||
35
journal.txt
35
journal.txt
|
|
@ -20,7 +20,40 @@ analysis: linear scaling, each drawCircle = separate GPU draw call
|
|||
|
||||
---
|
||||
|
||||
optimization 1: [pending]
|
||||
optimization 1: texture blitting
|
||||
--------------------------------
|
||||
technique: pre-render circle to 16x16 texture, drawTexture() per entity
|
||||
code: sandbox_main.zig:109-124, 170-177
|
||||
|
||||
benchmark2.log results:
|
||||
- 60fps stable: ~50,000 entities
|
||||
- 60fps breaks: ~52,000-55,000 entities (18-21ms frame)
|
||||
- 23k entities: 16.7ms frame (still vsync-locked)
|
||||
- 59k entities: 20.6ms frame
|
||||
|
||||
extended benchmark (benchmark3):
|
||||
- 50k entities: 16.7ms (vsync-locked, briefly touches 19ms)
|
||||
- 60k entities: 20.7ms
|
||||
- 70k entities: 23.7ms
|
||||
- 80k entities: 30.1ms
|
||||
- 100k entities: 33-37ms (~30fps)
|
||||
|
||||
comparison to baseline:
|
||||
- baseline broke 60fps at ~5,000 entities
|
||||
- texture blitting breaks at ~50,000 entities
|
||||
- ~10x improvement in entity ceiling
|
||||
|
||||
analysis: raylib batches texture draws internally when using same texture.
|
||||
individual drawCircle() = separate draw call each. drawTexture() with same
|
||||
texture = batched into fewer GPU calls.
|
||||
|
||||
notes: render_ms stays ~16-18ms up to ~50k, then scales roughly linearly.
|
||||
at 100k entities we're at ~30fps which is still playable. update loop
|
||||
remains negligible (<0.6ms even at 100k).
|
||||
|
||||
---
|
||||
|
||||
optimization 2: [pending]
|
||||
-------------------------
|
||||
technique:
|
||||
results:
|
||||
|
|
|
|||
|
|
@ -12,6 +12,10 @@ const SCREEN_HEIGHT = sandbox.SCREEN_HEIGHT;
|
|||
const BG_COLOR = rl.Color{ .r = 10, .g = 10, .b = 18, .a = 255 };
|
||||
const CYAN = rl.Color{ .r = 0, .g = 255, .b = 255, .a = 255 };
|
||||
|
||||
// entity rendering
|
||||
const ENTITY_RADIUS: f32 = 4.0;
|
||||
const TEXTURE_SIZE: i32 = 16; // must be >= 2 * radius
|
||||
|
||||
// logging thresholds
|
||||
const TARGET_FRAME_MS: f32 = 16.7; // 60fps
|
||||
const THRESHOLD_MARGIN: f32 = 2.0; // hysteresis margin to avoid bounce
|
||||
|
|
@ -102,11 +106,35 @@ const BenchmarkLogger = struct {
|
|||
}
|
||||
};
|
||||
|
||||
fn createCircleTexture() ?rl.Texture2D {
|
||||
// create a render texture to draw circle into
|
||||
const target = rl.loadRenderTexture(TEXTURE_SIZE, TEXTURE_SIZE) catch return null;
|
||||
|
||||
rl.beginTextureMode(target);
|
||||
rl.clearBackground(rl.Color{ .r = 0, .g = 0, .b = 0, .a = 0 }); // transparent
|
||||
rl.drawCircle(
|
||||
@divTrunc(TEXTURE_SIZE, 2),
|
||||
@divTrunc(TEXTURE_SIZE, 2),
|
||||
ENTITY_RADIUS,
|
||||
CYAN,
|
||||
);
|
||||
rl.endTextureMode();
|
||||
|
||||
return target.texture;
|
||||
}
|
||||
|
||||
pub fn main() !void {
|
||||
rl.initWindow(@intCast(SCREEN_WIDTH), @intCast(SCREEN_HEIGHT), "lofivor sandbox");
|
||||
defer rl.closeWindow();
|
||||
rl.setTargetFPS(60);
|
||||
|
||||
// create circle texture for batched rendering
|
||||
const circle_texture = createCircleTexture() orelse {
|
||||
std.debug.print("failed to create circle texture\n", .{});
|
||||
return;
|
||||
};
|
||||
defer rl.unloadTexture(circle_texture);
|
||||
|
||||
var entities = sandbox.Entities.init();
|
||||
var prng = std.Random.DefaultPrng.init(@intCast(std.time.timestamp()));
|
||||
var rng = prng.random();
|
||||
|
|
@ -140,13 +168,14 @@ pub fn main() !void {
|
|||
rl.beginDrawing();
|
||||
rl.clearBackground(BG_COLOR);
|
||||
|
||||
// draw entities as filled circles
|
||||
// draw entities using pre-rendered circle texture
|
||||
const half_size = @as(f32, @floatFromInt(TEXTURE_SIZE)) / 2.0;
|
||||
for (entities.items[0..entities.count]) |entity| {
|
||||
rl.drawCircle(
|
||||
@intFromFloat(entity.x),
|
||||
@intFromFloat(entity.y),
|
||||
4,
|
||||
CYAN,
|
||||
rl.drawTexture(
|
||||
circle_texture,
|
||||
@intFromFloat(entity.x - half_size),
|
||||
@intFromFloat(entity.y - half_size),
|
||||
rl.Color.white, // tint (white = use original colors)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue