Skip to content

Create a render pass directly and keep the builder for composition - #469

Open
botoddly wants to merge 4 commits into
mainfrom
worktree-render-pass-builder-struct
Open

Create a render pass directly and keep the builder for composition#469
botoddly wants to merge 4 commits into
mainfrom
worktree-render-pass-builder-struct

Conversation

@botoddly

@botoddly botoddly commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

Addresses the first of the three allocation sources in #468: new RenderPassBuilder(commandBuffer) allocated the builder plus the two List<> it holds, once per render pass per frame, and docs/render-pass-flow.md taught that as the pattern.

Rather than making the builder allocation-free, this separates the two jobs it was doing.

A direct method for the per-frame path

CommandBuffer.CreateRenderPass now takes the description directly, allocating nothing:

using IRenderPass pass = commandBuffer.CreateRenderPass(texture, ColorTargetSettings.Clear);

using IRenderPass pass = commandBuffer.CreateRenderPass(
    texture, ColorTargetSettings.Clear, depthBuffer, DepthBufferSettings.Default);

using IRenderPass pass = commandBuffer.CreateDepthOnlyRenderPass(depthBuffer, DepthBufferSettings.Default);

// several targets, from storage the caller owns
using IRenderPass pass = commandBuffer.CreateRenderPass(
    _gBufferTextures, _gBufferSettings, _depthBuffer, DepthBufferSettings.Default);

The general overload takes ReadOnlySpan<Texture> / ReadOnlySpan<ColorTargetSettings> instead of List<>, and validates its own arguments: one settings entry per color target, at most CommandBuffer.MaxColorTargets targets, and at least one color target or a depth buffer. MaxColorTargets is 8, the point at which SDL_BeginGPURenderPass itself rejects the pass (MAX_COLOR_TARGET_BINDINGS in SDL_sysgpu.h; SDL exposes no constant, and the prose in SDL_gpu.h still says four).

The builder stays a class, for composition

RenderPassBuilder keeps its shape and its List<> storage — no cap, no value-type surprises — and is documented as the option for a pass composed conditionally or from a varying number of targets, which allocates. Every per-frame caller now uses the direct method instead.

All 13 call sites in src/Pixely.Pencuil and tutorials/ moved over; nothing outside the builder's own tests constructs one any more. docs/render-pass-flow.md teaches CreateRenderPass and describes the builder as the composition option; docs/subrenderers.md shows the G-buffer case with caller-owned arrays.

Behaviour changes

  • IRenderPassBuilder is gone. It had no users outside its own file, and the builder's fluent methods now return the concrete type.
  • Mixing AddColorTarget(texture) and AddColorTarget(texture, settings) used to misalign the two lists silently — the settings given for the second target were applied to the first. The counts must now match, and Build() throws when they do not.

Verification

  • Full solution builds with 0 warnings; dotnet test Pixely.slnx green.
  • RenderPassBuilderTests covers the builder's four validation paths.
  • The Triangle, DepthOnly and StencilBuffer tutorials each run under SDL_VIDEODRIVER=offscreen without error, exercising the single-target, depth and depth-only overloads on a real device. Nothing asserts the rendered output.

Not in scope

The other two sources in #468 are untouched: Build() and CreateRenderPass still return a heap-allocated RenderPass, and CreateAndTrackTransferBuffer still creates a native transfer buffer per Update* call.

@botoddly botoddly changed the title Describe a render pass with a value type instead of allocating a builder Create a render pass directly and keep the builder for composition Sep 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant