playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
288 lines (287 loc) • 12 kB
JavaScript
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
import { TRACEID_RENDER_PASS_DETAIL } from "../../core/constants.js";
import { Debug } from "../../core/debug.js";
import { now } from "../../core/time.js";
import { Tracing } from "../../core/tracing.js";
import { BlendState } from "../../platform/graphics/blend-state.js";
import { DebugGraphics } from "../../platform/graphics/debug-graphics.js";
import { RenderPass } from "../../platform/graphics/render-pass.js";
import { RenderAction } from "../composition/render-action.js";
import { EVENT_POSTRENDER, EVENT_POSTRENDER_LAYER, EVENT_PRERENDER, EVENT_PRERENDER_LAYER, SHADER_FORWARD } from "../constants.js";
class RenderPassForward extends RenderPass {
constructor(device, layerComposition, scene, renderer) {
super(device);
/**
* @type {LayerComposition}
*/
__publicField(this, "layerComposition");
/**
* @type {Scene}
*/
__publicField(this, "scene");
/**
* @type {Renderer}
*/
__publicField(this, "renderer");
/**
* @type {RenderAction[]}
*/
__publicField(this, "renderActions", []);
/**
* The gamma correction setting for the render pass. If not set, the setting from the camera
* is used. This allows render passes to override the camera's gamma correction during the
* render pass.
*
* For HDR pipelines, scene render passes typically set this to {@link GAMMA_NONE} to output
* linear values to an HDR render target, while subsequent passes (like UI) leave it undefined
* to use the camera's default {@link GAMMA_SRGB} for correct display output.
*
* Can be:
* - {@link GAMMA_NONE}
* - {@link GAMMA_SRGB}
* - `undefined` (uses camera setting)
*
* @type {number|undefined}
*/
__publicField(this, "gammaCorrection");
/**
* The tone mapping setting for the render pass. In not set, setting from the camera is used.
*
* @type {number|undefined}
*/
__publicField(this, "toneMapping");
/**
* If true, do not clear the depth buffer before rendering, as it was already primed by a depth
* pre-pass.
*/
__publicField(this, "noDepthClear", false);
this.layerComposition = layerComposition;
this.scene = scene;
this.renderer = renderer;
}
get rendersAnything() {
return this.renderActions.length > 0;
}
addRenderAction(renderAction) {
this.renderActions.push(renderAction);
}
/**
* Adds a layer to be rendered by this render pass.
*
* @param {CameraComponent} cameraComponent - The camera component that is used to render the
* layers.
* @param {Layer} layer - The layer to be added.
* @param {boolean} transparent - True if the layer is transparent.
* @param {boolean} autoClears - True if the render target should be cleared based on the camera
* and layer clear flags. Defaults to true.
*/
addLayer(cameraComponent, layer, transparent, autoClears = true) {
Debug.assert(cameraComponent);
Debug.assert(this.renderTarget !== void 0, "Render pass needs to be initialized before adding layers");
Debug.assert(cameraComponent.camera.layersSet.has(layer.id), `Camera ${cameraComponent.entity.name} does not render layer ${layer.name}.`);
const ra = new RenderAction();
ra.renderTarget = this.renderTarget;
ra.camera = cameraComponent;
ra.layer = layer;
ra.transparent = transparent;
if (autoClears) {
const firstRa = this.renderActions.length === 0;
ra.setupClears(firstRa ? cameraComponent : void 0, layer);
}
this.addRenderAction(ra);
}
/**
* Adds layers to be rendered by this render pass, starting from the given index of the layer
* in the layer composition, till the end of the layer list, or till the last layer with the
* given id and transparency is reached (inclusive). Note that only layers that are rendered by
* the specified camera are added.
*
* @param {LayerComposition} composition - The layer composition containing the layers to be
* added, typically the scene layer composition.
* @param {CameraComponent} cameraComponent - The camera component that is used to render the
* layers.
* @param {number} startIndex - The index of the first layer to be considered for adding.
* @param {boolean} firstLayerClears - True if the first layer added should clear the render
* target.
* @param {number} [lastLayerId] - The id of the last layer to be added. If not specified, all
* layers till the end of the layer list are added.
* @param {boolean} [lastLayerIsTransparent] - True if the last layer to be added is transparent.
* Defaults to true.
* @returns {number} Returns the index of last layer added.
*/
addLayers(composition, cameraComponent, startIndex, firstLayerClears, lastLayerId, lastLayerIsTransparent = true) {
const { layerList, subLayerList } = composition;
let clearRenderTarget = firstLayerClears;
let index = startIndex;
while (index < layerList.length) {
const layer = layerList[index];
const isTransparent = subLayerList[index];
const renderedByCamera = cameraComponent.camera.layersSet.has(layer.id);
if (renderedByCamera) {
this.addLayer(cameraComponent, layer, isTransparent, clearRenderTarget);
clearRenderTarget = false;
}
index++;
if (layer.id === lastLayerId && isTransparent === lastLayerIsTransparent) {
break;
}
}
return index;
}
// Collect before-passes from cameras whose first render action lives in this
// RenderPassForward. Uses the existing firstCameraUse flag (set by LayerComposition)
// to guarantee each camera's before-passes are scheduled exactly once, even when
// multiple RenderPassForward instances reference the same camera (e.g. CameraFrame's
// scenePass vs afterPass).
updateCameraBeforePasses() {
for (let i = 0; i < this.renderActions.length; i++) {
const ra = this.renderActions[i];
if (ra.firstCameraUse) {
const camera = ra.camera?.camera;
if (camera) {
const { beforePasses } = camera;
for (let j = 0; j < beforePasses.length; j++) {
this.beforePasses.push(beforePasses[j]);
}
}
}
}
}
updateDirectionalShadows() {
const { renderer, renderActions } = this;
for (let i = 0; i < renderActions.length; i++) {
const renderAction = renderActions[i];
const cameraComp = renderAction.camera;
const camera = cameraComp.camera;
const shadowDirLights = this.renderer.cameraDirShadowLights.get(camera);
if (shadowDirLights) {
for (let l = 0; l < shadowDirLights.length; l++) {
const light = shadowDirLights[l];
if (renderer.dirLightShadows.get(light) !== camera) {
renderer.dirLightShadows.set(light, camera);
const shadowPass = renderer._shadowRendererDirectional.getLightRenderPass(light, camera);
if (shadowPass) {
this.beforePasses.push(shadowPass);
}
}
}
}
}
}
updateClears() {
const renderAction = this.renderActions[0];
if (renderAction) {
const cameraComponent = renderAction.camera;
const camera = cameraComponent.camera;
const fullSizeClearRect = camera.fullSizeClearRect;
this.setClearColor(fullSizeClearRect && renderAction.clearColor ? camera.clearColor : void 0);
this.setClearDepth(fullSizeClearRect && renderAction.clearDepth && !this.noDepthClear ? camera.clearDepth : void 0);
this.setClearStencil(fullSizeClearRect && renderAction.clearStencil ? camera.clearStencil : void 0);
}
}
frameUpdate() {
super.frameUpdate();
this.updateCameraBeforePasses();
this.updateDirectionalShadows();
this.updateClears();
}
before() {
const { renderActions } = this;
for (let i = 0; i < renderActions.length; i++) {
const ra = renderActions[i];
if (ra.firstCameraUse) {
this.scene.fire(EVENT_PRERENDER, ra.camera);
}
}
}
execute() {
const { layerComposition, renderActions } = this;
for (let i = 0; i < renderActions.length; i++) {
const ra = renderActions[i];
const layer = ra.layer;
Debug.call(() => {
const compLayer = layerComposition.getLayerByName(layer.name);
if (!compLayer) {
Debug.warnOnce(`Layer ${layer.name} is not found in the scene and will not be rendered. Your render pass setup might need to be updated.`);
}
});
if (layerComposition.isEnabled(layer, ra.transparent)) {
this.renderRenderAction(ra, i === 0);
}
}
}
after() {
for (let i = 0; i < this.renderActions.length; i++) {
const ra = this.renderActions[i];
if (ra.lastCameraUse) {
this.scene.fire(EVENT_POSTRENDER, ra.camera);
}
}
this.beforePasses.length = 0;
}
/**
* @param {RenderAction} renderAction - The render action.
* @param {boolean} firstRenderAction - True if this is the first render action in the render pass.
*/
renderRenderAction(renderAction, firstRenderAction) {
const { renderer, scene } = this;
const device = renderer.device;
const { layer, transparent, camera } = renderAction;
DebugGraphics.pushGpuMarker(this.device, `Camera: ${camera ? camera.entity.name : "Unnamed"}, Layer: ${layer.name}(${transparent ? "TRANSP" : "OPAQUE"})`);
const drawTime = now();
if (camera) {
const originalGammaCorrection = camera.gammaCorrection;
const originalToneMapping = camera.toneMapping;
if (this.gammaCorrection !== void 0) camera.gammaCorrection = this.gammaCorrection;
if (this.toneMapping !== void 0) camera.toneMapping = this.toneMapping;
scene.fire(EVENT_PRERENDER_LAYER, camera, layer, transparent);
const options = {
lightClusters: renderAction.lightClusters
};
const shaderPass = camera.camera.shaderPassInfo?.index ?? SHADER_FORWARD;
if (!firstRenderAction || !camera.camera.fullSizeClearRect) {
options.clearColor = renderAction.clearColor;
options.clearDepth = renderAction.clearDepth;
options.clearStencil = renderAction.clearStencil;
}
const renderTarget = renderAction.renderTarget ?? device.backBuffer;
renderer.renderForwardLayer(
camera.camera,
renderTarget,
layer,
transparent,
shaderPass,
renderAction.viewBindGroups,
options
);
device.setBlendState(BlendState.NOBLEND);
device.setStencilState(null, null);
device.setAlphaToCoverage(false);
scene.fire(EVENT_POSTRENDER_LAYER, camera, layer, transparent);
if (this.gammaCorrection !== void 0) camera.gammaCorrection = originalGammaCorrection;
if (this.toneMapping !== void 0) camera.toneMapping = originalToneMapping;
}
DebugGraphics.popGpuMarker(this.device);
layer._renderTime += now() - drawTime;
}
log(device, index) {
super.log(device, index);
if (Tracing.get(TRACEID_RENDER_PASS_DETAIL)) {
const { layerComposition } = this;
this.renderActions.forEach((ra, index2) => {
const layer = ra.layer;
const enabled = layer.enabled && layerComposition.isEnabled(layer, ra.transparent);
const camera = ra.camera;
Debug.trace(
TRACEID_RENDER_PASS_DETAIL,
` ${index2}:${` Cam: ${camera ? camera.entity.name : "-"}`.padEnd(22, " ")}${` Lay: ${layer.name}`.padEnd(22, " ")}${ra.transparent ? " TRANSP" : " OPAQUE"}${enabled ? " ENABLED" : " DISABLED"}${` Meshes: ${layer.meshInstances.length}`.padEnd(5, " ")}`
);
});
}
}
}
export {
RenderPassForward
};