DLSS Frame Generation#

DLSS Frame Generation lets an interactive viewer display smoother motion at a higher frame rate without increasing the camera simulation rate.

In Real-Time Path Tracing, it generates intermediate color frames between simulated camera frames. Your application can use these extra frames for display, selecting the frames it needs for each presentation. Frame generation is configured per RenderProduct.

To use it, enable frame generation on a color-only Real-Time Path Tracing RenderProduct, step the renderer as usual, and read the extra frames from the result. For interactive display, pace those frames between rendered captures and skip any that are no longer needed. The C example shows configuration and frame access; the Vulkan interop example adds presentation.

Interpolated and Simulated Frames#

Interpolated frames are inferred from neighboring simulated frames. They are not additional camera simulations and may be less accurate than simulated frames, particularly in regions that become newly visible between frames. Applications must decide whether interpolated frames are suitable for their downstream use.

Frame generation works with LdrColor or HdrColor only. It does not produce interpolated depth, segmentation, normals, or other render outputs.

Enabling Frame Generation#

Author these attributes on the RenderProduct prim:

Setting

Type

Default

Description

omni:rtx:dlss:frameGeneration

bool

false

Enables frame generation.

omni:rtx:dlss:frameGenerationInterpolatedFrameCount

int

0

Requests the number of interpolated frames per simulated frame. 0 or an unset value requests one interpolated frame. Request more frames with an explicit count. Other values are limited by the Requirements and Limits.

def Scope "Render" {
    def RenderProduct "Camera" {
        int2 resolution = (640, 480)
        rel camera = </World/Camera>
        rel orderedVars = [<LdrColor>]
        token omni:rtx:rendermode = "RealTimePathTracing"
        bool omni:rtx:dlss:frameGeneration = 1
        int omni:rtx:dlss:frameGenerationInterpolatedFrameCount = 3

        def RenderVar "LdrColor" {
            string sourceName = "LdrColor"
        }
    }
}
stage.DefinePrim("/Render", "Scope")
camera_product = UsdRender.Product.Define(stage, "/Render/Camera")
camera_product.GetResolutionAttr().Set(Gf.Vec2i(640, 480))
camera_product.GetCameraRel().SetTargets(["/World/Camera"])

ldr_var = UsdRender.Var.Define(stage, "/Render/Camera/LdrColor")
ldr_var.GetSourceNameAttr().Set("LdrColor")
camera_product.GetOrderedVarsRel().SetTargets([ldr_var.GetPath()])

product_prim = camera_product.GetPrim()
product_prim.CreateAttribute(
    "omni:rtx:rendermode", Sdf.ValueTypeNames.Token
).Set("RealTimePathTracing")
product_prim.CreateAttribute(
    "omni:rtx:dlss:frameGeneration", Sdf.ValueTypeNames.Bool
).Set(True)
product_prim.CreateAttribute(
    "omni:rtx:dlss:frameGenerationInterpolatedFrameCount",
    Sdf.ValueTypeNames.Int,
).Set(3)

The same attributes may be written at runtime instead of authored in the loaded USD layer.

Reading the Frames#

Each completed camera capture contributes its interpolated frames, if any, followed by its simulated frame. For exactly one capture per RenderProduct per step, this gives N + 1 frames: N interpolated frames followed by one simulated frame. The snippets below assume this single-capture case, with all returned frames belonging to that capture, as in the unscheduled cameras used by these examples.

A scheduled camera can complete several captures during one step. For example, a 120 Hz camera stepped at 60 Hz can return two simulated captures even with frame generation disabled. Do not classify that whole result using frames[:-1] or output_frame_count - 1: earlier simulated captures would be misidentified as interpolated frames. See Sensor Scheduling for camera scheduling.

Every frame exposes the same color render var path. Within a single-capture result, position in the frames array identifies the interpolated frames and the final simulated frame.

product = products["/Render/Camera"]

# All returned frames must belong to one completed capture, as with this
# unscheduled camera. Multiple scheduled captures invalidate this classification.
# Derive the interpolated count from the result; never hardcode it. The count is
# clamped to the hardware maximum and can change at runtime, so a step may
# return fewer interpolated frames than were requested.
interpolated_count = len(product.frames) - 1

# The simulated frame is LAST. The frames before it are interpolated, in
# presentation order. Nothing on a frame marks it as interpolated -- position in
# the list is the only signal.
interpolated_frames = product.frames[:-1]
simulated_frame = product.frames[-1]
// All returned frames must belong to one completed capture, as in this example.
// Scheduled cameras can return multiple simulated captures, so their total frame
// count cannot classify interpolation. For one capture, frames [0, N) are
// interpolated in presentation order and the last frame is simulated; N is zero
// when frame generation is off or declined.
//
// Never hardcode N. Derive it from output_frame_count on every step -- the count
// can change at runtime when the renderer starts or stops honouring the request.
/// Frame indices for a result containing exactly one completed camera capture.
struct FrameLayout {
    size_t interpolated_count;  // N
    size_t simulated_frame_index;  // always output_frame_count - 1
    bool frame_generation_active;
};

/// Classify a nonempty, single-capture result; multi-capture steps are unsupported.
FrameLayout classify_output_frames(ovrtx_render_product_output_t const& output) {
    FrameLayout layout{};
    layout.simulated_frame_index = output.output_frame_count - 1;
    layout.interpolated_count = output.output_frame_count - 1;
    layout.frame_generation_active = output.output_frame_count > 1;
    return layout;
}

Read the frame count on every step instead of assuming the requested count was produced.

Presenting Generated Frames#

For paced presentation, select the frame due at the current time and skip interpolated frames whose presentation times have passed. Showing those frames late would delay the rest of the batch. When a newer complete step arrives, replace the previous batch and discard its unshown frames. Re-present the current image when no newer frame is due.

The Vulkan interop example implements this policy. It waits until all frames of a step are ready and orders them by copy-submission timeline. With pacing enabled, it selects a frame using the elapsed time since that batch became ready, rather than advancing one frame per presentation. A late presentation can therefore skip several interpolated frames at once; the selection never moves backward within a batch.

Rendering and copying continue independently of presentation. The example’s ten-image ring holds the published batch and the next batch being copied, plus spare slots, so holding a displayed frame does not block the next copy. It reports render steps, visible frames, presents, dropped frames, and visible-frame gaps separately. The presentation loop selects the frame as follows:

// Pick the frame due for this presentation from the published step.
// Showing the whole batch immediately would create a burst followed
// by a gap until the next step arrives. Skip overdue interpolated
// frames so late presentation does not delay the rest of the batch.
if (!sequence.empty()) {
    size_t const previous = scheduler.shown();
    size_t const last = sequence.size() - 1;
    // Once `--num-frames` is reached, jump to the step's real frame:
    // newer steps keep replacing the sequence, so waiting for the
    // schedule to reach it can starve the exit indefinitely.
    size_t index = last;
    if (dlssg_force_frame >= 0) {
        index = std::min(static_cast<size_t>(dlssg_force_frame), last);
    } else if (num_frames <= 0 || total_frames < num_frames) {
        index = scheduler.choose(present_time);
    }
    if (index != previous) {
        size_t const first_due =
            (previous == SubframeScheduler::NONE) ? 0 : previous + 1;
        for (size_t skipped = first_due; skipped < index; ++skipped) {
            slots[sequence[skipped]].state = FrameSlot::State::Free;
            ++metrics.dropped;
        }
        slots[read_idx].state = FrameSlot::State::Free;
        read_idx = sequence[index];
        slots[read_idx].state = FrameSlot::State::Showing;
        read_timeline_value = slots[read_idx].timeline_value;
        read_holds_real_frame = slots[read_idx].holds_real_frame;
        scheduler.on_shown(index);

        ++total_frames;
        metrics.record_frame_promotion(present_time);
    }
}

Detecting Whether Frame Generation Is Active#

Count-based detection requires exactly one completed camera capture per RenderProduct per step. Under this precondition, more than one frame indicates that interpolation occurred. The renderer handles warm-up automatically; you do not need to discard initial steps. Multiple scheduled captures invalidate this detector, even when frame generation is disabled.

There is no public capability query. For the single-capture case, inspect each returned frame count:

def _frame_generation_active(products):
    """Detect interpolation when exactly one camera capture completes per step.

    All returned frames must belong to that capture, as with the unscheduled
    camera in this example. A scheduled
    camera can return several simulated captures without interpolation, so total
    frame count cannot detect frame generation for a multi-capture step.

    There is no public frame-generation capability query. The renderer can decline --
    unsupported GPU, non-display device, incompatible render outputs, resolution
    too small -- and the step still succeeds, returning a single simulated frame. The
    frame count is the signal: more than one frame means interpolation happened.
    """
    product = products["/Render/Camera"]
    return len(product.frames) > 1

If subsequent single-capture steps still contain one frame, check the requirements below. The step succeeds and returns its normal simulated outputs when frame generation is unavailable or a requirement is not met.

Requirements and Limits#

The known public requirements and common renderer blockers are listed below. Meeting them does not guarantee that the installed driver and operating system support frame generation, so always inspect the returned frame count. When frame generation is unavailable, the step normally succeeds with only its simulated frame. The renderer logs warnings for some, but not all, causes.

Requirement

Behavior when the requirement is not met

Application action

The RenderProduct uses an Ada (RTX 40-series) or newer GPU.

Unsupported GPUs return only the simulated frame. No warning is logged.

Use Ada for one interpolated frame or Blackwell for up to three. Inspect the frame count because no public capability query is available.

The RenderProduct uses a GPU that has an active display attached to it.

A display-less GPU returns only the simulated frame. No warning is logged.

Select a display-connected GPU with RenderProduct device pinning.

The driver and operating system support DLSS Frame Generation.

The RenderProduct returns only the simulated frame. Startup logs may report an NGX, driver, or operating-system requirement.

Use a supported NVIDIA driver and operating system.

omni:rtx:rendermode is RealTimePathTracing.

Other render modes return only the simulated frame.

Change the RenderProduct render mode.

The RenderProduct requests only LdrColor or only HdrColor.

Frame generation is disabled for the whole RenderProduct. The step still returns the requested simulated outputs.

Remove every additional RenderVar, including SPG outputs.

The color output does not contain alpha.

The RenderProduct returns only the simulated frame and logs output contains alpha.

Disable alpha output.

Both resolution dimensions are at least 126 pixels.

The RenderProduct returns only the simulated frame and logs unsupported viewport resolution.

Increase the RenderProduct resolution.

OmniGraph postprocessing is not active on the RenderProduct.

The RenderProduct returns only the simulated frame and logs OmniGraph postprocessing being active.

Remove the OmniGraph postprocessing graph.

No renderer debug view is active.

The RenderProduct returns only the simulated frame and logs not supported with debug views.

Disable the debug view.

SDG disable-color rendering is not active.

The RenderProduct returns only the simulated frame and logs not supported with SDG disable color render mode.

Enable color rendering.

Requests above the hardware maximum are reduced rather than rejected. Ada produces at most one interpolated frame per simulated frame; Blackwell produces at most three. For example, requesting three on Ada produces one interpolated frame.

For incompatible outputs, alpha, resolution, or OmniGraph postprocessing, the renderer logs Disabling DLSS Frame Generation for at least one view due to ... once for each distinct combination of causes. Debug views and SDG disable-color mode use Disabling DLSS Frame Generation for ... because it is .... Inspect the log from the start of the run. Unsupported and display-less GPUs do not produce these blocker warnings.