Skip to content

Migration Guide for PR 3256

halx99 edited this page Jul 30, 2026 · 1 revision

Migrating Custom Rendering Code to SceneRenderState

This change replaces the implicit global visiting-Camera model with explicit per-view render state.

Why this changed

A persistent Camera does not uniquely identify the View currently being rendered.

The same Camera may produce multiple View snapshots, including:

  • OpenXR left and right eyes
  • offscreen rendering
  • transition captures
  • custom projections
  • reflection or preview Views

SceneRenderState carries the resolved View for one traversal pass without mutating the Camera.

Custom Node::draw() overrides

Before

void MyNode::draw(ax::Renderer* renderer,
                  const ax::Mat4& transform,
                  uint32_t flags)
{
    const auto& vp =
        ax::Camera::getVisitingViewProjectionMatrix();

    ax::Mat4 mvp = vp * transform;
    // Configure and submit commands through renderer.
}

After

void MyNode::draw(const ax::SceneRenderState& state,
                  const ax::Mat4& transform,
                  uint32_t flags)
{
    const auto& vp = state.getViewProjectionMatrix();

    ax::Mat4 mvp = vp * transform;
    // Configure commands and submit through state.getRenderer().
}

Update the declaration as well:

void draw(const ax::SceneRenderState& state,
          const ax::Mat4& transform,
          uint32_t flags) override;

Custom Node::visit() overrides

Before

void MyNode::visit(ax::Renderer* renderer,
                   const ax::Mat4& parentTransform,
                   uint32_t parentFlags)
{
    uint32_t flags =
        processParentFlags(parentTransform, parentFlags);

    child->visit(renderer, transform, flags);
}

After

void MyNode::visit(const ax::SceneRenderState& state,
                   const ax::Mat4& parentTransform,
                   uint32_t parentFlags)
{
    uint32_t flags =
        processParentFlags(state, parentTransform, parentFlags);

    child->visit(state, transform, flags);
}

Use:

isVisitableByCamera(state.cameraFlag)

instead of the removed visiting-Camera helper.

Accessing current View data

Use SceneRenderState for all View-dependent mathematics:

state.getViewMatrix()
state.getProjectionMatrix()
state.getViewProjectionMatrix()
state.getView().position
state.getDepthInView(transform)

Use state.getCamera() only for persistent Camera metadata and configuration, such as:

  • camera flag
  • background brush
  • render target
  • camera depth
  • camera mode

Do not use state.getCamera() to obtain the current View, projection, observer position, or billboard orientation. An overridden View may differ from the persistent Camera.

Submitting render commands

Use the Renderer from the traversal state:

auto* renderer = state.getRenderer();
renderer->addCommand(&command);

Commands that execute later must receive the View snapshot:

command.init(globalZOrder,
             transform,
             flags,
             state.getView());

Do not store a pointer or reference to a stack-allocated SceneRenderState inside a delayed command.

Manual scene or node traversal

Before

node->visit(renderer,
            ax::Mat4::identity,
            ax::Node::FLAGS_TRANSFORM_DIRTY);

After

ax::SceneRenderState state(renderer, camera);

node->visit(state,
            ax::Mat4::identity,
            ax::Node::FLAGS_TRANSFORM_DIRTY);

For a custom View:

const auto view = ax::SceneViewData::fromMatrices(
    viewMatrix,
    projectionMatrix,
    viewPosition);

ax::SceneRenderState state(renderer, camera, view);
node->visit(state,
            ax::Mat4::identity,
            ax::Node::FLAGS_TRANSFORM_DIRTY);

Construct the snapshot after any render-pass setup that intentionally changes the Camera projection.

Camera background brushes

Custom background brushes must override the new state-based method.

Before

void drawBackground(ax::Camera* camera) override;

After

void drawBackground(
    const ax::SceneRenderState& state) override;

Use state.getRenderer() and the matrices in state.

Removed Camera APIs

The following implicit draw-context APIs were removed:

Camera::getVisitingCamera()
Camera::setVisitingCamera()
Camera::getVisitingViewProjectionMatrix()
Camera::setAdditionalProjection()

Replace visiting-Camera queries with SceneRenderState.

For a persistent projection change, build the final projection and call:

camera->setProjectionMatrix(projection);

For a temporary per-pass projection, create a custom SceneViewData rather than mutating the Camera.

Lua changes

The following Lua Camera bindings were removed:

Camera:getVisitingCamera()
Camera:setVisitingCamera()
Camera:getVisitingViewProjectionMatrix()
Camera:setAdditionalProjection()

Lua code should use explicit Camera references for persistent configuration. Internal per-pass View state is no longer exposed through global Camera state.

OpenXR and custom stereo rendering

Do not temporarily modify the Camera for each eye.

Build a resolved eye View instead:

const ax::Mat4 eyeToWorld =
    camera->getNodeToWorldTransform() * eyeToTracking;

const ax::Mat4 eyeView =
    eyeToWorld.getInversed();

const auto viewData =
    ax::SceneViewData::fromMatrices(
        eyeView,
        eyeProjection,
        eyePosition);

ax::SceneRenderState state(
    renderer,
    camera,
    viewData);

The persistent Camera remains unchanged while each eye receives its own immutable View snapshot.

Migration checklist

  • Update every overridden draw() and visit() signature.
  • Replace visiting-Camera globals with SceneRenderState.
  • Pass state recursively to child traversal.
  • Initialize delayed render commands with state.getView().
  • Use the View snapshot for billboards, depth sorting, culling, skyboxes, and projection.
  • Keep Camera* usage limited to persistent Camera metadata.
  • Update custom background-brush overrides.
  • Remove Lua calls to the deleted Camera APIs.

Clone this wiki locally