@babylonjs/core
Version:
Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.
331 lines (330 loc) • 16.2 kB
JavaScript
import { EffectRenderer } from "../Materials/effectRenderer.js";
import { CopyTextureToTexture } from "../Misc/copyTextureToTexture.js";
import { FrameGraphContext } from "./frameGraphContext.js";
import { IsDepthTexture } from "../Materials/Textures/textureHelper.functions.js";
const SamplingModeHasMipMapFiltering = [
false, // not used
false, // TEXTURE_NEAREST_SAMPLINGMODE / TEXTURE_NEAREST_NEAREST
false, // TEXTURE_BILINEAR_SAMPLINGMODE / TEXTURE_LINEAR_LINEAR
true, // TEXTURE_TRILINEAR_SAMPLINGMODE / TEXTURE_LINEAR_LINEAR_MIPLINEAR
true, // TEXTURE_NEAREST_NEAREST_MIPNEAREST
true, // TEXTURE_NEAREST_LINEAR_MIPNEAREST
true, // TEXTURE_NEAREST_LINEAR_MIPLINEAR
false, // TEXTURE_NEAREST_LINEAR
true, // TEXTURE_NEAREST_NEAREST_MIPLINEAR
true, // TEXTURE_LINEAR_NEAREST_MIPNEAREST
true, // TEXTURE_LINEAR_NEAREST_MIPLINEAR
true, // TEXTURE_LINEAR_LINEAR_MIPNEAREST
false, // TEXTURE_LINEAR_NEAREST
];
/**
* Frame graph context used render passes.
*/
export class FrameGraphRenderContext extends FrameGraphContext {
static _IsObjectRenderer(value) {
return value.initRender !== undefined;
}
/** @internal */
constructor(engine, textureManager, scene) {
super(engine, textureManager, scene);
this._renderTargetIsBound = true;
this._effectRenderer = new EffectRenderer(this._engine);
this._effectRendererBack = new EffectRenderer(this._engine, {
positions: [1, 1, -1, 1, -1, -1, 1, -1],
indices: [0, 2, 1, 0, 3, 2],
});
this._copyTexture = new CopyTextureToTexture(this._engine);
this._copyDepthTexture = new CopyTextureToTexture(this._engine, true);
}
/**
* Checks whether a texture handle points to the backbuffer's color or depth texture
* @param handle The handle to check
* @returns True if the handle points to the backbuffer's color or depth texture, otherwise false
*/
isBackbuffer(handle) {
return this._textureManager._isBackbuffer(handle);
}
/**
* Checks whether a texture handle points to the backbuffer's color texture
* @param handle The handle to check
* @returns True if the handle points to the backbuffer's color texture, otherwise false
*/
isBackbufferColor(handle) {
return this._textureManager.isBackbufferColor(handle);
}
/**
* Checks whether a texture handle points to the backbuffer's depth texture
* @param handle The handle to check
* @returns True if the handle points to the backbuffer's depth texture, otherwise false
*/
isBackbufferDepthStencil(handle) {
return this._textureManager.isBackbufferDepthStencil(handle);
}
/**
* Creates a (frame graph) render target wrapper
* Note that renderTargets or renderTargetDepth can be undefined, but not both at the same time!
* @param name Name of the render target wrapper
* @param renderTargets Render target handles (textures) to use
* @param renderTargetDepth Render target depth handle (texture) to use
* @param depthReadOnly If true, the depth buffer will be read-only
* @param stencilReadOnly If true, the stencil buffer will be read-only
* @returns The created render target wrapper
*/
createRenderTarget(name, renderTargets, renderTargetDepth, depthReadOnly, stencilReadOnly) {
return this._textureManager.createRenderTarget(name, renderTargets, renderTargetDepth, depthReadOnly, stencilReadOnly);
}
/**
* Clears the current render buffer or the current render target (if any is set up)
* @param color Defines the color to use
* @param backBuffer Defines if the back buffer must be cleared
* @param depth Defines if the depth buffer must be cleared
* @param stencil Defines if the stencil buffer must be cleared
* @param stencilClearValue Defines the value to use to clear the stencil buffer (default is 0)
*/
clear(color, backBuffer, depth, stencil, stencilClearValue = 0) {
this._applyRenderTarget();
this._engine.clear(color, backBuffer, depth, stencil, stencilClearValue);
}
/**
* Clears the color attachments of the current render target
* @param color Defines the color to use
* @param attachments The attachments to clear
*/
clearColorAttachments(color, attachments) {
this._applyRenderTarget();
this._engine.bindAttachments(attachments);
this._engine.clear(color, true, false, false);
}
/**
* Clears all attachments (color(s) + depth/stencil) of the current render target
* @param color Defines the color to use
* @param attachments The attachments to clear
* @param backBuffer Defines if the back buffer must be cleared
* @param depth Defines if the depth buffer must be cleared
* @param stencil Defines if the stencil buffer must be cleared
* @param stencilClearValue Defines the value to use to clear the stencil buffer (default is 0)
*/
clearAttachments(color, attachments, backBuffer, depth, stencil, stencilClearValue = 0) {
this._applyRenderTarget();
this._engine.bindAttachments(attachments);
this._engine.clear(color, backBuffer, depth, stencil, stencilClearValue);
}
/**
* Binds the attachments to the current render target
* @param attachments The attachments to bind
*/
bindAttachments(attachments) {
this._applyRenderTarget();
this._engine.bindAttachments(attachments);
}
/**
* Generates mipmaps for the current render target
* @param handle Optional handle of the texture to generate mipmaps for (if not provided, will generate mipmaps for all textures in the current render target)
*/
generateMipMaps(handle) {
if (handle !== undefined) {
const internalTexture = this._textureManager.getTextureFromHandle(handle);
if (internalTexture) {
this._engine.generateMipmaps(internalTexture);
}
return;
}
if (this._currentRenderTarget?.renderTargetWrapper === undefined) {
return;
}
if (this._engine._currentRenderTarget && (!this._engine.isWebGPU || this._renderTargetIsBound)) {
// we can't generate the mipmaps if the render target (which is the texture we want to generate mipmaps for) is bound
// Also, for some reasons, on WebGL2, generating mipmaps doesn't work if a render target is bound, even if it's not the texture we want to generate mipmaps for...
this._engine.unBindFramebuffer(this._engine._currentRenderTarget);
this._renderTargetIsBound = false;
}
const textures = this._currentRenderTarget.renderTargetWrapper.textures;
if (textures) {
for (const texture of textures) {
this._engine.generateMipmaps(texture);
}
}
}
/**
* Sets the texture sampling mode for a given texture handle
* @param handle Handle of the texture to set the sampling mode for
* @param samplingMode Sampling mode to set
*/
setTextureSamplingMode(handle, samplingMode) {
const internalTexture = this._textureManager.getTextureFromHandle(handle);
if (internalTexture && internalTexture.samplingMode !== samplingMode) {
internalTexture.useMipMaps = SamplingModeHasMipMapFiltering[samplingMode];
this._engine.updateTextureSamplingMode(samplingMode, internalTexture);
}
}
/**
* Binds a texture handle to a given effect (resolves the handle to a texture and binds it to the effect)
* @param effect The effect to bind the texture to
* @param name The name of the texture in the effect
* @param handle The handle of the texture to bind
*/
bindTextureHandle(effect, name, handle) {
let texture;
const historyEntry = this._textureManager._historyTextures.get(handle);
if (historyEntry) {
texture = historyEntry.textures[historyEntry.index]; // texture we write to in this frame
if (this._currentRenderTarget !== undefined &&
this._currentRenderTarget.renderTargetWrapper !== undefined &&
this._currentRenderTarget.renderTargetWrapper.textures.includes(texture)) {
// If the current render target renders to the history write texture, we bind the read texture instead
texture = historyEntry.textures[historyEntry.index ^ 1];
}
}
else {
texture = this._textureManager._textures.get(handle).texture;
}
effect._bindTexture(name, texture);
}
/**
* Applies a full-screen effect to the current render target
* @param drawWrapper The draw wrapper containing the effect to apply
* @param customBindings The custom bindings to use when applying the effect (optional)
* @param stencilState The stencil state to use when applying the effect (optional)
* @param disableColorWrite If true, color write will be disabled when applying the effect (optional)
* @param drawBackFace If true, the fullscreen quad will be drawn as a back face (in CW - optional)
* @param depthTest If true, depth testing will be enabled when applying the effect (default is false)
* @param noViewport If true, the current viewport will be left unchanged (optional). If false or undefined, the viewport will be set to the full render target size.
* @param alphaMode The alpha mode to use when applying the effect (default is ALPHA_DISABLE)
* @returns True if the effect was applied, otherwise false (effect not ready)
*/
applyFullScreenEffect(drawWrapper, customBindings, stencilState, disableColorWrite, drawBackFace, depthTest, noViewport, alphaMode = 0) {
if (!drawWrapper.effect?.isReady()) {
return false;
}
this._applyRenderTarget();
const engineDepthMask = this._engine.getDepthWrite(); // for some reasons, depthWrite is not restored by EffectRenderer.restoreStates
const engineDepthFunc = this._engine.getDepthFunction();
const effectRenderer = drawBackFace ? this._effectRendererBack : this._effectRenderer;
effectRenderer.saveStates();
if (!noViewport) {
effectRenderer.setViewport();
}
this._engine.enableEffect(drawWrapper);
this._engine.setState(false, undefined, undefined, undefined, undefined, stencilState);
this._engine.setDepthBuffer(!!depthTest);
if (disableColorWrite) {
this._engine.setColorWrite(false);
}
this._engine.setDepthWrite(false);
this._engine.setAlphaMode(alphaMode);
effectRenderer.bindBuffers(drawWrapper.effect);
customBindings?.();
effectRenderer.draw();
effectRenderer.restoreStates();
if (disableColorWrite) {
this._engine.setColorWrite(true);
}
this._engine.setDepthWrite(engineDepthMask);
if (engineDepthFunc) {
this._engine.setDepthFunction(engineDepthFunc);
}
this._engine.setAlphaMode(0);
return true;
}
/**
* Copies a texture to the current render target
* @param sourceTexture The source texture to copy from
* @param forceCopyToBackbuffer If true, the copy will be done to the back buffer regardless of the current render target
* @param noViewport If true, the current viewport will be left unchanged (optional). If false or undefined, the viewport will be set to the full render target size.
* @param lodLevel The LOD level to use when copying the texture (default: 0).
*/
copyTexture(sourceTexture, forceCopyToBackbuffer = false, noViewport, lodLevel = 0) {
if (forceCopyToBackbuffer) {
this.bindRenderTarget();
}
const texture = this._textureManager.getTextureFromHandle(sourceTexture, true);
const copyTexture = IsDepthTexture(texture.format) ? this._copyDepthTexture : this._copyTexture;
copyTexture.source = texture;
copyTexture.lodLevel = lodLevel;
this.applyFullScreenEffect(copyTexture.effectWrapper.drawWrapper, () => {
copyTexture.effectWrapper.onApplyObservable.notifyObservers({});
}, undefined, undefined, undefined, undefined, noViewport);
}
/**
* Renders a RenderTargetTexture or a layer
* @param object The RenderTargetTexture/Layer to render
* @param viewportWidth The width of the viewport (optional for Layer, but mandatory for ObjectRenderer)
* @param viewportHeight The height of the viewport (optional for Layer, but mandatory for ObjectRenderer)
* @param restoreDefaultFramebuffer If true, the default framebuffer will be restored after rendering (default: false)
*/
render(object, viewportWidth, viewportHeight, restoreDefaultFramebuffer = false) {
if (FrameGraphRenderContext._IsObjectRenderer(object)) {
this._scene._intermediateRendering = true;
if (object.shouldRender()) {
this._scene.incrementRenderId();
this._scene.resetCachedMaterial();
object.prepareRenderList();
object.initRender(viewportWidth, viewportHeight);
this._applyRenderTarget();
object.render();
object.finishRender();
if (restoreDefaultFramebuffer) {
this.restoreDefaultFramebuffer();
}
}
this._scene._intermediateRendering = false;
}
else {
this._applyRenderTarget();
object.render();
}
}
/**
* Binds a render target texture so that upcoming draw calls will render to it
* Note: it is a lazy operation, so the render target will only be bound when needed. This way, it is possible to call
* this method several times with different render targets without incurring the cost of binding if no draw calls are made
* @param renderTarget The handle of the render target texture to bind (default: undefined, meaning "back buffer"). Pass an array for MRT rendering.
* @param applyImmediately If true, the render target will be applied immediately (otherwise it will be applied at first use). Default is false (delayed application).
*/
bindRenderTarget(renderTarget, applyImmediately = false) {
this._currentRenderTarget = renderTarget?.renderTargetWrapper === undefined ? undefined : renderTarget;
this._renderTargetIsBound = false;
if (applyImmediately) {
this._applyRenderTarget();
}
}
/**
* Restores the default framebuffer (back buffer) as the current render target
*/
restoreDefaultFramebuffer() {
this._engine.restoreDefaultFramebuffer(true);
this._renderTargetIsBound = false;
this._currentRenderTarget = undefined;
}
/** @internal */
_applyRenderTarget() {
if (this._renderTargetIsBound) {
return;
}
const renderTargetWrapper = this._currentRenderTarget?.renderTargetWrapper;
if (renderTargetWrapper === undefined) {
if (this._engine._currentRenderTarget) {
this._engine.restoreDefaultFramebuffer(true);
}
}
else if (this._engine._currentRenderTarget !== renderTargetWrapper) {
if (this._engine._currentRenderTarget) {
this._engine.unBindFramebuffer(this._engine._currentRenderTarget);
}
this._engine.bindFramebuffer(renderTargetWrapper);
}
this._renderTargetIsBound = true;
}
/** @internal */
_isReady() {
return this._copyTexture.isReady() && this._copyDepthTexture.isReady();
}
/** @internal */
_dispose() {
this._effectRenderer.dispose();
this._effectRendererBack.dispose();
this._copyTexture.dispose();
this._copyDepthTexture.dispose();
}
}
//# sourceMappingURL=frameGraphRenderContext.js.map