Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Double buffer TAA’s accumulation buffer in preparation for last frame reprojection #6025

Merged
merged 1 commit into from Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 21 additions & 3 deletions extras/render-passes/render-pass-bloom.js
Expand Up @@ -24,7 +24,7 @@ class RenderPassBloom extends RenderPass {

constructor(device, sourceTexture, format) {
super(device);
this.sourceTexture = sourceTexture;
this._sourceTexture = sourceTexture;
this.textureFormat = format;

this.bloomRenderTarget = this.createRenderTarget(0);
Expand Down Expand Up @@ -87,7 +87,7 @@ class RenderPassBloom extends RenderPass {
const device = this.device;

// progressive downscale
let passSourceTexture = this.sourceTexture;
let passSourceTexture = this._sourceTexture;
for (let i = 0; i < numPasses; i++) {

const pass = new RenderPassDownsample(device, passSourceTexture);
Expand Down Expand Up @@ -124,11 +124,29 @@ class RenderPassBloom extends RenderPass {
this.destroyRenderTargets(1);
}

set sourceTexture(value) {
this._sourceTexture = value;

if (this.beforePasses.length > 0) {
const firstPass = this.beforePasses[0];

// change resize source
firstPass.options.resizeSource = value;

// change downsample source
firstPass.sourceTexture = value;
}
}

get sourceTexture() {
return this._sourceTexture;
}

frameUpdate() {
super.frameUpdate();

// create an appropriate amount of render passes
let numPasses = this.calcMipLevels(this.sourceTexture.width, this.sourceTexture.height, 2 ** this.lastMipLevel);
let numPasses = this.calcMipLevels(this._sourceTexture.width, this._sourceTexture.height, 2 ** this.lastMipLevel);
numPasses = Math.max(1, numPasses);

if (this.renderTargets.length !== numPasses) {
Expand Down
25 changes: 20 additions & 5 deletions extras/render-passes/render-pass-camera-frame.js
Expand Up @@ -23,6 +23,8 @@ class RenderPassCameraFrame extends RenderPass {

bloomPass;

taaPass;

_bloomEnabled = true;

_renderTargetScale = 1;
Expand Down Expand Up @@ -174,11 +176,10 @@ class RenderPassCameraFrame extends RenderPass {

// ------ TAA ------

let taaPass;
let sceneTextureWithTaa = sceneTexture;
if (options.taaEnabled) {
taaPass = new RenderPassTAA(device, sceneTexture);
sceneTextureWithTaa = taaPass.accumulationTexture;
this.taaPass = new RenderPassTAA(device, sceneTexture);
sceneTextureWithTaa = this.taaPass.accumulationTexture;
}

// ------ BLOOM GENERATION ------
Expand All @@ -190,7 +191,7 @@ class RenderPassCameraFrame extends RenderPass {

// create a compose pass, which combines the scene texture with the bloom texture
this.composePass = new RenderPassCompose(app.graphicsDevice);
this.composePass.sceneTexture = sceneTextureWithTaa;
// this.composePass.sceneTexture = sceneTextureWithTaa;
this.composePass.bloomTexture = this.bloomPass.bloomTexture;

// compose pass renders directly to target renderTarget
Expand All @@ -206,9 +207,23 @@ class RenderPassCameraFrame extends RenderPass {
afterPass.addLayers(composition, cameraComponent, lastAddedIndex, clearRenderTarget);

// use these prepared render passes in the order they should be executed
const allPasses = [this.scenePass, colorGrabPass, scenePassTransparent, taaPass, this.bloomPass, this.composePass, afterPass];
const allPasses = [this.scenePass, colorGrabPass, scenePassTransparent, this.taaPass, this.bloomPass, this.composePass, afterPass];
this.beforePasses = allPasses.filter(element => element !== undefined);
}

frameUpdate() {

super.frameUpdate();

// scene texture is either output of taa pass or the scene render target
const sceneTexture = this.taaPass?.update() ?? this._rt.colorBuffer;

// TAA accumulation buffer is double buffered, assign the current one to the follow up passes.
this.composePass.sceneTexture = sceneTexture;
if (this.bloomEnabled) {
this.bloomPass.sourceTexture = sceneTexture;
}
}
}

export { RenderPassCameraFrame };
90 changes: 52 additions & 38 deletions extras/render-passes/render-pass-taa.js
@@ -1,18 +1,29 @@
import {
FILTER_LINEAR, ADDRESS_CLAMP_TO_EDGE,
BLENDEQUATION_ADD, BLENDMODE_CONSTANT, BLENDMODE_ONE_MINUS_CONSTANT,
Color,
BlendState,
RenderPassShaderQuad,
Texture,
RenderTarget
} from "playcanvas";

class RenderPassTAA extends RenderPassShaderQuad {
/**
* @type {Texture}
* The index of the accumulation texture to render to.
*
* @type {number}
*/
accumulationTexture;
accumulationIndex = 0;

accumulationTexture = null;

/**
* @type {Texture[]}
*/
accumulationTextures = [];

/**
* @type {RenderTarget[]}
*/
accumulationRenderTargets = [];

constructor(device, sourceTexture) {
super(device);
Expand All @@ -21,18 +32,19 @@ class RenderPassTAA extends RenderPassShaderQuad {
this.shader = this.createQuadShader('TaaResolveShader', `

uniform sampler2D sourceTexture;
uniform sampler2D accumulationTexture;
varying vec2 uv0;

void main()
{
vec4 src = texture2D(sourceTexture, uv0);
gl_FragColor = src;
vec4 acc = texture2D(accumulationTexture, uv0);
gl_FragColor = mix(acc, src, 0.05);
}`
);

this.sourceTextureId = device.scope.resolve('sourceTexture');

this.blendState = new BlendState(true, BLENDEQUATION_ADD, BLENDMODE_CONSTANT, BLENDMODE_ONE_MINUS_CONSTANT);
this.accumulationTextureId = device.scope.resolve('accumulationTexture');

this.setup();
}
Expand All @@ -47,46 +59,48 @@ class RenderPassTAA extends RenderPassShaderQuad {

setup() {

const { device } = this;

// create the accumulation render target
const texture = new Texture(device, {
name: 'TAA Accumulation Texture',
width: 4,
height: 4,
format: this.sourceTexture.format,
mipmaps: false,
minFilter: FILTER_LINEAR,
magFilter: FILTER_LINEAR,
addressU: ADDRESS_CLAMP_TO_EDGE,
addressV: ADDRESS_CLAMP_TO_EDGE
});
this.accumulationTexture = texture;

const rt = new RenderTarget({
colorBuffer: texture,
depth: false
});
// double buffered accumulation render target
for (let i = 0; i < 2; ++i) {
this.accumulationTextures[i] = new Texture(this.device, {
name: `TAA-Accumulation-${i}`,
width: 4,
height: 4,
format: this.sourceTexture.format,
mipmaps: false,
minFilter: FILTER_LINEAR,
magFilter: FILTER_LINEAR,
addressU: ADDRESS_CLAMP_TO_EDGE,
addressV: ADDRESS_CLAMP_TO_EDGE
});

this.accumulationRenderTargets[i] = new RenderTarget({
colorBuffer: this.accumulationTextures[i],
depth: false
});
}

this.init(rt, {
this.accumulationTexture = this.accumulationTextures[0];
this.init(this.accumulationRenderTargets[0], {
resizeSource: this.sourceTexture
});

// clear it to black initially
this.setClearColor(Color.BLACK);
}

execute() {
this.sourceTextureId.setValue(this.sourceTexture);

// TODO: add this to the parent class
const blend = 0.05;
this.device.setBlendColor(blend, blend, blend, blend);
this.accumulationTextureId.setValue(this.accumulationTextures[1 - this.accumulationIndex]);

super.execute();
}

// called when the parent render pass gets added to the frame graph
update() {

// swap source and destination accumulation texture
this.accumulationIndex = 1 - this.accumulationIndex;
this.accumulationTexture = this.accumulationTextures[this.accumulationIndex];
this.renderTarget = this.accumulationRenderTargets[this.accumulationIndex];

// disable clearing
this.setClearColor();
return this.accumulationTexture;
}
}

Expand Down