Skip to content

Extract render passes into RenderPass subclasses #77

Description

@brendancol

Follow-up to #73. The render graph framework (RenderPass, RenderGraph, CompiledGraph) landed in #76 but doesn't touch the actual render pipeline. This issue is about extracting the stages from render() and _update_frame() into concrete RenderPass subclasses.

Goal

Replace the hardcoded sequential pipeline in analysis/render.py and engine.py with render graph execution. Right now, adding or reordering passes means editing control flow by hand.

Passes to extract

Each pass wraps existing CUDA kernel calls. No new GPU code, just reorganizing.

PassCurrent locationInputsOutputs
GBufferPass_generate_perspective_rays() + optix.trace() (primary)scene, camera paramsprimary_rays, primary_hits, albedo, instance_ids, primitive_ids
ShadowPass_generate_shadow_rays_from_hits() + optix.trace() (occlusion)primary_rays, primary_hits, sun paramsshadow_hits
AOPassAO sample loop + _generate_ao_rays() + _accumulate_ao/gi()primary_rays, primary_hitsao_factor, gi_color
ReflectionPass_generate_reflection_rays() + optix.trace()primary_rays, primary_hits, instance_idsreflection_hits
ShadePass_shade_terrain()all ray buffers, colormap, overlayscolor, albedo
DenoisePassdenoise() from rtx.pycolor, albedo, normalsdenoised_color
EdgeOutlinePass_edge_outline()color, instance_idscolor (in-place)
EDLPass_edl()color, depthcolor (in-place)
BloomPass_bloom()colorcolor (in-place)
TonemapPass_tone_map_aces()colorcolor (in-place)

Implementation approach

  1. Start with render() (the offline path) since it's simpler — no accumulation loop, no particle splatting, no async readback.
  2. Pass classes go in rtxpy/render_passes.py.
  3. A build_default_graph() factory builds the same pipeline the current code runs.
  4. Add use_render_graph=False to render() so the graph path is opt-in while both paths coexist.
  5. Once the graph path matches existing output pixel-for-pixel, flip the default and remove the old code path.
  6. Adapt _update_frame() in the viewer to the same graph, with particle splatting and accumulation as viewer-specific passes.

Watch out for

  • In-place passes (EdgeOutline, EDL, Bloom, Tonemap) read and write the same buffer. The graph handles this (a pass can list a buffer in both inputs and outputs), but they have to run in the right order after shading.
  • AO accumulation runs multiple samples per frame with progressive accumulation in _update_frame(). Probably best modeled as a single AOPass that loops internally, not N separate pass executions.
  • External state — camera params, sun direction, colormap LUTs, overlay textures aren't graph buffers. Pass them via external_buffers or a config dict on the pass.
  • Double allocation_RenderBuffers currently handles allocation, the graph's AllocationPlan will replace it. During the opt-in period, can't have both allocating at once.

Done when

  • render(use_render_graph=True) produces identical output to the current path (pixel-level comparison)
  • explore() works with the graph path
  • A custom pass can be inserted into the default graph (e.g. a user post-process)
  • Disabling a pass via the graph (e.g. AO, denoise) matches disabling via render parameters

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions