diff --git a/examples/src/examples/graphics/ambient-occlusion.example.mjs b/examples/src/examples/graphics/ambient-occlusion.example.mjs index dcca2ac0c16..99eca983c13 100644 --- a/examples/src/examples/graphics/ambient-occlusion.example.mjs +++ b/examples/src/examples/graphics/ambient-occlusion.example.mjs @@ -168,7 +168,7 @@ assetListLoader.load(() => { // ------ Custom render passes set up ------ const currentOptions = new pc.CameraFrameOptions(); - currentOptions.samples = 1; + currentOptions.samples = 4; currentOptions.sceneColorMap = false; currentOptions.ssaoType = pc.SSAOTYPE_LIGHTING; currentOptions.ssaoBlurEnabled = true; diff --git a/src/extras/render-passes/render-pass-camera-frame.js b/src/extras/render-passes/render-pass-camera-frame.js index 7743d96ba71..ab7d8201adf 100644 --- a/src/extras/render-passes/render-pass-camera-frame.js +++ b/src/extras/render-passes/render-pass-camera-frame.js @@ -1,10 +1,7 @@ import { LAYERID_SKYBOX, LAYERID_IMMEDIATE, TONEMAP_NONE, GAMMA_NONE } from '../../scene/constants.js'; import { - ADDRESS_CLAMP_TO_EDGE, - FILTER_LINEAR, - FILTER_NEAREST, - PIXELFORMAT_DEPTH, - PIXELFORMAT_RGBA8 + ADDRESS_CLAMP_TO_EDGE, FILTER_LINEAR, FILTER_NEAREST, + PIXELFORMAT_DEPTH, PIXELFORMAT_R32F, PIXELFORMAT_RGBA8 } from '../../platform/graphics/constants.js'; import { Texture } from '../../platform/graphics/texture.js'; import { RenderPass } from '../../platform/graphics/render-pass.js'; @@ -205,11 +202,19 @@ class RenderPassCameraFrame extends RenderPass { addressV: ADDRESS_CLAMP_TO_EDGE }); + // TODO: handle stencil support + let depthFormat = PIXELFORMAT_DEPTH; + if (options.prepassEnabled && device.isWebGPU && options.samples > 1) { + // on WebGPU the depth format cannot be resolved, so we need to use a float format in that case + // TODO: ideally we expose this using some option or similar public API to hide this implementation detail + depthFormat = PIXELFORMAT_R32F; + } + this.sceneDepth = new Texture(device, { name: 'SceneDepth', width: 4, height: 4, - format: PIXELFORMAT_DEPTH, // TODO: handle stencil support + format: depthFormat, mipmaps: false, minFilter: FILTER_NEAREST, magFilter: FILTER_NEAREST, @@ -271,8 +276,8 @@ class RenderPassCameraFrame extends RenderPass { const { app, device, cameraComponent } = this; const { scene, renderer } = app; - // ssao needs resolved depth - const resolveDepth = this.options.ssaoType !== SSAOTYPE_NONE; + // ssao & taa need resolved depth + const resolveDepth = this.options.ssaoType !== SSAOTYPE_NONE || this.options.taaEnabled; this.prePass = new RenderPassPrepass(device, scene, renderer, cameraComponent, this.sceneDepth, resolveDepth, this.sceneOptions, options.samples); } diff --git a/src/platform/graphics/render-target.js b/src/platform/graphics/render-target.js index 977e600c5f6..1feeb822105 100644 --- a/src/platform/graphics/render-target.js +++ b/src/platform/graphics/render-target.js @@ -1,6 +1,6 @@ import { Debug } from '../../core/debug.js'; import { TRACEID_RENDER_TARGET_ALLOC } from '../../core/constants.js'; -import { PIXELFORMAT_DEPTH, PIXELFORMAT_DEPTHSTENCIL, isSrgbPixelFormat } from './constants.js'; +import { PIXELFORMAT_DEPTH, PIXELFORMAT_DEPTHSTENCIL, PIXELFORMAT_R32F, isSrgbPixelFormat } from './constants.js'; import { DebugGraphics } from './debug-graphics.js'; import { GraphicsDevice } from './graphics-device.js'; @@ -135,6 +135,19 @@ class RenderTarget { Debug.assert(!(options instanceof GraphicsDevice), 'pc.RenderTarget constructor no longer accepts GraphicsDevice parameter.'); this.id = id++; + // device, from one of the buffers + const device = options.colorBuffer?.device || options.depthBuffer?.device || options.graphicsDevice; + Debug.assert(device, 'Failed to obtain the device, colorBuffer nor depthBuffer store it.'); + this._device = device; + + // samples + const { maxSamples } = this._device; + this._samples = Math.min(options.samples ?? 1, maxSamples); + if (device.isWebGPU) { + // WebGPU only supports values of 1 or 4 for samples + this._samples = this._samples > 1 ? maxSamples : 1; + } + // Use the single colorBuffer in the colorBuffers array. This allows us to always just use the array internally. this._colorBuffer = options.colorBuffer; if (options.colorBuffer) { @@ -153,6 +166,11 @@ class RenderTarget { } else if (format === PIXELFORMAT_DEPTHSTENCIL) { this._depth = true; this._stencil = true; + } else if (format === PIXELFORMAT_R32F && this._depthBuffer.device.isWebGPU && this._samples > 1) { + // on WebGPU, when multisampling is enabled, we use R32F format for the specified buffer, + // which we can resolve depth to using a shader + this._depth = true; + this._stencil = false; } else { Debug.warn('Incorrect depthBuffer format. Must be pc.PIXELFORMAT_DEPTH or pc.PIXELFORMAT_DEPTHSTENCIL'); this._depth = false; @@ -175,19 +193,6 @@ class RenderTarget { } } - // device, from one of the buffers - const device = this._colorBuffer?.device || this._depthBuffer?.device || options.graphicsDevice; - Debug.assert(device, 'Failed to obtain the device, colorBuffer nor depthBuffer store it.'); - this._device = device; - - const { maxSamples } = this._device; - this._samples = Math.min(options.samples ?? 1, maxSamples); - - // WebGPU only supports values of 1 or 4 for samples - if (device.isWebGPU) { - this._samples = this._samples > 1 ? maxSamples : 1; - } - this.autoResolve = options.autoResolve ?? true; // use specified name, otherwise get one from color or depth buffer diff --git a/src/platform/graphics/webgpu/webgpu-graphics-device.js b/src/platform/graphics/webgpu/webgpu-graphics-device.js index 4d4ac9f6ef3..120a20a82f9 100644 --- a/src/platform/graphics/webgpu/webgpu-graphics-device.js +++ b/src/platform/graphics/webgpu/webgpu-graphics-device.js @@ -713,6 +713,22 @@ class WebgpuGraphicsDevice extends GraphicsDevice { // each render pass can use different number of bind groups this.bindGroupFormats.length = 0; + // resolve depth if needed after the pass has finished + const target = this.renderTarget; + if (target) { + + // resolve depth buffer (stencil resolve is not yet implemented) + if (target.depthBuffer && renderPass.depthStencilOps.resolveDepth) { + if (renderPass.samples > 1 && target.autoResolve) { + const depthAttachment = target.impl.depthAttachment; + const destTexture = target.depthBuffer.impl.gpuTexture; + if (depthAttachment && destTexture) { + this.resolver.resolveDepth(this.commandEncoder, depthAttachment.multisampledDepthBuffer, destTexture); + } + } + } + } + // generate mipmaps using the same command buffer encoder for (let i = 0; i < renderPass.colorArrayOps.length; i++) { const colorOps = renderPass.colorArrayOps[i]; diff --git a/src/platform/graphics/webgpu/webgpu-render-target.js b/src/platform/graphics/webgpu/webgpu-render-target.js index f1ecf71be71..dcaec6b76e2 100644 --- a/src/platform/graphics/webgpu/webgpu-render-target.js +++ b/src/platform/graphics/webgpu/webgpu-render-target.js @@ -330,8 +330,14 @@ class WebgpuRenderTarget { if (samples > 1) { // create a multi-sampled depth buffer for the provided depth buffer + // single-sampled depthBuffer.impl.format can be R32F in some cases, but that cannot be used as a depth + // buffer, only as a texture to resolve it to. We always use depth24plus-stencil8 for msaa depth buffers. + const depthFormat = 'depth24plus-stencil8'; + this.depthAttachment.format = depthFormat; + this.depthAttachment.hasStencil = depthFormat === 'depth24plus-stencil8'; + // key for matching multi-sampled depth buffer - const key = `${depthBuffer.id}:${width}:${height}:${samples}:${depthBuffer.impl.format}`; + const key = `${depthBuffer.id}:${width}:${height}:${samples}:${depthFormat}`; // check if we have already allocated a multi-sampled depth buffer for the depth buffer const msTextures = getMultisampledTextureCache(device); @@ -343,8 +349,10 @@ class WebgpuRenderTarget { size: [width, height, 1], dimension: '2d', sampleCount: samples, - format: depthBuffer.impl.format, - usage: GPUTextureUsage.RENDER_ATTACHMENT + format: depthFormat, + usage: GPUTextureUsage.RENDER_ATTACHMENT | + // if msaa and resolve targets are different formats, we need to be able to bind the msaa target as a texture for manual shader resolve + (depthFormat !== depthBuffer.impl.format ? GPUTextureUsage.TEXTURE_BINDING : 0) }; // allocate multi-sampled depth buffer