273 lines
11 KiB
JavaScript
273 lines
11 KiB
JavaScript
import { Matrix } from '../../../../maths/matrix/Matrix.mjs';
|
|
import { Rectangle } from '../../../../maths/shapes/Rectangle.mjs';
|
|
import { CLEAR } from '../../gl/const.mjs';
|
|
import { calculateProjection } from '../../gpu/renderTarget/calculateProjection.mjs';
|
|
import { SystemRunner } from '../system/SystemRunner.mjs';
|
|
import { CanvasSource } from '../texture/sources/CanvasSource.mjs';
|
|
import { TextureSource } from '../texture/sources/TextureSource.mjs';
|
|
import { Texture } from '../texture/Texture.mjs';
|
|
import { getCanvasTexture } from '../texture/utils/getCanvasTexture.mjs';
|
|
import { isRenderingToScreen } from './isRenderingToScreen.mjs';
|
|
import { RenderTarget } from './RenderTarget.mjs';
|
|
|
|
"use strict";
|
|
class RenderTargetSystem {
|
|
constructor(renderer) {
|
|
/** This is the root viewport for the render pass*/
|
|
this.rootViewPort = new Rectangle();
|
|
/** the current viewport that the gpu is using */
|
|
this.viewport = new Rectangle();
|
|
/**
|
|
* a runner that lets systems know if the active render target has changed.
|
|
* Eg the Stencil System needs to know so it can manage the stencil buffer
|
|
*/
|
|
this.onRenderTargetChange = new SystemRunner("onRenderTargetChange");
|
|
/** the projection matrix that is used by the shaders based on the active render target and the viewport */
|
|
this.projectionMatrix = new Matrix();
|
|
/** the default clear color for render targets */
|
|
this.defaultClearColor = [0, 0, 0, 0];
|
|
/**
|
|
* a hash that stores the render target for a given render surface. When you pass in a texture source,
|
|
* a render target is created for it. This map stores and makes it easy to retrieve the render target
|
|
*/
|
|
this._renderSurfaceToRenderTargetHash = /* @__PURE__ */ new Map();
|
|
/** A hash that stores a gpu render target for a given render target. */
|
|
this._gpuRenderTargetHash = /* @__PURE__ */ Object.create(null);
|
|
/**
|
|
* A stack that stores the render target and frame that is currently being rendered to.
|
|
* When push is called, the current render target is stored in this stack.
|
|
* When pop is called, the previous render target is restored.
|
|
*/
|
|
this._renderTargetStack = [];
|
|
this._renderer = renderer;
|
|
}
|
|
/** called when dev wants to finish a render pass */
|
|
finishRenderPass() {
|
|
this.adaptor.finishRenderPass(this.renderTarget);
|
|
}
|
|
/**
|
|
* called when the renderer starts to render a scene.
|
|
* @param options
|
|
* @param options.target - the render target to render to
|
|
* @param options.clear - the clear mode to use. Can be true or a CLEAR number 'COLOR | DEPTH | STENCIL' 0b111
|
|
* @param options.clearColor - the color to clear to
|
|
* @param options.frame - the frame to render to
|
|
*/
|
|
renderStart({
|
|
target,
|
|
clear,
|
|
clearColor,
|
|
frame
|
|
}) {
|
|
this._renderTargetStack.length = 0;
|
|
this.push(
|
|
target,
|
|
clear,
|
|
clearColor,
|
|
frame
|
|
);
|
|
this.rootViewPort.copyFrom(this.viewport);
|
|
this.rootRenderTarget = this.renderTarget;
|
|
this.renderingToScreen = isRenderingToScreen(this.rootRenderTarget);
|
|
}
|
|
postrender() {
|
|
this.adaptor.postrender?.(this.rootRenderTarget);
|
|
}
|
|
/**
|
|
* Binding a render surface! This is the main function of the render target system.
|
|
* It will take the RenderSurface (which can be a texture, canvas, or render target) and bind it to the renderer.
|
|
* Once bound all draw calls will be rendered to the render surface.
|
|
*
|
|
* If a frame is not provide and the render surface is a texture, the frame of the texture will be used.
|
|
* @param renderSurface - the render surface to bind
|
|
* @param clear - the clear mode to use. Can be true or a CLEAR number 'COLOR | DEPTH | STENCIL' 0b111
|
|
* @param clearColor - the color to clear to
|
|
* @param frame - the frame to render to
|
|
* @returns the render target that was bound
|
|
*/
|
|
bind(renderSurface, clear = true, clearColor, frame) {
|
|
const renderTarget = this.getRenderTarget(renderSurface);
|
|
const didChange = this.renderTarget !== renderTarget;
|
|
this.renderTarget = renderTarget;
|
|
this.renderSurface = renderSurface;
|
|
const gpuRenderTarget = this.getGpuRenderTarget(renderTarget);
|
|
if (renderTarget.pixelWidth !== gpuRenderTarget.width || renderTarget.pixelHeight !== gpuRenderTarget.height) {
|
|
this.adaptor.resizeGpuRenderTarget(renderTarget);
|
|
gpuRenderTarget.width = renderTarget.pixelWidth;
|
|
gpuRenderTarget.height = renderTarget.pixelHeight;
|
|
}
|
|
const source = renderTarget.colorTexture;
|
|
const viewport = this.viewport;
|
|
const pixelWidth = source.pixelWidth;
|
|
const pixelHeight = source.pixelHeight;
|
|
if (!frame && renderSurface instanceof Texture) {
|
|
frame = renderSurface.frame;
|
|
}
|
|
if (frame) {
|
|
const resolution = source._resolution;
|
|
viewport.x = frame.x * resolution + 0.5 | 0;
|
|
viewport.y = frame.y * resolution + 0.5 | 0;
|
|
viewport.width = frame.width * resolution + 0.5 | 0;
|
|
viewport.height = frame.height * resolution + 0.5 | 0;
|
|
} else {
|
|
viewport.x = 0;
|
|
viewport.y = 0;
|
|
viewport.width = pixelWidth;
|
|
viewport.height = pixelHeight;
|
|
}
|
|
calculateProjection(
|
|
this.projectionMatrix,
|
|
0,
|
|
0,
|
|
viewport.width / source.resolution,
|
|
viewport.height / source.resolution,
|
|
!renderTarget.isRoot
|
|
);
|
|
this.adaptor.startRenderPass(renderTarget, clear, clearColor, viewport);
|
|
if (didChange) {
|
|
this.onRenderTargetChange.emit(renderTarget);
|
|
}
|
|
return renderTarget;
|
|
}
|
|
clear(target, clear = CLEAR.ALL, clearColor) {
|
|
if (!clear)
|
|
return;
|
|
if (target) {
|
|
target = this.getRenderTarget(target);
|
|
}
|
|
this.adaptor.clear(
|
|
target || this.renderTarget,
|
|
clear,
|
|
clearColor,
|
|
this.viewport
|
|
);
|
|
}
|
|
contextChange() {
|
|
this._gpuRenderTargetHash = /* @__PURE__ */ Object.create(null);
|
|
}
|
|
/**
|
|
* Push a render surface to the renderer. This will bind the render surface to the renderer,
|
|
* @param renderSurface - the render surface to push
|
|
* @param clear - the clear mode to use. Can be true or a CLEAR number 'COLOR | DEPTH | STENCIL' 0b111
|
|
* @param clearColor - the color to clear to
|
|
* @param frame - the frame to use when rendering to the render surface
|
|
*/
|
|
push(renderSurface, clear = CLEAR.ALL, clearColor, frame) {
|
|
const renderTarget = this.bind(renderSurface, clear, clearColor, frame);
|
|
this._renderTargetStack.push({
|
|
renderTarget,
|
|
frame
|
|
});
|
|
return renderTarget;
|
|
}
|
|
/** Pops the current render target from the renderer and restores the previous render target. */
|
|
pop() {
|
|
this._renderTargetStack.pop();
|
|
const currentRenderTargetData = this._renderTargetStack[this._renderTargetStack.length - 1];
|
|
this.bind(currentRenderTargetData.renderTarget, false, null, currentRenderTargetData.frame);
|
|
}
|
|
/**
|
|
* Gets the render target from the provide render surface. Eg if its a texture,
|
|
* it will return the render target for the texture.
|
|
* If its a render target, it will return the same render target.
|
|
* @param renderSurface - the render surface to get the render target for
|
|
* @returns the render target for the render surface
|
|
*/
|
|
getRenderTarget(renderSurface) {
|
|
if (renderSurface.isTexture) {
|
|
renderSurface = renderSurface.source;
|
|
}
|
|
return this._renderSurfaceToRenderTargetHash.get(renderSurface) ?? this._initRenderTarget(renderSurface);
|
|
}
|
|
/**
|
|
* Copies a render surface to another texture
|
|
* @param sourceRenderSurfaceTexture - the render surface to copy from
|
|
* @param destinationTexture - the texture to copy to
|
|
* @param originSrc - the origin of the copy
|
|
* @param originSrc.x - the x origin of the copy
|
|
* @param originSrc.y - the y origin of the copy
|
|
* @param size - the size of the copy
|
|
* @param size.width - the width of the copy
|
|
* @param size.height - the height of the copy
|
|
* @param originDest - the destination origin (top left to paste from!)
|
|
* @param originDest.x - the x origin of the paste
|
|
* @param originDest.y - the y origin of the paste
|
|
*/
|
|
copyToTexture(sourceRenderSurfaceTexture, destinationTexture, originSrc, size, originDest) {
|
|
if (originSrc.x < 0) {
|
|
size.width += originSrc.x;
|
|
originDest.x -= originSrc.x;
|
|
originSrc.x = 0;
|
|
}
|
|
if (originSrc.y < 0) {
|
|
size.height += originSrc.y;
|
|
originDest.y -= originSrc.y;
|
|
originSrc.y = 0;
|
|
}
|
|
const { pixelWidth, pixelHeight } = sourceRenderSurfaceTexture;
|
|
size.width = Math.min(size.width, pixelWidth - originSrc.x);
|
|
size.height = Math.min(size.height, pixelHeight - originSrc.y);
|
|
return this.adaptor.copyToTexture(
|
|
sourceRenderSurfaceTexture,
|
|
destinationTexture,
|
|
originSrc,
|
|
size,
|
|
originDest
|
|
);
|
|
}
|
|
/**
|
|
* ensures that we have a depth stencil buffer available to render to
|
|
* This is used by the mask system to make sure we have a stencil buffer.
|
|
*/
|
|
ensureDepthStencil() {
|
|
if (!this.renderTarget.stencil) {
|
|
this.renderTarget.stencil = true;
|
|
this.adaptor.startRenderPass(this.renderTarget, false, null, this.viewport);
|
|
}
|
|
}
|
|
/** nukes the render target system */
|
|
destroy() {
|
|
this._renderer = null;
|
|
this._renderSurfaceToRenderTargetHash.forEach((renderTarget, key) => {
|
|
if (renderTarget !== key) {
|
|
renderTarget.destroy();
|
|
}
|
|
});
|
|
this._renderSurfaceToRenderTargetHash.clear();
|
|
this._gpuRenderTargetHash = /* @__PURE__ */ Object.create(null);
|
|
}
|
|
_initRenderTarget(renderSurface) {
|
|
let renderTarget = null;
|
|
if (CanvasSource.test(renderSurface)) {
|
|
renderSurface = getCanvasTexture(renderSurface).source;
|
|
}
|
|
if (renderSurface instanceof RenderTarget) {
|
|
renderTarget = renderSurface;
|
|
} else if (renderSurface instanceof TextureSource) {
|
|
renderTarget = new RenderTarget({
|
|
colorTextures: [renderSurface]
|
|
});
|
|
if (CanvasSource.test(renderSurface.source.resource)) {
|
|
renderTarget.isRoot = true;
|
|
}
|
|
renderSurface.once("destroy", () => {
|
|
renderTarget.destroy();
|
|
this._renderSurfaceToRenderTargetHash.delete(renderSurface);
|
|
const gpuRenderTarget = this._gpuRenderTargetHash[renderTarget.uid];
|
|
if (gpuRenderTarget) {
|
|
this._gpuRenderTargetHash[renderTarget.uid] = null;
|
|
this.adaptor.destroyGpuRenderTarget(gpuRenderTarget);
|
|
}
|
|
});
|
|
}
|
|
this._renderSurfaceToRenderTargetHash.set(renderSurface, renderTarget);
|
|
return renderTarget;
|
|
}
|
|
getGpuRenderTarget(renderTarget) {
|
|
return this._gpuRenderTargetHash[renderTarget.uid] || (this._gpuRenderTargetHash[renderTarget.uid] = this.adaptor.initGpuRenderTarget(renderTarget));
|
|
}
|
|
}
|
|
|
|
export { RenderTargetSystem };
|
|
//# sourceMappingURL=RenderTargetSystem.mjs.map
|