UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

361 lines (360 loc) 14.2 kB
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 };