Harbor

branch main
showing the latest snapshot on main
gpu_concepts.md 7.0 KB · Markdown
gpu_concepts.md 0644 Raw

No, that distinction is dead-on.

You’re not building:

gpu = Vulkan replacement

You’re building:

gpu = drawing orchestrator over Vulkan/DX/GL/etc.

So gpu’s job is not “be the OS GPU API.” It’s more like:

App/UI/Compositor/Engine
        ↓
gpu intent API
        ↓
Backend adapter
        ↓
Vulkan / D3D / GL / Metal / software

That’s much closer to Raylib, SDL Renderer, Skia, bgfx, WGPU, etc. — except your version is OS-foundational and IR-first instead of “call this function and hope the backend maps cleanly.”

The industry has pieces of this, but they’re all compromised:

Raylib: friendly, not OS foundation
SDL: window/input first, renderer is modest
Skia: excellent 2D, not general engine/render orchestration
bgfx: strong backend abstraction, still fairly render-API-shaped
WGPU: modern GPU abstraction, lower-level than your intent layer
Qt/GTK: UI toolkit first, not universal render substrate

Your niche is:

stable drawing intent layer
for UI + compositor + game/editor rendering
with backend lowering as implementation detail

That’s a good boundary.

gpu should know:

resources
passes
surfaces
draw intents
clips
transforms
materials
text/glyph streams maybe
presentation
backend capabilities

It should not know:

buttons
windows
game entities
scene hierarchy
business logic
desktop policies

So yeah: orchestrator is the right word. gpu is the conductor, not the instruments.

Correct. No ParticleSurface in gpu.

Particles should be a producer above gpu:

Particle System
    owns emitters, lifetimes, simulation, sorting, spawn rules
        ↓
emits draw intent
        ↓
gpu
        ↓
GPU backend

gpu should only see the reduced graphical result:

Draw_Billboards
Draw_Instanced_Quads
Draw_Mesh_Instances
Draw_Point_Sprites
Draw_Compute_Dispatch maybe
Draw_Texture_Atlas_Quads

Not:

Emitter
Burst
Gravity
Lifetime
Trail
Smoke
Firework
Blood splatter
Magic sparkle

Those are engine concepts.

The only slight caveat: gpu may need generic high-throughput primitives that particles can use well:

instance buffers
dynamic vertex streams
storage buffers
compute dispatch
indirect draws
sort keys
blend modes
depth modes

But those are still rendering concepts, not particle concepts.

So the boundary is:

Particle engine:
    “There are 40,000 smoke sprites here with these transforms/colors/UVs.”

gpu:
    “Cool, I’ll batch/lower/submit that efficiently.”

That keeps gpu clean. It becomes the universal rendering funnel, not a junk drawer for every cool visual system you build.

The funnel should look like ordered drawing intent + optional optimization hints, not “gpu asks the host what shapes it wants.”

Host pushes intent into gpu:

Host systems
    UI / particles / scene / compositor / debug
        ↓
submit draw packets
        ↓
gpu builds frame IR
        ↓
gpu sorts/batches/culls where safe
        ↓
backend lowers to GPU/software

So the host says:

draw these quads
draw this mesh
draw this text run
draw these instances
draw this surface

gpu should not say:

give me all buttons
give me all particles
give me all windows

That would make gpu too aware of the world above it.

The draw packet shape should be something like:

Draw :: struct {
	kind: Draw_Kind,
	target: Surface_Handle,
	layer: i32,
	sort_key: u64,
	bounds: Rect3,
	clip: Clip_Handle,
	transform: Mat4,
	material: Material_Handle,
	geometry: Geometry_View,
	instances: Instance_View,
	flags: Draw_Flags,
}

The host provides:

what to draw
where it draws
what material/blend/depth behavior it needs
rough bounds
ordering constraints

gpu handles:

backend capability selection
pipeline selection
batching
resource transitions
uploads
descriptor/binding layout
command buffer generation
simple culling
clip/scissor lowering
software fallback routing

On overlap/occlusion: split it.

The host owns semantic occlusion.

gpu owns render-mechanical occlusion.

For example, host/UI/compositor owns:

window A is above window B
modal dialog blocks clicks behind it
collapsed panel hides children
invisible widget should not emit draw packets
offscreen UI subtree should not emit
game object outside camera may be skipped
portal/room visibility
LOD selection
particle emitter disabled

gpu owns:

scissor clipping
clip rect stacks
depth test setup
stencil/clip masks if requested
front-to-back sorting where legal
batch sorting where legal
discarding draws outside render target
occlusion query support maybe
tile/software dirty region clipping

The golden rule:

If deciding visibility requires knowing meaning, host handles it.
If deciding visibility only requires geometry/render state, gpu may handle it.

So for UI:

UI runtime:
    computes layout
    knows z-order
    knows scroll regions
    emits only visible-ish boxes

gpu:
    applies scissors/clips
    batches quads/text
    drops things outside target

For desktop compositor:

Compositor:
    knows window stacking
    knows damaged regions
    knows minimized/hidden windows
    knows input focus

gpu:
    composites surfaces
    clips to damage/scissor
    handles opacity/transforms

For 3D:

Scene renderer:
    knows camera, lights, spatial tree, portals, LOD

gpu:
    receives visible draw list
    sorts by pass/material/depth rules
    lowers to backend

I’d make the funnel frame-based:

frame := gpu.begin_frame(device)

target := gpu.begin_target(frame, swapchain_surface)

gpu.push_layer(frame, .UI, sort_base = 10_000)
gpu.draw_rect(frame, ...)
gpu.draw_text(frame, ...)
gpu.pop_layer(frame)

gpu.push_layer(frame, .World, sort_base = 0)
gpu.draw_mesh(frame, ...)
gpu.draw_instances(frame, ...)
gpu.pop_layer(frame)

gpu.end_target(frame, target)
gpu.submit(frame)

Internally that becomes:

FrameIR
 ├── Targets
 ├── Passes
 ├── Draw packets
 ├── Uploads
 ├── Resources used
 ├── Clips
 ├── Sort constraints
 └── Presentation

Ordering should be explicit but flexible:

strict order:
    preserve exactly; needed for UI painter's algorithm

layered order:
    preserve between layers, sort inside layer if allowed

free order:
    gpu may reorder for batching/performance

depth order:
    gpu may sort based on depth/material rules

So a draw packet might include:

Order_Mode :: enum {
	Strict,
	Layered,
	Sortable,
	Depth,
}

UI mostly uses:

Strict / Layered

3D uses:

Depth / Sortable

Particles often use:

Depth sorted for alpha
or Sortable for additive effects

The cleanest mental model:

Host builds the truth.
gpu builds the frame.
Backend builds the commands.
GPU builds the pixels.

Don’t let gpu ask for shapes. Let every system pour already-decided drawing intent into the funnel. gpu’s superpower is making all those unrelated producers become one coherent, optimized frame.