playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
364 lines (363 loc) • 12.3 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 { 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
};