UNPKG

playcanvas

Version:

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

364 lines (363 loc) 12.3 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 { hash32Fnv1a } from "../../../core/hash.js"; import { array } from "../../../core/array-utils.js"; import { TRACEID_RENDERPIPELINE_ALLOC } from "../../../core/constants.js"; import { WebgpuVertexBufferLayout } from "./webgpu-vertex-buffer-layout.js"; import { WebgpuDebug } from "./webgpu-debug.js"; import { WebgpuPipeline } from "./webgpu-pipeline.js"; import { DebugGraphics } from "../debug-graphics.js"; import { bindGroupNames, PRIMITIVE_LINESTRIP, PRIMITIVE_TRISTRIP } from "../constants.js"; let _pipelineId = 0; const _primitiveTopology = [ "point-list", // PRIMITIVE_POINTS "line-list", // PRIMITIVE_LINES void 0, // PRIMITIVE_LINELOOP "line-strip", // PRIMITIVE_LINESTRIP "triangle-list", // PRIMITIVE_TRIANGLES "triangle-strip", // PRIMITIVE_TRISTRIP void 0 // PRIMITIVE_TRIFAN ]; const _blendOperation = [ "add", // BLENDEQUATION_ADD "subtract", // BLENDEQUATION_SUBTRACT "reverse-subtract", // BLENDEQUATION_REVERSE_SUBTRACT "min", // BLENDEQUATION_MIN "max" // BLENDEQUATION_MAX ]; const _blendFactor = [ "zero", // BLENDMODE_ZERO "one", // BLENDMODE_ONE "src", // BLENDMODE_SRC_COLOR "one-minus-src", // BLENDMODE_ONE_MINUS_SRC_COLOR "dst", // BLENDMODE_DST_COLOR "one-minus-dst", // BLENDMODE_ONE_MINUS_DST_COLOR "src-alpha", // BLENDMODE_SRC_ALPHA "src-alpha-saturated", // BLENDMODE_SRC_ALPHA_SATURATE "one-minus-src-alpha", // BLENDMODE_ONE_MINUS_SRC_ALPHA "dst-alpha", // BLENDMODE_DST_ALPHA "one-minus-dst-alpha", // BLENDMODE_ONE_MINUS_DST_ALPHA "constant", // BLENDMODE_CONSTANT "one-minus-constant" // BLENDMODE_ONE_MINUS_CONSTANT ]; const _compareFunction = [ "never", // FUNC_NEVER "less", // FUNC_LESS "equal", // FUNC_EQUAL "less-equal", // FUNC_LESSEQUAL "greater", // FUNC_GREATER "not-equal", // FUNC_NOTEQUAL "greater-equal", // FUNC_GREATEREQUAL "always" // FUNC_ALWAYS ]; const _cullModes = [ "none", // CULLFACE_NONE "back", // CULLFACE_BACK "front" // CULLFACE_FRONT ]; const _frontFace = [ "ccw", // FRONTFACE_CCW "cw" // FRONTFACE_CW ]; const _stencilOps = [ "keep", // STENCILOP_KEEP "zero", // STENCILOP_ZERO "replace", // STENCILOP_REPLACE "increment-clamp", // STENCILOP_INCREMENT "increment-wrap", // STENCILOP_INCREMENTWRAP "decrement-clamp", // STENCILOP_DECREMENT "decrement-wrap", // STENCILOP_DECREMENTWRAP "invert" // STENCILOP_INVERT ]; const _indexFormat = [ "", // INDEXFORMAT_UINT8 "uint16", // INDEXFORMAT_UINT16 "uint32" // INDEXFORMAT_UINT32 ]; class CacheEntry { constructor() { /** * Render pipeline * * @type {GPURenderPipeline} * @private */ __publicField(this, "pipeline"); /** * The full array of hashes used to lookup the pipeline, used in case of hash collision. * * @type {Uint32Array} */ __publicField(this, "hashes"); } } class WebgpuRenderPipeline extends WebgpuPipeline { constructor(device) { super(device); __publicField(this, "lookupHashes", new Uint32Array(15)); this.vertexBufferLayout = new WebgpuVertexBufferLayout(); this.cache = /* @__PURE__ */ new Map(); } /** * @param {object} primitive - The primitive. * @param {VertexFormat} vertexFormat0 - The first vertex format. * @param {VertexFormat} vertexFormat1 - The second vertex format. * @param {number|undefined} ibFormat - The index buffer format. * @param {Shader} shader - The shader. * @param {RenderTarget} renderTarget - The render target. * @param {BindGroupFormat[]} bindGroupFormats - An array of bind group formats. * @param {BlendState} blendState - The blend state. * @param {DepthState} depthState - The depth state. * @param {number} cullMode - The cull mode. * @param {boolean} stencilEnabled - Whether stencil is enabled. * @param {StencilParameters} stencilFront - The stencil state for front faces. * @param {StencilParameters} stencilBack - The stencil state for back faces. * @param {number} frontFace - The front face. * @returns {GPURenderPipeline} Returns the render pipeline. * @private */ get(primitive, vertexFormat0, vertexFormat1, ibFormat, shader, renderTarget, bindGroupFormats, blendState, depthState, cullMode, stencilEnabled, stencilFront, stencilBack, frontFace) { Debug.assert(bindGroupFormats.length <= 3); const primitiveType = primitive.type; if (ibFormat && primitiveType !== PRIMITIVE_LINESTRIP && primitiveType !== PRIMITIVE_TRISTRIP) { ibFormat = void 0; } Debug.assert(bindGroupFormats[0], `BindGroup with index 0 [${bindGroupNames[0]}] is not set.`); Debug.assert(bindGroupFormats[1], `BindGroup with index 1 [${bindGroupNames[1]}] is not set.`); Debug.assert(bindGroupFormats[2], `BindGroup with index 2 [${bindGroupNames[2]}] is not set.`); const lookupHashes = this.lookupHashes; lookupHashes[0] = primitiveType; lookupHashes[1] = shader.id; lookupHashes[2] = cullMode; lookupHashes[3] = depthState.key; lookupHashes[4] = blendState.key; lookupHashes[5] = vertexFormat0?.renderingHash ?? 0; lookupHashes[6] = vertexFormat1?.renderingHash ?? 0; lookupHashes[7] = renderTarget.impl.key; lookupHashes[8] = bindGroupFormats[0]?.key ?? 0; lookupHashes[9] = bindGroupFormats[1]?.key ?? 0; lookupHashes[10] = bindGroupFormats[2]?.key ?? 0; lookupHashes[11] = stencilEnabled ? stencilFront.key : 0; lookupHashes[12] = stencilEnabled ? stencilBack.key : 0; lookupHashes[13] = ibFormat ?? 0; lookupHashes[14] = frontFace; const hash = hash32Fnv1a(lookupHashes); let cacheEntries = this.cache.get(hash); if (cacheEntries) { for (let i = 0; i < cacheEntries.length; i++) { const entry = cacheEntries[i]; if (array.equals(entry.hashes, lookupHashes)) { return entry.pipeline; } } } const primitiveTopology = _primitiveTopology[primitiveType]; Debug.assert(primitiveTopology, "Unsupported primitive topology", primitive); const pipelineLayout = this.getPipelineLayout(bindGroupFormats); const vertexBufferLayout = this.vertexBufferLayout.get(vertexFormat0, vertexFormat1); const cacheEntry = new CacheEntry(); cacheEntry.hashes = new Uint32Array(lookupHashes); cacheEntry.pipeline = this.create( primitiveTopology, ibFormat, shader, renderTarget, pipelineLayout, blendState, depthState, vertexBufferLayout, cullMode, stencilEnabled, stencilFront, stencilBack, frontFace ); if (cacheEntries) { cacheEntries.push(cacheEntry); } else { cacheEntries = [cacheEntry]; } this.cache.set(hash, cacheEntries); return cacheEntry.pipeline; } getBlend(blendState) { let blend; if (blendState.blend) { blend = { color: { operation: _blendOperation[blendState.colorOp], srcFactor: _blendFactor[blendState.colorSrcFactor], dstFactor: _blendFactor[blendState.colorDstFactor] }, alpha: { operation: _blendOperation[blendState.alphaOp], srcFactor: _blendFactor[blendState.alphaSrcFactor], dstFactor: _blendFactor[blendState.alphaDstFactor] } }; Debug.assert(blend.color.srcFactor !== void 0); Debug.assert(blend.color.dstFactor !== void 0); Debug.assert(blend.alpha.srcFactor !== void 0); Debug.assert(blend.alpha.dstFactor !== void 0); } return blend; } /** * @param {DepthState} depthState - The depth state. * @param {RenderTarget} renderTarget - The render target. * @param {boolean} stencilEnabled - Whether stencil is enabled. * @param {StencilParameters} stencilFront - The stencil state for front faces. * @param {StencilParameters} stencilBack - The stencil state for back faces. * @param {string} primitiveTopology - The primitive topology. * @returns {object} Returns the depth stencil state. * @private */ getDepthStencil(depthState, renderTarget, stencilEnabled, stencilFront, stencilBack, primitiveTopology) { let depthStencil; const { depth, stencil } = renderTarget; if (depth || stencil) { depthStencil = { format: renderTarget.impl.depthAttachment.format }; if (depth) { depthStencil.depthWriteEnabled = depthState.write; depthStencil.depthCompare = _compareFunction[depthState.func]; const biasAllowed = primitiveTopology === "triangle-list" || primitiveTopology === "triangle-strip"; depthStencil.depthBias = biasAllowed ? depthState.depthBias : 0; depthStencil.depthBiasSlopeScale = biasAllowed ? depthState.depthBiasSlope : 0; } else { depthStencil.depthWriteEnabled = false; depthStencil.depthCompare = "always"; } if (stencil && stencilEnabled) { depthStencil.stencilReadMas = stencilFront.readMask; depthStencil.stencilWriteMask = stencilFront.writeMask; depthStencil.stencilFront = { compare: _compareFunction[stencilFront.func], failOp: _stencilOps[stencilFront.fail], passOp: _stencilOps[stencilFront.zpass], depthFailOp: _stencilOps[stencilFront.zfail] }; depthStencil.stencilBack = { compare: _compareFunction[stencilBack.func], failOp: _stencilOps[stencilBack.fail], passOp: _stencilOps[stencilBack.zpass], depthFailOp: _stencilOps[stencilBack.zfail] }; } } return depthStencil; } create(primitiveTopology, ibFormat, shader, renderTarget, pipelineLayout, blendState, depthState, vertexBufferLayout, cullMode, stencilEnabled, stencilFront, stencilBack, frontFace) { const wgpu = this.device.wgpu; const webgpuShader = shader.impl; const desc = { vertex: { module: webgpuShader.getVertexShaderModule(), entryPoint: webgpuShader.vertexEntryPoint, buffers: vertexBufferLayout }, primitive: { topology: primitiveTopology, frontFace: _frontFace[frontFace], cullMode: _cullModes[cullMode] }, depthStencil: this.getDepthStencil(depthState, renderTarget, stencilEnabled, stencilFront, stencilBack, primitiveTopology), multisample: { count: renderTarget.samples }, // uniform / texture binding layout layout: pipelineLayout }; if (ibFormat) { desc.primitive.stripIndexFormat = _indexFormat[ibFormat]; } desc.fragment = { module: webgpuShader.getFragmentShaderModule(), entryPoint: webgpuShader.fragmentEntryPoint, targets: [] }; const colorAttachments = renderTarget.impl.colorAttachments; if (colorAttachments.length > 0) { let writeMask = 0; if (blendState.redWrite) writeMask |= GPUColorWrite.RED; if (blendState.greenWrite) writeMask |= GPUColorWrite.GREEN; if (blendState.blueWrite) writeMask |= GPUColorWrite.BLUE; if (blendState.alphaWrite) writeMask |= GPUColorWrite.ALPHA; const blend = this.getBlend(blendState); colorAttachments.forEach((attachment) => { desc.fragment.targets.push({ format: attachment.format, writeMask, blend }); }); } WebgpuDebug.validate(this.device); _pipelineId++; DebugHelper.setLabel(desc, `RenderPipelineDescr-${_pipelineId}`); const pipeline = wgpu.createRenderPipeline(desc); DebugHelper.setLabel(pipeline, `RenderPipeline-${_pipelineId}`); Debug.trace(TRACEID_RENDERPIPELINE_ALLOC, `Alloc: Id ${_pipelineId}, stack: ${DebugGraphics.toString()}`, desc); WebgpuDebug.end(this.device, "RenderPipeline creation", { renderPipeline: this, desc, shader }); return pipeline; } } export { WebgpuRenderPipeline };