Compare commits

...
Author SHA1 Message Date
Cursor Agentandcodegirl007 3bd49f61c0 docs: add internal render scale FPS findings
Co-authored-by: codegirl007 <s.raide@gmail.com>
2026-08-09 08:46:04 +00:00
Cursor Agentandcodegirl007 dc66203259 perf: add resolution and overdraw scenarios
Co-authored-by: codegirl007 <s.raide@gmail.com>
2026-08-09 08:33:15 +00:00
Cursor Agentandcodegirl007 95723af6a5 docs: link performance issues to benchmark harnesses
Co-authored-by: codegirl007 <s.raide@gmail.com>
2026-08-09 08:29:30 +00:00
Cursor Agentandcodegirl007 5e4758ce16 perf: add reproducible sprite benchmarks
Co-authored-by: codegirl007 <s.raide@gmail.com>
2026-08-09 08:26:59 +00:00
Cursor Agentandcodegirl007 7a04cdaa8b docs: add fixes for sprite performance issues
Co-authored-by: codegirl007 <s.raide@gmail.com>
2026-08-09 06:04:34 +00:00
Cursor Agentandcodegirl007 c49302d90c docs: add renderer enhancement benchmarks
Co-authored-by: codegirl007 <s.raide@gmail.com>
2026-08-09 05:56:12 +00:00
Cursor Agentandcodegirl007 1388c818f4 docs: record sprite draw performance findings
Co-authored-by: codegirl007 <s.raide@gmail.com>
2026-08-09 05:10:52 +00:00
codegirl007 cd29426bbe initial perf tools 2026-08-07 00:45:56 -07:00
codegirl007 b32255ebc5 fix CI 2026-08-06 23:40:19 -07:00
codegirl007 e6be17fb71 initial perf tools 2026-08-03 13:45:59 -07:00
codegirl007 85d475314f Add more tests + explicit failure messages 2026-08-03 00:40:36 -07:00
codegirl007 4dced062a5 Add missing changes that causes failures 2026-08-03 00:39:30 -07:00
codegirl007 cf888f26cd Add explicit failure reasons to tests 2026-08-02 23:53:34 -07:00
codegirl007 248e57d3f0 add more tests 2026-08-02 23:50:37 -07:00
codegirl007 3539943e4e Adding more examples so that we have a good basis on how to do things 2026-08-02 23:46:18 -07:00
codegirl007 209e774230 add missing input file 2026-08-02 23:45:02 -07:00
codegirl007 4dc173ecee Add associated make commands for added examples 2026-08-02 23:37:29 -07:00
codegirl007 5041753bad add more examples 2026-08-02 23:37:29 -07:00
codegirl007 9e680c62fe Initial Camera implementation 2026-08-01 23:53:08 -07:00
codegirl007 589f2bd45c camera 2026-08-01 23:50:03 -07:00
codegirl007 c720774c79 sprite batching
Batch adjacent same-texture sprite draws in end_frame.
Replaces per-sprite DrawGPUPrimitives with texture runs so shared atlases issue one GPU draw.
2026-08-01 23:49:06 -07:00
codegirl007 c1f3d0e61b revert example testing 2026-08-01 23:27:08 -07:00
codegirl007 a85380fb71 sprite batching 2026-08-01 23:25:17 -07:00
codegirl007 21d2a2c0e4 trim atlas of excess transparent pixels from pngs 2026-08-01 12:42:21 -07:00
codegirl007 d488161433 trim atlas of excess transparent pixels 2026-08-01 12:34:11 -07:00
codegirl007 49a3b8a682 queue drawing
- `draw_sprite` now builds verts on the CPU and appends to a per-frame draw list instead of uploading mid-render-pass.
- `begin_frame` only acquires the command buffer / swapchain; `end_frame` does one copy pass on that same buffer, then the render pass, then submit.
- Removes the second command buffer + `WaitForGPUFences` that stalled the CPU once per sprite every frame.
- Vertex/transfer buffers sized for `MAX_SPRITES` (128). Example API unchanged: `begin_frame` → `draw_sprite` → `end_frame`.
2026-08-01 12:15:43 -07:00
codegirl007 35420b7344 queue drawing 2026-08-01 12:10:19 -07:00
codegirl007 2c209da3b2 Merge pull request #7 from Codegirl-Games/sprite-flipping-movement
Movement and sprite flipping
2026-08-01 12:03:41 -07:00
38 changed files with 6681 additions and 441 deletions
@@ -0,0 +1,208 @@
# Add configurable internal render scale to improve fill-bound FPS
## Summary
Complete-frame benchmarks show that the current sprite renderer is primarily
pixel/fill bound on Vulkan/Lavapipe, not CPU draw-preparation bound. Rendering
the world into a smaller offscreen color target and nearest-blitting it to the
native swapchain produced the largest measured FPS improvement.
This should be an opt-in render-quality setting with a native-resolution
fallback. For pixel art, a 50% scale is especially useful because it maps to an
exact 2× nearest-neighbor upscale.
## Evidence
Environment:
- Commit `dc66203259e92ce39291556f7b6c26c8dd999b84`
- Odin `dev-2026-05-nightly:ea5175d`
- `-debug -o:speed`
- SDL 3.4.12, Vulkan/Lavapipe, immediate present
- 800×600 swapchain
- Five 150-frame trials per invocation after 50 warm-up frames
- Order-balanced baseline/candidate invocations
- GPU idle wait included before stopping each trial timer
### Fill and overdraw scaling
The committed `perf-frame` harness produced:
| Workload | Median frame time | Median FPS |
| --- | ---: | ---: |
| 1 visible sprite | 0.592 ms | 1,690 |
| 24 visible sprites | 2.249 ms | 445 |
| 64 visible sprites | 4.017 ms | 249 |
| 128 visible sprites | 6.255 ms | 160 |
| 128 stacked sprites | 7.692 ms | 130 |
Stacking the same 128 sprites increased frame time by approximately 23%,
confirming that overdraw matters.
With one centered sprite, render-target scaling produced:
| Window size | Pixels | Median frame time |
| --- | ---: | ---: |
| 400×300 | 120,000 | 0.268 ms |
| 800×600 | 480,000 | 0.545 ms |
| 1600×1200 | 1,920,000 | 2.075 ms |
The near-linear increase at larger sizes is further evidence of a pixel-bound
workload.
### Internal render-scale prototype
The temporary candidate kept the physical swapchain at 800×600, rendered
sprites into a smaller `COLOR_TARGET | SAMPLER` texture, and used
`BlitGPUTexture` with nearest filtering to upscale into the swapchain.
Aggregate medians across ten trials per mode:
| Workload | Native | 75% scale | 50% scale |
| --- | ---: | ---: | ---: |
| 128 spread sprites | 6.082 ms / 164 FPS | 4.082 ms / 245 FPS | 3.082 ms / 325 FPS |
| 128 stacked sprites | 7.912 ms / 126 FPS | 5.116 ms / 195 FPS | 3.034 ms / 330 FPS |
Compared with native resolution:
- 75% reduced frame time by 3335% and increased FPS by 4955%.
- 50% reduced frame time by 4962% and increased FPS by 97161%.
A visual smoke test confirmed that the 50% path rendered the complete scene at
the correct orientation and 800×600 output size. It was visibly coarser, as
expected. A 75% scale at 800×600 does not produce an integer upscale and can
create uneven pixel sizing with nearest filtering.
## Reproduction harness
Native spread and stacked workloads:
```bash
make perf-frame \
PERF_FRAME_SCENARIO=0 \
PERF_FRAME_SPRITES=128 \
PERF_FRAME_WIDTH=800 \
PERF_FRAME_HEIGHT=600
make perf-frame \
PERF_FRAME_SCENARIO=3 \
PERF_FRAME_SPRITES=128 \
PERF_FRAME_WIDTH=800 \
PERF_FRAME_HEIGHT=600
```
Run the same commands on the candidate branch with internal render scale set to
75% and 50%. Keep every `PERF_FRAME_*` value unchanged between comparisons.
## Suggested fix
Add explicit logical/output and internal-render dimensions to `App`:
```odin
Render_Scale :: enum {
Native,
Three_Quarter,
Half,
}
App :: struct {
// Existing swapchain fields remain the logical/output dimensions.
swapchain_texture: ^sdl.GPUTexture,
swapchain_w: u32,
swapchain_h: u32,
render_scale: Render_Scale,
scene_texture: ^sdl.GPUTexture,
scene_w: u32,
scene_h: u32,
}
```
Create the offscreen target with the swapchain format and both usages required
by SDL's blit path:
```odin
app.scene_texture = sdl.CreateGPUTexture(
app.device,
{
type = .D2,
format = sdl.GetGPUSwapchainTextureFormat(app.device, app.window),
usage = {.COLOR_TARGET, .SAMPLER},
width = app.scene_w,
height = app.scene_h,
layer_count_or_depth = 1,
num_levels = 1,
sample_count = ._1,
},
)
```
Render the sprite pass into `scene_texture`. Continue using the logical
swapchain dimensions for camera and clip-space calculations so world layout
does not change with render scale:
```odin
color_info := sdl.GPUColorTargetInfo {
texture = app.scene_texture,
clear_color = app.clear_color,
load_op = .CLEAR,
store_op = .STORE,
cycle = true,
}
```
After ending the render pass, upscale into the full swapchain:
```odin
blit := sdl.GPUBlitInfo {
source = {
texture = app.scene_texture,
w = app.scene_w,
h = app.scene_h,
},
destination = {
texture = app.swapchain_texture,
w = app.swapchain_w,
h = app.swapchain_h,
},
load_op = .DONT_CARE,
filter = .NEAREST,
}
sdl.BlitGPUTexture(app.cmd, blit)
```
Use the existing direct-to-swapchain path at native scale to avoid an
unnecessary blit. Recreate the offscreen texture whenever the swapchain size,
format, or render-scale setting changes. If native-resolution UI or text is
added later, render it after the world blit in a separate swapchain pass.
## Rejected experiment: fragment alpha discard
Approximately 49.7% of pixels inside the baked frame rectangles have exact
alpha zero. A temporary fragment shader discarded those texels:
```glsl
vec4 texel = texture(u_tex, v_uv);
if (texel.a == 0.0) {
discard;
}
out_color = texel;
```
Despite the high transparent coverage, this regressed frame time by roughly
56% in both spread and stacked workloads. Do not add alpha discard without
contradictory hardware-GPU evidence.
## Acceptance criteria
- Native, 75%, and 50% internal render-scale settings are available.
- Native scale retains the current direct-to-swapchain path.
- The scene target is recreated safely on resize, format change, or scale
change and released during shutdown.
- Camera/world coordinates remain stable when render scale changes.
- Nearest filtering is used for pixel-art output.
- Add screenshot-based checks for output orientation, viewport coverage, and
stable sprite placement at every scale.
- Benchmark spread and stacked scenarios on at least one hardware GPU.
- Document the quality tradeoff and recommend integer upscale ratios for pixel
art.
@@ -0,0 +1,218 @@
# Add deterministic CPU and frame benchmarks for sprite rendering
## Summary
The current `make flame` workflow is valuable for finding call stacks, but it
does not provide a reproducible performance metric:
- `examples/crowd` draws only 24 sprites.
- `MAX_SPRITES` limits the renderer to 128 sprites.
- Recording starts an interactive application and asks the user to play for
1020 seconds before quitting.
- The two checked-in reports contain only 499 and 469 samples.
- CPU queue construction and GPU submission are combined in one profile.
- The profiler currently builds unoptimized code.
These limitations make it difficult to tell whether a change made
`draw_sprite` faster, changed driver behavior, or merely changed sampling noise.
## Evidence
A temporary deterministic benchmark exposed two very different results:
1. CPU-only `draw_sprite`, two million calls and seven trials:
- `-debug`: median 192.560 ns/draw
- `-debug -o:speed`: median 19.154 ns/draw
2. Full 128-sprite frames through SDL GPU on Lavapipe, 1,000 measured frames
and five trials:
- `-debug`: median 2.486 ms/frame
- `-debug -o:speed`: median 2.447 ms/frame
At the current cap, optimized CPU queue construction is approximately 3.2
microseconds for 128 sprites. The full software-rendered frame is around 2.45
milliseconds, so optimizing `draw_sprite` cannot materially improve that
specific end-to-end workload. A hardware GPU or a larger future sprite limit
may have a different balance.
This split also explains why percentages from the current unoptimized
flamegraphs overstate small helper functions.
### Follow-up enhancement benchmarks
Six proposed renderer changes were implemented temporarily and measured before
being discarded. Full-frame tests used:
- Odin `dev-2026-05-nightly:ea5175d`
- `-debug -o:speed`
- SDL 3.4.12 with Vulkan/Lavapipe
- 128 animated sprites using real baked toad metadata and textures
- Warm-up before measurement
- Paired baseline/change samples on the same device with alternating order
- Ten 400-frame samples per mode, except culling, which used seven 750-frame
samples per mode
Each row is a separate paired run, so absolute frame times should only be
compared within that row.
| Enhancement | Baseline median | Changed median | Result |
| --- | ---: | ---: | ---: |
| Viewport culling, all visible | 4.845 ms | 4.822 ms | 0.5% faster |
| Viewport culling, 50% offscreen | 2.856 ms | 2.878 ms | 0.8% slower |
| SDL transfer and vertex buffer cycling | 4.869 ms | 4.834 ms | 0.7% faster |
| Contiguous vertex queue and one upload-side copy | 4.793 ms | 4.809 ms | 0.3% slower |
| Four-vertex indexed quads | 4.745 ms | 4.830 ms | 1.8% slower |
| GPU instancing with 32-byte instance records | 5.527 ms | 5.467 ms | 1.1% faster |
| Texture sorting, including sort cost, 128 runs to 2 | 5.765 ms | 5.654 ms | 1.9% faster |
Interpretation:
- Viewport culling is neutral at the current cap. The GPU already clips
offscreen triangles, and the sprites remain in one batched draw.
- SDL buffer cycling is a small performance improvement and is also the
documented way to avoid overwriting resources still bound by prior frames.
- Repacking the CPU queue does not help at 128 sprites; extra dynamic-array
work offsets the saved small-copy loop.
- Indexed quads regress performance despite reducing dynamic vertex data.
- Instancing reduces per-sprite upload data from 96 to 32 bytes, but the 1.1%
gain does not justify a second pipeline and shader path at the current cap.
- Texture sorting has the largest full-frame gain, but unrestricted sorting can
change alpha compositing. It is only safe within compatible layer/order
groups.
The recommended order is:
1. Profile optimized builds and establish the deterministic benchmark.
2. Apply the clip-space math simplification documented in the related issue.
3. Enable SDL buffer cycling for correct cross-frame resource reuse.
4. Consider layer-aware texture grouping if a 1.9% workload-specific gain is
worth the ordering complexity.
5. Defer culling, queue repacking, indexed quads, and instancing until the
sprite limit or measured workload grows substantially.
These results are from a software Vulkan backend. Hardware drivers may have a
different balance, which is another reason to keep the benchmark reproducible
and report backend details.
## Suggested fix
Add a non-interactive benchmark target with two explicitly separate workloads.
The committed harnesses are:
```make
PERF_ODIN_FLAGS ?= -debug -o:speed
perf-draw:
odin run benchmarks/draw_sprite \
-collection:pkg=. \
$(PERF_ODIN_FLAGS) \
-define:PERF_ITERATIONS=$(PERF_DRAW_ITERATIONS)
perf-frame:
odin run benchmarks/sprite_frame \
-collection:pkg=. \
$(PERF_ODIN_FLAGS) \
-define:PERF_FRAMES=$(PERF_FRAME_FRAMES) \
-define:PERF_SCENARIO=$(PERF_FRAME_SCENARIO)
```
Run the standard workloads with:
```bash
# CPU-only draw preparation.
make perf-draw
# Complete frame: 128 visible sprites sharing one texture.
make perf-frame PERF_FRAME_SCENARIO=0
# Complete frame: every second sprite is fully offscreen.
make perf-frame PERF_FRAME_SCENARIO=1
# Complete frame: 128 sprites alternate between two texture objects.
make perf-frame PERF_FRAME_SCENARIO=2
```
Every invocation prints the Git commit, Odin version, compiler flags, workload
configuration, every trial, and the median. `perf-frame` waits for GPU idle
after warm-up and after each measured frame batch so outstanding work is
included.
### CPU queue benchmark
- Construct `App`, `Character_Data`, and `Sprite` with real baked metadata.
- Use safe fake non-null GPU handles; `draw_sprite` only checks/stores these.
- Preallocate the draw list.
- Clear the queue whenever it reaches `MAX_SPRITES`.
- Vary sprite position between calls so the compiler cannot hoist the work.
- Warm up before timing.
- Run at least one million calls and report nanoseconds per draw.
- Build with `-o:speed` by default.
The measured loop should clear the queue at its cap, vary input to prevent
compiler hoisting, and report time per draw:
```odin
PERF_ITERATIONS :: #config(PERF_ITERATIONS, 2_000_000)
start := sdl.GetTicksNS()
for i in 0 ..< PERF_ITERATIONS {
if len(app.draw_list) == eng.MAX_SPRITES {
clear(&app.draw_list)
}
sprite.position.x = f32(i & 1023)
eng.draw_sprite(&app, &sprite)
}
elapsed := sdl.GetTicksNS() - start
fmt.printfln(
"%.3f ns/draw",
f64(elapsed) / f64(PERF_ITERATIONS),
)
```
### Full-frame benchmark
- Use a real SDL GPU device and baked texture.
- Warm up before timing.
- Run a fixed number of frames without interactive input.
- Report milliseconds per frame and sprites per second.
- Record GPU backend, present mode, compiler version, compiler flags, and sprite
count.
Use a fixed frame count rather than an interactive quit time:
```odin
PERF_FRAMES :: #config(PERF_FRAMES, 1_000)
for _ in 0 ..< 100 {
draw_benchmark_frame(&app, sprites[:]) // warm-up
}
start := sdl.GetTicksNS()
for _ in 0 ..< PERF_FRAMES {
draw_benchmark_frame(&app, sprites[:])
}
elapsed := sdl.GetTicksNS() - start
fmt.printfln(
"%.3f ms/frame",
f64(elapsed) / f64(PERF_FRAMES) / 1_000_000.0,
)
```
The CPU benchmark should be available without a display or GPU. The full-frame
benchmark may remain opt-in where a suitable GPU backend is unavailable.
Do not add a strict CI regression threshold initially; hosted runner variance
will make a single threshold flaky. CI can still compile the benchmark and
verify that it completes.
## Acceptance criteria
- A Makefile target runs the optimized CPU benchmark non-interactively.
- Results include compiler flags, iteration count, median, and per-trial values.
- CPU queue time is reported separately from complete frame time.
- Sprite positions or frames vary during the measured loop.
- The draw list never silently exceeds `MAX_SPRITES`.
- The benchmark has documented commands for repeatable local comparison.
- `make check` and `make test` continue to pass.
@@ -0,0 +1,116 @@
# Add opt-in order-safe texture batching
## Summary
`end_frame` batches only consecutive sprites that use the same texture.
Alternating two textures therefore produces one sampler bind and draw call per
sprite even when some sprites could safely be regrouped.
Globally sorting transparent sprites by texture is not correct: overlapping
sprites may blend differently when submission order changes. Batching should
therefore be opt-in within explicit groups whose members are safe to reorder.
## Evidence
A temporary paired benchmark used:
- Odin `dev-2026-05-nightly:ea5175d` with `-debug -o:speed`
- SDL 3.4.12 and Vulkan/Lavapipe
- 128 animated sprites alternating between two equivalent textures
- Sorting cost included in the measured frame
- Ten order-alternated samples of 400 frames per mode
| Mode | Texture runs | Median frame time |
| --- | ---: | ---: |
| Submission order | 128 | 5.765 ms |
| Texture grouped | 2 | 5.654 ms |
Sorting and grouping improved median frame time by approximately **1.9%**.
Hardware drivers with higher draw-call overhead may show a different result.
## Reproduction harness
Run the committed alternating-texture workload on the baseline and candidate
commits:
```bash
make perf-frame \
PERF_FRAME_SCENARIO=2 \
PERF_ODIN_FLAGS="-debug -o:speed"
```
Keep all `PERF_FRAME_*` values unchanged. The baseline should produce one
texture run per sprite; the candidate should reduce runs only inside explicit
reorder-safe groups. Compare `median_ms_per_frame` and verify rendered output.
## Suggested fix
Add an explicit batch group to queued sprites. Group `0` keeps strict submission
order; nonzero groups may be reordered only when the caller guarantees that
their members are order-independent.
```odin
Queued_Sprite :: struct {
texture: ^sdl.GPUTexture,
verts: [SPRITE_VERT_COUNT]Vertex,
batch_group: u32, // 0 = strict order; nonzero = caller permits regrouping
}
```
Sort each contiguous, nonzero group by texture immediately before upload:
```odin
group_texture_runs :: proc(list: []Queued_Sprite) {
start := 0
for start < len(list) {
group := list[start].batch_group
if group == 0 {
start += 1
continue
}
end := start + 1
for end < len(list) && list[end].batch_group == group {
end += 1
}
// Stable insertion sort is sufficient while MAX_SPRITES is 128.
for i in start + 1 ..< end {
item := list[i]
j := i
for j > start {
if uintptr(list[j - 1].texture) <= uintptr(item.texture) {
break
}
list[j] = list[j - 1]
j -= 1
}
list[j] = item
}
start = end
}
}
```
Call it after all sprites are queued and before the transfer-buffer copy:
```odin
group_texture_runs(app.draw_list[:])
```
Expose batching through a separate API or explicit parameter so existing
`draw_sprite` calls remain strict-order by default:
```odin
draw_sprite_batched(&app, &sprite, batch_group = 1)
```
## Acceptance criteria
- Existing `draw_sprite` behavior preserves exact submission order.
- Reordering requires an explicit nonzero batch group.
- Sorting never moves a sprite across a strict-order entry or group boundary.
- Add tests for strict order, group boundaries, stable same-texture ordering,
and reduced texture-run count.
- Add a visual overlap test confirming default alpha compositing is unchanged.
- Benchmark sorting cost and draw-call reduction on a hardware GPU.
@@ -0,0 +1,83 @@
# Enable SDL GPU buffer cycling for per-frame sprite uploads
## Summary
`end_frame` overwrites the same transfer buffer and vertex buffer every frame,
but both SDL calls currently pass `cycle = false`:
```odin
sdl.MapGPUTransferBuffer(app.device, app.transfer_buffer, false)
sdl.UploadToGPUBuffer(copy_pass, src, dst, false)
```
SDL documents cycling as the mechanism that rotates to an unbound internal
resource when the previous frame still references the current one. Enabling it
avoids an unnecessary resource dependency and makes the overwrite pattern
explicitly safe.
## Evidence
A temporary paired benchmark used:
- Odin `dev-2026-05-nightly:ea5175d` with `-debug -o:speed`
- SDL 3.4.12 and Vulkan/Lavapipe
- 128 animated sprites
- Ten order-alternated samples of 400 frames per mode
| Mode | Median frame time |
| --- | ---: |
| Cycling disabled | 4.869 ms |
| Cycling enabled | 4.834 ms |
Cycling improved median frame time by approximately **0.7%**. This is a small
performance change, but it also follows SDL's documented resource-reuse model.
## Reproduction harness
Run the committed full-frame harness on the baseline commit and candidate
commit:
```bash
make perf-frame \
PERF_FRAME_SCENARIO=0 \
PERF_ODIN_FLAGS="-debug -o:speed"
```
Keep all `PERF_FRAME_*` values unchanged. Compare `median_ms_per_frame`; the
harness waits for GPU idle before stopping each trial timer.
## Suggested fix
Cycle both resources that are fully overwritten each frame:
```odin
map_ptr := sdl.MapGPUTransferBuffer(
app.device,
app.transfer_buffer,
true, // rotate if the previous frame still binds this transfer buffer
)
// Write the complete [0, n * SPRITE_VERTS_SIZE) range, then unmap.
sdl.UnmapGPUTransferBuffer(app.device, app.transfer_buffer)
copy_pass := sdl.BeginGPUCopyPass(cmd)
sdl.UploadToGPUBuffer(
copy_pass,
src,
dst,
true, // rotate the destination vertex buffer if it is still bound
)
sdl.EndGPUCopyPass(copy_pass)
```
Cycling makes previous contents undefined, so this remains correct only because
the renderer writes the complete vertex range used by the frame before drawing.
Do not enable cycling for partial updates that depend on untouched data.
## Acceptance criteria
- Both `MapGPUTransferBuffer` and `UploadToGPUBuffer` use `cycle = true`.
- The complete submitted vertex range is rewritten every frame.
- Existing engine tests and examples continue to pass.
- A full-frame benchmark confirms no regression on a hardware GPU backend.
- Add a comment explaining why cycling is safe for this full-overwrite path.
@@ -0,0 +1,98 @@
# Profile optimized sprite builds instead of unoptimized debug code
## Summary
`make flame-build` currently compiles the selected example with `-debug` but
without an optimization mode:
```make
odin build examples/$(FLAME_EXAMPLE) -collection:pkg=. -out:$(FLAME_BIN) -debug
```
This makes the flamegraph useful for debugging but misleading for performance
decisions. The current profiles largely describe code that will disappear or
be inlined in an optimized build.
## Evidence
The two checked-in `crowd` profiles were captured from this unoptimized binary.
They report:
- `engine::draw_sprite`: 9.56% and 12.22% self time
- `engine::to_clip`: 5.33% and 5.51% self time
- `engine::sprite_feet_quad`: 3.98% and 3.30% self time
- Additional time in string hashing/map lookup, bounds checks, and dynamic
array append helpers
A CPU-only benchmark using the real `draw_sprite`, real baked toad metadata,
preallocated draw list, changing sprite positions, and two million draws per
trial produced:
| Build | Median time per draw | Trials |
| --- | ---: | ---: |
| `-debug` (current Makefile behavior) | 192.560 ns | 7 |
| `-debug -o:speed` | 19.154 ns | 7 |
The optimized build is about **10.1x faster** without an engine code change.
An end-to-end 128-sprite benchmark on SDL 3.4.12 with Lavapipe showed only a
small full-frame difference (median 2.486 ms debug versus 2.447 ms optimized)
because software GPU/driver work dominated. This does not invalidate the CPU
result; it shows why CPU queue time and full-frame time must be reported
separately.
Environment:
- Odin `dev-2026-05-nightly:ea5175d` (the version pinned by CI)
- SDL 3.4.12
- Linux x86-64
## Suggested fix
Compile profiling binaries with optimization while retaining symbols:
```make
FLAME_ODIN_FLAGS ?= -debug -o:speed
flame-build:
odin build examples/$(FLAME_EXAMPLE) \
-collection:pkg=. \
-out:$(FLAME_BIN) \
$(FLAME_ODIN_FLAGS)
```
Keeping the flags configurable allows an explicitly unoptimized diagnostic run
without making it the performance default.
Example usage:
```bash
# Representative performance profile: optimized code with debug symbols.
make flame FLAME_EXAMPLE=crowd
# Explicitly profile unoptimized code when investigating debug-only behavior.
make flame FLAME_EXAMPLE=crowd FLAME_ODIN_FLAGS="-debug -o:none"
```
Reproduce the isolated build-mode comparison with the committed CPU harness:
```bash
make perf-draw PERF_ODIN_FLAGS="-debug -o:none"
make perf-draw PERF_ODIN_FLAGS="-debug -o:speed"
```
Both runs print the Git commit, Odin version, compiler flags, every trial, and
the median nanoseconds per draw.
Consider applying an explicit optimization mode to performance-oriented example
runs as well. Plain `odin run` currently uses Odin's unoptimized default.
## Acceptance criteria
- `make flame-build` produces an optimized binary with debug symbols.
- `FLAME_ODIN_FLAGS` can override the default for diagnostic builds.
- `make check` and `make test` continue to pass.
- A new `crowd` profile records the exact compiler flags in its report or
accompanying documentation.
- Performance conclusions distinguish CPU `draw_sprite` cost from complete
frame/GPU submission cost.
@@ -0,0 +1,139 @@
# Prototype instanced sprite rendering for larger batches
## Summary
The renderer currently generates and uploads six complete vertices per sprite:
```text
6 vertices × (position float2 + UV float2) = 96 bytes/sprite/frame
```
Instancing can keep one immutable six-corner unit quad on the GPU and upload one
rectangle/UV record per sprite:
```text
clip rectangle float4 + UV rectangle float4 = 32 bytes/sprite/frame
```
This reduces dynamic upload volume by two thirds, but requires another pipeline
and backend-specific vertex shader. At the current 128-sprite cap, the measured
gain is too small to justify enabling it unconditionally.
## Evidence
A complete temporary instanced path was implemented with:
- A static six-corner vertex buffer
- A 32-byte per-instance buffer
- Vertex-rate and instance-rate pipeline inputs
- One instanced draw per texture run
- Validated SPIR-V and SDL's debug GPU device
The paired benchmark used ten order-alternated samples of 400 frames:
| Mode | Median frame time |
| --- | ---: |
| Six dynamic vertices per sprite | 5.527 ms |
| 32-byte instance per sprite | 5.467 ms |
Instancing improved median frame time by approximately **1.1%** on
Vulkan/Lavapipe with 128 sprites.
## Reproduction harness
Run the committed visible full-frame workload on baseline and candidate commits:
```bash
make perf-frame \
PERF_FRAME_SCENARIO=0 \
PERF_FRAME_SPRITES=128 \
PERF_ODIN_FLAGS="-debug -o:speed"
```
Keep all other `PERF_FRAME_*` values unchanged and compare
`median_ms_per_frame`. To test 512 or more sprites, first raise the engine's
`MAX_SPRITES` and associated buffer capacities on the candidate branch, then
set `PERF_FRAME_SPRITES` to the same value.
## Suggested fix
Treat this as a prototype gated by larger sprite counts or a demonstrated
hardware bottleneck, not as an immediate replacement.
Define the compact instance payload:
```odin
Sprite_Instance :: struct {
clip_rect: [4]f32, // left, top, right, bottom
uv_rect: [4]f32, // u0, v0, u1, v1
}
```
Queue one record after the existing quad and UV calculations:
```odin
instance := Sprite_Instance {
clip_rect = {p0.x, p0.y, p2.x, p2.y},
uv_rect = {u0, v0, u1, v1},
}
append(&app.instance_list, instance)
```
Use a static unit quad:
```odin
UNIT_QUAD := [6]Vec2 {
{0, 0}, {1, 0}, {1, 1},
{0, 0}, {1, 1}, {0, 1},
}
```
The instanced vertex shader reconstructs position and UV:
```glsl
#version 450
layout(location = 0) in vec2 in_corner;
layout(location = 1) in vec4 in_clip_rect;
layout(location = 2) in vec4 in_uv_rect;
layout(location = 0) out vec2 v_uv;
void main() {
vec2 position = mix(in_clip_rect.xy, in_clip_rect.zw, in_corner);
v_uv = mix(in_uv_rect.xy, in_uv_rect.zw, in_corner);
gl_Position = vec4(position, 0.0, 1.0);
}
```
Configure slot 0 as vertex-rate and slot 1 as instance-rate, then draw each
texture run with six vertices and `run` instances:
```odin
vb_descs := [2]sdl.GPUVertexBufferDescription {
{slot = 0, pitch = u32(size_of(Vec2)), input_rate = .VERTEX},
{slot = 1, pitch = u32(size_of(Sprite_Instance)), input_rate = .INSTANCE},
}
sdl.DrawGPUPrimitives(
app.render_pass,
6, // unit-quad vertices
u32(run), // sprite instances in this texture run
0,
0,
)
```
Keep the current path as a fallback until the instanced implementation exists
for Vulkan, D3D12, and Metal and demonstrates a meaningful hardware win.
## Acceptance criteria
- Benchmark at 128, 512, 2,048, and 10,000 sprites or the highest supported
counts.
- Report CPU queue time, bytes uploaded, and complete frame time separately.
- Require a meaningful hardware improvement before changing the default path.
- Supply equivalent Vulkan, D3D12, and Metal shaders.
- Preserve texture-run batching and sprite flip/trim behavior.
- Add visual equivalence tests for position, UVs, animation frames, and flip.
- Retain the existing six-vertex path as a fallback during evaluation.
@@ -0,0 +1,130 @@
# Reduce repeated clip-space work in `draw_sprite`
## Summary
`draw_sprite` calls `to_clip` four times for an axis-aligned quad:
```odin
p0 := to_clip(x0_px, y0_px, sw, sh)
p1 := to_clip(x1_px, y0_px, sw, sh)
p2 := to_clip(x1_px, y1_px, sw, sh)
p3 := to_clip(x0_px, y1_px, sw, sh)
```
This repeats the same divisions and converts duplicate x/y coordinates. An
axis-aligned sprite has only two unique x values and two unique y values.
This is a measurable optimization, but it is low priority at the current
128-sprite limit because the absolute saving is small.
## Evidence
The checked-in unoptimized profiles report `engine::to_clip` at 5.33% and 5.51%
self time. Those percentages are inflated by the unoptimized profiling build,
so the change was also measured with `-debug -o:speed`.
A temporary benchmark used the real baked toad metadata, changed sprite
position on every iteration, preallocated the queue, and performed two million
draws per mode over seven trials:
| Mode | Median time per draw |
| --- | ---: |
| Current `draw_sprite` | 25.128 ns |
| Precomputed clip scale and reused coordinates | 21.531 ns |
| Same math plus cached `Frame_Def` | 21.778 ns |
Simplifying the math improved isolated draw time by approximately **14.3%**.
Caching the resolved frame did not provide an additional benefit and should not
be included without new evidence.
At `MAX_SPRITES == 128`, the measured math saving is only about 0.46
microseconds per completely full frame. GPU/driver work dominated the
end-to-end benchmark, so this should follow the profiling and deterministic
benchmark improvements.
Follow-up paired full-frame experiments at the 128-sprite cap found no larger
renderer-architecture win: buffer cycling improved median frame time by 0.7%,
GPU instancing by 1.1%, and texture sorting by 1.9%, while culling, queue
repacking, and indexed quads were neutral or slower. The clip-space change
therefore remains the strongest measured optimization specifically inside
`draw_sprite`, although its absolute frame impact is still small.
Environment:
- Odin `dev-2026-05-nightly:ea5175d`
- Optimized with `-debug -o:speed`
- Linux x86-64
## Reproduction harness
Run the committed CPU harness on the baseline commit and again after applying
the suggested fix:
```bash
make perf-draw PERF_ODIN_FLAGS="-debug -o:speed"
```
Keep `PERF_DRAW_ITERATIONS`, `PERF_DRAW_WARMUP`, and `PERF_DRAW_TRIALS`
unchanged between commits. Compare `median_ns_per_draw`.
## Suggested fix
Compute clip scaling once and construct corners from the unique coordinates:
```odin
sprite_quad_to_clip :: proc(x0, y0, x1, y1, sw, sh: f32) -> [4]Vec2 {
sx := 2.0 / sw
sy := 2.0 / sh
left := x0 * sx - 1
right := x1 * sx - 1
top := 1 - y0 * sy
bottom := 1 - y1 * sy
return {
{left, top},
{right, top},
{right, bottom},
{left, bottom},
}
}
points := sprite_quad_to_clip(x0_px, y0_px, x1_px, y1_px, sw, sh)
p0, p1, p2, p3 := points[0], points[1], points[2], points[3]
```
Keep `to_clip` for general callers and its existing tests; this change only
specializes quad construction inside `draw_sprite`.
Add an equivalence test before replacing the current calls:
```odin
@(test)
sprite_quad_clip_math_matches_to_clip :: proc(t: ^testing.T) {
x0, y0 := f32(125), f32(80)
x1, y1 := f32(325), f32(280)
sw, sh := f32(800), f32(600)
expected := [4]Vec2 {
to_clip(x0, y0, sw, sh),
to_clip(x1, y0, sw, sh),
to_clip(x1, y1, sw, sh),
to_clip(x0, y1, sw, sh),
}
actual := sprite_quad_to_clip(x0, y0, x1, y1, sw, sh)
for i in 0 ..< 4 {
testing.expect_value(t, actual[i], expected[i])
}
}
```
## Acceptance criteria
- Existing sprite geometry, camera, UV, and batching tests pass.
- Add or extend a test that compares all four generated corners against
`to_clip` for representative viewport and sprite coordinates.
- Flipped and unflipped sprites produce identical vertices to the current code.
- An optimized deterministic benchmark shows at least a 10% improvement in
isolated `draw_sprite` time under comparable conditions.
- Do not add a per-sprite frame cache as part of this issue.
+4
View File
@@ -48,6 +48,10 @@ jobs:
- name: Odin version
run: odin version
# assetbake tests need vendor:stb native libs
- name: Build Odin STB libraries
run: make -C "$(odin root)/vendor/stb/src"
- name: Unit tests
run: make test
+12
View File
@@ -1,2 +1,14 @@
*.bin
tutorials/
# Flamegraph / perf profiling artifacts
tools/FlameGraph/
perf.data
perf.data.old
flame-*.svg
flame-*.jpg
flame-*-report.txt
flame.svg
flame.jpg
flame-report.txt
*_perf
+115 -9
View File
@@ -1,15 +1,52 @@
.PHONY: shaders-vulkan shaders-d3d12 shaders-metal shaders-all bake toad check test help
.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
FLAME_BIN := $(FLAME_EXAMPLE)_perf
FLAMEGRAPH_DIR ?= tools/FlameGraph
FLAME_OUT_DIR ?= flame
# One stamp per `make` invocation so svg/jpg/report share a name.
ifndef FLAME_STAMP
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_ODIN_FLAGS ?= -debug -o:speed
PERF_FRAME_SPRITES ?= 128
PERF_FRAME_FRAMES ?= 400
PERF_FRAME_WARMUP ?= 100
PERF_FRAME_TRIALS ?= 10
# 0=visible, 1=half offscreen, 2=alternating textures, 3=stacked
PERF_FRAME_SCENARIO ?= 0
PERF_FRAME_WIDTH ?= 800
PERF_FRAME_HEIGHT ?= 600
help:
@echo "Targets:"
@echo " bake Bake all characters under content/characters/"
@echo " shaders-vulkan Compile SPIR-V into shaders/vulkan/"
@echo " shaders-d3d12 Compile DXIL into shaders/d3d12/ (needs shadercross)"
@echo " shaders-metal Compile MSL into shaders/metal/ (needs shadercross)"
@echo " shaders-all Build all shader backends"
@echo " test Run engine unit tests"
@echo " check Typecheck examples/toad"
@echo " toad Run the toad example"
@echo " bake Bake all characters under content/characters/"
@echo " shaders-vulkan Compile SPIR-V into shaders/vulkan/"
@echo " shaders-d3d12 Compile DXIL into shaders/d3d12/ (needs shadercross)"
@echo " shaders-metal Compile MSL into shaders/metal/ (needs shadercross)"
@echo " shaders-all Build all shader backends"
@echo " test Run engine unit tests"
@echo " check Typecheck all examples"
@echo " toad Run the toad example (full demo)"
@echo " hello_sprite Minimal load + draw"
@echo " crowd Many sprites, one Character_Data"
@echo " camera_sandbox Pan camera / Space toggles follow"
@echo " clips Keys 1/2 switch idle/walk"
@echo " flame Build+record+SVG+JPG+text report (FLAME_EXAMPLE=$(FLAME_EXAMPLE))"
@echo " flame-build Debug binary only ($(FLAME_BIN))"
@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/3)"
bake:
./scripts/bake_all.sh
@@ -27,9 +64,78 @@ shaders-all: shaders-vulkan shaders-d3d12 shaders-metal
test:
odin test engine
odin test assetbake
check:
odin check examples/toad -collection:pkg=.
odin check examples/hello_sprite -collection:pkg=.
odin check examples/crowd -collection:pkg=.
odin check examples/camera_sandbox -collection:pkg=.
odin check examples/clips -collection:pkg=.
perf-draw:
@echo "git_commit=$$(git rev-parse HEAD)"
@echo "odin_version=$$(odin version)"
@echo "odin_flags=$(PERF_ODIN_FLAGS)"
odin run benchmarks/draw_sprite -collection:pkg=. $(PERF_ODIN_FLAGS) \
-define:PERF_ITERATIONS=$(PERF_DRAW_ITERATIONS) \
-define:PERF_WARMUP=$(PERF_DRAW_WARMUP) \
-define:PERF_TRIALS=$(PERF_DRAW_TRIALS)
perf-frame:
@echo "git_commit=$$(git rev-parse HEAD)"
@echo "odin_version=$$(odin version)"
@echo "odin_flags=$(PERF_ODIN_FLAGS)"
odin run benchmarks/sprite_frame -collection:pkg=. $(PERF_ODIN_FLAGS) \
-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) \
-define:PERF_WIDTH=$(PERF_FRAME_WIDTH) \
-define:PERF_HEIGHT=$(PERF_FRAME_HEIGHT)
toad:
odin run examples/toad -collection:pkg=.
hello_sprite:
odin run examples/hello_sprite -collection:pkg=.
crowd:
odin run examples/crowd -collection:pkg=.
camera_sandbox:
odin run examples/camera_sandbox -collection:pkg=.
clips:
odin run examples/clips -collection:pkg=.
flame-tools:
@if [ ! -x "$(FLAMEGRAPH_DIR)/stackcollapse-perf.pl" ] || [ ! -x "$(FLAMEGRAPH_DIR)/flamegraph.pl" ]; then \
echo "Cloning FlameGraph scripts into $(FLAMEGRAPH_DIR)..."; \
git clone --depth 1 https://github.com/brendangregg/FlameGraph.git "$(FLAMEGRAPH_DIR)"; \
fi
flame-build:
odin build examples/$(FLAME_EXAMPLE) -collection:pkg=. -out:$(FLAME_BIN) -debug
flame-record: flame-build
@echo ">>> Profiling ./$(FLAME_BIN) — play for ~1020s under load, then quit the window."
@echo ">>> If perf fails with permissions: sudo sysctl kernel.perf_event_paranoid=1"
perf record -F 99 -g --call-graph dwarf -- ./$(FLAME_BIN)
flame-svg: flame-tools
@test -f perf.data || { echo "No perf.data — run: make flame-record"; exit 1; }
@mkdir -p "$(FLAME_OUT_DIR)"
perf script | "$(FLAMEGRAPH_DIR)/stackcollapse-perf.pl" | "$(FLAMEGRAPH_DIR)/flamegraph.pl" > "$(FLAME_PREFIX).svg"
magick "$(FLAME_PREFIX).svg" "$(FLAME_PREFIX).jpg"
@echo "Wrote $(FLAME_PREFIX).svg and $(FLAME_PREFIX).jpg"
flame-report:
@test -f perf.data || { echo "No perf.data — run: make flame-record"; exit 1; }
@mkdir -p "$(FLAME_OUT_DIR)"
perf report --stdio --no-children > "$(FLAME_PREFIX)-report.txt"
@echo "Wrote $(FLAME_PREFIX)-report.txt"
flame: flame-record flame-svg flame-report
@echo "Artifacts: $(FLAME_PREFIX).{svg,jpg} $(FLAME_PREFIX)-report.txt"
+67 -24
View File
@@ -56,10 +56,12 @@ Char_Def :: struct {
// Packed atlas pixels + per-frame rects (indexed like the combined frames list)
Atlas :: struct {
pixels: []u8,
width: int,
height: int,
rects: [][4]int, // [x, y, w, h] per frame; sizes may differ across clips
pixels: []u8,
width: int,
height: int,
rects: [][4]int,
source_sizes: [][2]int,
trim_offsets: [][2]int,
}
// Where one clip's frames sit inside the packed atlas frame list
@@ -95,6 +97,8 @@ bake_character :: proc(in_dir, out_dir: string) {
atlas := pack_atlas(frames)
defer delete(atlas.pixels)
defer delete(atlas.rects)
defer delete(atlas.source_sizes)
defer delete(atlas.trim_offsets)
ensure_dir(out_dir)
@@ -283,22 +287,60 @@ destroy_frames :: proc(frames: []^image.Image) {
delete(frames)
}
// pack_atlas packs variable-size frames with stb_rect_pack and blits them into one sheet.
// Caller owns atlas.pixels and atlas.rects.
opaque_bounds_rgba :: proc(pixels: []u8, iw, ih: int, alpha_min: u8 = 1) -> (x, y, w, h: int) {
min_x, min_y := iw, ih
max_x, max_y := -1, -1
for py in 0 ..< ih {
for px in 0 ..< iw {
i := (py * iw + px) * 4
if i + 3 >= len(pixels) do continue
a := pixels[i + 3]
if a >= alpha_min {
if px < min_x do min_x = px
if py < min_y do min_y = py
if px > max_x do max_x = px
if py > max_y do max_y = py
}
}
}
if max_x < min_x {
return 0, 0, 1, 1
}
return min_x, min_y, max_x - min_x + 1, max_y - min_y + 1
}
opaque_bounds :: proc(img: ^image.Image, alpha_min: u8 = 1) -> (x, y, w, h: int) {
iw, ih := int(img.width), int(img.height)
pixels := bytes.buffer_to_bytes(&img.pixels)
return opaque_bounds_rgba(pixels, iw, ih, alpha_min)
}
pack_atlas :: proc(frames: []^image.Image) -> Atlas {
PADDING :: 1 // 1px gap to reduce bleeding if filtered later
PADDING :: 1
pack_rects := make([]stbrp.Rect, len(frames))
defer delete(pack_rects)
trims := make([][4]int, len(frames))
defer delete(trims)
source_sizes := make([][2]int, len(frames))
trim_offsets := make([][2]int, len(frames))
total_area := 0
max_w := 0
max_h := 0
for frame, i in frames {
w := frame.width + PADDING
h := frame.height + PADDING
tx, ty, tw, th := opaque_bounds(frame)
trims[i] = {tx, ty, tw, th}
source_sizes[i] = {int(frame.width), int(frame.height)}
trim_offsets[i] = {tx, ty}
w := tw + PADDING
h := th + PADDING
pack_rects[i] = stbrp.Rect {
id = c.int(i), // pack_rects may reorder; id maps back to frame index
id = c.int(i),
w = stbrp.Coord(w),
h = stbrp.Coord(h),
}
@@ -307,7 +349,6 @@ pack_atlas :: proc(frames: []^image.Image) -> Atlas {
max_h = max(max_h, h)
}
// Grow a square-ish atlas until everything fits
side := max(max_w, max_h, int(math.ceil(math.sqrt(f64(total_area)))))
atlas_w, atlas_h := 0, 0
packed := false
@@ -326,10 +367,9 @@ pack_atlas :: proc(frames: []^image.Image) -> Atlas {
os.exit(1)
}
pixels := make([]u8, atlas_w * atlas_h * 4) // transparent
pixels := make([]u8, atlas_w * atlas_h * 4)
rects := make([][4]int, len(frames))
// Blit using each rect's id (array order may have changed during packing)
for pr in pack_rects {
if !pr.was_packed {
fmt.eprintfln("frame %d was not packed", pr.id)
@@ -337,27 +377,30 @@ pack_atlas :: proc(frames: []^image.Image) -> Atlas {
}
idx := int(pr.id)
frame := frames[idx]
fw := frame.width
fh := frame.height
trim := trims[idx]
tx, ty, tw, th := trim[0], trim[1], trim[2], trim[3]
dst_x := int(pr.x)
dst_y := int(pr.y)
src := bytes.buffer_to_bytes(&frame.pixels)
for y in 0 ..< fh {
src_row := src[y * fw * 4:(y + 1) * fw * 4]
src_w := int(frame.width)
for y in 0 ..< th {
src_row := src[((ty + y) * src_w + tx) * 4:][:tw * 4]
dst_i := ((dst_y + y) * atlas_w + dst_x) * 4
copy(pixels[dst_i:], src_row)
}
rects[idx] = {dst_x, dst_y, fw, fh}
rects[idx] = {dst_x, dst_y, tw, th}
}
fmt.printfln("packed atlas %dx%d", atlas_w, atlas_h)
return Atlas {
pixels = pixels,
width = atlas_w,
height = atlas_h,
rects = rects,
pixels = pixels,
width = atlas_w,
height = atlas_h,
rects = rects,
source_sizes = source_sizes,
trim_offsets = trim_offsets,
}
}
@@ -422,8 +465,8 @@ build_char_def :: proc(
r := atlas.rects[slot]
frame_defs[i] = Frame_Def {
rect = r,
source_size = {r[2], r[3]},
trim_offset = {0, 0}, // no trimming in v1
source_size = atlas.source_sizes[slot],
trim_offset = atlas.trim_offsets[slot],
}
}
+46
View File
@@ -0,0 +1,46 @@
package main
import "core:testing"
@(test)
opaque_bounds_inset :: proc(t: ^testing.T) {
// 4x4, opaque 2x2 at (1,1)
pixels := make([]u8, 4 * 4 * 4)
defer delete(pixels)
for y in 1 ..= 2 {
for x in 1 ..= 2 {
i := (y * 4 + x) * 4
pixels[i + 3] = 255
}
}
x, y, w, h := opaque_bounds_rgba(pixels, 4, 4)
testing.expect_value(t, x, 1)
testing.expect_value(t, y, 1)
testing.expect_value(t, w, 2)
testing.expect_value(t, h, 2)
}
@(test)
opaque_bounds_full :: proc(t: ^testing.T) {
pixels := make([]u8, 2 * 2 * 4)
defer delete(pixels)
for i := 3; i < len(pixels); i += 4 {
pixels[i] = 255
}
x, y, w, h := opaque_bounds_rgba(pixels, 2, 2)
testing.expect_value(t, x, 0)
testing.expect_value(t, y, 0)
testing.expect_value(t, w, 2)
testing.expect_value(t, h, 2)
}
@(test)
opaque_bounds_empty :: proc(t: ^testing.T) {
pixels := make([]u8, 3 * 3 * 4)
defer delete(pixels)
x, y, w, h := opaque_bounds_rgba(pixels, 3, 3)
testing.expect_value(t, x, 0)
testing.expect_value(t, y, 0)
testing.expect_value(t, w, 1)
testing.expect_value(t, h, 1)
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

+295 -295
View File
@@ -14,274 +14,274 @@
"frames": [
{
"rect": [
0,
0,
268,
326
1253,
630,
204,
311
],
"source_size": [
268,
326
],
"trim_offset": [
0,
0
9,
14
]
},
{
"rect": [
269,
1256,
0,
268,
326
218,
314
],
"source_size": [
268,
326
],
"trim_offset": [
0,
0
9,
10
]
},
{
"rect": [
538,
0,
268,
326
1458,
630,
231,
310
],
"source_size": [
268,
326
],
"trim_offset": [
0,
0
9,
8
]
},
{
"rect": [
807,
0,
268,
326
643,
943,
243,
295
],
"source_size": [
268,
326
],
"trim_offset": [
0,
0
10,
7
]
},
{
"rect": [
1076,
0,
268,
326
881,
1245,
254,
278
],
"source_size": [
268,
326
],
"trim_offset": [
0,
0
]
},
{
"rect": [
1345,
0,
268,
326
],
"source_size": [
268,
326
],
"trim_offset": [
0,
0
]
},
{
"rect": [
1614,
0,
268,
326
],
"source_size": [
268,
326
],
"trim_offset": [
0,
0
11,
8
]
},
{
"rect": [
0,
327,
268,
326
944,
247,
288
],
"source_size": [
268,
326
],
"trim_offset": [
0,
0
10,
7
]
},
{
"rect": [
269,
327,
268,
326
1669,
941,
236,
305
],
"source_size": [
268,
326
],
"trim_offset": [
0,
0
]
},
{
"rect": [
538,
327,
268,
326
],
"source_size": [
268,
326
],
"trim_offset": [
0,
0
]
},
{
"rect": [
807,
327,
268,
326
],
"source_size": [
268,
326
],
"trim_offset": [
0,
0
]
},
{
"rect": [
1076,
327,
268,
326
],
"source_size": [
268,
326
],
"trim_offset": [
0,
0
]
},
{
"rect": [
1345,
327,
268,
326
],
"source_size": [
268,
326
],
"trim_offset": [
0,
0
]
},
{
"rect": [
1614,
327,
268,
326
],
"source_size": [
268,
326
],
"trim_offset": [
0,
0
9,
8
]
},
{
"rect": [
0,
654,
268,
326
316,
223,
313
],
"source_size": [
268,
326
],
"trim_offset": [
0,
0
9,
9
]
},
{
"rect": [
269,
654,
268,
326
1458,
941,
210,
310
],
"source_size": [
268,
326
],
"trim_offset": [
0,
0
9,
13
]
},
{
"rect": [
538,
654,
268,
326
224,
316,
211,
313
],
"source_size": [
268,
326
],
"trim_offset": [
11,
11
]
},
{
"rect": [
1032,
630,
220,
311
],
"source_size": [
268,
326
],
"trim_offset": [
13,
7
]
},
{
"rect": [
1032,
942,
228,
302
],
"source_size": [
268,
326
],
"trim_offset": [
16,
4
]
},
{
"rect": [
0,
0
1233,
237,
286
],
"source_size": [
268,
326
],
"trim_offset": [
18,
2
]
},
{
"rect": [
643,
1239,
237,
286
],
"source_size": [
268,
326
],
"trim_offset": [
18,
2
]
},
{
"rect": [
414,
943,
228,
302
],
"source_size": [
268,
326
],
"trim_offset": [
16,
4
]
},
{
"rect": [
1690,
630,
220,
310
],
"source_size": [
268,
326
],
"trim_offset": [
13,
7
]
},
{
"rect": [
436,
316,
210,
313
],
"source_size": [
268,
326
],
"trim_offset": [
11,
11
]
}
]
@@ -292,290 +292,290 @@
"frames": [
{
"rect": [
807,
654,
213,
319
414,
630,
205,
312
],
"source_size": [
213,
319
],
"trim_offset": [
0,
0
6,
7
]
},
{
"rect": [
1021,
654,
213,
319
647,
316,
206,
313
],
"source_size": [
213,
319
],
"trim_offset": [
0,
0
5,
6
]
},
{
"rect": [
1235,
654,
213,
319
854,
316,
206,
313
],
"source_size": [
213,
319
],
"trim_offset": [
0,
0
5,
6
]
},
{
"rect": [
1449,
654,
213,
319
1684,
0,
207,
314
],
"source_size": [
213,
319
],
"trim_offset": [
0,
0
4,
5
]
},
{
"rect": [
1663,
654,
213,
319
1256,
315,
207,
314
],
"source_size": [
213,
319
],
"trim_offset": [
0,
0
4,
5
]
},
{
"rect": [
807,
974,
213,
319
420,
0,
208,
315
],
"source_size": [
213,
319
],
"trim_offset": [
0,
0
3,
4
]
},
{
"rect": [
1021,
974,
213,
319
1475,
0,
208,
314
],
"source_size": [
213,
319
],
"trim_offset": [
0,
0
3,
4
]
},
{
"rect": [
1235,
974,
213,
319
629,
0,
208,
315
],
"source_size": [
213,
319
],
"trim_offset": [
0,
0
]
},
{
"rect": [
1449,
974,
213,
319
],
"source_size": [
213,
319
],
"trim_offset": [
0,
0
]
},
{
"rect": [
1663,
974,
213,
319
],
"source_size": [
213,
319
],
"trim_offset": [
0,
0
3,
3
]
},
{
"rect": [
0,
981,
213,
319
0,
209,
315
],
"source_size": [
213,
319
],
"trim_offset": [
0,
0
2,
3
]
},
{
"rect": [
214,
981,
213,
319
210,
0,
209,
315
],
"source_size": [
213,
319
],
"trim_offset": [
0,
0
2,
3
]
},
{
"rect": [
428,
981,
213,
319
838,
0,
208,
315
],
"source_size": [
213,
319
],
"trim_offset": [
0,
0
3,
3
]
},
{
"rect": [
642,
1294,
213,
319
1047,
0,
208,
315
],
"source_size": [
213,
319
],
"trim_offset": [
0,
0
3,
4
]
},
{
"rect": [
856,
1294,
213,
319
1464,
315,
207,
314
],
"source_size": [
213,
319
],
"trim_offset": [
0,
0
4,
5
]
},
{
"rect": [
1070,
1294,
213,
319
1672,
315,
207,
314
],
"source_size": [
213,
319
],
"trim_offset": [
0,
0
4,
5
]
},
{
"rect": [
1284,
1294,
213,
319
0,
630,
206,
313
],
"source_size": [
213,
319
],
"trim_offset": [
0,
0
5,
6
]
},
{
"rect": [
1498,
1294,
213,
319
207,
630,
206,
313
],
"source_size": [
213,
319
],
"trim_offset": [
0,
0
5,
6
]
},
{
"rect": [
620,
630,
205,
312
],
"source_size": [
213,
319
],
"trim_offset": [
6,
7
]
},
{
"rect": [
826,
630,
205,
312
],
"source_size": [
213,
319
],
"trim_offset": [
6,
7
]
}
]
+102
View File
@@ -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[:]))
}
+174
View File
@@ -0,0 +1,174 @@
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)
PERF_WIDTH :: #config(PERF_WIDTH, 800)
PERF_HEIGHT :: #config(PERF_HEIGHT, 600)
Scenario :: enum {
Visible,
Half_Offscreen,
Alternating_Textures,
Stacked,
}
@(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"
case .Stacked:
return "stacked"
}
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 <= 3)
#assert(PERF_WIDTH > 0)
#assert(PERF_HEIGHT > 0)
scenario := Scenario(PERF_SCENARIO)
app: eng.App
if !eng.init(&app, "sprite frame benchmark", PERF_WIDTH, PERF_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}
} else if scenario == .Stacked {
position = {f32(PERF_WIDTH / 2), f32(PERF_HEIGHT / 2)}
}
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,
PERF_WIDTH,
PERF_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,
)
}
+5 -5
View File
@@ -74,13 +74,13 @@ character_clip_found_and_missing :: proc(t: ^testing.T) {
defer destroy_test_character(&data)
clip, ok := character_clip(&data, "idle")
testing.expect(t, ok)
testing.expect(t, ok, "character_clip should find idle")
testing.expect_value(t, len(clip.frames), 3)
testing.expect(t, clip.loop)
testing.expect(t, clip.loop, "idle clip should loop")
testing.expect_value(t, clip.fps, f32(10))
_, ok = character_clip(&data, "missing")
testing.expect(t, !ok)
testing.expect(t, !ok, "character_clip should miss unknown name")
data.def.clips["empty"] = Clip_Def {
loop = true,
@@ -88,7 +88,7 @@ character_clip_found_and_missing :: proc(t: ^testing.T) {
frames = nil,
}
_, ok = character_clip(&data, "empty")
testing.expect(t, !ok)
testing.expect(t, !ok, "character_clip should reject empty frames")
}
@(test)
@@ -161,7 +161,7 @@ update_sprite_advances_multiple_frames :: proc(t: ^testing.T) {
s := spawn_sprite(&data, {}, "idle", 0)
update_sprite(&s, 0.25)
testing.expect_value(t, s.frame, 2)
testing.expect(t, s.time > 0.049 && s.time < 0.051)
testing.expect(t, s.time > 0.049 && s.time < 0.051, "leftover time after multi-frame advance should be ~0.05")
}
@(test)
+136 -20
View File
@@ -1,6 +1,7 @@
package engine
import "core:fmt"
import "core:mem"
import "core:path/filepath"
import sdl "vendor:sdl3"
@@ -10,7 +11,14 @@ Vertex :: struct {
}
SPRITE_VERT_COUNT :: 6
VERTEX_BUFFER_SIZE :: SPRITE_VERT_COUNT * size_of(Vertex)
MAX_SPRITES :: 128
SPRITE_VERTS_SIZE :: SPRITE_VERT_COUNT * size_of(Vertex)
VERTEX_BUFFER_SIZE :: MAX_SPRITES * SPRITE_VERTS_SIZE
Queued_Sprite :: struct {
texture: ^sdl.GPUTexture,
verts: [SPRITE_VERT_COUNT]Vertex,
}
App :: struct {
window: ^sdl.Window,
@@ -25,6 +33,9 @@ App :: struct {
swapchain_h: u32,
vertex_buffer: ^sdl.GPUBuffer,
transfer_buffer: ^sdl.GPUTransferBuffer,
draw_list: [dynamic]Queued_Sprite,
clear_color: sdl.FColor,
camera: Camera,
}
Shader_Backend :: enum {
@@ -65,6 +76,19 @@ init :: proc(app: ^App, title: cstring, width, height: i32) -> bool {
return false
}
// Prefer uncapped present for profiling; fall back if unsupported.
present := sdl.GPUPresentMode.VSYNC
if sdl.WindowSupportsGPUPresentMode(app.device, app.window, .IMMEDIATE) {
present = .IMMEDIATE
} else if sdl.WindowSupportsGPUPresentMode(app.device, app.window, .MAILBOX) {
present = .MAILBOX
}
if present != .VSYNC {
if !sdl.SetGPUSwapchainParameters(app.device, app.window, .SDR, present) {
fmt.eprintfln("SetGPUSwapchainParameters failed: %s", sdl.GetError())
}
}
ok: bool
app.shader, ok = choose_shader_runtime(app.device)
if !ok do return false
@@ -112,10 +136,16 @@ init :: proc(app: ^App, title: cstring, width, height: i32) -> bool {
return false
}
app.draw_list = make([dynamic]Queued_Sprite)
app.camera = camera_default()
return true
}
shutdown :: proc(app: ^App) {
delete(app.draw_list)
if app.device != nil {
ok := sdl.WaitForGPUIdle(app.device)
if !ok {
@@ -164,9 +194,13 @@ events :: proc() -> bool {
return true
}
begin_frame :: proc(app: ^App, clear: sdl.FColor = {0.12, 0.12, 0.16, 1}) {
app.cmd = sdl.AcquireGPUCommandBuffer(app.device)
begin_frame :: proc(app: ^App, clear_color: sdl.FColor = {0.12, 0.12, 0.16, 1}) {
clear(&app.draw_list)
app.clear_color = clear_color
app.render_pass = nil
app.swapchain_texture = nil
app.cmd = sdl.AcquireGPUCommandBuffer(app.device)
if app.cmd == nil {
fmt.eprintfln("AcquireGPUCommandBuffer failed: %s", sdl.GetError())
return
@@ -181,36 +215,104 @@ begin_frame :: proc(app: ^App, clear: sdl.FColor = {0.12, 0.12, 0.16, 1}) {
)
if !ok || app.swapchain_texture == nil {
app.swapchain_texture = nil
return
}
}
end_frame :: proc(app: ^App) {
if app.cmd == nil {
clear(&app.draw_list)
return
}
cmd := app.cmd
defer {
app.render_pass = nil
app.swapchain_texture = nil
app.cmd = nil
clear(&app.draw_list)
}
if app.swapchain_texture == nil {
if !sdl.SubmitGPUCommandBuffer(cmd) {
fmt.eprintfln("SubmitGPUCommandBuffer failed")
}
return
}
n := len(app.draw_list)
if n > 0 {
map_ptr := sdl.MapGPUTransferBuffer(app.device, app.transfer_buffer, false)
if map_ptr == nil {
fmt.eprintfln("MapGPUTransferBuffer failed: %s", sdl.GetError())
if !sdl.SubmitGPUCommandBuffer(cmd) {
fmt.eprintfln("SubmitGPUCommandBuffer failed")
}
return
}
for i in 0 ..< n {
q := &app.draw_list[i]
offset := i * SPRITE_VERTS_SIZE
mem.copy(
rawptr(uintptr(map_ptr) + uintptr(offset)),
raw_data(q.verts[:]),
SPRITE_VERTS_SIZE,
)
}
sdl.UnmapGPUTransferBuffer(app.device, app.transfer_buffer)
copy_pass := sdl.BeginGPUCopyPass(cmd)
src := sdl.GPUTransferBufferLocation {
transfer_buffer = app.transfer_buffer,
offset = 0,
}
dst := sdl.GPUBufferRegion {
buffer = app.vertex_buffer,
offset = 0,
size = u32(n * SPRITE_VERTS_SIZE),
}
sdl.UploadToGPUBuffer(copy_pass, src, dst, false)
sdl.EndGPUCopyPass(copy_pass)
}
color_info := sdl.GPUColorTargetInfo {
texture = app.swapchain_texture,
clear_color = clear,
clear_color = app.clear_color,
load_op = .CLEAR,
store_op = .STORE,
}
app.render_pass = sdl.BeginGPURenderPass(app.cmd, &color_info, 1, nil)
app.render_pass = sdl.BeginGPURenderPass(cmd, &color_info, 1, nil)
sdl.BindGPUGraphicsPipeline(app.render_pass, app.pipeline)
}
end_frame :: proc(app: ^App) {
if app.render_pass != nil {
sdl.EndGPURenderPass(app.render_pass)
app.render_pass = nil
}
i := 0
for i < n {
run := texture_run_len(app.draw_list[:], i)
q0 := app.draw_list[i]
if app.cmd != nil {
ok := sdl.SubmitGPUCommandBuffer(app.cmd)
if !ok {
fmt.eprintfln("SubmitGPUCommandBuffer failed")
sampler_binding := sdl.GPUTextureSamplerBinding {
texture = q0.texture,
sampler = app.sampler,
}
app.cmd = nil
}
sdl.BindGPUFragmentSamplers(app.render_pass, 0, &sampler_binding, 1)
app.swapchain_texture = nil
vb_binding := sdl.GPUBufferBinding {
buffer = app.vertex_buffer,
offset = u32(i * SPRITE_VERTS_SIZE),
}
sdl.BindGPUVertexBuffers(app.render_pass, 0, &vb_binding, 1)
sdl.DrawGPUPrimitives(app.render_pass, u32(run * SPRITE_VERT_COUNT), 1, 0, 0)
i += run
}
sdl.EndGPURenderPass(app.render_pass)
app.render_pass = nil
if !sdl.SubmitGPUCommandBuffer(cmd) {
fmt.eprintfln("SubmitGPUCommandBuffer failed")
}
}
load_gpu_shader :: proc(
@@ -338,3 +440,17 @@ create_sprite_pipeline :: proc(app: ^App) -> ^sdl.GPUGraphicsPipeline {
return pipeline
}
texture_run_len :: proc(list: []Queued_Sprite, start: int) -> int {
if start < 0 || start >= len(list) do return 0
tex := list[start].texture
n := 1
for i in start + 1 ..< len(list) {
if list[i].texture != tex do break
n += 1
}
return n
}
+64
View File
@@ -0,0 +1,64 @@
package engine
import "core:testing"
import sdl "vendor:sdl3"
fake_tex :: proc(id: uintptr) -> ^sdl.GPUTexture {
return cast(^sdl.GPUTexture)id
}
@(test)
texture_run_len_empty_or_oob :: proc(t: ^testing.T) {
testing.expect_value(t, texture_run_len(nil, 0), 0)
list := []Queued_Sprite{}
testing.expect_value(t, texture_run_len(list, 0), 0)
list = make([]Queued_Sprite, 1)
defer delete(list)
list[0] = {texture = fake_tex(1)}
testing.expect_value(t, texture_run_len(list, -1), 0)
testing.expect_value(t, texture_run_len(list, 1), 0)
}
@(test)
texture_run_len_single :: proc(t: ^testing.T) {
list := make([]Queued_Sprite, 1)
defer delete(list)
list[0] = {texture = fake_tex(1)}
testing.expect_value(t, texture_run_len(list, 0), 1)
}
@(test)
texture_run_len_same_texture :: proc(t: ^testing.T) {
tex := fake_tex(1)
list := make([]Queued_Sprite, 3)
defer delete(list)
list[0] = {texture = tex}
list[1] = {texture = tex}
list[2] = {texture = tex}
testing.expect_value(t, texture_run_len(list, 0), 3)
}
@(test)
texture_run_len_breaks_on_change :: proc(t: ^testing.T) {
a := fake_tex(1)
b := fake_tex(2)
list := make([]Queued_Sprite, 3)
defer delete(list)
list[0] = {texture = a}
list[1] = {texture = a}
list[2] = {texture = b}
testing.expect_value(t, texture_run_len(list, 0), 2)
testing.expect_value(t, texture_run_len(list, 2), 1)
}
@(test)
texture_run_len_all_different :: proc(t: ^testing.T) {
list := make([]Queued_Sprite, 3)
defer delete(list)
list[0] = {texture = fake_tex(1)}
list[1] = {texture = fake_tex(2)}
list[2] = {texture = fake_tex(3)}
testing.expect_value(t, texture_run_len(list, 0), 1)
testing.expect_value(t, texture_run_len(list, 1), 1)
testing.expect_value(t, texture_run_len(list, 2), 1)
}
+17
View File
@@ -0,0 +1,17 @@
package engine
Camera :: struct {
position: Vec2,
anchor: Vec2,
}
camera_default :: proc() -> Camera {
return Camera{position = {0, 0}, anchor = {0.5, 0.5}}
}
world_to_screen :: proc(cam: Camera, world: Vec2, viewport: Vec2) -> Vec2 {
return {
world.x - cam.position.x + viewport.x * cam.anchor[0],
world.y - cam.position.y + viewport.y * cam.anchor[1],
}
}
+44
View File
@@ -0,0 +1,44 @@
package engine
import "core:testing"
@(test)
camera_default_values :: proc(t: ^testing.T) {
cam := camera_default()
testing.expect_value(t, cam.position, Vec2{0, 0})
testing.expect_value(t, cam.anchor, Vec2{0.5, 0.5})
}
@(test)
world_to_screen_centered_identity :: proc(t: ^testing.T) {
cam := Camera {
position = {400, 500},
anchor = {0.5, 0.5},
}
viewport := Vec2{800, 600}
screen := world_to_screen(cam, cam.position, viewport)
testing.expect_value(t, screen.x, f32(400))
testing.expect_value(t, screen.y, f32(300))
}
@(test)
world_to_screen_origin_cam :: proc(t: ^testing.T) {
cam := Camera {
position = {0, 0},
anchor = {0.5, 0.5},
}
screen := world_to_screen(cam, {10, 20}, {200, 100})
testing.expect_value(t, screen.x, f32(110))
testing.expect_value(t, screen.y, f32(70))
}
@(test)
world_to_screen_top_left_anchor :: proc(t: ^testing.T) {
cam := Camera {
position = {5, 7},
anchor = {0, 0},
}
screen := world_to_screen(cam, {15, 27}, {800, 600})
testing.expect_value(t, screen.x, f32(10))
testing.expect_value(t, screen.y, f32(20))
}
+19 -6
View File
@@ -210,6 +210,22 @@ character_clip :: proc(data: ^Character_Data, clip_name: string) -> (clip: Clip_
return character, true
}
character_frame :: proc(
data: ^Character_Data,
clip_name: string,
frame_index: int,
) -> (
frame: Frame_Def,
ok: bool,
) {
if data == nil do return {}, false
clip, found := data.def.clips[clip_name]
if !found || frame_index < 0 || frame_index >= len(clip.frames) {
return {}, false
}
return clip.frames[frame_index], true
}
character_frame_rect :: proc(
data: ^Character_Data,
clip_name: string,
@@ -218,10 +234,7 @@ character_frame_rect :: proc(
rect: [4]int,
ok: bool,
) {
clip, found := data.def.clips[clip_name]
if !found || frame_index < 0 || frame_index >= len(clip.frames) {
return {}, false
}
return clip.frames[frame_index].rect, true
frame, found := character_frame(data, clip_name, frame_index)
if !found do return {}, false
return frame.rect, true
}
+57 -8
View File
@@ -11,7 +11,7 @@ parse_char_def_happy_path :: proc(t: ^testing.T) {
def, ok := parse_char_def(src)
defer destroy_char_def(&def)
testing.expect(t, ok)
testing.expect(t, ok, "parse_char_def should succeed on valid JSON")
testing.expect_value(t, def.id, "toad")
testing.expect_value(t, def.atlas, "toad.atlas.png")
testing.expect_value(t, def.version, 1)
@@ -20,8 +20,8 @@ parse_char_def_happy_path :: proc(t: ^testing.T) {
testing.expect_value(t, def.pivot[1], f32(1.0))
idle, found := def.clips["idle"]
testing.expect(t, found)
testing.expect(t, idle.loop)
testing.expect(t, found, "expected idle clip in parsed def")
testing.expect(t, idle.loop, "idle clip should loop")
testing.expect_value(t, idle.fps, f32(8.0))
testing.expect_value(t, len(idle.frames), 1)
testing.expect_value(t, idle.frames[0].rect, [4]int{1, 2, 3, 4})
@@ -30,7 +30,7 @@ parse_char_def_happy_path :: proc(t: ^testing.T) {
@(test)
parse_char_def_bad_json :: proc(t: ^testing.T) {
_, ok := parse_char_def(transmute([]u8)string("{ not json"))
testing.expect(t, !ok)
testing.expect(t, !ok, "parse_char_def should fail on invalid JSON")
}
@(test)
@@ -51,17 +51,66 @@ character_frame_rect_hit_and_miss :: proc(t: ^testing.T) {
}
rect, ok := character_frame_rect(&data, "idle", 1)
testing.expect(t, ok)
testing.expect(t, ok, "character_frame_rect should find idle frame 1")
testing.expect_value(t, rect, [4]int{50, 60, 70, 80})
_, ok = character_frame_rect(&data, "missing", 0)
testing.expect(t, !ok)
testing.expect(t, !ok, "character_frame_rect should miss unknown clip")
_, ok = character_frame_rect(&data, "idle", -1)
testing.expect(t, !ok)
testing.expect(t, !ok, "character_frame_rect should reject negative index")
_, ok = character_frame_rect(&data, "idle", 2)
testing.expect(t, !ok)
testing.expect(t, !ok, "character_frame_rect should reject out-of-range index")
}
@(test)
character_frame_returns_full_def :: proc(t: ^testing.T) {
data: Character_Data
data.def.clips = make(map[string]Clip_Def)
defer delete(data.def.clips)
frames := make([]Frame_Def, 1)
frames[0] = Frame_Def {
rect = {10, 20, 30, 40},
source_size = {100, 200},
trim_offset = {5, 7},
}
defer delete(frames)
data.def.clips["idle"] = Clip_Def {
loop = true,
fps = 10,
frames = frames,
}
frame, ok := character_frame(&data, "idle", 0)
testing.expect(t, ok, "character_frame should find idle frame 0")
testing.expect_value(t, frame.rect, [4]int{10, 20, 30, 40})
testing.expect_value(t, frame.source_size, [2]int{100, 200})
testing.expect_value(t, frame.trim_offset, [2]int{5, 7})
}
@(test)
character_frame_nil_and_bad_index :: proc(t: ^testing.T) {
_, ok := character_frame(nil, "idle", 0)
testing.expect(t, !ok, "character_frame should fail on nil data")
data: Character_Data
data.def.clips = make(map[string]Clip_Def)
defer delete(data.def.clips)
frames := make([]Frame_Def, 1)
frames[0] = {rect = {1, 2, 3, 4}}
defer delete(frames)
data.def.clips["idle"] = Clip_Def{frames = frames}
_, ok = character_frame(&data, "idle", -1)
testing.expect(t, !ok, "character_frame should reject negative index")
_, ok = character_frame(&data, "idle", 1)
testing.expect(t, !ok, "character_frame should reject out-of-range index")
_, ok = character_frame(&data, "missing", 0)
testing.expect(t, !ok, "character_frame should miss unknown clip")
}
destroy_char_def :: proc(def: ^Char_Def) {
+15
View File
@@ -7,6 +7,11 @@ Key :: enum {
D,
Left,
Right,
Up,
Down,
Space,
N1,
N2,
}
key_down :: proc(key: Key) -> bool {
@@ -23,6 +28,16 @@ key_down :: proc(key: Key) -> bool {
scan_code = .LEFT
case .Right:
scan_code = .RIGHT
case .Up:
scan_code = .UP
case .Down:
scan_code = .DOWN
case .Space:
scan_code = .SPACE
case .N1:
scan_code = ._1
case .N2:
scan_code = ._2
}
return keys[scan_code]
}
+3 -3
View File
@@ -21,7 +21,7 @@ shader_filenames_per_backend :: proc(t: ^testing.T) {
@(test)
choose_shader_runtime_prefers_msl :: proc(t: ^testing.T) {
rt, ok := choose_shader_runtime_from_formats({.MSL, .SPIRV, .DXIL})
testing.expect(t, ok)
testing.expect(t, ok, "should pick a runtime when MSL is available")
testing.expect_value(t, rt.backend, Shader_Backend.Metal_MSL)
testing.expect_value(t, rt.shader_dir, "shaders/metal")
testing.expect_value(t, rt.format, sdl.GPUShaderFormat{.MSL})
@@ -30,7 +30,7 @@ choose_shader_runtime_prefers_msl :: proc(t: ^testing.T) {
@(test)
choose_shader_runtime_spirv_only :: proc(t: ^testing.T) {
rt, ok := choose_shader_runtime_from_formats({.SPIRV})
testing.expect(t, ok)
testing.expect(t, ok, "should pick SPIR-V when it is the only format")
testing.expect_value(t, rt.backend, Shader_Backend.Vulkan_SPIRV)
testing.expect_value(t, rt.shader_dir, "shaders/vulkan")
testing.expect_value(t, rt.format, sdl.GPUShaderFormat{.SPIRV})
@@ -39,5 +39,5 @@ choose_shader_runtime_spirv_only :: proc(t: ^testing.T) {
@(test)
choose_shader_runtime_empty_fails :: proc(t: ^testing.T) {
_, ok := choose_shader_runtime_from_formats({})
testing.expect(t, !ok)
testing.expect(t, !ok, "empty format set should fail")
}
+68 -71
View File
@@ -1,6 +1,5 @@
package engine
import "core:mem"
import sdl "vendor:sdl3"
Vec2 :: [2]f32
@@ -40,7 +39,6 @@ spawn_sprite :: proc(
return sprite
}
// stubbed for later
update_sprite :: proc(sprite: ^Sprite, dt: f32) {
if sprite == nil || sprite.data == nil do return
if dt <= 0 do return
@@ -85,45 +83,57 @@ to_clip :: proc(px, py, sw, sh: f32) -> [2]f32 {
}
draw_sprite :: proc(app: ^App, sprite: ^Sprite) {
if app.render_pass == nil || app.cmd == nil || app.swapchain_texture == nil {
return // begin_frame may have skipped (minimized window, etc.)
if app.cmd == nil || app.swapchain_texture == nil {
return
}
if sprite == nil || sprite.data == nil || sprite.data.texture == nil {
return
}
if len(app.draw_list) >= MAX_SPRITES {
return
}
rect, ok := character_frame_rect(sprite.data, sprite.clip, sprite.frame)
frame, ok := character_frame(sprite.data, sprite.clip, sprite.frame)
if !ok {
return
}
fw := f32(rect[2])
fh := f32(rect[3])
src_w := f32(frame.source_size[0])
src_h := f32(frame.source_size[1])
if src_w <= 0 do src_w = f32(frame.rect[2])
if src_h <= 0 do src_h = f32(frame.rect[3])
fw := f32(frame.rect[2])
fh := f32(frame.rect[3])
trim_x := f32(frame.trim_offset[0])
trim_y := f32(frame.trim_offset[1])
pivot := sprite.data.def.pivot
origin := sprite_quad_origin(sprite.position, {fw, fh}, pivot)
x0_px := origin.x
y0_px := origin.y
x1_px := x0_px + fw
y1_px := y0_px + fh
viewport := Vec2{f32(app.swapchain_w), f32(app.swapchain_h)}
feet := world_to_screen(app.camera, sprite.position, viewport)
x0_px, y0_px, x1_px, y1_px := sprite_feet_quad(
feet,
src_w,
src_h,
{trim_x, trim_y},
{fw, fh},
pivot,
sprite.flip_x,
)
sw := f32(app.swapchain_w)
sh := f32(app.swapchain_h)
p0 := to_clip(x0_px, y0_px, sw, sh) // top-left
p1 := to_clip(x1_px, y0_px, sw, sh) // top-right
p2 := to_clip(x1_px, y1_px, sw, sh) // bottom-right
p3 := to_clip(x0_px, y1_px, sw, sh) // bottom-left
p0 := to_clip(x0_px, y0_px, sw, sh)
p1 := to_clip(x1_px, y0_px, sw, sh)
p2 := to_clip(x1_px, y1_px, sw, sh)
p3 := to_clip(x0_px, y1_px, sw, sh)
tex_w := f32(sprite.data.width)
tex_h := f32(sprite.data.height)
u0 := f32(rect[0]) / tex_w
v0 := f32(rect[1]) / tex_h
u1 := f32(rect[0] + rect[2]) / tex_w
v1 := f32(rect[1] + rect[3]) / tex_h
if sprite.flip_x do u0, u1 = u1, u0
u0, v0, u1, v1 := frame_uvs(frame.rect, tex_w, tex_h, sprite.flip_x)
// Two triangles: (0,1,2) and (0,2,3)
verts := [6]Vertex {
verts := [SPRITE_VERT_COUNT]Vertex {
{pos = p0, uv = {u0, v0}},
{pos = p1, uv = {u1, v0}},
{pos = p2, uv = {u1, v1}},
@@ -132,54 +142,7 @@ draw_sprite :: proc(app: ^App, sprite: ^Sprite) {
{pos = p3, uv = {u0, v1}},
}
map_ptr := sdl.MapGPUTransferBuffer(app.device, app.transfer_buffer, false)
if map_ptr == nil {
return
}
mem.copy(map_ptr, raw_data(verts[:]), size_of(verts))
sdl.UnmapGPUTransferBuffer(app.device, app.transfer_buffer)
// IMPORTANT: SDL does not allow beginning a copy pass while a render pass
// is active on the same command buffer. Upload on a separate command buffer.
copy_cmd := sdl.AcquireGPUCommandBuffer(app.device)
if copy_cmd == nil {
return
}
copy_pass := sdl.BeginGPUCopyPass(copy_cmd)
src := sdl.GPUTransferBufferLocation {
transfer_buffer = app.transfer_buffer,
offset = 0,
}
dst := sdl.GPUBufferRegion {
buffer = app.vertex_buffer,
offset = 0,
size = u32(size_of(verts)),
}
sdl.UploadToGPUBuffer(copy_pass, src, dst, false)
sdl.EndGPUCopyPass(copy_pass)
fence := sdl.SubmitGPUCommandBufferAndAcquireFence(copy_cmd)
if fence == nil {
return
}
defer sdl.ReleaseGPUFence(app.device, fence)
if !sdl.WaitForGPUFences(app.device, true, &fence, 1) {
return
}
sampler_binding := sdl.GPUTextureSamplerBinding {
texture = sprite.data.texture,
sampler = app.sampler,
}
sdl.BindGPUFragmentSamplers(app.render_pass, 0, &sampler_binding, 1)
vb_binding := sdl.GPUBufferBinding {
buffer = app.vertex_buffer,
offset = 0,
}
sdl.BindGPUVertexBuffers(app.render_pass, 0, &vb_binding, 1)
sdl.DrawGPUPrimitives(app.render_pass, 6, 1, 0, 0)
append(&app.draw_list, Queued_Sprite{texture = sprite.data.texture, verts = verts})
}
set_sprite_clip :: proc(sprite: ^Sprite, clip: string) {
@@ -199,6 +162,40 @@ sprite_quad_origin :: proc(position: Vec2, size: Vec2, pivot: [2]f32) -> Vec2 {
return {position.x - size.x * pivot[0], position.y - size.y * pivot[1]}
}
frame_uvs :: proc(rect: [4]int, tex_w, tex_h: f32, flip_x: bool) -> (u0, v0, u1, v1: f32) {
u0 = f32(rect[0]) / tex_w
v0 = f32(rect[1]) / tex_h
u1 = f32(rect[0] + rect[2]) / tex_w
v1 = f32(rect[1] + rect[3]) / tex_h
if flip_x do u0, u1 = u1, u0
return
}
sprite_feet_quad :: proc(
feet: Vec2,
src_w, src_h: f32,
trim: Vec2,
size: Vec2,
pivot: [2]f32,
flip_x: bool,
) -> (
x0, y0, x1, y1: f32,
) {
canvas_top := feet.y - src_h * pivot[1]
if flip_x {
canvas_left := feet.x - (1.0 - pivot[0]) * src_w
x0 = canvas_left + (src_w - trim.x - size.x)
y0 = canvas_top + trim.y
} else {
canvas_left := feet.x - src_w * pivot[0]
x0 = canvas_left + trim.x
y0 = canvas_top + trim.y
}
x1 = x0 + size.x
y1 = y0 + size.y
return
}
set_sprite_flip_x :: proc(sprite: ^Sprite, flip: bool) {
if sprite == nil do return
sprite.flip_x = flip
+56
View File
@@ -33,3 +33,59 @@ sprite_quad_origin_center :: proc(t: ^testing.T) {
testing.expect_value(t, o.x, f32(350))
testing.expect_value(t, o.y, f32(400))
}
@(test)
frame_uvs_unflipped :: proc(t: ^testing.T) {
u0, v0, u1, v1 := frame_uvs({10, 20, 30, 40}, 100, 200, false)
testing.expect_value(t, u0, f32(0.1))
testing.expect_value(t, v0, f32(0.1))
testing.expect_value(t, u1, f32(0.4))
testing.expect_value(t, v1, f32(0.3))
testing.expect(t, u0 < u1, "unflipped UVs should have u0 < u1")
}
@(test)
frame_uvs_flipped :: proc(t: ^testing.T) {
a0, _, a1, _ := frame_uvs({10, 20, 30, 40}, 100, 200, false)
b0, v0, b1, v1 := frame_uvs({10, 20, 30, 40}, 100, 200, true)
testing.expect_value(t, b0, a1)
testing.expect_value(t, b1, a0)
testing.expect_value(t, v0, f32(0.1))
testing.expect_value(t, v1, f32(0.3))
}
@(test)
sprite_feet_quad_no_flip :: proc(t: ^testing.T) {
// feet (400,500), source 100x200, pivot feet, trim (10,20), packed 80x160
x0, y0, x1, y1 := sprite_feet_quad(
{400, 500},
100,
200,
{10, 20},
{80, 160},
{0.5, 1.0},
false,
)
testing.expect_value(t, x0, f32(360)) // 400 - 50 + 10
testing.expect_value(t, y0, f32(320)) // 500 - 200 + 20
testing.expect_value(t, x1, f32(440))
testing.expect_value(t, y1, f32(480))
}
@(test)
sprite_feet_quad_flip_x :: proc(t: ^testing.T) {
x0, y0, x1, y1 := sprite_feet_quad(
{400, 500},
100,
200,
{5, 20},
{80, 160},
{0.5, 1.0},
true,
)
// canvas_left = 350; trim_x_draw = 100 - 5 - 80 = 15 x0 = 365
testing.expect_value(t, x0, f32(365))
testing.expect_value(t, y0, f32(320))
testing.expect_value(t, x1, f32(445))
testing.expect_value(t, y1, f32(480))
}
+58
View File
@@ -0,0 +1,58 @@
package main
import eng "pkg:engine"
// Camera sandbox: toad stays put; arrows pan the camera. Space toggles follow mode.
main :: proc() {
app: eng.App
if !eng.init(&app, "camera sandbox", 800, 600) do return
defer eng.shutdown(&app)
data, ok := eng.load_character_data(&app, "assets_baked/characters/toad/toad.char.json")
if !ok do return
defer eng.destroy_character_data(&app, &data)
toad := eng.spawn_sprite(&data, {0, 0}, "idle", 0)
app.camera.position = {0, -100}
follow := false
space_was_down := false
CAM_SPEED :: f32(300)
last := eng.now_seconds()
for eng.events() {
now := eng.now_seconds()
dt := f32(now - last)
last = now
space := eng.key_down(.Space)
if space && !space_was_down {
follow = !follow
}
space_was_down = space
if follow {
app.camera.position = toad.position - {0, 100}
} else {
if eng.key_down(.Left) || eng.key_down(.A) {
app.camera.position.x -= CAM_SPEED * dt
}
if eng.key_down(.Right) || eng.key_down(.D) {
app.camera.position.x += CAM_SPEED * dt
}
if eng.key_down(.Up) {
app.camera.position.y -= CAM_SPEED * dt
}
if eng.key_down(.Down) {
app.camera.position.y += CAM_SPEED * dt
}
}
eng.update_sprite(&toad, dt)
eng.begin_frame(&app)
eng.draw_sprite(&app, &toad)
eng.end_frame(&app)
}
}
+38
View File
@@ -0,0 +1,38 @@
package main
import eng "pkg:engine"
// Clip switcher: 1 = idle, 2 = walk. No movement animation policy only.
main :: proc() {
app: eng.App
if !eng.init(&app, "clips", 800, 600) do return
defer eng.shutdown(&app)
data, ok := eng.load_character_data(&app, "assets_baked/characters/toad/toad.char.json")
if !ok do return
defer eng.destroy_character_data(&app, &data)
toad := eng.spawn_sprite(&data, {400, 500}, "idle", 0)
app.camera.position = toad.position - {0, 100}
last := eng.now_seconds()
for eng.events() {
now := eng.now_seconds()
dt := f32(now - last)
last = now
if eng.key_down(.N1) {
eng.set_sprite_clip(&toad, "idle")
}
if eng.key_down(.N2) {
eng.set_sprite_clip(&toad, "walk")
}
eng.update_sprite(&toad, dt)
eng.begin_frame(&app)
eng.draw_sprite(&app, &toad)
eng.end_frame(&app)
}
}
+49
View File
@@ -0,0 +1,49 @@
package main
import eng "pkg:engine"
// Many sprites sharing one Character_Data (Flyweight) good batching demo.
COUNT :: 24
main :: proc() {
app: eng.App
if !eng.init(&app, "crowd", 800, 600) do return
defer eng.shutdown(&app)
data, ok := eng.load_character_data(&app, "assets_baked/characters/toad/toad.char.json")
if !ok do return
defer eng.destroy_character_data(&app, &data)
sprites: [COUNT]eng.Sprite
for i in 0 ..< COUNT {
col := i % 8
row := i / 8
pos := eng.Vec2 {
f32(120 + col * 80),
f32(280 + row * 120),
}
clip := "idle" if (i % 2) == 0 else "walk"
sprites[i] = eng.spawn_sprite(&data, pos, clip, i % 5)
}
// Look at the middle of the grid
app.camera.position = {400, 400}
last := eng.now_seconds()
for eng.events() {
now := eng.now_seconds()
dt := f32(now - last)
last = now
for &s in sprites {
eng.update_sprite(&s, dt)
}
eng.begin_frame(&app)
for &s in sprites {
eng.draw_sprite(&app, &s)
}
eng.end_frame(&app)
}
}
+34
View File
@@ -0,0 +1,34 @@
package main
import eng "pkg:engine"
// Minimal: load one baked character and draw idle. No input, no camera follow.
main :: proc() {
app: eng.App
if !eng.init(&app, "hello sprite", 800, 600) do return
defer eng.shutdown(&app)
data, ok := eng.load_character_data(&app, "assets_baked/characters/toad/toad.char.json")
if !ok do return
defer eng.destroy_character_data(&app, &data)
sprite := eng.spawn_sprite(&data, {400, 500}, "idle", 0)
// Screen-centered framing without gameplay camera follow:
// treat spawn position as world; look slightly above feet.
app.camera.position = sprite.position - {0, 100}
last := eng.now_seconds()
for eng.events() {
now := eng.now_seconds()
dt := f32(now - last)
last = now
eng.update_sprite(&sprite, dt)
eng.begin_frame(&app)
eng.draw_sprite(&app, &sprite)
eng.end_frame(&app)
}
}
+2
View File
@@ -39,6 +39,8 @@ main :: proc() {
} else {
eng.set_sprite_clip(&toad, "idle")
}
app.camera.position = toad.position - {0, 100}
eng.update_sprite(&toad, dt)
eng.begin_frame(&app)
+645
View File
@@ -0,0 +1,645 @@
# To display the perf.data header info, please use --header/--header-only options.
#
#
# Total Lost Samples: 0
#
# Samples: 499 of event 'cpu/cycles/Pu'
# Event count (approx.): 2684910393
#
# Overhead Command Shared Object Symbol
# ........ ............... ............................. ...........................................
#
9.56% crowd_perf crowd_perf [.] engine::draw_sprite
|
---engine::draw_sprite
main::main
main
0x7fe3b6c27740
__libc_start_main
_start
5.33% crowd_perf crowd_perf [.] engine::to_clip
|
---engine::to_clip
engine::draw_sprite
main::main
main
0x7fe3b6c27740
__libc_start_main
_start
3.98% crowd_perf crowd_perf [.] engine::sprite_feet_quad
|
---engine::sprite_feet_quad
engine::draw_sprite
main::main
main
0x7fe3b6c27740
__libc_start_main
_start
3.01% crowd_perf crowd_perf [.] engine::update_sprite
|
---engine::update_sprite
main::main
main
0x7fe3b6c27740
__libc_start_main
_start
2.56% crowd_perf crowd_perf [.] runtime::default_hasher
|
---runtime::default_hasher
runtime::default_hasher_string
__$hasher$$string
|
|--1.36%--engine::character_frame
| engine::draw_sprite
| main::main
| main
| 0x7fe3b6c27740
| __libc_start_main
| _start
|
--1.20%--engine::character_clip
engine::update_sprite
main::main
main
0x7fe3b6c27740
__libc_start_main
_start
2.49% crowd_perf libc.so.6 [.] 0x000000000018a4c4
|
---0x7fe3b6d8a4c4
engine::draw_sprite
main::main
main
0x7fe3b6c27740
__libc_start_main
_start
2.47% crowd_perf crowd_perf [.] runtime::_append_elem
|
---runtime::_append_elem
runtime::append_elem:proc(array:^[dynamic]engine::Queued_Sprite,arg:engine::Queued_Sprite,loc:runtime::Source_Code_Location)->(n:int,err:runtime::Allocator_Error)
engine::draw_sprite
main::main
main
0x7fe3b6c27740
__libc_start_main
_start
1.97% crowd_perf crowd_perf [.] runtime::bounds_check_error
|
---runtime::bounds_check_error
engine::character_frame
engine::draw_sprite
main::main
main
0x7fe3b6c27740
__libc_start_main
_start
1.70% crowd_perf libwayland-client.so.0.25.0 [.] wl_proxy_marshal_array_flags
|
---wl_proxy_marshal_array_flags
wl_proxy_marshal_flags
|
--0.87%--0x7fe396d97539
0x7fe396d8a4f8
0x7fe3b724ffc9
engine::end_frame
main::main
main
0x7fe3b6c27740
__libc_start_main
_start
1.61% crowd_perf crowd_perf [.] __$map_get$$map[string]engine::Clip_Def
|
---__$map_get$$map[string]engine::Clip_Def
|
|--1.00%--engine::character_frame
| engine::draw_sprite
| main::main
| main
| 0x7fe3b6c27740
| __libc_start_main
| _start
|
--0.61%--engine::character_clip
engine::update_sprite
main::main
main
0x7fe3b6c27740
__libc_start_main
_start
1.57% wl_cursor_surfa libc.so.6 [.] 0x000000000009ca5c
1.50% crowd_perf crowd_perf [.] engine::world_to_screen
|
---engine::world_to_screen
engine::draw_sprite
main::main
main
0x7fe3b6c27740
__libc_start_main
_start
1.35% crowd_perf crowd_perf [.] runtime::memory_equal
|
---runtime::memory_equal
runtime::string_eq
__$map_get$$map[string]engine::Clip_Def
|
--1.06%--engine::character_clip
engine::update_sprite
main::main
main
0x7fe3b6c27740
__libc_start_main
_start
1.33% crowd_perf libc.so.6 [.] pthread_mutex_lock
|
---pthread_mutex_lock
1.31% crowd_perf crowd_perf [.] engine::character_frame
|
---engine::character_frame
engine::draw_sprite
main::main
main
0x7fe3b6c27740
__libc_start_main
_start
1.19% crowd_perf crowd_perf [.] runtime::string_eq
|
---runtime::string_eq
__$map_get$$map[string]engine::Clip_Def
|
|--0.62%--engine::character_frame
| engine::draw_sprite
| main::main
| main
| 0x7fe3b6c27740
| __libc_start_main
| _start
|
--0.57%--engine::character_clip
engine::update_sprite
main::main
main
0x7fe3b6c27740
__libc_start_main
_start
1.18% crowd_perf [unknown] [k] 0xffffffffab355a81
|
---0xffffffffab355a81
0xffffffffa9e0012f
ioctl
drmIoctl
|
--0.59%--drmSyncobjCreate
0x7fe396d8c878
0x7fe396d893c8
0x7fe396d89241
0x7fe3b72544bd
0x7fe3b7069860
engine::begin_frame
main::main
main
0x7fe3b6c27740
__libc_start_main
_start
1.11% crowd_perf libc.so.6 [.] clock_gettime
|
---clock_gettime
|
--0.68%--0x7fe396f7a09a
1.08% crowd_perf libc.so.6 [.] 0x0000000000189c40
|
---0x7fe3b6d89c40
0x7fe3b6e6ee65
wl_proxy_marshal_array_flags
wl_proxy_marshal_flags
0x7fe396d97614
0x7fe396d8a4f8
0x7fe3b724ffc9
engine::end_frame
main::main
main
0x7fe3b6c27740
__libc_start_main
_start
1.03% crowd_perf crowd_perf [.] main::main
|
---main::main
main
0x7fe3b6c27740
__libc_start_main
_start
0.94% crowd_perf libc.so.6 [.] ioctl
|
---ioctl
|
--0.94%--drmIoctl
0.91% crowd_perf libSDL3.so.0.4.12 [.] 0x00000000001428a1
|
---0x7fe3b71428a1
0x7fe3b714928f
0x7fe3b7141b9a
0x7fe3b71426da
0.89% crowd_perf ld-linux-x86-64.so.2 [.] 0x000000000000ac3a
|
---0x7fe3b739bc3a
0x7fe3b739c855
0x7fe3b73a0f57
0x7fe3b73a2e00
0x7fe3b739eeda
_dl_catch_exception
0x7fe3b739e242
_dl_catch_exception
0x7fe3b739e7a9
0x7fe3b6c93bb3
_dl_catch_exception
0x7fe3b73935c8
0x7fe3b6c936a2
dlopen
0x7fe3b61b2c10
0x7fe3b61b7b4b
0.85% crowd_perf libc.so.6 [.] 0x000000000018a4c0
|
---0x7fe3b6d8a4c0
engine::draw_sprite
main::main
main
0x7fe3b6c27740
__libc_start_main
_start
0.84% crowd_perf libSDL3.so.0.4.12 [.] 0x00000000000691d6
|
---0x7fe3b70691d6
engine::end_frame
main::main
main
0x7fe3b6c27740
__libc_start_main
_start
0.76% crowd_perf libc.so.6 [.] pthread_rwlock_wrlock
|
---pthread_rwlock_wrlock
0.74% crowd_perf libSDL3.so.0.4.12 [.] 0x0000000000142863
|
---0x7fe3b7142863
0x7fe3b71429b4
0x7fe3b714921a
0x7fe3b7141b9a
0x7fe3b71426da
0.74% crowd_perf libSDL3.so.0.4.12 [.] 0x0000000000149304
|
---0x7fe3b7149304
0x7fe3b7141b9a
0x7fe3b71426da
0.74% crowd_perf libc.so.6 [.] 0x000000000018a0e8
|
---0x7fe3b6d8a0e8
mem::copy
engine::load_character_data
main::main
main
0x7fe3b6c27740
__libc_start_main
_start
0.74% crowd_perf libc.so.6 [.] 0x000000000009ca5c
|
---0x7fe3b6c9ca5c
0.73% crowd_perf libSDL3.so.0.4.12 [.] 0x0000000000143ffe
|
---0x7fe3b7143ffe
0x7fe3b714a028
0x7fe3b7141b9a
0.72% crowd_perf libSDL3.so.0.4.12 [.] 0x0000000000143fe9
|
---0x7fe3b7143fe9
0x7fe3b714a028
0x7fe3b7141b9a
0.71% crowd_perf libwayland-client.so.0.25.0 [.] 0x0000000000003263
|
---0x7fe3b6e6d263
wl_proxy_marshal_flags
0x7fe396d97937
0x7fe396d8a4f8
0x7fe3b724ffc9
engine::end_frame
main::main
main
0x7fe3b6c27740
__libc_start_main
_start
0.70% crowd_perf crowd_perf [.] runtime::default_hasher_string
|
---runtime::default_hasher_string
__$hasher$$string
0.69% crowd_perf libdrm.so.2.134.0 [.] drmSyncobjTimelineWait
|
---drmSyncobjTimelineWait
0.68% crowd_perf libc.so.6 [.] 0x00000000000a590c
|
---0x7fe3b6ca590c
0x7fe396d827d6
0x7fe396d3cff4
0x7fe396ddcaf6
0x7fe396ddccd4
0x7fe396de6f2c
0x7fe3b724fec1
engine::end_frame
main::main
main
0x7fe3b6c27740
__libc_start_main
_start
0.66% crowd_perf libSDL3.so.0.4.12 [.] 0x000000000024d030
|
---0x7fe3b724d030
0x7fe3b724f5c0
0x7fe3b72545c5
0x7fe3b7069860
engine::begin_frame
main::main
main
0x7fe3b6c27740
__libc_start_main
_start
0.66% crowd_perf libc.so.6 [.] 0x000000000018a547
|
---0x7fe3b6d8a547
0.65% crowd_perf libvulkan_radeon.so [.] 0x00000000000d27b4
|
---0x7fe396cd27b4
0x7fe396de6325
0x7fe3b72546f1
0x7fe3b7069860
engine::begin_frame
main::main
main
0x7fe3b6c27740
__libc_start_main
_start
0.64% crowd_perf libvulkan_radeon.so [.] 0x0000000000022d88
|
---0x7fe396c22d88
0x7fe396cd2f6f
0x7fe396de6325
0.61% crowd_perf crowd_perf [.] engine::texture_run_len
|
---engine::texture_run_len
engine::end_frame
main::main
main
0x7fe3b6c27740
__libc_start_main
_start
0.55% crowd_perf libvulkan_radeon.so [.] 0x00000000000bbf60
|
---0x7fe396cbbf60
0x7fe396cc6c98
0x7fe396cc985a
0x7fe396cd7932
engine::end_frame
main::main
main
0x7fe3b6c27740
__libc_start_main
_start
0.54% crowd_perf libvulkan_radeon.so [.] 0x000000000022daad
|
---0x7fe396e2daad
0x7fe396d15bcb
0x7fe396d162ff
0.53% crowd_perf libSDL3.so.0.4.12 [.] 0x000000000023af6a
|
---0x7fe3b723af6a
0x7fe3b7249f38
engine::end_frame
main::main
main
0x7fe3b6c27740
__libc_start_main
_start
0.48% crowd_perf libwayland-client.so.0.25.0 [.] wl_display_flush
0.48% crowd_perf libvulkan_radeon.so [.] 0x00000000000cf827
0.47% crowd_perf libSDL3.so.0.4.12 [.] 0x00000000000166ae
0.44% crowd_perf libvulkan_radeon.so [.] 0x0000000000022daa
0.43% crowd_perf libc.so.6 [.] cfree
0.43% crowd_perf libvulkan_radeon.so [.] 0x00000000000d2c23
0.41% crowd_perf libc.so.6 [.] 0x00000000000a58f9
0.41% crowd_perf libvulkan_radeon.so [.] 0x000000000018d44f
0.40% crowd_perf libvulkan_radeon.so [.] 0x000000000002b9b1
0.40% crowd_perf libvulkan_radeon.so [.] 0x00000000001e4771
0.40% crowd_perf libwayland-client.so.0.25.0 [.] 0x00000000000055ca
0.39% crowd_perf libvulkan_radeon.so [.] 0x0000000000116639
0.39% crowd_perf libvulkan_radeon.so [.] 0x00000000001d1196
0.39% crowd_perf libwayland-client.so.0.25.0 [.] 0x000000000000315e
0.39% crowd_perf libvulkan_radeon.so [.] 0x000000000002d957
0.39% crowd_perf libvulkan_radeon.so [.] 0x000000000013d072
0.39% crowd_perf libvulkan_radeon.so [.] 0x00000000000cb17b
0.38% crowd_perf libSDL3.so.0.4.12 [.] 0x000000000005485a
0.38% crowd_perf libc.so.6 [.] malloc
0.38% crowd_perf libc.so.6 [.] 0x0000000000180625
0.38% crowd_perf libvulkan_radeon.so [.] 0x000000000033b85f
0.38% crowd_perf libvulkan_radeon.so [.] 0x00000000000ccd20
0.38% crowd_perf libvulkan_radeon.so [.] 0x00000000000c9899
0.38% crowd_perf libvulkan_radeon.so [.] 0x00000000001deb23
0.38% crowd_perf libvulkan_radeon.so [.] 0x00000000000284e5
0.38% crowd_perf libvulkan_radeon.so [.] 0x00000000001f41b7
0.37% crowd_perf libvulkan_radeon.so [.] 0x0000000000027cca
0.37% crowd_perf libSDL3.so.0.4.12 [.] 0x00000000002476b9
0.37% crowd_perf libvulkan_radeon.so [.] 0x00000000001f41d6
0.37% crowd_perf libc.so.6 [.] 0x000000000018a52e
0.37% crowd_perf crowd_perf [.] runtime::map_seed_from_map_data
0.36% crowd_perf libdbus-1.so.3.38.3 [.] _dbus_rmutex_lock
0.36% crowd_perf libvulkan_radeon.so [.] 0x00000000001e2e46
0.36% crowd_perf libvulkan_radeon.so [.] 0x000000000022e017
0.36% crowd_perf libvulkan_radeon.so [.] 0x00000000000b7b5f
0.35% crowd_perf libdbus-1.so.3.38.3 [.] _dbus_message_loader_queue_messages
0.35% crowd_perf libc.so.6 [.] pthread_rwlock_rdlock
0.35% crowd_perf libvulkan_radeon.so [.] 0x00000000000be015
0.35% crowd_perf libvulkan_radeon.so [.] 0x00000000000c49da
0.34% crowd_perf libc.so.6 [.] 0x000000000018a507
0.34% crowd_perf libwayland-client.so.0.25.0 [.] wl_display_dispatch_queue_pending
0.34% crowd_perf libvulkan_radeon.so [.] 0x0000000000197523
0.34% crowd_perf libvulkan_radeon.so [.] 0x00000000000ec905
0.34% crowd_perf libvulkan_radeon.so [.] 0x00000000000c4a56
0.34% crowd_perf libvulkan_radeon.so [.] 0x00000000000cb864
0.34% crowd_perf libdrm.so.2.134.0 [.] drmIoctl
0.34% crowd_perf crowd_perf [.] engine::character_clip
0.34% crowd_perf libvulkan_radeon.so [.] 0x000000000002d8f7
0.33% crowd_perf libvulkan_radeon.so [.] 0x000000000002f104
0.33% crowd_perf libc.so.6 [.] pthread_mutex_unlock
0.33% crowd_perf libSDL3.so.0.4.12 [.] 0x00000000000f7a2c
0.33% crowd_perf libvulkan_radeon.so [.] 0x00000000000b92b8
0.33% crowd_perf crowd_perf [.] engine::begin_frame
0.33% crowd_perf libSDL3.so.0.4.12 [.] 0x000000000023b8f4
0.33% crowd_perf libvulkan_radeon.so [.] 0x00000000000c2207
0.33% crowd_perf libvulkan_radeon.so [.] 0x000000000018c7d5
0.32% wl_cursor_surfa [unknown] [k] 0xffffffffa9e000ad
0.32% crowd_perf libSDL3.so.0.4.12 [.] 0x000000000024ab50
0.32% crowd_perf libvulkan_radeon.so [.] 0x0000000000022a03
0.32% crowd_perf libvulkan_radeon.so [.] 0x00000000000c9cd7
0.32% crowd_perf libvulkan_radeon.so [.] 0x0000000000116770
0.32% crowd_perf libvulkan_radeon.so [.] 0x0000000000182332
0.32% crowd_perf libvulkan_radeon.so [.] 0x00000000000ebd64
0.32% crowd_perf libvulkan_radeon.so [.] 0x00000000001f3c19
0.31% crowd_perf libc.so.6 [.] __errno_location
0.31% crowd_perf libc.so.6 [.] 0x00000000000a57e4
0.31% crowd_perf libc.so.6 [.] 0x000000000018a4b0
0.31% wl_cursor_surfa libwayland-client.so.0.25.0 [.] wl_display_dispatch_queue_pending
0.30% wl_cursor_surfa libc.so.6 [.] 0x00000000000a7491
0.30% crowd_perf libvulkan_radeon.so [.] 0x00000000001801f0
0.30% crowd_perf libwayland-client.so.0.25.0 [.] 0x00000000000039f1
0.30% crowd_perf libSDL3.so.0.4.12 [.] 0x000000000023c110
0.30% crowd_perf libvulkan_radeon.so [.] 0x00000000001dd301
0.30% crowd_perf libSDL3.so.0.4.12 [.] 0x00000000000176c9
0.30% crowd_perf libc.so.6 [.] 0x0000000000189c53
0.30% crowd_perf libc.so.6 [.] 0x0000000000180600
0.30% crowd_perf crowd_perf [.] __$hasher$$string
0.30% crowd_perf libvulkan_radeon.so [.] 0x00000000000c8b16
0.30% crowd_perf libvulkan_radeon.so [.] 0x00000000000cd419
0.30% crowd_perf libvulkan_radeon.so [.] 0x000000000021b321
0.30% crowd_perf libSDL3.so.0.4.12 [.] 0x000000000023b39a
0.30% crowd_perf libc.so.6 [.] 0x000000000018a4ea
0.30% crowd_perf libSDL3.so.0.4.12 [.] 0x000000000023be20
0.29% crowd_perf libvulkan_radeon.so [.] 0x0000000000027f50
0.29% crowd_perf libvulkan_radeon.so [.] 0x0000000000022e83
0.28% crowd_perf libvulkan_radeon.so [.] 0x00000000000d178c
0.28% crowd_perf libvulkan_radeon.so [.] 0x00000000000d6734
0.28% crowd_perf libvulkan_radeon.so [.] 0x00000000000bb103
0.28% crowd_perf libvulkan_radeon.so [.] 0x00000000000b70b2
0.28% crowd_perf libvulkan_radeon.so [.] 0x00000000000cc0c4
0.26% crowd_perf libSDL3.so.0.4.12 [.] 0x0000000000017694
0.26% crowd_perf libvulkan_radeon.so [.] 0x000000000017e9d4
0.26% crowd_perf libvulkan_radeon.so [.] 0x00000000000d64a8
0.26% crowd_perf libvulkan_radeon.so [.] 0x0000000000335bd4
0.26% crowd_perf libSDL3.so.0.4.12 [.] 0x0000000000243e20
0.26% wl_cursor_surfa libwayland-client.so.0.25.0 [.] 0x0000000000004951
0.25% crowd_perf libvulkan_radeon.so [.] 0x0000000000189397
0.25% crowd_perf libwayland-client.so.0.25.0 [.] 0x0000000000003147
0.24% crowd_perf libvulkan_radeon.so [.] 0x000000000068d01a
0.24% crowd_perf libvulkan_radeon.so [.] 0x00000000001e379c
0.24% crowd_perf libdrm.so.2.134.0 [.] drmSyncobjWait
0.23% crowd_perf [unknown] [k] 0xffffffffa9e000ca
0.23% crowd_perf libvulkan_radeon.so [.] 0x000000000028eca3
0.20% crowd_perf libvulkan_radeon.so [.] 0x000000000002d851
0.20% wl_cursor_surfa libc.so.6 [.] 0x00000000000a73ff
0.18% crowd_perf libxkbcommon.so.0.13.2 [.] xkb_keymap_key_get_syms_by_level
0.18% crowd_perf libvulkan_radeon.so [.] 0x00000000001dce94
0.10% crowd_perf libdbus-1.so.3.38.3 [.] _dbus_connection_unlock
0.08% wl_cursor_surfa libc.so.6 [.] 0x00000000000a6761
0.01% crowd_perf libc.so.6 [.] 0x0000000000189d47
0.00% wl_cursor_surfa libc.so.6 [.] 0x0000000000094084
0.00% wl_cursor_surfa libc.so.6 [.] 0x000000000018a507
0.00% wl_cursor_surfa libc.so.6 [.] pthread_mutex_lock
0.00% crowd_perf libnvidia-glcore.so.610.43.03 [.] 0x0000000000e922c9
0.00% crowd_perf [unknown] [k] 0xffffffffab35a5c1
0.00% crowd_perf libnvidia-glcore.so.610.43.03 [.] 0x0000000000fcd461
0.00% wl_cursor_surfa libSDL3.so.0.4.12 [.] 0x00000000001e3f74
0.00% wl_cursor_surfa libSDL3.so.0.4.12 [.] 0x00000000001cd7b4
0.00% crowd_perf libvulkan_radeon.so [.] 0x000000000017ff33
0.00% crowd_perf ld-linux-x86-64.so.2 [.] 0x000000000000af32
0.00% wl_cursor_surfa libwayland-client.so.0.25.0 [.] wl_display_read_events
0.00% crowd_perf libvulkan_radeon.so [.] 0x00000000000d65a4
0.00% crowd_perf libc.so.6 [.] vsnprintf
0.00% crowd_perf libc.so.6 [.] 0x0000000000184dd2
0.00% crowd_perf libvulkan_radeon.so [.] 0x0000000000193ec9
0.00% crowd_perf ld-linux-x86-64.so.2 [.] 0x00000000000147f8
0.00% crowd_perf libvulkan_radeon.so [.] 0x0000000000345adb
0.00% crowd_perf libc.so.6 [.] __ctype_init
0.00% crowd_perf libc.so.6 [.] 0x0000000000064834
0.00% crowd_perf libvulkan_radeon.so [.] 0x0000000000345ad6
0.00% crowd_perf libvulkan_radeon.so [.] 0x0000000000344ddb
0.00% crowd_perf ld-linux-x86-64.so.2 [.] 0x0000000000005d0b
0.00% crowd_perf libvulkan_radeon.so [.] 0x0000000000345b18
0.00% crowd_perf libvulkan_radeon.so [.] 0x0000000000345bb0
0.00% wl_cursor_surfa libSDL3.so.0.4.12 [.] 0x00000000001e3f76
0.00% crowd_perf libvulkan_radeon.so [.] 0x0000000000345ce7
0.00% crowd_perf ld-linux-x86-64.so.2 [.] 0x000000000000616e
0.00% crowd_perf libc.so.6 [.] 0x0000000000094040
0.00% crowd_perf libvulkan_radeon.so [.] 0x00000000001f3362
0.00% crowd_perf libc.so.6 [.] realloc
0.00% crowd_perf libvulkan_radeon.so [.] 0x0000000000345bb3
0.00% crowd_perf ld-linux-x86-64.so.2 [.] 0x000000000000ac94
0.00% crowd_perf libc.so.6 [.] pthread_setaffinity_np
0.00% crowd_perf libc.so.6 [.] 0x0000000000097417
0.00% crowd_perf libc.so.6 [.] 0x00000000000973bb
0.00% crowd_perf libdrm_amdgpu.so.1.134.0 [.] 0x00000000000078b0
0.00% crowd_perf [unknown] [k] 0xffffffffa9e01284
0.00% crowd_perf libc.so.6 [.] 0x0000000000103a40
0.00% crowd_perf ld-linux-x86-64.so.2 [.] 0x000000000001fcc4
0.00% crowd_perf libvulkan_radeon.so [.] 0x0000000000344ccb
0.00% crowd_perf libvulkan_radeon.so [.] 0x0000000000344cd4
0.00% wl_cursor_surfa libc.so.6 [.] ppoll
0.00% wl_cursor_surfa libc.so.6 [.] 0x0000000000094040
0.00% crowd_perf libc.so.6 [.] 0x000000000011bed0
0.00% crowd_perf [unknown] [k] 0xffffffffa9e01280
0.00% wl_cursor_surfa libc.so.6 [.] 0x0000000000094047
0.00% wl_cursor_surfa libc.so.6 [.] 0x0000000000094085
0.00% crowd_perf libc.so.6 [.] 0x00000000000973a0
0.00% crowd_perf libc.so.6 [.] 0x000000000011bed7
0.00% crowd_perf ld-linux-x86-64.so.2 [.] 0x0000000000002ede
0.00% crowd_perf ld-linux-x86-64.so.2 [.] 0x0000000000006167
0.00% crowd_perf ld-linux-x86-64.so.2 [.] 0x000000000000ac75
0.00% crowd_perf ld-linux-x86-64.so.2 [.] 0x000000000000b7f3
0.00% crowd_perf ld-linux-x86-64.so.2 [.] 0x000000000000b84a
0.00% crowd_perf ld-linux-x86-64.so.2 [.] 0x0000000000019a7d
0.00% crowd_perf ld-linux-x86-64.so.2 [.] 0x0000000000019a84
0.00% crowd_perf ld-linux-x86-64.so.2 [.] 0x000000000001f103
0.00% crowd_perf ld-linux-x86-64.so.2 [.] 0x000000000001fcc0
0.00% crowd_perf libc.so.6 [.] 0x000000000011bed2
0.00% crowd_perf libc.so.6 [.] 0x000000000011bed5
0.00% crowd_perf libdrm.so.2.134.0 [.] drmSyncobjImportSyncFile
0.00% crowd_perf libdrm_amdgpu.so.1.134.0 [.] amdgpu_device_initialize
0.00% crowd_perf libnvidia-glcore.so.610.43.03 [.] 0x0000000000fcd464
0.00% crowd_perf libnvidia-glcore.so.610.43.03 [.] 0x0000000000fcd46a
0.00% crowd_perf libvulkan_radeon.so [.] 0x0000000000344ce2
0.00% crowd_perf libvulkan_radeon.so [.] 0x0000000000344ddd
0.00% crowd_perf libz.so.1.3.2 [.] 0x0000000000003004
0.00% crowd_perf libz.so.1.3.2 [.] 0x000000000000300f
0.00% crowd_perf [unknown] [k] 0xffffffffa9e01670
0.00% wl_cursor_surfa libc.so.6 [.] recvmsg
#
# (Tip: To change sampling frequency to 100 Hz: perf record -F 100)
#
Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 62 KiB

+700
View File
@@ -0,0 +1,700 @@
# To display the perf.data header info, please use --header/--header-only options.
#
#
# Total Lost Samples: 0
#
# Samples: 469 of event 'cpu/cycles/Pu'
# Event count (approx.): 2304609671
#
# Overhead Command Shared Object Symbol
# ........ ............... ............................. ......................................................................................................................................................................
#
12.22% crowd_perf crowd_perf [.] engine::draw_sprite
|
---engine::draw_sprite
main::main
main
0x7fe9b3c27740
__libc_start_main
_start
5.51% crowd_perf crowd_perf [.] engine::to_clip
|
---engine::to_clip
engine::draw_sprite
main::main
main
0x7fe9b3c27740
__libc_start_main
_start
4.04% crowd_perf libc.so.6 [.] pthread_mutex_lock
|
---pthread_mutex_lock
|
|--0.81%--0x7fe9b424f559
| 0x7fe9b42545c5
| 0x7fe9b4069860
| engine::begin_frame
| main::main
| main
| 0x7fe9b3c27740
| __libc_start_main
| _start
|
|--0.79%--wl_display_prepare_read_queue
| wl_display_dispatch_queue_timeout
| 0x7fe9b228fead
| 0x7fe9b1d936f3
| 0x7fe9b1d97300
| 0x7fe9b1d8a4f8
| 0x7fe9b424ffc9
| engine::end_frame
| main::main
| main
| 0x7fe9b3c27740
| __libc_start_main
| _start
|
|--0.76%--0x7fe9b3e94a51
| 0x7fe9b4054fca
| 0x7fe9b4055bbd
| engine::events
| main::main
| main
| 0x7fe9b3c27740
| __libc_start_main
| _start
|
|--0.76%--wl_display_cancel_read
|
--0.52%--0x7fe9b3eab81f
0x7fe9b3e905f6
dbus_connection_dispatch
0x7fe9b4054fe2
0x7fe9b4055bbd
engine::events
main::main
main
0x7fe9b3c27740
__libc_start_main
_start
3.30% crowd_perf crowd_perf [.] engine::sprite_feet_quad
|
---engine::sprite_feet_quad
engine::draw_sprite
main::main
main
0x7fe9b3c27740
__libc_start_main
_start
3.12% crowd_perf crowd_perf [.] main::main
|
---main::main
main
0x7fe9b3c27740
__libc_start_main
_start
3.01% crowd_perf crowd_perf [.] engine::update_sprite
|
---engine::update_sprite
main::main
main
0x7fe9b3c27740
__libc_start_main
_start
2.34% crowd_perf crowd_perf [.] engine::world_to_screen
|
---engine::world_to_screen
engine::draw_sprite
main::main
main
0x7fe9b3c27740
__libc_start_main
_start
1.95% crowd_perf libvulkan_radeon.so [.] 0x00000000001f41d6
|
---0x7fe9b1df41d6
|
--1.18%--0x7fe9b1df44e5
0x7fe9b1c27d3c
0x7fe9b1c28070
0x7fe9b1c2d63c
|
--0.87%--0x7fe9b1cd16b8
0x7fe9b1cd2520
0x7fe9b1cd2d0a
0x7fe9b1de6325
0x7fe9b42546f1
0x7fe9b4069860
engine::begin_frame
main::main
main
0x7fe9b3c27740
__libc_start_main
_start
1.95% crowd_perf crowd_perf [.] runtime::default_hasher
|
---runtime::default_hasher
runtime::default_hasher_string
__$hasher$$string
|
|--1.28%--engine::character_clip
| engine::update_sprite
| main::main
| main
| 0x7fe9b3c27740
| __libc_start_main
| _start
|
--0.66%--engine::character_frame
engine::draw_sprite
main::main
main
0x7fe9b3c27740
__libc_start_main
_start
1.60% crowd_perf libc.so.6 [.] clock_gettime
|
---clock_gettime
|
--0.83%--wl_display_dispatch_queue_timeout
0x7fe9b228fead
0x7fe9b1d936f3
0x7fe9b1d97300
0x7fe9b1d8a4f8
0x7fe9b424ffc9
engine::end_frame
main::main
main
0x7fe9b3c27740
__libc_start_main
_start
1.50% crowd_perf crowd_perf [.] __$map_get$$map[string]engine::Clip_Def
|
---__$map_get$$map[string]engine::Clip_Def
|
|--0.79%--engine::character_frame
| engine::draw_sprite
| main::main
| main
| 0x7fe9b3c27740
| __libc_start_main
| _start
|
--0.71%--engine::character_clip
engine::update_sprite
main::main
main
0x7fe9b3c27740
__libc_start_main
_start
1.49% wl_cursor_surfa [unknown] [k] 0xffffffffab041cef
|
---0xffffffffab041cef
0xffffffffab355aba
0xffffffffa9e0012f
1.41% crowd_perf crowd_perf [.] engine::character_frame
|
---engine::character_frame
engine::draw_sprite
main::main
main
0x7fe9b3c27740
__libc_start_main
_start
1.28% crowd_perf [unknown] [k] 0xffffffffab355a81
|
---0xffffffffab355a81
0xffffffffa9e0012f
|
|--0.77%--ioctl
| drmIoctl
|
--0.51%--0x7fe9b3c9fff2
0x7fe9b3c9403b
0x7fe9b3c94083
__close
0x7fe9b1d8cacd
0x7fe9b1d893c8
0x7fe9b1d89241
0x7fe9b42544bd
0x7fe9b4069860
engine::begin_frame
main::main
main
0x7fe9b3c27740
__libc_start_main
_start
1.26% wl_cursor_surfa libwayland-client.so.0.25.0 [.] wl_display_read_events
1.25% crowd_perf libwayland-client.so.0.25.0 [.] wl_proxy_marshal_array_flags
|
---wl_proxy_marshal_array_flags
wl_proxy_marshal_flags
|
--0.83%--0x7fe9b1d97614
0x7fe9b1d8a4f8
0x7fe9b424ffc9
engine::end_frame
main::main
main
0x7fe9b3c27740
__libc_start_main
_start
1.22% crowd_perf crowd_perf [.] runtime::string_eq
|
---runtime::string_eq
__$map_get$$map[string]engine::Clip_Def
|
|--0.72%--engine::character_clip
| engine::update_sprite
| main::main
| main
| 0x7fe9b3c27740
| __libc_start_main
| _start
|
--0.50%--engine::character_frame
engine::draw_sprite
main::main
main
0x7fe9b3c27740
__libc_start_main
_start
1.22% crowd_perf libvulkan_radeon.so [.] 0x00000000000ce007
|
---0x7fe9b1cce007
0x7fe9b1dcae1a
0x7fe9b424437c
0x7fe9b406920d
engine::end_frame
main::main
1.17% crowd_perf crowd_perf [.] runtime::memory_equal
|
---runtime::memory_equal
runtime::string_eq
__$map_get$$map[string]engine::Clip_Def
|
--0.72%--engine::character_frame
engine::draw_sprite
main::main
main
0x7fe9b3c27740
__libc_start_main
_start
1.05% crowd_perf crowd_perf [.] engine::character_clip
|
---engine::character_clip
engine::update_sprite
main::main
main
0x7fe9b3c27740
__libc_start_main
_start
0.99% crowd_perf crowd_perf [.] runtime::bounds_check_error
|
---runtime::bounds_check_error
|
--0.54%--engine::texture_run_len
engine::end_frame
main::main
main
0x7fe9b3c27740
__libc_start_main
_start
0.95% crowd_perf libc.so.6 [.] 0x000000000018a4c4
|
---0x7fe9b3d8a4c4
|
--0.52%--engine::draw_sprite
main::main
main
0x7fe9b3c27740
__libc_start_main
_start
0.94% crowd_perf crowd_perf [.] engine::texture_run_len
|
---engine::texture_run_len
engine::end_frame
main::main
main
0x7fe9b3c27740
__libc_start_main
_start
0.93% crowd_perf libSDL3.so.0.4.12 [.] 0x00000000001428e7
|
---0x7fe9b41428e7
0.85% crowd_perf libc.so.6 [.] 0x00000000000a44b7
|
---0x7fe9b3ca44b7
0x7fe9b3ca6ed2
0.85% crowd_perf libc.so.6 [.] 0x000000000009ca5c
|
---0x7fe9b3c9ca5c
0.84% crowd_perf libzstd.so.1.5.7 [.] 0x0000000000092a16
|
---0x7fe9b0567a16
ZSTD_decompressSequences (inlined)
ZSTD_decompressBlock_internal
ZSTD_decompressBlock_internal
0x7fe9b0562e85
ZSTD_decompress_usingDDict
ZSTD_decompress
0x7fe9b1f5f8cf
0x7fe9b1f380f5
0x7fe9b1f3978d
0x7fe9b1f37a86
0x7fe9b1e05d1b
0x7fe9b1d28dcd
0x7fe9b1e0583a
0x7fe9b1e05d49
0x7fe9b1d28abb
0x7fe9b1d292b4
0x7fe9b1d2ab09
0.83% crowd_perf libSDL3.so.0.4.12 [.] 0x0000000000143ffe
|
---0x7fe9b4143ffe
0x7fe9b414a028
0x7fe9b4141b9a
0.83% crowd_perf libwayland-client.so.0.25.0 [.] 0x0000000000003263
|
---0x7fe9b433a263
wl_proxy_marshal_flags
0x7fe9b1d97937
0x7fe9b1d8a4f8
0x7fe9b424ffc9
engine::end_frame
main::main
main
0x7fe9b3c27740
__libc_start_main
_start
0.83% crowd_perf libvulkan_radeon.so [.] 0x000000000033b851
|
---0x7fe9b1f3b851
0x7fe9b1df41ce
0x7fe9b1df44e5
0x7fe9b1c27d3c
0x7fe9b1c28070
0x7fe9b1c2d63c
0.83% crowd_perf libSDL3.so.0.4.12 [.] 0x000000000014400b
|
---0x7fe9b414400b
0x7fe9b414a028
0x7fe9b4141b9a
0.82% crowd_perf crowd_perf [.] engine::frame_uvs
|
---engine::frame_uvs
engine::draw_sprite
main::main
main
0x7fe9b3c27740
__libc_start_main
_start
0.82% crowd_perf libSDL3.so.0.4.12 [.] 0x0000000000144008
|
---0x7fe9b4144008
0x7fe9b414a028
0x7fe9b4141b9a
0.81% crowd_perf libvulkan_radeon.so [.] 0x00000000000d2c2b
|
---0x7fe9b1cd2c2b
0x7fe9b1de6325
0x7fe9b42546f1
0x7fe9b4069860
engine::begin_frame
main::main
main
0x7fe9b3c27740
__libc_start_main
_start
0.79% crowd_perf libc.so.6 [.] pthread_rwlock_rdlock
|
---pthread_rwlock_rdlock
0.78% crowd_perf libSDL3.so.0.4.12 [.] 0x00000000001428a1
|
---0x7fe9b41428a1
0x7fe9b414921a
0x7fe9b4141b9a
0x7fe9b41426da
0.76% wl_cursor_surfa libc.so.6 [.] 0x00000000000a66e2
0.69% crowd_perf libc.so.6 [.] 0x000000000018a547
|
---0x7fe9b3d8a547
0x7fe9b1cd1554
0x7fe9b1de165e
0x7fe9b1ddee47
0x7fe9b1de35f1
0x7fe9b1de157e
0x7fe9b423be46
0x7fe9b4068a74
engine::end_frame
main::main
main
0x7fe9b3c27740
__libc_start_main
_start
0.67% crowd_perf libSDL3.so.0.4.12 [.] 0x000000000014288a
|
---0x7fe9b414288a
0x7fe9b41429b4
0x7fe9b414921a
0x7fe9b4141b9a
0x7fe9b41426da
0.61% crowd_perf libvulkan_radeon.so [.] 0x0000000000180ab2
|
---0x7fe9b1d80ab2
0.58% crowd_perf libvulkan_radeon.so [.] 0x0000000000114c26
|
---0x7fe9b1d14c26
0x7fe9b1d15976
0x7fe9b1d162ff
0x7fe9b1d1692c
0x7fe9b1c2bd93
0x7fe9b1c2da03
0x7fe9b1c2e4ca
0x7fe9b1c2f277
0x7fe9b1de052c
0x7fe9b1de1521
0x7fe9b4247581
0x7fe9b4064bab
engine::end_frame
main::main
0.52% crowd_perf libvulkan_radeon.so [.] 0x0000000000022daa
|
---0x7fe9b1c22daa
0x7fe9b1cd2f6f
0x7fe9b1de6325
0x7fe9b423bc48
0x7fe9b423beb3
0x7fe9b4068a74
engine::end_frame
main::main
main
0x7fe9b3c27740
__libc_start_main
_start
0.51% wl_cursor_surfa libc.so.6 [.] 0x00000000000a6747
0.49% crowd_perf libc.so.6 [.] 0x00000000001904ba
0.48% crowd_perf libc.so.6 [.] 0x000000000018a52e
0.48% crowd_perf libvulkan_radeon.so [.] 0x00000000000d17cc
0.47% crowd_perf libvulkan_radeon.so [.] 0x00000000001f3c6b
0.47% crowd_perf [vdso] [.] __vdso_clock_gettime
0.47% crowd_perf libvulkan_radeon.so [.] 0x00000000001d6365
0.46% crowd_perf libc.so.6 [.] 0x000000000018a486
0.46% crowd_perf libvulkan_radeon.so [.] 0x00000000001e4c30
0.46% crowd_perf libvulkan_radeon.so [.] 0x00000000001f3ff4
0.45% crowd_perf libdbus-1.so.3.38.3 [.] dbus_connection_get_dispatch_status
0.45% crowd_perf libc.so.6 [.] 0x0000000000093fe4
0.44% crowd_perf libvulkan_radeon.so [.] 0x000000000017e9d4
0.44% crowd_perf libvulkan_radeon.so [.] 0x0000000000114197
0.43% crowd_perf libnvidia-glcore.so.610.43.03 [.] 0x0000000000fcd46c
0.43% crowd_perf libvulkan_radeon.so [.] 0x00000000000ce927
0.43% crowd_perf crowd_perf [.] runtime::map_seed_from_map_data
0.43% crowd_perf libvulkan_radeon.so [.] 0x000000000033cb4a
0.42% crowd_perf libc.so.6 [.] 0x0000000000189c4b
0.42% crowd_perf libSDL3.so.0.4.12 [.] 0x0000000000018567
0.42% crowd_perf libc.so.6 [.] 0x0000000000189cca
0.41% crowd_perf libvulkan_radeon.so [.] 0x00000000001de7c1
0.41% crowd_perf libc.so.6 [.] 0x000000000009ca21
0.41% crowd_perf libSDL3.so.0.4.12 [.] 0x00000000000176c6
0.41% crowd_perf crowd_perf [.] __$hasher$$string
0.41% crowd_perf libxkbcommon.so.0.13.2 [.] 0x0000000000005fe7
0.40% crowd_perf libvulkan_radeon.so [.] 0x00000000000d2c23
0.40% crowd_perf libSDL3.so.0.4.12 [.] 0x00000000000185a0
0.40% crowd_perf libSDL3.so.0.4.12 [.] 0x000000000024f5ea
0.40% crowd_perf libvulkan_radeon.so [.] 0x00000000000e662b
0.40% crowd_perf libwayland-client.so.0.25.0 [.] 0x0000000000005555
0.40% crowd_perf libSDL3.so.0.4.12 [.] 0x00000000000541ae
0.40% crowd_perf libvulkan_radeon.so [.] 0x00000000000e6624
0.40% crowd_perf libvulkan_radeon.so [.] 0x000000000033e954
0.40% crowd_perf libvulkan_radeon.so [.] 0x00000000001ded02
0.40% crowd_perf [unknown] [k] 0xffffffffa9e000ab
0.39% crowd_perf libSDL3.so.0.4.12 [.] 0x0000000000054314
0.39% crowd_perf libvulkan_radeon.so [.] 0x00000000001f34c7
0.39% crowd_perf libSDL3.so.0.4.12 [.] 0x0000000000064bba
0.39% crowd_perf libvulkan_radeon.so [.] 0x00000000000ebaa4
0.39% crowd_perf libvulkan_radeon.so [.] 0x00000000000b8cb1
0.38% crowd_perf libc.so.6 [.] pthread_rwlock_wrlock
0.38% wl_cursor_surfa libSDL3.so.0.4.12 [.] 0x00000000001e3f76
0.38% crowd_perf libwayland-client.so.0.25.0 [.] 0x000000000000326a
0.38% crowd_perf libSDL3.so.0.4.12 [.] 0x0000000000064a44
0.37% crowd_perf libvulkan_radeon.so [.] 0x00000000000cf7ea
0.37% crowd_perf libvulkan_radeon.so [.] 0x00000000000c4a65
0.37% crowd_perf libSDL3.so.0.4.12 [.] 0x00000000002501f2
0.37% crowd_perf libSDL3.so.0.4.12 [.] 0x000000000024e78a
0.37% crowd_perf libvulkan_radeon.so [.] 0x000000000035f600
0.37% crowd_perf libvulkan_radeon.so [.] 0x00000000001f41b7
0.37% crowd_perf libvulkan_radeon.so [.] 0x00000000000bdd41
0.37% crowd_perf libvulkan_radeon.so [.] 0x000000000013c80b
0.37% crowd_perf libvulkan_radeon.so [.] 0x00000000001e61b8
0.37% crowd_perf libSDL3.so.0.4.12 [.] 0x000000000023bc49
0.37% crowd_perf libc.so.6 [.] 0x0000000000189cc4
0.36% crowd_perf libSDL3.so.0.4.12 [.] 0x0000000000247677
0.36% wl_cursor_surfa libc.so.6 [.] pthread_mutex_lock
0.36% crowd_perf libdbus-1.so.3.38.3 [.] 0x000000000003186f
0.36% crowd_perf libvulkan_radeon.so [.] 0x00000000000d19ec
0.36% crowd_perf libvulkan_radeon.so [.] 0x000000000017ff7a
0.36% crowd_perf libSDL3.so.0.4.12 [.] 0x0000000000053d7e
0.36% crowd_perf libc.so.6 [.] 0x000000000018a4ca
0.36% crowd_perf libSDL3.so.0.4.12 [.] 0x0000000000246f63
0.36% crowd_perf libvulkan_radeon.so [.] 0x00000000001e6473
0.35% crowd_perf libvulkan_radeon.so [.] 0x00000000000d6725
0.34% crowd_perf libc.so.6 [.] pthread_mutex_unlock
0.32% crowd_perf libSDL3.so.0.4.12 [.] 0x000000000024fc98
0.32% crowd_perf libvulkan_radeon.so [.] 0x00000000000cf078
0.32% crowd_perf libwayland-client.so.0.25.0 [.] 0x0000000000004dc1
0.31% wl_cursor_surfa libc.so.6 [.] 0x0000000000094047
0.31% crowd_perf crowd_perf [.] runtime::_append_elem
0.29% crowd_perf libvulkan_radeon.so [.] 0x0000000000022d80
0.28% crowd_perf libSDL3.so.0.4.12 [.] 0x000000000023b5cb
0.24% crowd_perf crowd_perf [.] runtime::append_elem:proc(array:^[dynamic]engine::Queued_Sprite,arg:engine::Queued_Sprite,loc:runtime::Source_Code_Location)->(n:int,err:runtime::Allocator_Error)
0.23% wl_cursor_surfa libwayland-client.so.0.25.0 [.] wl_list_insert
0.23% crowd_perf libc.so.6 [.] 0x00000000000a718d
0.19% crowd_perf libvulkan_radeon.so [.] 0x000000000033b80b
0.17% crowd_perf ld-linux-x86-64.so.2 [.] 0x000000000000ffcb
0.16% crowd_perf libc.so.6 [.] 0x0000000000189c40
0.09% crowd_perf libvulkan_radeon.so [.] 0x0000000000335bdd
0.02% crowd_perf libc.so.6 [.] 0x00000000000a4451
0.02% wl_cursor_surfa [unknown] [k] 0xffffffffa9e000ad
0.02% wl_cursor_surfa libc.so.6 [.] 0x00000000000a6ea4
0.00% crowd_perf libvulkan_radeon.so [.] 0x00000000000e5f87
0.00% crowd_perf libGLX_nvidia.so.610.43.03 [.] 0x00000000000ab456
0.00% crowd_perf libc.so.6 [.] 0x00000000000a6968
0.00% crowd_perf libvulkan_radeon.so [.] 0x00000000001e4d25
0.00% crowd_perf libc.so.6 [.] 0x000000000018325e
0.00% crowd_perf ld-linux-x86-64.so.2 [.] 0x00000000000144d1
0.00% crowd_perf libvulkan_radeon.so [.] 0x000000000018d5e9
0.00% wl_cursor_surfa libwayland-client.so.0.25.0 [.] wl_display_dispatch_queue_pending
0.00% wl_cursor_surfa libc.so.6 [.] 0x00000000000a44b7
0.00% crowd_perf libvulkan_radeon.so [.] 0x0000000000344db9
0.00% wl_cursor_surfa libc.so.6 [.] 0x00000000000a74c7
0.00% crowd_perf libc.so.6 [.] 0x0000000000185195
0.00% wl_cursor_surfa [unknown] [k] 0xffffffffab041d04
0.00% wl_cursor_surfa libffi.so.8.4.1 [.] 0x0000000000002807
0.00% crowd_perf libdrm.so.2.134.0 [.] drmSyncobjTimelineWait
0.00% crowd_perf libexpat.so.1.12.2 [.] 0x0000000000002ea9
0.00% crowd_perf libvulkan_radeon.so [.] 0x0000000000344c64
0.00% crowd_perf libc.so.6 [.] 0x00000000000a5735
0.00% crowd_perf libdbus-1.so.3.38.3 [.] _dbus_string_equal_c_str
0.00% wl_cursor_surfa libwayland-client.so.0.25.0 [.] wl_display_get_fd
0.00% crowd_perf libvulkan_radeon.so [.] 0x00000000001f2fa5
0.00% crowd_p:disk$0 libc.so.6 [.] 0x00000000000a590f
0.00% wl_cursor_surfa libc.so.6 [.] 0x00000000000a6732
0.00% crowd_perf ld-linux-x86-64.so.2 [.] 0x000000000002647b
0.00% crowd_perf libc.so.6 [.] realloc
0.00% crowd_perf libc.so.6 [.] 0x00000000000534a3
0.00% crowd_perf libc.so.6 [.] 0x00000000000649f8
0.00% crowd_perf libvulkan_radeon.so [.] 0x00000000001e4d24
0.00% crowd_perf libvulkan_radeon.so [.] 0x0000000000344ccb
0.00% wl_cursor_surfa libc.so.6 [.] 0x00000000000a66c4
0.00% wl_cursor_surfa libc.so.6 [.] 0x0000000000189cf7
0.00% crowd_perf libvulkan_radeon.so [.] 0x0000000000345b18
0.00% crowd_perf libc.so.6 [.] strchrnul@plt
0.00% crowd_perf libc.so.6 [.] 0x000000000008b802
0.00% crowd_perf [unknown] [k] 0xffffffffab355a39
0.00% crowd_perf [unknown] [k] 0xffffffffab355a10
0.00% wl_cursor_surfa libc.so.6 [.] ppoll
0.00% crowd_perf libvulkan_radeon.so [.] 0x0000000000345ad6
0.00% crowd_perf libvulkan_radeon.so [.] 0x0000000000345adb
0.00% crowd_perf libvulkan_radeon.so [.] 0x0000000000345ce7
0.00% crowd_perf ld-linux-x86-64.so.2 [.] 0x0000000000014464
0.00% wl_cursor_surfa libc.so.6 [.] recvmsg
0.00% crowd_perf libxcb.so.1.1.0 [.] 0x000000000000caad
0.00% crowd_perf libc.so.6 [.] __ctype_init
0.00% crowd_perf libc.so.6 [.] 0x00000000000a713d
0.00% crowd_perf [unknown] [k] 0xffffffffa9e01690
0.00% wl_cursor_surfa libSDL3.so.0.4.12 [.] 0x00000000001cd7b1
0.00% crowd_perf libc.so.6 [.] 0x0000000000097417
0.00% crowd_perf libc.so.6 [.] 0x0000000000103a63
0.00% crowd_perf libvulkan_radeon.so [.] 0x00000000001dd5e5
0.00% crowd_perf libdrm.so.2.134.0 [.] drmSyncobjWait
0.00% crowd_perf libexpat.so.1.12.2 [.] 0x000000000000b5db
0.00% crowd_perf libc.so.6 [.] __errno_location
0.00% crowd_p:disk$0 libc.so.6 [.] 0x000000000009404d
0.00% crowd_perf ld-linux-x86-64.so.2 [.] 0x000000000001fce9
0.00% crowd_perf libvulkan_radeon.so [.] 0x0000000000344ddd
0.00% crowd_perf libvulkan_radeon.so [.] 0x0000000000345bb3
0.00% crowd_perf [unknown] [k] 0xffffffffa9e01280
0.00% crowd_perf libvulkan_radeon.so [.] 0x0000000000344cd4
0.00% wl_cursor_surfa libc.so.6 [.] 0x0000000000094085
0.00% crowd_perf libc.so.6 [.] 0x000000000011bed0
0.00% crowd_perf libvulkan_radeon.so [.] 0x0000000000344ddb
0.00% wl_cursor_surfa libc.so.6 [.] 0x0000000000094040
0.00% crowd_perf libdrm.so.2.134.0 [.] drmIoctl
0.00% crowd_perf crowd_perf [.] runtime::multi_pointer_slice_expr_error
0.00% crowd_perf libc.so.6 [.] ioctl
0.00% crowd_perf libc.so.6 [.] 0x00000000000973a0
0.00% crowd_perf libc.so.6 [.] 0x0000000000103a40
0.00% crowd_perf [unknown] [k] 0xffffffffa9e01670
0.00% wl_cursor_surfa libc.so.6 [.] 0x0000000000094050
0.00% wl_cursor_surfa libc.so.6 [.] 0x0000000000094084
0.00% crowd_p:disk$0 libc.so.6 [.] 0x0000000000094047
0.00% crowd_p:disk$0 libc.so.6 [.] 0x0000000000094048
0.00% crowd_p:disk$0 libc.so.6 [.] 0x0000000000094049
0.00% crowd_perf crowd_perf [.] main
0.00% crowd_perf crowd_perf [.] memset@plt
0.00% crowd_perf ld-linux-x86-64.so.2 [.] 0x000000000001f103
0.00% crowd_perf libc.so.6 [.] __poll
0.00% crowd_perf libc.so.6 [.] malloc
0.00% crowd_perf libc.so.6 [.] 0x0000000000094047
0.00% crowd_perf libc.so.6 [.] 0x000000000009741b
0.00% crowd_perf libc.so.6 [.] 0x00000000000a710f
0.00% crowd_perf libdbus-1.so.3.38.3 [.] 0x0000000000032197
0.00% crowd_perf libexpat.so.1.12.2 [.] 0x0000000000021792
0.00% crowd_perf libexpat.so.1.12.2 [.] 0x000000000002179b
0.00% crowd_perf libexpat.so.1.12.2 [.] 0x00000000000217aa
0.00% crowd_perf libvulkan_radeon.so [.] 0x00000000001dd564
0.00% crowd_perf libvulkan_radeon.so [.] 0x00000000001dd592
0.00% crowd_perf libvulkan_radeon.so [.] 0x00000000001dd61c
0.00% crowd_perf libvulkan_radeon.so [.] 0x00000000003458a3
0.00% crowd_perf libxcb.so.1.1.0 [.] 0x000000000000c8db
0.00% crowd_perf libxcb.so.1.1.0 [.] 0x000000000000ce6f
0.00% crowd_perf [unknown] [k] 0xffffffffa9e015f0
0.00% wl_cursor_surfa libSDL3.so.0.4.12 [.] 0x00000000001e3f74
0.00% wl_cursor_surfa libc.so.6 [.] 0x0000000000094048
0.00% wl_cursor_surfa libc.so.6 [.] 0x0000000000094049
0.00% wl_cursor_surfa libc.so.6 [.] 0x0000000000094097
#
# (Tip: To show IPC for sampling periods use perf record -e '{cycles,instructions}:S' and then browse context)
#
Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 61 KiB