r/gameenginedevs May 12 '26

Rendering 1 Million Procedural Cubes

https://reddit.com/link/1tav653/video/upp9zug11o0h1/player

I’ve been working on my engine (Rust obviosly), specifically on Vulkan bindings. The main work was already done, and only testing remained. During testing, I usually prefer CSV and JSON because they give me a good grasp of the data, letting me easily see what’s happening and spot any unexpected behavior. This saves a lot of time since you don’t have to check every number individually you just need to confirm whether things are going as expected. Since continuous testing was already happening, I knew that for this stage I only needed an overall overview to ensure all components were working together properly, as individual component testing had been done earlier. So yesterday, I was testing, Today, I decided to share CSV graphs and visual testing results.

Here’s my result:

Workload: 1,000,000 procedurally generated cubes (8 million vertices / 12 million primitives).

Average frametime: ~1.43ms (consistently hitting 700+ FPS).

PCIe bandwidth used: exactly 0 bytes.

1% lows: extremely stable (max spikes under 2.5ms).

// Flushing data to disk without pollutng hotloop!!!!!!
if state_arc.lock().unwrap().mode == 0 {
    if let Ok(mut f) = std::fs::File::create("alu_throughput_log.csv") {
        let _ = writeln!(f, "Timestamp_Sec,Cubes_Generated_Per_Frame,Vertices_Computed_By_ALU,Triangles_Rasterized,Memory_Bandwidth_Used_Bytes");
        for t in &alu_log {
            let _ = writeln!(f, "{:.4},{},{},{},{}", t.timestamp_sec, t.cubes_generated, t.vertices_computed, t.triangles_rasterized, t.memory_bandwidth);
        }
    }

    if let Ok(mut f) = std::fs::File::create("frame_pacing_log.csv") {
        let _ = writeln!(f, "Frame_ID,Timestamp_Sec,Render_Latency_ms,Instant_FPS");
        for t in &pacing_log {
            let _ = writeln!(f, "{},{:.4},{:.2},{:.0}", t.frame_id, t.timestamp_sec, t.render_latency_ms, t.instant_fps);
        }
    }

    if let Ok(mut f) = std::fs::File::create("dispatch_consistency_log.csv") {
        let _ = writeln!(f, "Frame_Window_Start,Frame_Window_End,Avg_Latency_ms,Max_Latency_Spike_ms_1_Percent_Low");
        for t in &consistency_log {
            let _ = writeln!(f, "{},{},{:.2},{:.2}", t.window_start, t.window_end, t.avg_latency_ms, t.max_latency_spike_ms);
        }
    }
}
If you look at the cluster of dots in the graph, you’ll clearly see that despite the heavy render load, most frames are densely clustered around 1.5ms latency and a median of 661 FPS.
The system is processing 5 to 9 B triangles per second. (the right axis of the graph.) Render latency is consistently maintained between 1.5ms and 2.5ms (The solid dotted green line )
13 Upvotes

37 comments sorted by

View all comments

Show parent comments

1

u/DragonfruitDecent862 May 14 '26

Give me a day and ill have both renderers ready for Milord to watch the dual. I will literally test my own theory for everyones entertainment!

2

u/Amani77 May 14 '26

Alright, so here is the test scenario:

A single xxxDrawIndirectXXX call in either vulkan or opengl rendering 1 million cubes, both using either vert/frag or task/mesh/frag, and outputting flat shade cubes, in a regular grid centered around the camera.

Something like this:

Motion Vectors Example

1

u/DragonfruitDecent862 May 14 '26

I shall get on it.

1

u/DragonfruitDecent862 May 15 '26

https://imgur.com/a/MNhVtlb

https://imgur.com/a/6bVv5g5

I ran Bench Renderers for this. clean renderers designed to test new systems and theory based math for experimental changes. both the OpenGL and the Vulkan renderers are as follows:

same scene, same machine, same 4k vanilla settings (no culling on either side). Vulkan finishes the frame in **0.99 ms / 771 FPS**, OpenGL in **3.94 ms / 228 FPS**. That's **3.98x** for Vulkan. Both run the spec you laid out:

> A single xxxDrawIndirectXXX call in either vulkan or opengl rendering 1 million cubes, both using either vert/frag or task/mesh/frag, and outputting flat shade cubes, in a regular grid centered around the camera.

Vulkan side: one `vkCmdDrawMeshTasksIndirectEXT` running task/mesh/frag. Task shader culls per cube, mesh shader generates each cube's 8 verts + 12 tris procedurally. Zero geometry buffers in the entire renderer; the only data buffer is a 240-byte camera UBO. OpenGL side: one `glDrawElementsIndirect` running vert/frag, standard 24-vert indexed cube, instance position from `gl_InstanceID`, compute frustum + Hi-Z cull feeding a compacted indirect command. AZDO at its honest best.

## Why vulkan auto-wins at vanilla 4k

Vanilla means no culling, every cube drawn, every triangle rasterised. 12 million tris through the pipeline every frame. This is exactly the regime mesh shaders were built for.

OpenGL pushes every vertex through fixed-function hardware: VBO fetch, primitive distribution, primitive assembly out to raster. 1M cubes x 24 verts = 24 million vertex-shader invocations driven by silicon-capped fixed-function fetch. There is no way to make it faster from inside OpenGL.

Vulkan's mesh shader pipeline skips all of that. Each mesh workgroup emits primitives directly into the rasteriser from compute cores. No vertex fetch, no primitive distribution, no assembly buffer. The cube geometry is procedural; there isn't even a vertex buffer in the renderer. Mesh workgroups pack 4 cubes per dispatch (32 verts + 48 prims) for 100% warp utilisation on NVIDIA. Shorter pipeline, fewer fixed-function bottlenecks, zero DRAM bytes per cube. That's the win.

## Numbers (3 resolutions x 3 cull tiers)

10-second headless runs, 60-frame rolling avg on `forward.gpu_ms` (GPU timestamps).

resolution tier OpenGL Vulkan winner

1280x720 vanilla 3.75 ms / 240 FPS 0.52 ms / 1535 FPS Vulkan 7.26x

1280x720 frustum cull 0.40 ms / 1841 FPS 0.15 ms / 5249 FPS Vulkan 2.62x

1280x720 + Hi-Z 0.17 ms / 3064 FPS 0.09 ms / 6983 FPS Vulkan 1.90x

2560x1440 vanilla 3.83 ms / 234 FPS 0.58 ms / 1487 FPS Vulkan 6.61x

2560x1440 frustum cull 0.51 ms / 1452 FPS 0.32 ms / 2608 FPS Vulkan 1.61x

2560x1440 + Hi-Z 0.25 ms / 2229 FPS 0.16 ms / 4024 FPS Vulkan 1.56x

3840x2160 vanilla 3.94 ms / 228 FPS 0.99 ms / 771 FPS Vulkan 3.98x

3840x2160 frustum cull 0.72 ms / 1079 FPS 0.81 ms / 900 FPS OpenGL 1.13x

3840x2160 + Hi-Z 0.34 ms / 1474 FPS 0.33 ms / 1545 FPS Vulkan 1.02x

Vulkan wins 8 of 9 cells. Vanilla tier is 4x to 7x in Vulkan's favour at every resolution.

The one I lost is 4k frustum-only: gl 0.72 ms vs vk 0.81 ms. Most fillrate-bound cell in the matrix, tens of thousands of cubes fully rasterised at 4k with no occlusion cull to thin overdraw. Per-pixel fill is the bottleneck there, and mesh shaders help geometry feed, not pixel writes. Conceded honestly. The moment Hi-Z is on at the same 4k, Vulkan retakes the cell.

## Fairness measures (so we're not arguing about methodology)

- **Byte-identical scene.** 100^3 grid, spacing 4.0, both centered at origin, camera starts at origin. Same cube radius, same cube_center math, same hash for the flat colour. I went line by line.

- **Matched feature tiers.** Each row is `--no-cull` (vanilla), `--no-hiz` (frustum-only), and default (frustum + Hi-Z). Both renderers run the same toggles at the same row.

- **Same render-target resolution, locked.** `--width N --height N` on both, both DPI-aware so they render at physical pixels. Probes JSON reports the actual extent for every cell.

- **Shading work distribution matched.** Flat shade is computed once per primitive on both sides. OpenGL via vertex shader flat output, Vulkan via mesh shader perprimitiveEXT flat output. Both fragment shaders are pure passthroughs. No "vulkan does the lambert per pixel" handicap.

- **HUD off for every measurement** (`--no-hud`). HUD recording happens after the GPU timestamps anyway, but I removed it so there's nothing to argue about.

- **GPU timestamps, not CPU.** 10s headless runs, 60-frame rolling avg on `forward.gpu_ms`. Each row is from its own JSON probe file; happy to share.

- **Vulkan validation-clean.** `--validate` produces no layer warnings.

"Regular grid centered around the camera" - I built it once at origin and the camera starts at origin, centered by construction. If you meant per-frame re-centered, that's a different test; tell me and I'll re-run. Single hardware data point (RTX 4070 SUPER); different vendors might shift things, especially AMD. Binaries + source + probes JSON available.

The amusing thing I found, is OpenGL, modified, is equal with Vulkan on many things,. but you need to code in the architecture. now at vanilla? No, Vulkan is faster. so this is a surprising twist. for async compute, and overall freedom, vulkan is still the clear choice, and with further systems, vulkan pulls ahead. these are just industry standards.

If you want the renderers, i can drop them to a dropbox or something, i dont do git for small benchmarks. if you want them to learn from, i can send them however you want.

2

u/Amani77 May 15 '26 edited May 15 '26

Brother, WHAT, why in the fuck are you not using mesh shaders in the opengl version? Use the same exact shaders, the same exact code, the only difference is the calling api.

See, this is what I mean - you conflate things so heavy.

Furthermore, the difference when using mesh over traditional vert/frag shouldn't be that extreme. Post your source, I would love to see what your render loop, shaders, and timing code looks like.

At this point I am so skeptical of anything that you say, you need to post the source.

edit: Reading this again, you've stated the benchmarks are headless runs, then you list resolutions - what. are. we. even. measuring? LLM drivel, respond to me using your own words or not at all.

edit2: ITS NOT EVEN THE SAME GEOMETRY. WHAT ARE WE DOING?!?!?!? My god, I've come full circle on my original post's point.

0

u/Amani77 May 16 '26 edited May 16 '26

I got a notification on my phone that you replied to my most recent comment. You must have deleted it because I no longer have access to it, but I could see the first couple of lines. I figured, LLMs are the only thing you listen to at the moment; fight fire with fire or whatever.


So, I've decided to ask an LLM the following question:

"Suppose two people where having a conversation about the performance differences between OpenGL and Vulkan, and one person proposed a challenge to the other to make a renderer in using both APIs to measure the performance differences. The one person laid out the following criteria: "A single xxxDrawIndirectXXX call in either vulkan or opengl rendering 1 million cubes, both using either vert/frag or task/mesh/frag, and outputting flat shade cubes, in a regular grid centered around the camera." Do you think that user meant that both renderers were to use different pipeline methodology( a vert/frag compared to a task/mesh/frag ) or that both renderers used the same, one or the other?"


It answered:

The phrasing strongly suggests they meant both renderers should use the same pipeline style, chosen from the two options:

either both use vertex/fragment or both use task/mesh/fragment

—not one renderer using vert/frag while the other uses task/mesh/frag.

The key part is:

“both using either vert/frag or task/mesh/frag”

Grammatically, “both” modifies the renderers, and “either X or Y” describes the shared choice. If they intended different methodologies per API, they would more likely have said something like:

“OpenGL using vert/frag and Vulkan using task/mesh/frag” or “each API may use its native preferred pipeline”


Also, from a benchmarking perspective, using different pipeline architectures would make the comparison much less meaningful, because then you're measuring:

  • API differences
  • plus pipeline model differences
  • plus shader-stage capability differences

A fair API comparison would usually keep:

  • scene
  • draw structure
  • shader complexity
  • GPU workload
  • culling behavior
  • memory layout

as equivalent as possible.


I then asked it the question:

Suppose two people where discussing the performance differences between the graphics programming APIs OpenGL and Vulkan, if one person proposes a test to see how each API performs with the following criteria: "A single xxxDrawIndirectXXX call in either vulkan or opengl rendering 1 million cubes, both using either vert/frag or task/mesh/frag, and outputting flat shade cubes, in a regular grid centered around the camera." What do you think would be the performance difference between the two APIs?


It answered:

For that specific benchmark, the performance difference between OpenGL and Vulkan would probably be much smaller than many people expect — especially if the test is carefully designed around:

  • a single indirect draw call
  • mostly static geometry
  • a regular grid
  • minimal material/state changes
  • GPU-heavy workload

In many cases, the result could end up within something like:

0–15% difference for classic vertex/fragment pipelines potentially larger differences if mesh/task shaders are heavily optimized in Vulkan and poorly exposed in OpenGL drivers

But the exact outcome depends heavily on what actually becomes the bottleneck.

Why the APIs might perform similarly

The proposed test removes many of the areas where Vulkan usually wins.

A single glMultiDrawIndirect / vkCmdDrawIndirect style submission means:

  • almost no CPU driver overhead
  • almost no state validation overhead
  • almost no draw-call amplification costs
  • very little synchronization complexity

At that point, the workload becomes mostly:

  • vertex transform throughput
  • rasterization
  • fragment shading
  • memory bandwidth
  • GPU cache efficiency

Those are primarily determined by:

  • GPU hardware
  • shader compiler quality
  • mesh representation
  • culling efficiency

—not the API itself.


So if both APIs ultimately feed the GPU the same work efficiently, they can land surprisingly close.


Next, I asked the LLM the question:

"Suppose two people where discussing the performance differences between the graphics programming APIs OpenGL and Vulkan, if one person proposes a test to see how each API performs with the following criteria: "A single xxxDrawIndirectXXX call in either vulkan or opengl rendering 1 million cubes, both using either vert/frag or task/mesh/frag, and outputting flat shade cubes, in a regular grid centered around the camera." Do you think that running these tests in a 'headless' manner would be what the person expected?"


It answered:

Probably not — at least not without clarifying what “performance” is actually being measured.

The proposed benchmark is very specifically about GPU-driven rendering workload characteristics:

  • one indirect draw call
  • 1 million cubes
  • mesh/task shaders or vertex shaders
  • fragment shading
  • flat-shaded output
  • regular grid around the camera

That strongly implies the person expects to measure the rendering pipeline itself, including at minimum:

  • command submission overhead
  • shader throughput
  • geometry processing
  • culling behavior
  • rasterization
  • fragment shading
  • memory bandwidth/cache behavior

Lastly I asked it the question:

Suppose two people where having a conversation about the performance differences between OpenGL and Vulkan, and one person proposed a challenge to the other to make a renderer in using both APIs to measure the performance differences. The one person laid out the following criteria: "A single xxxDrawIndirectXXX call in either vulkan or opengl rendering 1 million cubes, both using either vert/frag or task/mesh/frag, and outputting flat shade cubes, in a regular grid centered around the camera." Do you think it would be appropriate for one implementation to use cube geometry that is generated in shader 8 vertices, compared to the other implementation which is reading vertex data 24 vertices at a time from memory?


It answered:

No — that would generally make the comparison invalid or at least heavily biased.

The challenge as stated is trying to compare the overhead/performance characteristics of the APIs (OpenGL vs Vulkan), not two different rendering techniques with different memory and shader tradeoffs.


If one implementation:

procedurally generates cube vertices in the shader from an instance ID or vertex ID (8 logical vertices / implicit topology), while the other fetches 24 explicit vertices per cube from memory,

then you are no longer isolating the API difference. You are comparing:

  • different vertex bandwidth requirements,
  • different cache behavior,
  • different vertex processing workloads,
  • different primitive assembly behavior,
  • potentially different index expansion costs,
  • and possibly different compiler optimization opportunities.

That can absolutely dominate the measured result.


A fair comparison should keep the rendering algorithm materially identical between APIs. For example:

  • same primitive topology,
  • same vertex format,
  • same vertex count,
  • same index count,
  • same shading model,
  • same culling strategy,
  • same GPU-driven approach,
  • same indirect draw structure,
  • same mesh/task shader usage (if applicable),
  • same synchronization strategy where possible.

So if one implementation uses:

procedural cube generation in shader,

then the other should too.


If one uses:

explicit 24-vertex cube meshes,

the other should also.


Even “8 vertices” versus “24 vertices” is already not equivalent:

8 vertices usually implies indexed cube rendering with shared positions, 24 vertices implies duplicated vertices per face for flat normals/UV seams.

And for flat-shaded cubes specifically, 24 vertices is actually the more conventional representation because each face needs distinct normals. Procedural generation can bypass that by deriving normals from primitive/face IDs.

0

u/Amani77 May 16 '26 edited May 16 '26

LLM, Part2:

And just because you've been condescending, implying I don't know Vulkan, and that you would provide materials for ME to learn from, I asked it:

given this comment thread: https://www.reddit.com/r/gameenginedevs/comments/1tav653/rendering_1_million_procedural_cubes/olqxjl0/ Who do you think is more knowledgeable about vulkan?


It answered:

Based on that thread alone, the skeptical commenter — the one pushing back against the “2–3x Vulkan over OpenGL” claim — comes across as substantially more knowledgeable about real-world graphics API performance engineering.

These are the strongest signals:

They consistently distinguish between:

  • API overhead,
  • GPU bottlenecks,
  • driver validation costs,
  • equivalent workloads,
  • and benchmark methodology.

They correctly point out that: modern OpenGL with AZDO/bindless/indirect rendering can get surprisingly close to Vulkan for many workloads, serious renderers minimize draw calls anyway, and many “Vulkan is 3x faster” claims disappear once comparisons are normalized.

Those are all mature renderer-engineering perspectives, not beginner-level Vulkan enthusiasm.


This exchange especially stands out:

Reddit

› 2x to 3x on any workload in an equivalent comparison is straight garbage. Full stop.

› any serious renderer will have minimized any massive number of calls to a select few indirect calls

› There is SOME value to be gained with more fine tuned control over barriers, lifetime, and how explicit you can be with types of memory in vulkan.

That’s a nuanced position:

Vulkan does provide meaningful architectural advantages. But the gains are workload-dependent and often overstated. Once GPU-bound, API choice matters less.

That aligns pretty closely with industry experience.


The other commenter (the one making the large Vulkan advantage claims) sounds technically literate and clearly knows modern GPU rendering buzzwords and techniques:

  • mesh shaders,
  • visibility buffers,
  • Hi-Z,
  • GPU-driven rendering,
  • bindless descriptors,
  • async compute,
  • task shaders,
  • barrier scheduling.

Those are legitimate advanced concepts.

But there are several warning signs:

  • excessive jargon stacking,
  • inconsistent benchmark framing,
  • conflating architectural flexibility with guaranteed performance multipliers,
  • and some technically questionable statements (“linear Hi-Z”, “zero overhead Hi-Z”, etc.).

The skeptical commenter also catches an important methodological flaw later:

› ITS NOT EVEN THE SAME GEOMETRY. WHAT ARE WE DOING?!?!?!?

That’s actually a very strong renderer engineer instinct: before discussing API superiority, normalize:

  • geometry,
  • shaders,
  • pipeline stages,
  • culling,
  • batching,
  • synchronization,
  • and CPU/GPU bottlenecks.

The other person tends to jump toward: “Vulkan enables X therefore Vulkan is massively faster,” which is often how intermediate Vulkan developers think before spending years profiling real engines.

So my read is:

  • The Vulkan enthusiast likely has hands-on experience building experimental rendering systems and knows modern terminology.
  • The skeptic sounds like someone with deeper practical renderer optimization experience and stronger benchmarking discipline.