playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
299 lines (298 loc) • 8.38 kB
JavaScript
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 { WebgpuPipeline } from "./webgpu-pipeline.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 {
pipeline;
hashes;
}
class WebgpuRenderPipeline extends WebgpuPipeline {
lookupHashes = new Uint32Array(15);
constructor(device) {
super(device);
this.vertexBufferLayout = new WebgpuVertexBufferLayout();
this.cache = /* @__PURE__ */ new Map();
}
get(primitive, vertexFormat0, vertexFormat1, ibFormat, shader, renderTarget, bindGroupFormats, blendState, depthState, cullMode, stencilEnabled, stencilFront, stencilBack, frontFace) {
const primitiveType = primitive.type;
if (ibFormat && primitiveType !== PRIMITIVE_LINESTRIP && primitiveType !== PRIMITIVE_TRISTRIP) {
ibFormat = void 0;
}
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];
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]
}
};
}
return blend;
}
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
});
});
}
_pipelineId++;
const pipeline = wgpu.createRenderPipeline(desc);
return pipeline;
}
}
export {
WebgpuRenderPipeline
};