playcanvas
Version:
PlayCanvas WebGL game engine
226 lines (223 loc) • 7.42 kB
JavaScript
import { hash32Fnv1a } from '../../../core/hash.js';
import { array } from '../../../core/array-utils.js';
import { WebgpuVertexBufferLayout } from './webgpu-vertex-buffer-layout.js';
import { WebgpuPipeline } from './webgpu-pipeline.js';
import { PRIMITIVE_LINESTRIP, PRIMITIVE_TRISTRIP } from '../constants.js';
const _primitiveTopology = [
'point-list',
'line-list',
undefined,
'line-strip',
'triangle-list',
'triangle-strip',
undefined
];
const _blendOperation = [
'add',
'subtract',
'reverse-subtract',
'min',
'max'
];
const _blendFactor = [
'zero',
'one',
'src',
'one-minus-src',
'dst',
'one-minus-dst',
'src-alpha',
'src-alpha-saturated',
'one-minus-src-alpha',
'dst-alpha',
'one-minus-dst-alpha',
'constant',
'one-minus-constant'
];
const _compareFunction = [
'never',
'less',
'equal',
'less-equal',
'greater',
'not-equal',
'greater-equal',
'always'
];
const _cullModes = [
'none',
'back',
'front'
];
const _stencilOps = [
'keep',
'zero',
'replace',
'increment-clamp',
'increment-wrap',
'decrement-clamp',
'decrement-wrap',
'invert'
];
const _indexFormat = [
'',
'uint16',
'uint32'
];
class CacheEntry {
}
class WebgpuRenderPipeline extends WebgpuPipeline {
constructor(device){
super(device), this.lookupHashes = new Uint32Array(14);
this.vertexBufferLayout = new WebgpuVertexBufferLayout();
this.cache = new Map();
}
get(primitive, vertexFormat0, vertexFormat1, ibFormat, shader, renderTarget, bindGroupFormats, blendState, depthState, cullMode, stencilEnabled, stencilFront, stencilBack) {
const primitiveType = primitive.type;
if (ibFormat && primitiveType !== PRIMITIVE_LINESTRIP && primitiveType !== PRIMITIVE_TRISTRIP) {
ibFormat = undefined;
}
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;
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);
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) {
const wgpu = this.device.wgpu;
const webgpuShader = shader.impl;
const desc = {
vertex: {
module: webgpuShader.getVertexShaderModule(),
entryPoint: webgpuShader.vertexEntryPoint,
buffers: vertexBufferLayout
},
primitive: {
topology: primitiveTopology,
frontFace: 'ccw',
cullMode: _cullModes[cullMode]
},
depthStencil: this.getDepthStencil(depthState, renderTarget, stencilEnabled, stencilFront, stencilBack, primitiveTopology),
multisample: {
count: renderTarget.samples
},
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: writeMask,
blend: blend
});
});
}
const pipeline = wgpu.createRenderPipeline(desc);
return pipeline;
}
}
export { WebgpuRenderPipeline };