@babylonjs/core
Version:
Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.
860 lines (859 loc) • 44.9 kB
JavaScript
/* eslint-disable @typescript-eslint/naming-convention */
/* eslint-disable babylonjs/available */
/* eslint-disable jsdoc/require-jsdoc */
import { VertexBuffer } from "../../Buffers/buffer.js";
import { WebGPUTextureHelper } from "./webgpuTextureHelper.js";
import { renderableTextureFormatToIndex } from "./webgpuTextureManager.js";
import { checkNonFloatVertexBuffers } from "../../Buffers/buffer.nonFloatVertexBuffers.js";
import { Logger } from "../../Misc/logger.js";
var StatePosition;
(function (StatePosition) {
StatePosition[StatePosition["StencilReadMask"] = 0] = "StencilReadMask";
StatePosition[StatePosition["StencilWriteMask"] = 1] = "StencilWriteMask";
//DepthBiasClamp = 1, // not used, so remove it to improve perf
StatePosition[StatePosition["DepthBias"] = 2] = "DepthBias";
StatePosition[StatePosition["DepthBiasSlopeScale"] = 3] = "DepthBiasSlopeScale";
StatePosition[StatePosition["DepthStencilState"] = 4] = "DepthStencilState";
StatePosition[StatePosition["MRTAttachments1"] = 5] = "MRTAttachments1";
StatePosition[StatePosition["MRTAttachments2"] = 6] = "MRTAttachments2";
StatePosition[StatePosition["RasterizationState"] = 7] = "RasterizationState";
StatePosition[StatePosition["ColorStates"] = 8] = "ColorStates";
StatePosition[StatePosition["ShaderStage"] = 9] = "ShaderStage";
StatePosition[StatePosition["TextureStage"] = 10] = "TextureStage";
StatePosition[StatePosition["VertexState"] = 11] = "VertexState";
StatePosition[StatePosition["NumStates"] = 12] = "NumStates";
})(StatePosition || (StatePosition = {}));
const alphaBlendFactorToIndex = {
0: 1, // Zero
1: 2, // One
0x0300: 3, // SrcColor
0x0301: 4, // OneMinusSrcColor
0x0302: 5, // SrcAlpha
0x0303: 6, // OneMinusSrcAlpha
0x0304: 7, // DstAlpha
0x0305: 8, // OneMinusDstAlpha
0x0306: 9, // DstColor
0x0307: 10, // OneMinusDstColor
0x0308: 11, // SrcAlphaSaturated
0x8001: 12, // BlendColor
0x8002: 13, // OneMinusBlendColor
0x8003: 12, // BlendColor (alpha)
0x8004: 13, // OneMinusBlendColor (alpha)
};
const stencilOpToIndex = {
0x0000: 0, // ZERO
0x1e00: 1, // KEEP
0x1e01: 2, // REPLACE
0x1e02: 3, // INCR
0x1e03: 4, // DECR
0x150a: 5, // INVERT
0x8507: 6, // INCR_WRAP
0x8508: 7, // DECR_WRAP
};
/** @internal */
export class WebGPUCacheRenderPipeline {
constructor(device, emptyVertexBuffer) {
this.mrtTextureCount = 0;
this._device = device;
this._useTextureStage = true; // we force usage because we must handle depth textures with "float" filtering, which can't be fixed by a caps (like "textureFloatLinearFiltering" can for float textures)
this._states = new Array(30); // pre-allocate enough room so that no new allocation will take place afterwards
this._statesLength = 0;
this._stateDirtyLowestIndex = 0;
this._emptyVertexBuffer = emptyVertexBuffer;
this._mrtFormats = [];
this._parameter = { token: undefined, pipeline: null };
this.disabled = false;
this.vertexBuffers = [];
this._kMaxVertexBufferStride = device.limits.maxVertexBufferArrayStride || 2048;
this.reset();
}
reset() {
this._isDirty = true;
this.vertexBuffers.length = 0;
this.setAlphaToCoverage(false);
this.resetDepthCullingState();
this.setClampDepth(false);
this.setDepthBias(0);
//this.setDepthBiasClamp(0);
this._webgpuColorFormat = ["bgra8unorm" /* WebGPUConstants.TextureFormat.BGRA8Unorm */];
this.setColorFormat("bgra8unorm" /* WebGPUConstants.TextureFormat.BGRA8Unorm */);
this.setMRT([]);
this.setAlphaBlendEnabled(false);
this.setAlphaBlendFactors([null, null, null, null], [null, null]);
this.setWriteMask(0xf);
this.setDepthStencilFormat("depth24plus-stencil8" /* WebGPUConstants.TextureFormat.Depth24PlusStencil8 */);
this.setStencilEnabled(false);
this.resetStencilState();
this.setBuffers(null, null, null);
this._setTextureState(0);
}
get colorFormats() {
return this._mrtAttachments1 > 0 ? this._mrtFormats : this._webgpuColorFormat;
}
getRenderPipeline(fillMode, effect, sampleCount, textureState = 0) {
sampleCount = WebGPUTextureHelper.GetSample(sampleCount);
if (this.disabled) {
const topology = WebGPUCacheRenderPipeline._GetTopology(fillMode);
this._setVertexState(effect); // to fill this.vertexBuffers with correct data
this._setTextureState(textureState);
this._parameter.pipeline = this._createRenderPipeline(effect, topology, sampleCount);
WebGPUCacheRenderPipeline.NumCacheMiss++;
WebGPUCacheRenderPipeline._NumPipelineCreationCurrentFrame++;
return this._parameter.pipeline;
}
this._setShaderStage(effect.uniqueId);
this._setRasterizationState(fillMode, sampleCount);
this._setColorStates();
this._setDepthStencilState();
this._setVertexState(effect);
this._setTextureState(textureState);
this.lastStateDirtyLowestIndex = this._stateDirtyLowestIndex;
if (!this._isDirty && this._parameter.pipeline) {
this._stateDirtyLowestIndex = this._statesLength;
WebGPUCacheRenderPipeline.NumCacheHitWithoutHash++;
return this._parameter.pipeline;
}
this._getRenderPipeline(this._parameter);
this._isDirty = false;
this._stateDirtyLowestIndex = this._statesLength;
if (this._parameter.pipeline) {
WebGPUCacheRenderPipeline.NumCacheHitWithHash++;
return this._parameter.pipeline;
}
const topology = WebGPUCacheRenderPipeline._GetTopology(fillMode);
this._parameter.pipeline = this._createRenderPipeline(effect, topology, sampleCount);
this._setRenderPipeline(this._parameter);
WebGPUCacheRenderPipeline.NumCacheMiss++;
WebGPUCacheRenderPipeline._NumPipelineCreationCurrentFrame++;
return this._parameter.pipeline;
}
endFrame() {
WebGPUCacheRenderPipeline.NumPipelineCreationLastFrame = WebGPUCacheRenderPipeline._NumPipelineCreationCurrentFrame;
WebGPUCacheRenderPipeline._NumPipelineCreationCurrentFrame = 0;
}
setAlphaToCoverage(enabled) {
this._alphaToCoverageEnabled = enabled;
}
setFrontFace(frontFace) {
this._frontFace = frontFace;
}
setCullEnabled(enabled) {
this._cullEnabled = enabled;
}
setCullFace(cullFace) {
this._cullFace = cullFace;
}
setClampDepth(clampDepth) {
this._clampDepth = clampDepth;
}
resetDepthCullingState() {
this.setDepthCullingState(false, 2, 1, 0, 0, true, true, 519);
}
setDepthCullingState(cullEnabled, frontFace, cullFace, zOffset, zOffsetUnits, depthTestEnabled, depthWriteEnabled, depthCompare) {
this._depthWriteEnabled = depthWriteEnabled;
this._depthTestEnabled = depthTestEnabled;
this._depthCompare = (depthCompare ?? 519) - 0x0200;
this._cullFace = cullFace;
this._cullEnabled = cullEnabled;
this._frontFace = frontFace;
this.setDepthBiasSlopeScale(zOffset);
this.setDepthBias(zOffsetUnits);
}
setDepthBias(depthBias) {
if (this._depthBias !== depthBias) {
this._depthBias = depthBias;
this._states[StatePosition.DepthBias] = depthBias;
this._isDirty = true;
this._stateDirtyLowestIndex = Math.min(this._stateDirtyLowestIndex, StatePosition.DepthBias);
}
}
/*public setDepthBiasClamp(depthBiasClamp: number): void {
if (this._depthBiasClamp !== depthBiasClamp) {
this._depthBiasClamp = depthBiasClamp;
this._states[StatePosition.DepthBiasClamp] = depthBiasClamp.toString();
this._isDirty = true;
}
}*/
setDepthBiasSlopeScale(depthBiasSlopeScale) {
if (this._depthBiasSlopeScale !== depthBiasSlopeScale) {
this._depthBiasSlopeScale = depthBiasSlopeScale;
this._states[StatePosition.DepthBiasSlopeScale] = depthBiasSlopeScale;
this._isDirty = true;
this._stateDirtyLowestIndex = Math.min(this._stateDirtyLowestIndex, StatePosition.DepthBiasSlopeScale);
}
}
setColorFormat(format) {
this._webgpuColorFormat[0] = format;
this._colorFormat = renderableTextureFormatToIndex[format ?? ""];
}
setMRTAttachments(attachments) {
this.mrtAttachments = attachments;
let mask = 0;
for (let i = 0; i < attachments.length; ++i) {
if (attachments[i] !== 0) {
mask += 1 << i;
}
}
if (this._mrtEnabledMask !== mask) {
this._mrtEnabledMask = mask;
this._isDirty = true;
this._stateDirtyLowestIndex = Math.min(this._stateDirtyLowestIndex, StatePosition.MRTAttachments1);
}
}
setMRT(textureArray, textureCount) {
textureCount = textureCount ?? textureArray.length;
if (textureCount > 10) {
// If we want more than 10 attachments we need to change this method (and the StatePosition enum) but 10 seems plenty: note that WebGPU only supports 8 at the time (2021/12/13)!
// As we need ~39 different values we are using 6 bits to encode a texture format, meaning we can encode 5 texture formats in 32 bits
// We are using 2x32 bit values to handle 10 textures
// eslint-disable-next-line no-throw-literal
throw "Can't handle more than 10 attachments for a MRT in cache render pipeline!";
}
this.mrtTextureArray = textureArray;
this.mrtTextureCount = textureCount;
this._mrtEnabledMask = 0xffff; // all textures are enabled at start (meaning we can write to them). Calls to setMRTAttachments may disable some
const bits = [0, 0];
let indexBits = 0, mask = 0, numRT = 0;
for (let i = 0; i < textureCount; ++i) {
const texture = textureArray[i];
const gpuWrapper = texture?._hardwareTexture;
this._mrtFormats[numRT] = gpuWrapper?.format ?? this._webgpuColorFormat[0];
bits[indexBits] += renderableTextureFormatToIndex[this._mrtFormats[numRT] ?? ""] << mask;
mask += 6;
numRT++;
if (mask >= 32) {
mask = 0;
indexBits++;
}
}
this._mrtFormats.length = numRT;
if (this._mrtAttachments1 !== bits[0] || this._mrtAttachments2 !== bits[1]) {
this._mrtAttachments1 = bits[0];
this._mrtAttachments2 = bits[1];
this._states[StatePosition.MRTAttachments1] = bits[0];
this._states[StatePosition.MRTAttachments2] = bits[1];
this._isDirty = true;
this._stateDirtyLowestIndex = Math.min(this._stateDirtyLowestIndex, StatePosition.MRTAttachments1);
}
}
setAlphaBlendEnabled(enabled) {
this._alphaBlendEnabled = enabled;
}
setAlphaBlendFactors(factors, operations) {
this._alphaBlendFuncParams = factors;
this._alphaBlendEqParams = operations;
}
setWriteMask(mask) {
this._writeMask = mask;
}
setDepthStencilFormat(format) {
this._webgpuDepthStencilFormat = format;
this._depthStencilFormat = format === undefined ? 0 : renderableTextureFormatToIndex[format];
}
setDepthTestEnabled(enabled) {
this._depthTestEnabled = enabled;
}
setDepthWriteEnabled(enabled) {
this._depthWriteEnabled = enabled;
}
setDepthCompare(func) {
this._depthCompare = (func ?? 519) - 0x0200;
}
setStencilEnabled(enabled) {
this._stencilEnabled = enabled;
}
setStencilCompare(func) {
this._stencilFrontCompare = (func ?? 519) - 0x0200;
}
setStencilDepthFailOp(op) {
this._stencilFrontDepthFailOp = op === null ? 1 /* KEEP */ : stencilOpToIndex[op];
}
setStencilPassOp(op) {
this._stencilFrontPassOp = op === null ? 2 /* REPLACE */ : stencilOpToIndex[op];
}
setStencilFailOp(op) {
this._stencilFrontFailOp = op === null ? 1 /* KEEP */ : stencilOpToIndex[op];
}
setStencilReadMask(mask) {
if (this._stencilReadMask !== mask) {
this._stencilReadMask = mask;
this._states[StatePosition.StencilReadMask] = mask;
this._isDirty = true;
this._stateDirtyLowestIndex = Math.min(this._stateDirtyLowestIndex, StatePosition.StencilReadMask);
}
}
setStencilWriteMask(mask) {
if (this._stencilWriteMask !== mask) {
this._stencilWriteMask = mask;
this._states[StatePosition.StencilWriteMask] = mask;
this._isDirty = true;
this._stateDirtyLowestIndex = Math.min(this._stateDirtyLowestIndex, StatePosition.StencilWriteMask);
}
}
resetStencilState() {
this.setStencilState(false, 519, 7680, 7681, 7680, 0xff, 0xff);
}
setStencilState(stencilEnabled, compare, depthFailOp, passOp, failOp, readMask, writeMask) {
this._stencilEnabled = stencilEnabled;
this._stencilFrontCompare = (compare ?? 519) - 0x0200;
this._stencilFrontDepthFailOp = depthFailOp === null ? 1 /* KEEP */ : stencilOpToIndex[depthFailOp];
this._stencilFrontPassOp = passOp === null ? 2 /* REPLACE */ : stencilOpToIndex[passOp];
this._stencilFrontFailOp = failOp === null ? 1 /* KEEP */ : stencilOpToIndex[failOp];
this.setStencilReadMask(readMask);
this.setStencilWriteMask(writeMask);
}
setBuffers(vertexBuffers, indexBuffer, overrideVertexBuffers) {
this._vertexBuffers = vertexBuffers;
this._overrideVertexBuffers = overrideVertexBuffers;
this._indexBuffer = indexBuffer;
}
static _GetTopology(fillMode) {
switch (fillMode) {
// Triangle views
case 0:
return "triangle-list" /* WebGPUConstants.PrimitiveTopology.TriangleList */;
case 2:
return "point-list" /* WebGPUConstants.PrimitiveTopology.PointList */;
case 1:
return "line-list" /* WebGPUConstants.PrimitiveTopology.LineList */;
// Draw modes
case 3:
return "point-list" /* WebGPUConstants.PrimitiveTopology.PointList */;
case 4:
return "line-list" /* WebGPUConstants.PrimitiveTopology.LineList */;
case 5:
// return this._gl.LINE_LOOP;
// TODO WEBGPU. Line Loop Mode Fallback at buffer load time.
// eslint-disable-next-line no-throw-literal
throw "LineLoop is an unsupported fillmode in WebGPU";
case 6:
return "line-strip" /* WebGPUConstants.PrimitiveTopology.LineStrip */;
case 7:
return "triangle-strip" /* WebGPUConstants.PrimitiveTopology.TriangleStrip */;
case 8:
// return this._gl.TRIANGLE_FAN;
// TODO WEBGPU. Triangle Fan Mode Fallback at buffer load time.
// eslint-disable-next-line no-throw-literal
throw "TriangleFan is an unsupported fillmode in WebGPU";
default:
return "triangle-list" /* WebGPUConstants.PrimitiveTopology.TriangleList */;
}
}
static _GetAphaBlendOperation(operation) {
switch (operation) {
case 32774:
return "add" /* WebGPUConstants.BlendOperation.Add */;
case 32778:
return "subtract" /* WebGPUConstants.BlendOperation.Subtract */;
case 32779:
return "reverse-subtract" /* WebGPUConstants.BlendOperation.ReverseSubtract */;
case 32775:
return "min" /* WebGPUConstants.BlendOperation.Min */;
case 32776:
return "max" /* WebGPUConstants.BlendOperation.Max */;
default:
return "add" /* WebGPUConstants.BlendOperation.Add */;
}
}
static _GetAphaBlendFactor(factor) {
switch (factor) {
case 0:
return "zero" /* WebGPUConstants.BlendFactor.Zero */;
case 1:
return "one" /* WebGPUConstants.BlendFactor.One */;
case 768:
return "src" /* WebGPUConstants.BlendFactor.Src */;
case 769:
return "one-minus-src" /* WebGPUConstants.BlendFactor.OneMinusSrc */;
case 770:
return "src-alpha" /* WebGPUConstants.BlendFactor.SrcAlpha */;
case 771:
return "one-minus-src-alpha" /* WebGPUConstants.BlendFactor.OneMinusSrcAlpha */;
case 772:
return "dst-alpha" /* WebGPUConstants.BlendFactor.DstAlpha */;
case 773:
return "one-minus-dst-alpha" /* WebGPUConstants.BlendFactor.OneMinusDstAlpha */;
case 774:
return "dst" /* WebGPUConstants.BlendFactor.Dst */;
case 775:
return "one-minus-dst" /* WebGPUConstants.BlendFactor.OneMinusDst */;
case 776:
return "src-alpha-saturated" /* WebGPUConstants.BlendFactor.SrcAlphaSaturated */;
case 32769:
return "constant" /* WebGPUConstants.BlendFactor.Constant */;
case 32770:
return "one-minus-constant" /* WebGPUConstants.BlendFactor.OneMinusConstant */;
case 32771:
return "constant" /* WebGPUConstants.BlendFactor.Constant */;
case 32772:
return "one-minus-constant" /* WebGPUConstants.BlendFactor.OneMinusConstant */;
case 35065:
return "src1" /* WebGPUConstants.BlendFactor.Src1 */;
case 35066:
return "one-minus-src1" /* WebGPUConstants.BlendFactor.OneMinusSrc1 */;
case 34185:
return "src1-alpha" /* WebGPUConstants.BlendFactor.Src1Alpha */;
case 35067:
return "one-minus-src1-alpha" /* WebGPUConstants.BlendFactor.OneMinusSrc1Alpha */;
default:
return "one" /* WebGPUConstants.BlendFactor.One */;
}
}
static _GetCompareFunction(compareFunction) {
switch (compareFunction) {
case 0: // NEVER
return "never" /* WebGPUConstants.CompareFunction.Never */;
case 1: // LESS
return "less" /* WebGPUConstants.CompareFunction.Less */;
case 2: // EQUAL
return "equal" /* WebGPUConstants.CompareFunction.Equal */;
case 3: // LEQUAL
return "less-equal" /* WebGPUConstants.CompareFunction.LessEqual */;
case 4: // GREATER
return "greater" /* WebGPUConstants.CompareFunction.Greater */;
case 5: // NOTEQUAL
return "not-equal" /* WebGPUConstants.CompareFunction.NotEqual */;
case 6: // GEQUAL
return "greater-equal" /* WebGPUConstants.CompareFunction.GreaterEqual */;
case 7: // ALWAYS
return "always" /* WebGPUConstants.CompareFunction.Always */;
}
return "never" /* WebGPUConstants.CompareFunction.Never */;
}
static _GetStencilOpFunction(operation) {
switch (operation) {
case 0:
return "zero" /* WebGPUConstants.StencilOperation.Zero */;
case 1:
return "keep" /* WebGPUConstants.StencilOperation.Keep */;
case 2:
return "replace" /* WebGPUConstants.StencilOperation.Replace */;
case 3:
return "increment-clamp" /* WebGPUConstants.StencilOperation.IncrementClamp */;
case 4:
return "decrement-clamp" /* WebGPUConstants.StencilOperation.DecrementClamp */;
case 5:
return "invert" /* WebGPUConstants.StencilOperation.Invert */;
case 6:
return "increment-wrap" /* WebGPUConstants.StencilOperation.IncrementWrap */;
case 7:
return "decrement-wrap" /* WebGPUConstants.StencilOperation.DecrementWrap */;
}
return "keep" /* WebGPUConstants.StencilOperation.Keep */;
}
static _GetVertexInputDescriptorFormat(vertexBuffer) {
const type = vertexBuffer.type;
const normalized = vertexBuffer.normalized;
const size = vertexBuffer.getSize();
switch (type) {
case VertexBuffer.BYTE:
switch (size) {
case 1:
case 2:
return normalized ? "snorm8x2" /* WebGPUConstants.VertexFormat.Snorm8x2 */ : "sint8x2" /* WebGPUConstants.VertexFormat.Sint8x2 */;
case 3:
case 4:
return normalized ? "snorm8x4" /* WebGPUConstants.VertexFormat.Snorm8x4 */ : "sint8x4" /* WebGPUConstants.VertexFormat.Sint8x4 */;
}
break;
case VertexBuffer.UNSIGNED_BYTE:
switch (size) {
case 1:
case 2:
return normalized ? "unorm8x2" /* WebGPUConstants.VertexFormat.Unorm8x2 */ : "uint8x2" /* WebGPUConstants.VertexFormat.Uint8x2 */;
case 3:
case 4:
return normalized ? "unorm8x4" /* WebGPUConstants.VertexFormat.Unorm8x4 */ : "uint8x4" /* WebGPUConstants.VertexFormat.Uint8x4 */;
}
break;
case VertexBuffer.SHORT:
switch (size) {
case 1:
case 2:
return normalized ? "snorm16x2" /* WebGPUConstants.VertexFormat.Snorm16x2 */ : "sint16x2" /* WebGPUConstants.VertexFormat.Sint16x2 */;
case 3:
case 4:
return normalized ? "snorm16x4" /* WebGPUConstants.VertexFormat.Snorm16x4 */ : "sint16x4" /* WebGPUConstants.VertexFormat.Sint16x4 */;
}
break;
case VertexBuffer.UNSIGNED_SHORT:
switch (size) {
case 1:
case 2:
return normalized ? "unorm16x2" /* WebGPUConstants.VertexFormat.Unorm16x2 */ : "uint16x2" /* WebGPUConstants.VertexFormat.Uint16x2 */;
case 3:
case 4:
return normalized ? "unorm16x4" /* WebGPUConstants.VertexFormat.Unorm16x4 */ : "uint16x4" /* WebGPUConstants.VertexFormat.Uint16x4 */;
}
break;
case VertexBuffer.INT:
switch (size) {
case 1:
return "sint32" /* WebGPUConstants.VertexFormat.Sint32 */;
case 2:
return "sint32x2" /* WebGPUConstants.VertexFormat.Sint32x2 */;
case 3:
return "sint32x3" /* WebGPUConstants.VertexFormat.Sint32x3 */;
case 4:
return "sint32x4" /* WebGPUConstants.VertexFormat.Sint32x4 */;
}
break;
case VertexBuffer.UNSIGNED_INT:
switch (size) {
case 1:
return "uint32" /* WebGPUConstants.VertexFormat.Uint32 */;
case 2:
return "uint32x2" /* WebGPUConstants.VertexFormat.Uint32x2 */;
case 3:
return "uint32x3" /* WebGPUConstants.VertexFormat.Uint32x3 */;
case 4:
return "uint32x4" /* WebGPUConstants.VertexFormat.Uint32x4 */;
}
break;
case VertexBuffer.FLOAT:
switch (size) {
case 1:
return "float32" /* WebGPUConstants.VertexFormat.Float32 */;
case 2:
return "float32x2" /* WebGPUConstants.VertexFormat.Float32x2 */;
case 3:
return "float32x3" /* WebGPUConstants.VertexFormat.Float32x3 */;
case 4:
return "float32x4" /* WebGPUConstants.VertexFormat.Float32x4 */;
}
break;
}
throw new Error(`Invalid Format '${vertexBuffer.getKind()}' - type=${type}, normalized=${normalized}, size=${size}`);
}
_getAphaBlendState() {
if (!this._alphaBlendEnabled) {
return null;
}
return {
srcFactor: WebGPUCacheRenderPipeline._GetAphaBlendFactor(this._alphaBlendFuncParams[2]),
dstFactor: WebGPUCacheRenderPipeline._GetAphaBlendFactor(this._alphaBlendFuncParams[3]),
operation: WebGPUCacheRenderPipeline._GetAphaBlendOperation(this._alphaBlendEqParams[1]),
};
}
_getColorBlendState() {
if (!this._alphaBlendEnabled) {
return null;
}
return {
srcFactor: WebGPUCacheRenderPipeline._GetAphaBlendFactor(this._alphaBlendFuncParams[0]),
dstFactor: WebGPUCacheRenderPipeline._GetAphaBlendFactor(this._alphaBlendFuncParams[1]),
operation: WebGPUCacheRenderPipeline._GetAphaBlendOperation(this._alphaBlendEqParams[0]),
};
}
_setShaderStage(id) {
if (this._shaderId !== id) {
this._shaderId = id;
this._states[StatePosition.ShaderStage] = id;
this._isDirty = true;
this._stateDirtyLowestIndex = Math.min(this._stateDirtyLowestIndex, StatePosition.ShaderStage);
}
}
_setRasterizationState(topology, sampleCount) {
const frontFace = this._frontFace;
const cullMode = this._cullEnabled ? this._cullFace : 0;
const clampDepth = this._clampDepth ? 1 : 0;
const alphaToCoverage = this._alphaToCoverageEnabled ? 1 : 0;
const rasterizationState = frontFace - 1 + (cullMode << 1) + (clampDepth << 3) + (alphaToCoverage << 4) + (topology << 5) + (sampleCount << 8);
if (this._rasterizationState !== rasterizationState) {
this._rasterizationState = rasterizationState;
this._states[StatePosition.RasterizationState] = this._rasterizationState;
this._isDirty = true;
this._stateDirtyLowestIndex = Math.min(this._stateDirtyLowestIndex, StatePosition.RasterizationState);
}
}
_setColorStates() {
let colorStates = ((this._writeMask ? 1 : 0) << 22) + (this._colorFormat << 23) + ((this._depthWriteEnabled ? 1 : 0) << 29); // this state has been moved from depthStencilState here because alpha and depth are related (generally when alpha is on, depth write is off and the other way around)
if (this._alphaBlendEnabled) {
colorStates +=
((this._alphaBlendFuncParams[0] === null ? 2 : alphaBlendFactorToIndex[this._alphaBlendFuncParams[0]]) << 0) +
((this._alphaBlendFuncParams[1] === null ? 2 : alphaBlendFactorToIndex[this._alphaBlendFuncParams[1]]) << 4) +
((this._alphaBlendFuncParams[2] === null ? 2 : alphaBlendFactorToIndex[this._alphaBlendFuncParams[2]]) << 8) +
((this._alphaBlendFuncParams[3] === null ? 2 : alphaBlendFactorToIndex[this._alphaBlendFuncParams[3]]) << 12) +
((this._alphaBlendEqParams[0] === null ? 1 : this._alphaBlendEqParams[0] - 0x8005) << 16) +
((this._alphaBlendEqParams[1] === null ? 1 : this._alphaBlendEqParams[1] - 0x8005) << 19);
}
if (colorStates !== this._colorStates) {
this._colorStates = colorStates;
this._states[StatePosition.ColorStates] = this._colorStates;
this._isDirty = true;
this._stateDirtyLowestIndex = Math.min(this._stateDirtyLowestIndex, StatePosition.ColorStates);
}
}
_setDepthStencilState() {
const stencilState = !this._stencilEnabled
? 7 /* ALWAYS */ + (1 /* KEEP */ << 3) + (1 /* KEEP */ << 6) + (1 /* KEEP */ << 9)
: this._stencilFrontCompare + (this._stencilFrontDepthFailOp << 3) + (this._stencilFrontPassOp << 6) + (this._stencilFrontFailOp << 9);
const depthStencilState = this._depthStencilFormat + ((this._depthTestEnabled ? this._depthCompare : 7) /* ALWAYS */ << 6) + (stencilState << 10); // stencil front - stencil back is the same
if (this._depthStencilState !== depthStencilState) {
this._depthStencilState = depthStencilState;
this._states[StatePosition.DepthStencilState] = this._depthStencilState;
this._isDirty = true;
this._stateDirtyLowestIndex = Math.min(this._stateDirtyLowestIndex, StatePosition.DepthStencilState);
}
}
_setVertexState(effect) {
const currStateLen = this._statesLength;
let newNumStates = StatePosition.VertexState;
const webgpuPipelineContext = effect._pipelineContext;
const attributes = webgpuPipelineContext.shaderProcessingContext.attributeNamesFromEffect;
const locations = webgpuPipelineContext.shaderProcessingContext.attributeLocationsFromEffect;
let currentGPUBuffer;
let numVertexBuffers = 0;
for (let index = 0; index < attributes.length; index++) {
const location = locations[index];
let vertexBuffer = (this._overrideVertexBuffers && this._overrideVertexBuffers[attributes[index]]) ?? this._vertexBuffers[attributes[index]];
if (!vertexBuffer) {
// In WebGL it's valid to not bind a vertex buffer to an attribute, but it's not valid in WebGPU
// So we must bind a dummy buffer when we are not given one for a specific attribute
vertexBuffer = this._emptyVertexBuffer;
if (WebGPUCacheRenderPipeline.LogErrorIfNoVertexBuffer) {
Logger.Error(`No vertex buffer is provided for the "${attributes[index]}" attribute. A default empty vertex buffer will be used, but this may generate errors in some browsers.`);
}
}
const buffer = vertexBuffer.effectiveBuffer?.underlyingResource;
// We optimize usage of GPUVertexBufferLayout: we will create a single GPUVertexBufferLayout for all the attributes which follow each other and which use the same GPU buffer
// However, there are some constraints in the attribute.offset value range, so we must check for them before being able to reuse the same GPUVertexBufferLayout
// See _getVertexInputDescriptor() below
if (vertexBuffer._validOffsetRange === undefined) {
const offset = vertexBuffer.effectiveByteOffset;
const formatSize = vertexBuffer.getSize(true);
const byteStride = vertexBuffer.effectiveByteStride;
vertexBuffer._validOffsetRange =
(offset + formatSize <= this._kMaxVertexBufferStride && byteStride === 0) || (byteStride !== 0 && offset + formatSize <= byteStride);
}
if (!(currentGPUBuffer && currentGPUBuffer === buffer && vertexBuffer._validOffsetRange)) {
// we can't combine the previous vertexBuffer with the current one
this.vertexBuffers[numVertexBuffers++] = vertexBuffer;
currentGPUBuffer = vertexBuffer._validOffsetRange ? buffer : null;
}
const vid = vertexBuffer.hashCode + (location << 7);
this._isDirty = this._isDirty || this._states[newNumStates] !== vid;
this._states[newNumStates++] = vid;
}
this.vertexBuffers.length = numVertexBuffers;
this._statesLength = newNumStates;
this._isDirty = this._isDirty || newNumStates !== currStateLen;
if (this._isDirty) {
this._stateDirtyLowestIndex = Math.min(this._stateDirtyLowestIndex, StatePosition.VertexState);
}
}
_setTextureState(textureState) {
if (this._textureState !== textureState) {
this._textureState = textureState;
this._states[StatePosition.TextureStage] = this._textureState;
this._isDirty = true;
this._stateDirtyLowestIndex = Math.min(this._stateDirtyLowestIndex, StatePosition.TextureStage);
}
}
_createPipelineLayout(webgpuPipelineContext) {
if (this._useTextureStage) {
return this._createPipelineLayoutWithTextureStage(webgpuPipelineContext);
}
const bindGroupLayouts = [];
const bindGroupLayoutEntries = webgpuPipelineContext.shaderProcessingContext.bindGroupLayoutEntries;
for (let i = 0; i < bindGroupLayoutEntries.length; i++) {
const setDefinition = bindGroupLayoutEntries[i];
bindGroupLayouts[i] = this._device.createBindGroupLayout({
entries: setDefinition,
});
}
webgpuPipelineContext.bindGroupLayouts[0] = bindGroupLayouts;
return this._device.createPipelineLayout({ bindGroupLayouts });
}
_createPipelineLayoutWithTextureStage(webgpuPipelineContext) {
const shaderProcessingContext = webgpuPipelineContext.shaderProcessingContext;
const bindGroupLayoutEntries = shaderProcessingContext.bindGroupLayoutEntries;
let bitVal = 1;
for (let i = 0; i < bindGroupLayoutEntries.length; i++) {
const setDefinition = bindGroupLayoutEntries[i];
for (let j = 0; j < setDefinition.length; j++) {
const entry = bindGroupLayoutEntries[i][j];
if (entry.texture) {
const name = shaderProcessingContext.bindGroupLayoutEntryInfo[i][entry.binding].name;
const textureInfo = shaderProcessingContext.availableTextures[name];
const samplerInfo = textureInfo.autoBindSampler ? shaderProcessingContext.availableSamplers[name + `Sampler`] : null;
let sampleType = textureInfo.sampleType;
let samplerType = samplerInfo?.type ?? "filtering" /* WebGPUConstants.SamplerBindingType.Filtering */;
if (this._textureState & bitVal && sampleType !== "depth" /* WebGPUConstants.TextureSampleType.Depth */) {
// The texture is a 32 bits float texture but the system does not support linear filtering for them OR the texture is a depth texture with "float" filtering:
// we set the sampler to "non-filtering" and the texture sample type to "unfilterable-float"
if (textureInfo.autoBindSampler) {
samplerType = "non-filtering" /* WebGPUConstants.SamplerBindingType.NonFiltering */;
}
sampleType = "unfilterable-float" /* WebGPUConstants.TextureSampleType.UnfilterableFloat */;
}
entry.texture.sampleType = sampleType;
if (samplerInfo) {
const binding = shaderProcessingContext.bindGroupLayoutEntryInfo[samplerInfo.binding.groupIndex][samplerInfo.binding.bindingIndex].index;
bindGroupLayoutEntries[samplerInfo.binding.groupIndex][binding].sampler.type = samplerType;
}
bitVal = bitVal << 1;
}
}
}
const bindGroupLayouts = [];
for (let i = 0; i < bindGroupLayoutEntries.length; ++i) {
bindGroupLayouts[i] = this._device.createBindGroupLayout({
entries: bindGroupLayoutEntries[i],
});
}
webgpuPipelineContext.bindGroupLayouts[this._textureState] = bindGroupLayouts;
return this._device.createPipelineLayout({ bindGroupLayouts });
}
_getVertexInputDescriptor(effect) {
const descriptors = [];
const webgpuPipelineContext = effect._pipelineContext;
const attributes = webgpuPipelineContext.shaderProcessingContext.attributeNamesFromEffect;
const locations = webgpuPipelineContext.shaderProcessingContext.attributeLocationsFromEffect;
let currentGPUBuffer;
let currentGPUAttributes;
for (let index = 0; index < attributes.length; index++) {
const location = locations[index];
let vertexBuffer = (this._overrideVertexBuffers && this._overrideVertexBuffers[attributes[index]]) ?? this._vertexBuffers[attributes[index]];
if (!vertexBuffer) {
// In WebGL it's valid to not bind a vertex buffer to an attribute, but it's not valid in WebGPU
// So we must bind a dummy buffer when we are not given one for a specific attribute
vertexBuffer = this._emptyVertexBuffer;
}
let buffer = vertexBuffer.effectiveBuffer?.underlyingResource;
// We reuse the same GPUVertexBufferLayout for all attributes that use the same underlying GPU buffer (and for attributes that follow each other in the attributes array)
let offset = vertexBuffer.effectiveByteOffset;
const invalidOffsetRange = !vertexBuffer._validOffsetRange;
if (!(currentGPUBuffer && currentGPUAttributes && currentGPUBuffer === buffer) || invalidOffsetRange) {
const vertexBufferDescriptor = {
arrayStride: vertexBuffer.effectiveByteStride,
stepMode: vertexBuffer.getIsInstanced() ? "instance" /* WebGPUConstants.VertexStepMode.Instance */ : "vertex" /* WebGPUConstants.VertexStepMode.Vertex */,
attributes: [],
};
descriptors.push(vertexBufferDescriptor);
currentGPUAttributes = vertexBufferDescriptor.attributes;
if (invalidOffsetRange) {
offset = 0; // the offset will be set directly in the setVertexBuffer call
buffer = null; // buffer can't be reused
}
}
currentGPUAttributes.push({
shaderLocation: location,
offset,
format: WebGPUCacheRenderPipeline._GetVertexInputDescriptorFormat(vertexBuffer),
});
currentGPUBuffer = buffer;
}
return descriptors;
}
_createRenderPipeline(effect, topology, sampleCount) {
const webgpuPipelineContext = effect._pipelineContext;
const inputStateDescriptor = this._getVertexInputDescriptor(effect);
const pipelineLayout = this._createPipelineLayout(webgpuPipelineContext);
const colorStates = [];
const alphaBlend = this._getAphaBlendState();
const colorBlend = this._getColorBlendState();
if (this._vertexBuffers) {
checkNonFloatVertexBuffers(this._vertexBuffers, effect);
}
if (this._mrtAttachments1 > 0) {
for (let i = 0; i < this._mrtFormats.length; ++i) {
const format = this._mrtFormats[i];
if (format) {
const descr = {
format,
writeMask: (this._mrtEnabledMask & (1 << i)) !== 0 ? this._writeMask : 0,
};
if (alphaBlend && colorBlend) {
descr.blend = {
alpha: alphaBlend,
color: colorBlend,
};
}
colorStates.push(descr);
}
else {
colorStates.push(null);
}
}
}
else {
if (this._webgpuColorFormat[0]) {
const descr = {
format: this._webgpuColorFormat[0],
writeMask: this._writeMask,
};
if (alphaBlend && colorBlend) {
descr.blend = {
alpha: alphaBlend,
color: colorBlend,
};
}
colorStates.push(descr);
}
else {
colorStates.push(null);
}
}
const stencilFrontBack = {
compare: WebGPUCacheRenderPipeline._GetCompareFunction(this._stencilEnabled ? this._stencilFrontCompare : 7 /* ALWAYS */),
depthFailOp: WebGPUCacheRenderPipeline._GetStencilOpFunction(this._stencilEnabled ? this._stencilFrontDepthFailOp : 1 /* KEEP */),
failOp: WebGPUCacheRenderPipeline._GetStencilOpFunction(this._stencilEnabled ? this._stencilFrontFailOp : 1 /* KEEP */),
passOp: WebGPUCacheRenderPipeline._GetStencilOpFunction(this._stencilEnabled ? this._stencilFrontPassOp : 1 /* KEEP */),
};
const topologyIsTriangle = topology === "triangle-list" /* WebGPUConstants.PrimitiveTopology.TriangleList */ || topology === "triangle-strip" /* WebGPUConstants.PrimitiveTopology.TriangleStrip */;
let stripIndexFormat = undefined;
if (topology === "line-strip" /* WebGPUConstants.PrimitiveTopology.LineStrip */ || topology === "triangle-strip" /* WebGPUConstants.PrimitiveTopology.TriangleStrip */) {
stripIndexFormat = !this._indexBuffer || this._indexBuffer.is32Bits ? "uint32" /* WebGPUConstants.IndexFormat.Uint32 */ : "uint16" /* WebGPUConstants.IndexFormat.Uint16 */;
}
const depthStencilFormatHasStencil = this._webgpuDepthStencilFormat ? WebGPUTextureHelper.HasStencilAspect(this._webgpuDepthStencilFormat) : false;
return this._device.createRenderPipeline({
label: `RenderPipeline_${colorStates[0]?.format ?? "nooutput"}_${this._webgpuDepthStencilFormat ?? "nodepth"}_samples${sampleCount}_textureState${this._textureState}`,
layout: pipelineLayout,
vertex: {
module: webgpuPipelineContext.stages.vertexStage.module,
entryPoint: webgpuPipelineContext.stages.vertexStage.entryPoint,
buffers: inputStateDescriptor,
},
primitive: {
topology,
stripIndexFormat,
frontFace: this._frontFace === 1 ? "ccw" /* WebGPUConstants.FrontFace.CCW */ : "cw" /* WebGPUConstants.FrontFace.CW */,
cullMode: !this._cullEnabled ? "none" /* WebGPUConstants.CullMode.None */ : this._cullFace === 2 ? "front" /* WebGPUConstants.CullMode.Front */ : "back" /* WebGPUConstants.CullMode.Back */,
},
fragment: !webgpuPipelineContext.stages.fragmentStage
? undefined
: {
module: webgpuPipelineContext.stages.fragmentStage.module,
entryPoint: webgpuPipelineContext.stages.fragmentStage.entryPoint,
targets: colorStates,
},
multisample: {
count: sampleCount,
/*mask,
alphaToCoverageEnabled,*/
},
depthStencil: this._webgpuDepthStencilFormat === undefined
? undefined
: {
depthWriteEnabled: this._depthWriteEnabled,
depthCompare: this._depthTestEnabled ? WebGPUCacheRenderPipeline._GetCompareFunction(this._depthCompare) : "always" /* WebGPUConstants.CompareFunction.Always */,
format: this._webgpuDepthStencilFormat,
stencilFront: this._stencilEnabled && depthStencilFormatHasStencil ? stencilFrontBack : undefined,
stencilBack: this._stencilEnabled && depthStencilFormatHasStencil ? stencilFrontBack : undefined,
stencilReadMask: this._stencilEnabled && depthStencilFormatHasStencil ? this._stencilReadMask : undefined,
stencilWriteMask: this._stencilEnabled && depthStencilFormatHasStencil ? this._stencilWriteMask : undefined,
depthBias: this._depthBias,
depthBiasClamp: topologyIsTriangle ? this._depthBiasClamp : 0,
depthBiasSlopeScale: topologyIsTriangle ? this._depthBiasSlopeScale : 0,
},
});
}
}
WebGPUCacheRenderPipeline.LogErrorIfNoVertexBuffer = false;
WebGPUCacheRenderPipeline.NumCacheHitWithoutHash = 0;
WebGPUCacheRenderPipeline.NumCacheHitWithHash = 0;
WebGPUCacheRenderPipeline.NumCacheMiss = 0;
WebGPUCacheRenderPipeline.NumPipelineCreationLastFrame = 0;
WebGPUCacheRenderPipeline._NumPipelineCreationCurrentFrame = 0;
//# sourceMappingURL=webgpuCacheRenderPipeline.js.map