Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
---
title: Camera Passes
description: "Adding frame passes to a camera: passes rendering before and after it, replacing its rendering with your own passes, and who owns them."
---

Each camera renders its part of the [frame graph](index.md) as a group of [frame passes](frame-passes.md). By default, these are passes the engine creates to render the layers of the camera. You can add passes of your own before and after them, or replace them with your own passes.

## The passes of a camera

Each camera renders its passes in three groups, in this order, and all three finish before the next camera starts:

```mermaid
flowchart LR
CameraBefore[camera.beforePasses] --> Rendering[the camera's rendering:<br/>its default passes, or camera.framePasses] --> CameraAfter[camera.afterPasses]
```

- [`beforePasses`](#before-passes) render before the camera renders its scene.
- The camera's rendering is either its default rendering, or the passes in [`framePasses`](#replacing-the-cameras-rendering).
- [`afterPasses`](#after-passes) render after everything the camera renders.

## Before passes

The passes in [`CameraComponent#beforePasses`](https://api.playcanvas.com/engine/classes/CameraComponent.html#beforepasses) render after the directional shadow maps of the camera, and before it renders its scene. Use them to render into textures the materials of the scene read, or to run compute shaders whose results the scene uses, in the same frame.

They cannot include a pass rendering the layers of a camera, such as a [`RenderPassForward`](render-pass-forward.md#limitations). When the frame renders multiple XR views, they render for each view, apart from a compute pass, which executes in the view its [`xrViewIndex`](compute-pass.md#xr-views) selects.

## After passes

The passes in [`CameraComponent#afterPasses`](https://api.playcanvas.com/engine/classes/CameraComponent.html#afterpasses) render after everything the camera renders in the frame, its frame passes included. Use them to process what the camera rendered in the same frame, for example with a [compute pass](compute-pass.md) reading its color map. When the frame renders multiple XR views, they render once, after all the views.

## Replacing the camera's rendering

The passes in [`CameraComponent#framePasses`](https://api.playcanvas.com/engine/classes/CameraComponent.html#framepasses) render instead of the camera's default rendering, in the order of the array. With them, you decide how the camera renders: which of its layers render into which render targets, and which passes process the result. Usually, one of them is a [`RenderPassForward`](render-pass-forward.md), which renders the layers of the scene with the camera.

Here, a forward pass renders the scene into a texture, and a [shader quad pass](render-pass-shader-quad.md) draws it to the screen, tinted:

```javascript
// a texture the size of the back buffer, which the scene renders into
const texture = new Texture(app.graphicsDevice, {
name: 'SceneTexture',
width: 4,
height: 4,
format: PIXELFORMAT_RGBA8,
mipmaps: false,
minFilter: FILTER_LINEAR,
magFilter: FILTER_LINEAR,
addressU: ADDRESS_CLAMP_TO_EDGE,
addressV: ADDRESS_CLAMP_TO_EDGE
});
const renderTarget = new RenderTarget({ colorBuffer: texture, depth: true });

// render the world and the UI layers into the texture, resized to the back buffer each frame
const scenePass = new RenderPassForward(app.graphicsDevice, app.scene, cameraEntity.camera);
scenePass.name = 'ScenePass';
scenePass.init(renderTarget, { resizeSource: null });
scenePass.addLayer(worldLayer, false);
scenePass.addLayer(uiLayer, true);

// draw the texture to the screen, tinted
const tintPass = new TintPass(app.graphicsDevice, texture);
tintPass.init(null);

cameraEntity.camera.framePasses = [scenePass, tintPass];
```

To return to the camera's default rendering, set `framePasses` to `null`.

The before and after passes of the camera still render around its frame passes. The before passes render before the first of the frame passes rendering the layers of the camera, and do not render when none of them does. The after passes render after all of them.

A camera can also render its layers in several forward passes, for example its opaque layers, then a pass of your own reading what they rendered, and then its transparent layers.

## Adding and removing passes

Add passes to the arrays, and remove them to stop rendering them. The camera does not destroy its passes, so destroy a pass once it is removed, and then what it renders into:

```javascript
const pass = new MyPass(app.graphicsDevice);
pass.name = 'MyPass';
cameraEntity.camera.afterPasses.push(pass);

// when no longer needed, remove the pass and destroy it
const passes = cameraEntity.camera.afterPasses;
passes.splice(passes.indexOf(pass), 1);
pass.destroy();
```

## Examples

The camera renders with frame passes: a forward pass renders the world and the UI layers into a texture, which a shader quad pass draws to the screen, tinted.

<EngineExample id="render-targets/render-pass-camera" title="Render Pass Camera" />

A compute pass in the after passes of the camera builds a histogram of the colors it rendered.

<EngineExample id="compute/histogram" title="Histogram" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
---
title: Compute Passes
description: "ComputePass dispatches compute shaders at a position in the frame: adding it to a camera, sizing the dispatch from the scene maps, reading results back, and XR views."
---

A [`ComputePass`](https://api.playcanvas.com/engine/classes/ComputePass.html) is a [frame pass](frame-passes.md) which dispatches a list of [compute](/user-manual/graphics/shaders/compute-shaders/) instances, in order, in a single physical compute pass. It places compute work at a position in the frame, for example after a camera renders, to process its scene depth and color maps in the same frame.

To dispatch compute instances right away instead, when called, use [`GraphicsDevice#computeDispatch`](https://api.playcanvas.com/engine/classes/GraphicsDevice.html#computedispatch).

:::warning

Compute shaders are only supported on **WebGPU**, see [`GraphicsDevice#supportsCompute`](https://api.playcanvas.com/engine/classes/GraphicsDevice.html#supportscompute). On other devices, the pass executes without dispatching anything.

:::

## Creating the pass

Create the pass with the compute instances it dispatches. Its [`computes`](https://api.playcanvas.com/engine/classes/ComputePass.html#computes) array holds them, and you can add instances to it, or remove them, later. Set its name, which labels the dispatch in GPU profiling:

```javascript
const compute = new Compute(app.graphicsDevice, shader, 'Histogram');
compute.setParameter('bins', histogramBuffer);

const pass = new ComputePass(app.graphicsDevice, [compute]);
pass.name = 'HistogramPass';
```

## Adding the pass to a frame

Add the pass to the [`beforePasses`](https://api.playcanvas.com/engine/classes/CameraComponent.html#beforepasses) or the [`afterPasses`](https://api.playcanvas.com/engine/classes/CameraComponent.html#afterpasses) of a camera, to dispatch it before or after the camera renders, or to the before or after passes of another pass. A camera rendering with its own [`framePasses`](camera-passes.md#replacing-the-cameras-rendering) can also include the pass among them, to dispatch it at its position in the list, for example between a pass rendering the scene into a texture and a pass using what the compute shader wrote. A pass in the after passes of a camera reads what the camera rendered in the same frame:

```javascript
cameraEntity.camera.afterPasses.push(pass);
```

The pass executes at most once in a frame.

## Reading what the camera rendered

A compute pass in the after passes of a camera can read the depth and color maps the camera rendered in the same frame. Request the maps on the camera, with [`requestSceneDepthMap`](https://api.playcanvas.com/engine/classes/CameraComponent.html#requestscenedepthmap) and [`requestSceneColorMap`](https://api.playcanvas.com/engine/classes/CameraComponent.html#requestscenecolormap), and attach them to the compute instance, with [`setSceneDepthMap`](https://api.playcanvas.com/engine/classes/Compute.html#setscenedepthmap) and [`setSceneColorMap`](https://api.playcanvas.com/engine/classes/Compute.html#setscenecolormap):

```javascript
cameraEntity.camera.requestSceneColorMap(true);
compute.setSceneColorMap(cameraEntity.camera.sceneColorMapHandle);
```

See [Scene Depth and Color Maps](/user-manual/graphics/shaders/compute-shaders/#scene-depth-and-color-maps) for requesting them on a camera rendering with a `CameraFrame`, and for reading them in the compute shader.

## The dispatch

Each compute instance is dispatched with the dispatch size and the parameters set on it. Set them before the frame renders, or in [`onBefore`](https://api.playcanvas.com/engine/classes/FramePass.html#onbefore), right before the dispatch. Use `onBefore` also to prepare the buffers the shaders write, for example to clear them.

A shader processing each texel of the scene color map of a camera, attached [as above](#reading-what-the-camera-rendered), is dispatched over the size of the color map. Its handle has the size of the map the camera rendered most recently, which differs from the size of the canvas when the camera renders at a render scale, or into a render target of its own. The depth map handle has the same [`width`](https://api.playcanvas.com/engine/classes/SceneDepthMapHandle.html#width) and [`height`](https://api.playcanvas.com/engine/classes/SceneDepthMapHandle.html#height):

```javascript
pass.onBefore = () => {
// a thread for each texel of the color map the camera just rendered
const { width, height } = cameraEntity.camera.sceneColorMapHandle;
compute.setupDispatchThreads(width, height);
histogramBuffer.clear();
};
```

## Reading the results back

Read the results in [`onAfter`](https://api.playcanvas.com/engine/classes/FramePass.html#onafter), as a read requested there is recorded right after the dispatch. The data arrives a few frames later:

```javascript
pass.onAfter = () => {
histogramBuffer.read().then((data) => {
// the histogram, a few frames after it was computed
});
};
```

## XR views

When the frame renders multiple XR views, [`xrViewIndex`](https://api.playcanvas.com/engine/classes/ComputePass.html#xrviewindex) selects the view the pass executes in, at its position in that view. Use `-1` to execute it once, before all the views. As a compute instance is dispatched at most once in a frame, dispatching for several views needs a `ComputePass` for each, with separate compute instances. A pass in the after passes of a camera executes once, after all the views.

## Owning the pass

The pass does not destroy its compute instances, and the camera does not destroy the pass. When no longer needed, remove the pass, and destroy it and the compute instances:

```javascript
const passes = cameraEntity.camera.afterPasses;
passes.splice(passes.indexOf(pass), 1);
pass.destroy();
compute.destroy();
```

## Examples

A compute pass after the camera builds a histogram of the colors it rendered, which is read back and drawn on the screen.

<EngineExample id="compute/histogram" title="Histogram" />

A compute pass after the camera reads its scene depth and color maps, to draw a scanner pulse sweeping through the scene.

<EngineExample id="compute/scene-maps" title="Scene Maps" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
---
title: Frame Passes
description: "The engine's frame passes, the logical passes of the frame graph: the pass classes, their lifecycle, nesting, adding them to a camera, and who owns them."
---

In PlayCanvas, each logical pass of the [frame graph](index.md) is a **frame pass**: an object of the engine, which records the work of the pass when the frame renders. The engine renders its own frame this way: the shadow maps, the scene of each camera, and the post-processing are all frame passes, and you can add frame passes of your own, to render into textures, to process what a camera renders, or to run compute shaders on it.

Each frame, the engine collects the frame passes, in order, into the frame graph, and then runs them one after another, starting and ending the physical passes they need. You never start or end a physical pass yourself.

## The pass classes

Every frame pass is an instance of `FramePass`, or of a class derived from it. Two classes derived from it cover the two kinds of physical passes: `RenderPass` for rendering, and `ComputePass` for compute shaders:

```mermaid
classDiagram
FramePass <|-- RenderPass
FramePass <|-- ComputePass
```

| Class | Use it for |
| ----- | ---------- |
| [`FramePass`](https://api.playcanvas.com/engine/classes/FramePass.html) | The base class. Work which does not need a physical pass, or a group of other frame passes. |
| [`RenderPass`](render-pass.md) | Rendering into a render target, or into the back buffer. |
| [`ComputePass`](compute-pass.md) | Dispatching compute shaders at a position in the frame. |

A `RenderPass` is a logical render pass, which records its draw calls into a physical render pass for its render target. Usually, the engine starts a physical render pass for each `RenderPass` and ends it after it. When `RenderPass` objects in a row render into the same render target, they share a single physical render pass instead, see [Frame Graph](index.md#the-frame-graph). Similarly, the engine records the dispatches of a `ComputePass` into a physical compute pass.

The engine derives classes from `RenderPass` for common kinds of drawing, such as running a shader for every pixel of a render target, or rendering the scene. See [Render Passes](render-pass.md#classes-derived-from-renderpass).

## The lifecycle of a frame pass

A frame pass goes through two phases in each frame it is part of. While the frame is built, before anything renders, the engine updates the pass and works out which passes the frame renders. Then the frame renders the passes in order.

```mermaid
flowchart TB
subgraph Build[Building the frame]
direction LR
Enabled{enabled?} -->|yes| FrameUpdate[frameUpdate] --> Add[its before passes, the pass<br/>and its after passes are added]
Enabled -->|no| Skip([skipped, with its<br/>before and after passes])
end
subgraph Render[Rendering the frame]
direction LR
Before[before] --> OnBefore[onBefore] --> ExecuteEnabled{executeEnabled?}
ExecuteEnabled -->|yes| Execute[execute] --> After[after]
ExecuteEnabled -->|no| After
After --> OnAfter[onAfter]
end
Build --> Render
```

A subclass overrides these methods to do its work:

- [`frameUpdate`](https://api.playcanvas.com/engine/classes/FramePass.html#frameupdate) updates the pass for the frame, before anything renders: for example to resize its render targets, to set the textures it reads, or to add or remove its before and after passes.
- [`before`](https://api.playcanvas.com/engine/classes/FramePass.html#before) and [`after`](https://api.playcanvas.com/engine/classes/FramePass.html#after) run around the work of the pass.
- [`execute`](https://api.playcanvas.com/engine/classes/FramePass.html#execute) records the work. For a `RenderPass`, it runs inside a physical render pass for its render target.

The [`onBefore`](https://api.playcanvas.com/engine/classes/FramePass.html#onbefore) and [`onAfter`](https://api.playcanvas.com/engine/classes/FramePass.html#onafter) callbacks let you hook into a pass without writing a subclass: to set the parameters of the pass, or to clear a buffer it writes to, right before it executes, and to read back what it wrote right after:

```javascript
pass.onBefore = () => {
buffer.clear();
};

pass.onAfter = () => {
buffer.read().then((data) => {
// the results arrive a few frames later
});
};
```

### Enabled and executeEnabled

Two properties turn a pass off, at different points of the lifecycle:

- Setting [`enabled`](https://api.playcanvas.com/engine/classes/FramePass.html#enabled) to false skips the pass when the frame is built, together with its before and after passes, and its `frameUpdate` is not called. Change it before the frame is built: the `frameUpdate` of a pass cannot disable the pass itself.
- Setting [`executeEnabled`](https://api.playcanvas.com/engine/classes/FramePass.html#executeenabled) to false only skips `execute`. The pass is still updated, its before and after passes still render, and `before`, `after`, `onBefore` and `onAfter` are still called. Set it at the latest in `frameUpdate`.

## Before and after passes

Every pass has [`beforePasses`](https://api.playcanvas.com/engine/classes/FramePass.html#beforepasses) and [`afterPasses`](https://api.playcanvas.com/engine/classes/FramePass.html#afterpasses), which render right before and right after it, every frame the pass renders:

```mermaid
flowchart LR
BeforePasses[pass.beforePasses] --> Pass[pass] --> AfterPasses[pass.afterPasses]
```

This nests passes, and a plain `FramePass` can hold a group of passes which are enabled and disabled together:

```javascript
// a group of two blur passes: disabling the group skips both
const blur = new FramePass(app.graphicsDevice);
blur.name = 'Blur';
blur.beforePasses.push(horizontalBlurPass, verticalBlurPass);

blur.enabled = false;
```

## Adding passes to a camera

A camera renders [before passes](camera-passes.md#before-passes), then its own rendering, which you can replace with [frame passes](camera-passes.md#replacing-the-cameras-rendering) of your own, and then [after passes](camera-passes.md#after-passes). See [Camera Passes](camera-passes.md).

## Rendering a pass on its own

A pass can also render outside of a frame, for example to render into a texture once, by calling [`render`](https://api.playcanvas.com/engine/classes/FramePass.html#render). It runs `before`, `onBefore`, `execute`, `after` and `onAfter`, but the frame does not prepare it: its `frameUpdate` is not called, and its before and after passes do not render.

```javascript
pass.render();
```

## Owning passes

Nothing destroys a pass for you. A camera does not destroy the passes in its lists, and a pass does not destroy its before and after passes. The code which creates a pass destroys it, once it is removed from the lists it was added to, by calling [`destroy`](https://api.playcanvas.com/engine/classes/FramePass.html#destroy). A pass is not used after it is destroyed. A pass does not destroy what it was given either, such as its render target or its shader.

The name of a pass, set with [`name`](https://api.playcanvas.com/engine/classes/FramePass.html#name), identifies it in GPU profiling and graphics debuggers. Set it explicitly, as the default, the name of the class, is shortened in minified builds.
Loading
Loading