perf: add reproducible sprite benchmarks
Co-authored-by: codegirl007 <s.raide@gmail.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
.PHONY: shaders-vulkan shaders-d3d12 shaders-metal shaders-all bake toad hello_sprite crowd camera_sandbox clips check test help
|
||||
.PHONY: flame flame-build flame-record flame-svg flame-report flame-tools
|
||||
.PHONY: perf-draw perf-frame
|
||||
|
||||
# Flamegraph profiling (needs: pacman -S perf). Example: make flame or make flame FLAME_EXAMPLE=toad
|
||||
FLAME_EXAMPLE ?= crowd
|
||||
@@ -12,6 +13,16 @@ FLAME_STAMP := $(shell date +%Y%m%d-%H%M%S)
|
||||
endif
|
||||
FLAME_PREFIX := $(FLAME_OUT_DIR)/$(FLAME_EXAMPLE)-$(FLAME_STAMP)
|
||||
|
||||
PERF_DRAW_ITERATIONS ?= 2000000
|
||||
PERF_DRAW_WARMUP ?= 10000
|
||||
PERF_DRAW_TRIALS ?= 7
|
||||
PERF_FRAME_SPRITES ?= 128
|
||||
PERF_FRAME_FRAMES ?= 400
|
||||
PERF_FRAME_WARMUP ?= 100
|
||||
PERF_FRAME_TRIALS ?= 10
|
||||
# 0=visible, 1=half offscreen, 2=alternating textures
|
||||
PERF_FRAME_SCENARIO ?= 0
|
||||
|
||||
help:
|
||||
@echo "Targets:"
|
||||
@echo " bake Bake all characters under content/characters/"
|
||||
@@ -31,6 +42,8 @@ help:
|
||||
@echo " flame-record perf record (play, then quit)"
|
||||
@echo " flame-svg Convert perf.data -> $(FLAME_PREFIX).svg/.jpg"
|
||||
@echo " flame-report Convert perf.data -> $(FLAME_PREFIX)-report.txt"
|
||||
@echo " perf-draw Deterministic CPU draw benchmark"
|
||||
@echo " perf-frame Deterministic SDL GPU frame benchmark (scenario 0/1/2)"
|
||||
|
||||
bake:
|
||||
./scripts/bake_all.sh
|
||||
@@ -57,6 +70,20 @@ check:
|
||||
odin check examples/camera_sandbox -collection:pkg=.
|
||||
odin check examples/clips -collection:pkg=.
|
||||
|
||||
perf-draw:
|
||||
odin run benchmarks/draw_sprite -collection:pkg=. -debug -o:speed \
|
||||
-define:PERF_ITERATIONS=$(PERF_DRAW_ITERATIONS) \
|
||||
-define:PERF_WARMUP=$(PERF_DRAW_WARMUP) \
|
||||
-define:PERF_TRIALS=$(PERF_DRAW_TRIALS)
|
||||
|
||||
perf-frame:
|
||||
odin run benchmarks/sprite_frame -collection:pkg=. -debug -o:speed \
|
||||
-define:PERF_SPRITES=$(PERF_FRAME_SPRITES) \
|
||||
-define:PERF_FRAMES=$(PERF_FRAME_FRAMES) \
|
||||
-define:PERF_WARMUP_FRAMES=$(PERF_FRAME_WARMUP) \
|
||||
-define:PERF_TRIALS=$(PERF_FRAME_TRIALS) \
|
||||
-define:PERF_SCENARIO=$(PERF_FRAME_SCENARIO)
|
||||
|
||||
toad:
|
||||
odin run examples/toad -collection:pkg=.
|
||||
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
package main
|
||||
|
||||
import "core:fmt"
|
||||
import "core:os"
|
||||
import eng "pkg:engine"
|
||||
import sdl "vendor:sdl3"
|
||||
|
||||
PERF_ITERATIONS :: #config(PERF_ITERATIONS, 2_000_000)
|
||||
PERF_WARMUP :: #config(PERF_WARMUP, 10_000)
|
||||
PERF_TRIALS :: #config(PERF_TRIALS, 7)
|
||||
|
||||
@(private)
|
||||
median :: proc(values: []f64) -> f64 {
|
||||
for i in 1 ..< len(values) {
|
||||
value := values[i]
|
||||
j := i
|
||||
for j > 0 {
|
||||
if values[j - 1] <= value do break
|
||||
values[j] = values[j - 1]
|
||||
j -= 1
|
||||
}
|
||||
values[j] = value
|
||||
}
|
||||
|
||||
middle := len(values) / 2
|
||||
if len(values) & 1 == 1 do return values[middle]
|
||||
return (values[middle - 1] + values[middle]) / 2
|
||||
}
|
||||
|
||||
@(private)
|
||||
run_draws :: proc(app: ^eng.App, sprite: ^eng.Sprite, iterations: int) -> u64 {
|
||||
clear(&app.draw_list)
|
||||
start := sdl.GetTicksNS()
|
||||
for i in 0 ..< iterations {
|
||||
if len(app.draw_list) >= eng.MAX_SPRITES {
|
||||
clear(&app.draw_list)
|
||||
}
|
||||
|
||||
// Vary an input so optimized builds cannot hoist the draw calculations.
|
||||
sprite.position.x = f32(i & 1023)
|
||||
eng.draw_sprite(app, sprite)
|
||||
}
|
||||
return sdl.GetTicksNS() - start
|
||||
}
|
||||
|
||||
main :: proc() {
|
||||
#assert(PERF_ITERATIONS > 0)
|
||||
#assert(PERF_WARMUP > 0)
|
||||
#assert(PERF_TRIALS > 0)
|
||||
|
||||
file_data, err := os.read_entire_file(
|
||||
"assets_baked/characters/toad/toad.char.json",
|
||||
context.allocator,
|
||||
)
|
||||
if err != nil {
|
||||
fmt.eprintfln("benchmark asset read failed: %v", err)
|
||||
return
|
||||
}
|
||||
defer delete(file_data)
|
||||
|
||||
def, ok := eng.parse_char_def(file_data)
|
||||
if !ok do return
|
||||
|
||||
data := eng.Character_Data {
|
||||
def = def,
|
||||
texture = cast(^sdl.GPUTexture)uintptr(1),
|
||||
width = 1911,
|
||||
height = 1526,
|
||||
}
|
||||
app := eng.App {
|
||||
cmd = cast(^sdl.GPUCommandBuffer)uintptr(1),
|
||||
swapchain_texture = cast(^sdl.GPUTexture)uintptr(2),
|
||||
swapchain_w = 800,
|
||||
swapchain_h = 600,
|
||||
camera = eng.camera_default(),
|
||||
draw_list = make(
|
||||
[dynamic]eng.Queued_Sprite,
|
||||
0,
|
||||
eng.MAX_SPRITES,
|
||||
),
|
||||
}
|
||||
defer delete(app.draw_list)
|
||||
|
||||
sprite := eng.spawn_sprite(&data, {400, 400}, "walk", 4)
|
||||
_ = run_draws(&app, &sprite, PERF_WARMUP)
|
||||
|
||||
samples: [PERF_TRIALS]f64
|
||||
fmt.printfln(
|
||||
"benchmark=draw_sprite iterations=%d warmup=%d trials=%d sprites_per_queue=%d",
|
||||
PERF_ITERATIONS,
|
||||
PERF_WARMUP,
|
||||
PERF_TRIALS,
|
||||
eng.MAX_SPRITES,
|
||||
)
|
||||
for trial in 0 ..< PERF_TRIALS {
|
||||
elapsed := run_draws(&app, &sprite, PERF_ITERATIONS)
|
||||
samples[trial] = f64(elapsed) / f64(PERF_ITERATIONS)
|
||||
fmt.printfln("trial=%d ns_per_draw=%.3f", trial + 1, samples[trial])
|
||||
}
|
||||
|
||||
fmt.printfln("median_ns_per_draw=%.3f", median(samples[:]))
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
package main
|
||||
|
||||
import "core:fmt"
|
||||
import eng "pkg:engine"
|
||||
import sdl "vendor:sdl3"
|
||||
|
||||
PERF_SPRITES :: #config(PERF_SPRITES, 128)
|
||||
PERF_FRAMES :: #config(PERF_FRAMES, 400)
|
||||
PERF_WARMUP_FRAMES :: #config(PERF_WARMUP_FRAMES, 100)
|
||||
PERF_TRIALS :: #config(PERF_TRIALS, 10)
|
||||
PERF_SCENARIO :: #config(PERF_SCENARIO, 0)
|
||||
|
||||
WINDOW_WIDTH :: 800
|
||||
WINDOW_HEIGHT :: 600
|
||||
|
||||
Scenario :: enum {
|
||||
Visible,
|
||||
Half_Offscreen,
|
||||
Alternating_Textures,
|
||||
}
|
||||
|
||||
@(private)
|
||||
median :: proc(values: []f64) -> f64 {
|
||||
for i in 1 ..< len(values) {
|
||||
value := values[i]
|
||||
j := i
|
||||
for j > 0 {
|
||||
if values[j - 1] <= value do break
|
||||
values[j] = values[j - 1]
|
||||
j -= 1
|
||||
}
|
||||
values[j] = value
|
||||
}
|
||||
|
||||
middle := len(values) / 2
|
||||
if len(values) & 1 == 1 do return values[middle]
|
||||
return (values[middle - 1] + values[middle]) / 2
|
||||
}
|
||||
|
||||
@(private)
|
||||
scenario_name :: proc(scenario: Scenario) -> string {
|
||||
switch scenario {
|
||||
case .Visible:
|
||||
return "visible"
|
||||
case .Half_Offscreen:
|
||||
return "half_offscreen"
|
||||
case .Alternating_Textures:
|
||||
return "alternating_textures"
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
@(private)
|
||||
present_mode_name :: proc(app: ^eng.App) -> string {
|
||||
if sdl.WindowSupportsGPUPresentMode(app.device, app.window, .IMMEDIATE) {
|
||||
return "immediate"
|
||||
}
|
||||
if sdl.WindowSupportsGPUPresentMode(app.device, app.window, .MAILBOX) {
|
||||
return "mailbox"
|
||||
}
|
||||
return "vsync"
|
||||
}
|
||||
|
||||
@(private)
|
||||
draw_frame :: proc(app: ^eng.App, sprites: []eng.Sprite) {
|
||||
for &sprite in sprites {
|
||||
eng.update_sprite(&sprite, 1.0 / 60.0)
|
||||
}
|
||||
|
||||
eng.begin_frame(app)
|
||||
for &sprite in sprites {
|
||||
eng.draw_sprite(app, &sprite)
|
||||
}
|
||||
eng.end_frame(app)
|
||||
}
|
||||
|
||||
main :: proc() {
|
||||
#assert(PERF_SPRITES > 0)
|
||||
#assert(PERF_SPRITES <= eng.MAX_SPRITES)
|
||||
#assert(PERF_FRAMES > 0)
|
||||
#assert(PERF_WARMUP_FRAMES > 0)
|
||||
#assert(PERF_TRIALS > 0)
|
||||
#assert(PERF_SCENARIO >= 0 && PERF_SCENARIO <= 2)
|
||||
|
||||
scenario := Scenario(PERF_SCENARIO)
|
||||
|
||||
app: eng.App
|
||||
if !eng.init(&app, "sprite frame benchmark", WINDOW_WIDTH, WINDOW_HEIGHT) {
|
||||
return
|
||||
}
|
||||
defer eng.shutdown(&app)
|
||||
|
||||
path := "assets_baked/characters/toad/toad.char.json"
|
||||
data_a, ok := eng.load_character_data(&app, path)
|
||||
if !ok do return
|
||||
defer eng.destroy_character_data(&app, &data_a)
|
||||
|
||||
data_b: eng.Character_Data
|
||||
data_b, ok = eng.load_character_data(&app, path)
|
||||
if !ok do return
|
||||
defer eng.destroy_character_data(&app, &data_b)
|
||||
|
||||
sprites: [PERF_SPRITES]eng.Sprite
|
||||
for i in 0 ..< PERF_SPRITES {
|
||||
data := &data_a
|
||||
if scenario == .Alternating_Textures && (i & 1) == 1 {
|
||||
data = &data_b
|
||||
}
|
||||
|
||||
position := eng.Vec2 {
|
||||
f32(40 + (i % 16) * 48),
|
||||
f32(120 + (i / 16) * 60),
|
||||
}
|
||||
if scenario == .Half_Offscreen && (i & 1) == 1 {
|
||||
position = {-10_000, -10_000}
|
||||
}
|
||||
sprites[i] = eng.spawn_sprite(data, position, "walk", i % 17)
|
||||
}
|
||||
|
||||
for _ in 0 ..< PERF_WARMUP_FRAMES {
|
||||
draw_frame(&app, sprites[:])
|
||||
}
|
||||
if !sdl.WaitForGPUIdle(app.device) {
|
||||
fmt.eprintfln("GPU wait failed after warm-up: %s", sdl.GetError())
|
||||
return
|
||||
}
|
||||
|
||||
fmt.printfln(
|
||||
"benchmark=sprite_frame scenario=%s sprites=%d resolution=%dx%d frames=%d warmup=%d trials=%d backend=%v driver=%s present=%s",
|
||||
scenario_name(scenario),
|
||||
PERF_SPRITES,
|
||||
WINDOW_WIDTH,
|
||||
WINDOW_HEIGHT,
|
||||
PERF_FRAMES,
|
||||
PERF_WARMUP_FRAMES,
|
||||
PERF_TRIALS,
|
||||
app.shader.backend,
|
||||
sdl.GetGPUDeviceDriver(app.device),
|
||||
present_mode_name(&app),
|
||||
)
|
||||
|
||||
samples: [PERF_TRIALS]f64
|
||||
for trial in 0 ..< PERF_TRIALS {
|
||||
start := sdl.GetTicksNS()
|
||||
for _ in 0 ..< PERF_FRAMES {
|
||||
draw_frame(&app, sprites[:])
|
||||
}
|
||||
if !sdl.WaitForGPUIdle(app.device) {
|
||||
fmt.eprintfln("GPU wait failed after trial %d: %s", trial + 1, sdl.GetError())
|
||||
return
|
||||
}
|
||||
elapsed := sdl.GetTicksNS() - start
|
||||
samples[trial] = f64(elapsed) / f64(PERF_FRAMES) / 1_000_000.0
|
||||
fmt.printfln(
|
||||
"trial=%d ms_per_frame=%.3f fps=%.1f",
|
||||
trial + 1,
|
||||
samples[trial],
|
||||
1_000.0 / samples[trial],
|
||||
)
|
||||
}
|
||||
|
||||
median_ms := median(samples[:])
|
||||
fmt.printfln(
|
||||
"median_ms_per_frame=%.3f median_fps=%.1f",
|
||||
median_ms,
|
||||
1_000.0 / median_ms,
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user