playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
361 lines (360 loc) • 14.2 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 { Debug, DebugHelper } from "../../../core/debug.js";
import { StringIds } from "../../../core/string-ids.js";
import { getMultisampledTextureCache } from "../multi-sampled-texture-cache.js";
import { WebgpuDebug } from "./webgpu-debug.js";
const stringIds = new StringIds();
class ColorAttachment {
constructor() {
/**
* @type {GPUTextureFormat}
* @private
*/
__publicField(this, "format");
/**
* @type {GPUTexture}
* @private
*/
__publicField(this, "multisampledBuffer");
}
destroy(device) {
device.deferDestroy(this.multisampledBuffer);
this.multisampledBuffer = null;
}
}
class DepthAttachment {
/**
* @param {string} gpuFormat - The WebGPU format (GPUTextureFormat).
*/
constructor(gpuFormat) {
/**
* @type {GPUTextureFormat}
* @private
*/
__publicField(this, "format");
/** @type {boolean} */
__publicField(this, "hasStencil");
/**
* @type {GPUTexture|null}
* @private
*/
__publicField(this, "depthTexture", null);
/**
* True if the depthTexture is internally allocated / owned
*/
__publicField(this, "depthTextureInternal", false);
/**
* Multi-sampled depth buffer allocated over the user provided depth buffer.
*
* @type {GPUTexture|null}
* @private
*/
__publicField(this, "multisampledDepthBuffer", null);
/**
* Key used to store multisampledDepthBuffer in the cache.
*/
__publicField(this, "multisampledDepthBufferKey");
Debug.assert(gpuFormat);
this.format = gpuFormat;
this.hasStencil = gpuFormat === "depth24plus-stencil8";
}
destroy(device) {
if (this.depthTextureInternal) {
device.deferDestroy(this.depthTexture);
this.depthTexture = null;
}
if (this.multisampledDepthBuffer) {
this.multisampledDepthBuffer = null;
getMultisampledTextureCache(device).release(this.multisampledDepthBufferKey);
}
}
}
class WebgpuRenderTarget {
/**
* @param {RenderTarget} renderTarget - The render target owning this implementation.
*/
constructor(renderTarget) {
/** @type {boolean} */
__publicField(this, "initialized", false);
/**
* Unique key used by render pipeline creation
*
* @type {number}
*/
__publicField(this, "key");
/** @type {ColorAttachment[]} */
__publicField(this, "colorAttachments", []);
/** @type {DepthAttachment|null} */
__publicField(this, "depthAttachment", null);
/**
* Texture assigned each frame, and not owned by this render target. This is used on the
* framebuffer to assign per frame texture obtained from the context.
*
* @type {GPUTexture}
* @private
*/
__publicField(this, "assignedColorTexture", null);
/**
* Render pass descriptor used when starting a render pass for this render target.
*
* @type {GPURenderPassDescriptor}
* @private
*/
__publicField(this, "renderPassDescriptor", {});
/**
* True if this is the backbuffer of the device.
*/
__publicField(this, "isBackbuffer", false);
this.renderTarget = renderTarget;
}
/**
* Release associated resources. Note that this needs to leave this instance in a state where
* it can be re-initialized again, which is used by render target resizing.
*
* @param {WebgpuGraphicsDevice} device - The graphics device.
*/
destroy(device) {
this.initialized = false;
this.assignedColorTexture = null;
this.colorAttachments.forEach((colorAttachment) => {
colorAttachment.destroy(device);
});
this.colorAttachments.length = 0;
this.depthAttachment?.destroy(device);
this.depthAttachment = null;
}
updateKey() {
const rt = this.renderTarget;
let key = `${rt.samples}:${this.depthAttachment ? this.depthAttachment.format : "nodepth"}`;
this.colorAttachments.forEach((colorAttachment) => {
key += `:${colorAttachment.format}`;
});
this.key = stringIds.get(key);
}
/**
* Assign a color buffer. This allows the color buffer of the main framebuffer
* to be swapped each frame to a buffer provided by the context.
*
* @param {any} gpuTexture - // `GPUTexture`; using `any` to avoid exporting WebGPU types in published typings.
* @param {any} viewFormat - // `GPUTextureFormat`; using `any` to avoid exporting WebGPU types in published typings (may differ from texture storage for sRGB).
*/
assignColorTexture(gpuTexture, viewFormat) {
Debug.assert(gpuTexture);
this.assignedColorTexture = gpuTexture;
const wgpuDevice = (
/** @type {WebgpuGraphicsDevice} */
this.renderTarget.device
);
const xrViewDesc = wgpuDevice?.xrColorTextureViewDescriptor;
const xrSlice = xrViewDesc && gpuTexture === wgpuDevice.xrColorTexture;
const view = gpuTexture.createView(
xrSlice ? xrViewDesc : { format: viewFormat }
);
DebugHelper.setLabel(view, xrSlice ? "Framebuffer.xrColorTextureView" : "Framebuffer.contextColorTextureView");
const colorAttachment = this.renderPassDescriptor.colorAttachments[0];
const samples = this.renderTarget.samples;
if (samples > 1) {
colorAttachment.resolveTarget = view;
} else {
colorAttachment.view = view;
}
this.setColorAttachment(0, void 0, viewFormat);
this.updateKey();
}
setColorAttachment(index, multisampledBuffer, format) {
if (!this.colorAttachments[index]) {
this.colorAttachments[index] = new ColorAttachment();
}
if (multisampledBuffer) {
this.colorAttachments[index].multisampledBuffer = multisampledBuffer;
}
if (format) {
this.colorAttachments[index].format = format;
}
}
/**
* Initialize render target for rendering one time.
*
* @param {WebgpuGraphicsDevice} device - The graphics device.
* @param {RenderTarget} renderTarget - The render target.
*/
init(device, renderTarget) {
const wgpu = device.wgpu;
Debug.assert(!this.initialized);
WebgpuDebug.memory(device);
WebgpuDebug.validate(device);
this.initDepthStencil(device, wgpu, renderTarget);
if (renderTarget._colorBuffers) {
renderTarget._colorBuffers.forEach((colorBuffer, index) => {
this.setColorAttachment(index, void 0, colorBuffer.impl.format);
});
}
this.renderPassDescriptor.colorAttachments = [];
const count = this.isBackbuffer ? 1 : renderTarget._colorBuffers?.length ?? 0;
for (let i = 0; i < count; ++i) {
const colorAttachment = this.initColor(device, wgpu, renderTarget, i);
const isDefaultFramebuffer = i === 0 && this.colorAttachments[0]?.format;
if (colorAttachment.view || isDefaultFramebuffer) {
this.renderPassDescriptor.colorAttachments.push(colorAttachment);
}
}
this.updateKey();
this.initialized = true;
WebgpuDebug.end(device, "RenderTarget initialization", { renderTarget });
WebgpuDebug.end(device, "RenderTarget initialization", { renderTarget });
}
initDepthStencil(device, wgpu, renderTarget) {
const { samples, width, height, depth, depthBuffer } = renderTarget;
if (depth || depthBuffer) {
let renderingView;
if (!depthBuffer) {
this.depthAttachment = new DepthAttachment("depth24plus-stencil8");
const depthTextureDesc = {
size: [width, height, 1],
dimension: "2d",
sampleCount: samples,
format: this.depthAttachment.format,
usage: GPUTextureUsage.RENDER_ATTACHMENT
};
if (samples > 1) {
depthTextureDesc.usage |= GPUTextureUsage.TEXTURE_BINDING;
} else {
depthTextureDesc.usage |= GPUTextureUsage.COPY_SRC;
}
const depthTexture = wgpu.createTexture(depthTextureDesc);
DebugHelper.setLabel(depthTexture, `${renderTarget.name}.autoDepthTexture`);
this.depthAttachment.depthTexture = depthTexture;
this.depthAttachment.depthTextureInternal = true;
renderingView = depthTexture.createView();
DebugHelper.setLabel(renderingView, `${renderTarget.name}.autoDepthView`);
} else {
this.depthAttachment = new DepthAttachment(depthBuffer.impl.format);
if (samples > 1) {
const depthFormat = "depth24plus-stencil8";
this.depthAttachment.format = depthFormat;
this.depthAttachment.hasStencil = depthFormat === "depth24plus-stencil8";
const key = `${depthBuffer.id}:${width}:${height}:${samples}:${depthFormat}`;
const msTextures = getMultisampledTextureCache(device);
let msDepthTexture = msTextures.get(key);
if (!msDepthTexture) {
const multisampledDepthDesc = {
size: [width, height, 1],
dimension: "2d",
sampleCount: samples,
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)
};
msDepthTexture = wgpu.createTexture(multisampledDepthDesc);
DebugHelper.setLabel(msDepthTexture, `${renderTarget.name}.multisampledDepth`);
msTextures.set(key, msDepthTexture);
}
this.depthAttachment.multisampledDepthBuffer = msDepthTexture;
this.depthAttachment.multisampledDepthBufferKey = key;
renderingView = msDepthTexture.createView();
DebugHelper.setLabel(renderingView, `${renderTarget.name}.multisampledDepthView`);
} else {
const depthTexture = depthBuffer.impl.gpuTexture;
this.depthAttachment.depthTexture = depthTexture;
renderingView = depthTexture.createView();
DebugHelper.setLabel(renderingView, `${renderTarget.name}.depthView`);
}
}
Debug.assert(renderingView);
this.renderPassDescriptor.depthStencilAttachment = {
view: renderingView
};
}
}
/**
* @param {WebgpuGraphicsDevice} device - The graphics device.
* @param {GPUDevice} wgpu - The WebGPU device.
* @param {RenderTarget} renderTarget - The render target.
* @param {number} index - The color buffer index.
* @returns {GPURenderPassColorAttachment} The color attachment.
* @private
*/
initColor(device, wgpu, renderTarget, index) {
const colorAttachment = {};
const { samples, width, height, mipLevel } = renderTarget;
const colorBuffer = renderTarget.getColorBuffer(index);
let colorView = null;
if (colorBuffer) {
const mipLevelCount = 1;
if (colorBuffer.cubemap) {
colorView = colorBuffer.impl.createView({
dimension: "2d",
baseArrayLayer: renderTarget.face,
arrayLayerCount: 1,
mipLevelCount,
baseMipLevel: mipLevel
});
} else {
colorView = colorBuffer.impl.createView({
mipLevelCount,
baseMipLevel: mipLevel
});
}
}
if (samples > 1) {
const format = this.isBackbuffer ? this.colorAttachments[index]?.format ?? device.backBufferViewFormat : colorBuffer.impl.format;
const multisampledTextureDesc = {
size: [width, height, 1],
dimension: "2d",
sampleCount: samples,
format,
usage: GPUTextureUsage.RENDER_ATTACHMENT
};
const multisampledColorBuffer = wgpu.createTexture(multisampledTextureDesc);
DebugHelper.setLabel(multisampledColorBuffer, `${renderTarget.name}.multisampledColor`);
this.setColorAttachment(index, multisampledColorBuffer, multisampledTextureDesc.format);
colorAttachment.view = multisampledColorBuffer.createView();
DebugHelper.setLabel(colorAttachment.view, `${renderTarget.name}.multisampledColorView`);
colorAttachment.resolveTarget = colorView;
} else {
colorAttachment.view = colorView;
}
return colorAttachment;
}
/**
* Update WebGPU render pass descriptor by RenderPass settings.
*
* @param {RenderPass} renderPass - The render pass to start.
* @param {RenderTarget} renderTarget - The render target to render to.
*/
setupForRenderPass(renderPass, renderTarget) {
Debug.assert(this.renderPassDescriptor);
const count = this.renderPassDescriptor.colorAttachments?.length ?? 0;
for (let i = 0; i < count; ++i) {
const colorAttachment = this.renderPassDescriptor.colorAttachments[i];
const colorOps = renderPass.colorArrayOps[i];
const srgb = renderTarget.isColorBufferSrgb(i);
colorAttachment.clearValue = srgb ? colorOps.clearValueLinear : colorOps.clearValue;
colorAttachment.loadOp = colorOps.clear ? "clear" : "load";
colorAttachment.storeOp = colorOps.store ? "store" : "discard";
}
const depthAttachment = this.renderPassDescriptor.depthStencilAttachment;
if (depthAttachment) {
depthAttachment.depthClearValue = renderPass.depthStencilOps.clearDepthValue;
depthAttachment.depthLoadOp = renderPass.depthStencilOps.clearDepth ? "clear" : "load";
depthAttachment.depthStoreOp = renderPass.depthStencilOps.storeDepth ? "store" : "discard";
depthAttachment.depthReadOnly = false;
if (this.depthAttachment.hasStencil) {
depthAttachment.stencilClearValue = renderPass.depthStencilOps.clearStencilValue;
depthAttachment.stencilLoadOp = renderPass.depthStencilOps.clearStencil ? "clear" : "load";
depthAttachment.stencilStoreOp = renderPass.depthStencilOps.storeStencil ? "store" : "discard";
depthAttachment.stencilReadOnly = false;
}
}
}
loseContext() {
this.initialized = false;
}
resolve(device, target, color, depth) {
}
}
export {
WebgpuRenderTarget
};