UNPKG

@animech-public/playcanvas

Version:
365 lines (338 loc) 12.7 kB
import { Debug, DebugHelper } from '../../../core/debug.js'; import { StringIds } from '../../../core/string-ids.js'; import { WebgpuDebug } from './webgpu-debug.js'; const stringIds = new StringIds(); /** * Private class storing info about color buffer. * * @ignore */ class ColorAttachment { constructor() { /** * @type {GPUTextureFormat} * @private */ this.format = void 0; /** * @type {GPUTexture} * @private */ this.multisampledBuffer = void 0; } destroy() { var _this$multisampledBuf; (_this$multisampledBuf = this.multisampledBuffer) == null || _this$multisampledBuf.destroy(); this.multisampledBuffer = null; } } /** * A WebGPU implementation of the RenderTarget. * * @ignore */ class WebgpuRenderTarget { /** * @param {import('../render-target.js').RenderTarget} renderTarget - The render target owning * this implementation. */ constructor(renderTarget) { /** @type {boolean} */ this.initialized = false; /** * Unique key used by render pipeline creation * * @type {number} */ this.key = void 0; /** @type {ColorAttachment[]} */ this.colorAttachments = []; /** * @type {GPUTextureFormat} * @private */ this.depthFormat = void 0; /** @type {boolean} */ this.hasStencil = void 0; /** * @type {GPUTexture} * @private */ this.depthTexture = null; /** * True if the depthTexture is internally allocated / owned * * @type {boolean} */ this.depthTextureInternal = false; /** * 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 */ this.assignedColorTexture = null; /** * Render pass descriptor used when starting a render pass for this render target. * * @type {GPURenderPassDescriptor} * @private */ this.renderPassDescriptor = {}; this.renderTarget = renderTarget; // color formats are based on the textures if (renderTarget._colorBuffers) { renderTarget._colorBuffers.forEach((colorBuffer, index) => { this.setColorAttachment(index, undefined, colorBuffer.impl.format); }); } this.updateKey(); } /** * 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 {import('../webgpu/webgpu-graphics-device.js').WebgpuGraphicsDevice} device - The * graphics device. */ destroy(device) { this.initialized = false; if (this.depthTextureInternal) { var _this$depthTexture; (_this$depthTexture = this.depthTexture) == null || _this$depthTexture.destroy(); this.depthTexture = null; } this.assignedColorTexture = null; this.colorAttachments.forEach(colorAttachment => { colorAttachment.destroy(); }); this.colorAttachments.length = 0; } updateKey() { const rt = this.renderTarget; // key used by render pipeline creation let key = `${rt.samples}:${rt.depth ? this.depthFormat : 'nodepth'}`; this.colorAttachments.forEach(colorAttachment => { key += `:${colorAttachment.format}`; }); // convert string to a unique number this.key = stringIds.get(key); } setDepthFormat(depthFormat) { Debug.assert(depthFormat); this.depthFormat = depthFormat; this.hasStencil = depthFormat === 'depth24plus-stencil8'; } /** * 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 - The color buffer. */ assignColorTexture(gpuTexture) { Debug.assert(gpuTexture); this.assignedColorTexture = gpuTexture; const view = gpuTexture.createView(); DebugHelper.setLabel(view, 'Framebuffer.assignedColor'); // use it as render buffer or resolve target const colorAttachment = this.renderPassDescriptor.colorAttachments[0]; const samples = this.renderTarget.samples; if (samples > 1) { colorAttachment.resolveTarget = view; } else { colorAttachment.view = view; } // for main framebuffer, this is how the format is obtained this.setColorAttachment(0, undefined, gpuTexture.format); 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 {import('../webgpu/webgpu-graphics-device.js').WebgpuGraphicsDevice} device - The * graphics device. * @param {import('../render-target.js').RenderTarget} renderTarget - The render target. */ init(device, renderTarget) { var _renderTarget$_colorB, _renderTarget$_colorB2; const wgpu = device.wgpu; Debug.assert(!this.initialized); WebgpuDebug.memory(device); WebgpuDebug.validate(device); // initialize depth/stencil this.initDepthStencil(wgpu, renderTarget); // initialize color attachments this.renderPassDescriptor.colorAttachments = []; const count = (_renderTarget$_colorB = (_renderTarget$_colorB2 = renderTarget._colorBuffers) == null ? void 0 : _renderTarget$_colorB2.length) != null ? _renderTarget$_colorB : 1; for (let i = 0; i < count; ++i) { var _this$colorAttachment; const colorAttachment = this.initColor(wgpu, renderTarget, i); // default framebuffer, buffer gets assigned later const isDefaultFramebuffer = i === 0 && ((_this$colorAttachment = this.colorAttachments[0]) == null ? void 0 : _this$colorAttachment.format); // if we have a color buffer, or is the default framebuffer if (colorAttachment.view || isDefaultFramebuffer) { this.renderPassDescriptor.colorAttachments.push(colorAttachment); } } this.initialized = true; WebgpuDebug.end(device, { renderTarget }); WebgpuDebug.end(device, { renderTarget }); } initDepthStencil(wgpu, renderTarget) { const { samples, width, height, depth, depthBuffer } = renderTarget; // depth buffer that we render to (single or multi-sampled). We don't create resolve // depth buffer as we don't currently resolve it. This might need to change in the future. if (depth || depthBuffer) { // allocate depth buffer if not provided if (!depthBuffer) { // TODO: support rendering to 32bit depth without a stencil as well this.setDepthFormat('depth24plus-stencil8'); /** @type {GPUTextureDescriptor} */ const depthTextureDesc = { size: [width, height, 1], dimension: '2d', sampleCount: samples, format: this.depthFormat, usage: GPUTextureUsage.RENDER_ATTACHMENT }; if (samples > 1) { // enable multi-sampled depth texture to be a source of our shader based resolver in WebgpuResolver // TODO: we do not always need to resolve it, and so might consider this flag to be optional depthTextureDesc.usage |= GPUTextureUsage.TEXTURE_BINDING; } else { // single sampled depth buffer can be copied out (grab pass) // TODO: we should not enable this for shadow maps, as it is not needed depthTextureDesc.usage |= GPUTextureUsage.COPY_SRC; } // allocate depth buffer this.depthTexture = wgpu.createTexture(depthTextureDesc); this.depthTextureInternal = true; } else { // use provided depth buffer this.depthTexture = depthBuffer.impl.gpuTexture; this.setDepthFormat(depthBuffer.impl.format); } Debug.assert(this.depthTexture); DebugHelper.setLabel(this.depthTexture, `${renderTarget.name}.depthTexture`); // @type {GPURenderPassDepthStencilAttachment} this.renderPassDescriptor.depthStencilAttachment = { view: this.depthTexture.createView() }; } } /** * @private */ initColor(wgpu, renderTarget, index) { // Single-sampled color buffer gets passed in: // - for normal render target, constructor takes the color buffer as an option // - for the main framebuffer, the device supplies the buffer each frame // And so we only need to create multi-sampled color buffer if needed here. /** @type {GPURenderPassColorAttachment} */ const colorAttachment = {}; const { samples, width, height } = renderTarget; const colorBuffer = renderTarget.getColorBuffer(index); // view used to write to the color buffer (either by rendering to it, or resolving to it) let colorView = null; if (colorBuffer) { // render to top mip level in case of mip-mapped buffer const mipLevelCount = 1; // cubemap face view - face is a single 2d array layer in order [+X, -X, +Y, -Y, +Z, -Z] if (colorBuffer.cubemap) { colorView = colorBuffer.impl.createView({ dimension: '2d', baseArrayLayer: renderTarget.face, arrayLayerCount: 1, mipLevelCount }); } else { colorView = colorBuffer.impl.createView({ mipLevelCount }); } } // multi-sampled color buffer if (samples > 1) { var _this$colorAttachment2, _this$colorAttachment3; /** @type {GPUTextureDescriptor} */ const multisampledTextureDesc = { size: [width, height, 1], dimension: '2d', sampleCount: samples, format: (_this$colorAttachment2 = (_this$colorAttachment3 = this.colorAttachments[index]) == null ? void 0 : _this$colorAttachment3.format) != null ? _this$colorAttachment2 : colorBuffer.impl.format, usage: GPUTextureUsage.RENDER_ATTACHMENT }; // allocate multi-sampled color buffer 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 {import('../render-pass.js').RenderPass} renderPass - The render pass to start. */ setupForRenderPass(renderPass) { var _this$renderPassDescr, _this$renderPassDescr2; Debug.assert(this.renderPassDescriptor); const count = (_this$renderPassDescr = (_this$renderPassDescr2 = this.renderPassDescriptor.colorAttachments) == null ? void 0 : _this$renderPassDescr2.length) != null ? _this$renderPassDescr : 0; for (let i = 0; i < count; ++i) { const colorAttachment = this.renderPassDescriptor.colorAttachments[i]; const colorOps = renderPass.colorArrayOps[i]; colorAttachment.clearValue = 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.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 };