Files
cocos-engine/cocos/rendering/custom/layout-graph-utils.ts
2023-09-25 09:42:15 +08:00

1716 lines
68 KiB
TypeScript

/****************************************************************************
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
/* eslint-disable max-len */
import { assert, error, warn } from '@base/debug';
import { EffectAsset } from '../../asset/assets';
import { DescriptorSetInfo, DescriptorSetLayout, DescriptorSetLayoutBinding, DescriptorSetLayoutInfo, DescriptorType, Device, PipelineLayout, PipelineLayoutInfo, ShaderStageFlagBit, Type, Uniform, UniformBlock } from '../../gfx';
import { DefaultVisitor, depthFirstSearch, GraphColor, MutableVertexPropertyMap } from './graph';
import { DescriptorBlockData, DescriptorData, DescriptorDB, DescriptorSetData, DescriptorSetLayoutData, LayoutGraph, LayoutGraphData, LayoutGraphDataValue, LayoutGraphValue, PipelineLayoutData, RenderPassType, RenderPhase, RenderPhaseData, RenderStageData, ShaderProgramData } from './layout-graph';
import { UpdateFrequency, getUpdateFrequencyName, getDescriptorTypeOrderName, Descriptor, DescriptorBlock, DescriptorBlockFlattened, DescriptorBlockIndex, DescriptorTypeOrder, ParameterType } from './types';
export const INVALID_ID = 0xFFFFFFFF;
export const ENABLE_SUBPASS = true;
// get name of gfx.Type
function getGfxTypeName (type: Type): string {
switch (type) {
case Type.UNKNOWN: return 'Unknown';
case Type.BOOL: return 'Bool';
case Type.BOOL2: return 'Bool2';
case Type.BOOL3: return 'Bool3';
case Type.BOOL4: return 'Bool4';
case Type.INT: return 'Int';
case Type.INT2: return 'Int2';
case Type.INT3: return 'Int3';
case Type.INT4: return 'Int4';
case Type.UINT: return 'Uint';
case Type.UINT2: return 'Uint2';
case Type.UINT3: return 'Uint3';
case Type.UINT4: return 'Uint4';
case Type.FLOAT: return 'Float';
case Type.FLOAT2: return 'Float2';
case Type.FLOAT3: return 'Float3';
case Type.FLOAT4: return 'Float4';
case Type.MAT2: return 'Mat2';
case Type.MAT2X3: return 'Mat2x3';
case Type.MAT2X4: return 'Mat2x4';
case Type.MAT3X2: return 'Mat3x2';
case Type.MAT3: return 'Mat3';
case Type.MAT3X4: return 'Mat3x4';
case Type.MAT4X2: return 'Mat4x2';
case Type.MAT4X3: return 'Mat4x3';
case Type.MAT4: return 'Mat4';
case Type.SAMPLER1D: return 'Sampler1D';
case Type.SAMPLER1D_ARRAY: return 'Sampler1DArray';
case Type.SAMPLER2D: return 'Sampler2D';
case Type.SAMPLER2D_ARRAY: return 'Sampler2DArray';
case Type.SAMPLER3D: return 'Sampler3D';
case Type.SAMPLER_CUBE: return 'SamplerCube';
case Type.SAMPLER: return 'Sampler';
case Type.TEXTURE1D: return 'Texture1D';
case Type.TEXTURE1D_ARRAY: return 'Texture1DArray';
case Type.TEXTURE2D: return 'Texture2D';
case Type.TEXTURE2D_ARRAY: return 'Texture2DArray';
case Type.TEXTURE3D: return 'Texture3D';
case Type.TEXTURE_CUBE: return 'TextureCube';
case Type.IMAGE1D: return 'Image1D';
case Type.IMAGE1D_ARRAY: return 'Image1DArray';
case Type.IMAGE2D: return 'Image2D';
case Type.IMAGE2D_ARRAY: return 'Image2DArray';
case Type.IMAGE3D: return 'Image3D';
case Type.IMAGE_CUBE: return 'ImageCube';
case Type.SUBPASS_INPUT: return 'SubpassInput';
case Type.COUNT: return 'Count';
default: return 'Unknown';
}
}
// get DescriptorType from DescriptorTypeOrder
export function getGfxDescriptorType (type: DescriptorTypeOrder): DescriptorType {
switch (type) {
case DescriptorTypeOrder.UNIFORM_BUFFER:
return DescriptorType.UNIFORM_BUFFER;
case DescriptorTypeOrder.DYNAMIC_UNIFORM_BUFFER:
return DescriptorType.DYNAMIC_UNIFORM_BUFFER;
case DescriptorTypeOrder.SAMPLER_TEXTURE:
return DescriptorType.SAMPLER_TEXTURE;
case DescriptorTypeOrder.SAMPLER:
return DescriptorType.SAMPLER;
case DescriptorTypeOrder.TEXTURE:
return DescriptorType.TEXTURE;
case DescriptorTypeOrder.STORAGE_BUFFER:
return DescriptorType.STORAGE_BUFFER;
case DescriptorTypeOrder.DYNAMIC_STORAGE_BUFFER:
return DescriptorType.DYNAMIC_STORAGE_BUFFER;
case DescriptorTypeOrder.STORAGE_IMAGE:
return DescriptorType.STORAGE_IMAGE;
case DescriptorTypeOrder.INPUT_ATTACHMENT:
return DescriptorType.INPUT_ATTACHMENT;
default:
error('DescriptorType not found');
return DescriptorType.INPUT_ATTACHMENT;
}
}
// get DescriptorTypeOrder from DescriptorType
export function getDescriptorTypeOrder (type: DescriptorType): DescriptorTypeOrder {
switch (type) {
case DescriptorType.UNIFORM_BUFFER:
return DescriptorTypeOrder.UNIFORM_BUFFER;
case DescriptorType.DYNAMIC_UNIFORM_BUFFER:
return DescriptorTypeOrder.DYNAMIC_UNIFORM_BUFFER;
case DescriptorType.SAMPLER_TEXTURE:
return DescriptorTypeOrder.SAMPLER_TEXTURE;
case DescriptorType.SAMPLER:
return DescriptorTypeOrder.SAMPLER;
case DescriptorType.TEXTURE:
return DescriptorTypeOrder.TEXTURE;
case DescriptorType.STORAGE_BUFFER:
return DescriptorTypeOrder.STORAGE_BUFFER;
case DescriptorType.DYNAMIC_STORAGE_BUFFER:
return DescriptorTypeOrder.DYNAMIC_STORAGE_BUFFER;
case DescriptorType.STORAGE_IMAGE:
return DescriptorTypeOrder.STORAGE_IMAGE;
case DescriptorType.INPUT_ATTACHMENT:
return DescriptorTypeOrder.INPUT_ATTACHMENT;
case DescriptorType.UNKNOWN:
default:
error('DescriptorTypeOrder not found');
return DescriptorTypeOrder.INPUT_ATTACHMENT;
}
}
// find passID using name
export function getCustomPassID (lg: LayoutGraphData, name: string | undefined): number {
return lg.locateChild(lg.nullVertex(), name || 'default');
}
// find subpassID using name
export function getCustomSubpassID (lg: LayoutGraphData, passID: number, name: string): number {
return lg.locateChild(passID, name);
}
// find phaseID using subpassOrPassID and phase name
export function getCustomPhaseID (lg: LayoutGraphData, subpassOrPassID: number, name: string | number | undefined): number {
if (name === undefined) {
return lg.locateChild(subpassOrPassID, 'default');
}
if (typeof (name) === 'number') {
return lg.locateChild(subpassOrPassID, name.toString());
}
return lg.locateChild(subpassOrPassID, name);
}
// check ShaderStageFlagBit has certain bits
function hasFlag (flags: ShaderStageFlagBit, flagToTest: ShaderStageFlagBit): boolean {
return (flags & flagToTest) !== 0;
}
// get name of visibility
function getVisibilityName (stage: ShaderStageFlagBit): string {
let count = 0;
let str = '';
if (hasFlag(stage, ShaderStageFlagBit.VERTEX)) {
if (count++) {
str += ' | ';
}
str += 'Vertex';
}
if (hasFlag(stage, ShaderStageFlagBit.CONTROL)) {
if (count++) {
str += ' | ';
}
str += 'Control';
}
if (hasFlag(stage, ShaderStageFlagBit.EVALUATION)) {
if (count++) {
str += ' | ';
}
str += 'Evaluation';
}
if (hasFlag(stage, ShaderStageFlagBit.GEOMETRY)) {
if (count++) {
str += ' | ';
}
str += 'Geometry';
}
if (hasFlag(stage, ShaderStageFlagBit.FRAGMENT)) {
if (count++) {
str += ' | ';
}
str += 'Fragment';
}
if (hasFlag(stage, ShaderStageFlagBit.COMPUTE)) {
if (count++) {
str += ' | ';
}
str += 'Compute';
}
if (stage === ShaderStageFlagBit.ALL) {
if (count++) {
str += ' | ';
}
str += 'All';
}
return str;
}
// print LayoutGraphData
export class PrintVisitor extends DefaultVisitor {
discoverVertex (u: number, g: LayoutGraphData): void {
const ppl: PipelineLayoutData = g.getLayout(u);
const name: string = g._names[u];
const freq: UpdateFrequency = g._updateFrequencies[u];
this.oss += `${this.space}"${name}": `;
if (g.holds(LayoutGraphDataValue.RenderStage, u)) {
this.oss += `RenderStage {\n`;
} else {
this.oss += `RenderPhase {\n`;
}
this.space = indent(this.space);
// eslint-disable-next-line no-loop-func
ppl.descriptorSets.forEach((value, key): void => {
this.oss += `${this.space}DescriptorSet<${getUpdateFrequencyName(key)}> {\n`;
this.space = indent(this.space);
const uniformBlocks = value.descriptorSetLayoutData.uniformBlocks;
uniformBlocks.forEach((uniformBlock, attrNameID): void => {
const name = g.valueNames[attrNameID];
this.oss += `${this.space}UniformBlock "${name}" {\n`;
for (const u of uniformBlock.members) {
if (u.count > 1) {
this.oss += `${this.space} ${u.name}[${u.count}]: ${getGfxTypeName(u.type)}\n`;
} else {
this.oss += `${this.space} ${u.name}: ${getGfxTypeName(u.type)}\n`;
}
}
this.oss += `${this.space}}\n`;
});
const blocks = value.descriptorSetLayoutData.descriptorBlocks;
for (let j = 0; j < blocks.length; ++j) {
const block = blocks[j];
this.oss += `${this.space}Block<${getDescriptorTypeOrderName(block.type)}, ${getVisibilityName(block.visibility)}> {\n`;
this.oss += `${this.space} offset: ${block.offset}\n`;
this.oss += `${this.space} capacity: ${block.capacity}\n`;
this.oss += `${this.space} count: ${block.descriptors.length}\n`;
if (block.descriptors.length > 0) {
this.oss += `${this.space} Descriptors{ \n`;
const count = 0;
for (let k = 0; k < block.descriptors.length; ++k) {
const d: DescriptorData = block.descriptors[k];
// if (count++) {
this.oss += this.space;
this.oss += ' ';
const n: string = g.valueNames[d.descriptorID];
this.oss += `"${n}`;
if (d.count !== 1) {
this.oss += `[${d.count}]`;
}
this.oss += '"';
// }
this.oss += '\n';
}
this.oss += `${this.space} }\n`;
}
this.oss += `${this.space}}\n`;
}
this.space = unindent(this.space);
this.oss += `${this.space}}\n`;
});
}
finishVertex (v: number, g: LayoutGraphData): void {
this.space = unindent(this.space);
this.oss += `${this.space}}\n`;
}
space = '';
oss = '';
}
// text tools, indent 4 spaces
function indent (space: string): string {
return `${space} `;
}
// text tools, unindent 4 spaces
function unindent (space: string): string {
return space.substring(0, space.length > 4 ? space.length - 4 : 0);
}
// flatten DescriptorBlock to DescriptorBlockFlattened
function convertDescriptorBlock (block: DescriptorBlock): DescriptorBlockFlattened {
const flattened = new DescriptorBlockFlattened();
// sort descriptors by name
const descriptors = Array.from(block.descriptors).sort(
(a, b): number => String(a[0]).localeCompare(b[0]),
);
// flatten descriptors
descriptors.forEach((v: [string, Descriptor]): void => {
const name: string = v[0];
const d = v[1];
flattened.descriptorNames.push(name);
flattened.descriptors.push(d);
});
// sort uniforms by name
const uniformBlocks = Array.from(block.uniformBlocks).sort(
(a, b): number => String(a[0]).localeCompare(b[0]),
);
// flatten uniforms
uniformBlocks.forEach((v: [string, UniformBlock]): void => {
const name = v[0];
const uniformBlock = v[1];
flattened.uniformBlockNames.push(name);
flattened.uniformBlocks.push(uniformBlock);
});
// calculate count and capacity
flattened.count = block.count;
flattened.capacity = block.capacity;
return flattened;
}
// cache of descriptor blocks
class DescriptorCounter {
public addDescriptor (key: string, name: string, count: number): void {
// key is DescriptorBlockIndex
// name is Descriptor name
// count is Descriptor count
const v = this.counter.get(key);
if (v === undefined) {
this.counter.set(key, count);
this.inspector.set(key, [name]);
return;
}
this.counter.set(key, v + count);
this.inspector.get(key)?.push(name);
}
// counter is the num of descriptors in each block
readonly counter = new Map<string, number>();
readonly inspector = new Map<string, Array<string>>();
}
// print LayoutGraph (not LayoutGraphData)
class LayoutGraphPrintVisitor extends DefaultVisitor {
discoverVertex (v: number, g: LayoutGraph): void {
const info: DescriptorDB = g.getDescriptors(v);
const name = g.getName(v);
this.oss += `${this.space}"${name}": `;
switch (g.id(v)) {
case LayoutGraphValue.RenderStage:
this.oss += `RenderStage {\n`;
break;
case LayoutGraphValue.RenderPhase:
this.oss += `RenderPhase {\n`;
break;
default:
this.oss += `unknown LayoutGraphValue {\n`;
break;
}
this.space = indent(this.space);
const sortedMap: Map<string, DescriptorBlock> = new Map<string, DescriptorBlock>(
Array.from(info.blocks).sort((a, b): number => String(a[0]).localeCompare(b[0])),
);
sortedMap.forEach((block: DescriptorBlock, key: string): void => {
const index: DescriptorBlockIndex = JSON.parse(key);
const flat = convertDescriptorBlock(block);
this.oss += `${this.space}DescriptorBlock {\n`;
this.space = indent(this.space);
this.oss += `${this.space}updateRate: ${getUpdateFrequencyName(index.updateFrequency)}\n`;
this.oss += `${this.space}type: ${getDescriptorTypeOrderName(index.descriptorType)}\n`;
this.oss += `${this.space}visibility: ${getVisibilityName(index.visibility)}\n`;
this.oss += `${this.space}descriptors: [${flat.descriptorNames.join(', ')}]\n`;
this.oss += `${this.space}uniformBlocks: [`;
for (let i = 0; i < flat.uniformBlocks.length; ++i) {
if (i) {
this.oss += ', ';
}
this.oss += `${flat.uniformBlocks[i].name}`;
}
this.oss += `]\n`;
this.oss += `${this.space}count: ${flat.count}\n`;
this.oss += `${this.space}capacity: ${flat.capacity}\n`;
this.space = unindent(this.space);
this.oss += `${this.space}}\n`;
});
}
finishVertex (v: number, g: LayoutGraphData): void {
this.space = unindent(this.space);
this.oss += `${this.space}}\n`;
}
space = '';
oss = '';
}
// get pass name from effect
function getPassName (pass: EffectAsset.IPassInfo): string {
if (pass.pass === undefined) {
return 'default';
}
return pass.pass;
}
// get phase name from effect
function getPhaseName (pass: EffectAsset.IPassInfo): string {
if (pass.phase === undefined) {
return 'default';
}
if (typeof (pass.phase) === 'number') {
return pass.phase.toString();
}
return pass.phase;
}
// key of Visibility
export class VisibilityIndex {
constructor (
updateFrequency = UpdateFrequency.PER_INSTANCE,
parameterType = ParameterType.TABLE,
descriptorType = DescriptorTypeOrder.UNIFORM_BUFFER,
) {
this.updateFrequency = updateFrequency;
this.parameterType = parameterType;
this.descriptorType = descriptorType;
}
updateFrequency: UpdateFrequency;
parameterType: ParameterType;
descriptorType: DescriptorTypeOrder;
}
// descriptors of same visibility
export class VisibilityBlock {
public mergeVisibility (name: string, vis: ShaderStageFlagBit): void {
// for each descriptor, merge visibility
// rate must >= PER_PHASE
const v0 = this.descriptors.get(name);
if (v0 === undefined) {
this.descriptors.set(name, vis);
} else {
this.descriptors.set(name, v0 | vis);
}
}
public getVisibility (name: string): ShaderStageFlagBit {
const v = this.descriptors.get(name);
if (v === undefined) {
error(`Can't find visibility for descriptor: ${name}`);
return ShaderStageFlagBit.NONE;
}
return v;
}
descriptors = new Map<string, ShaderStageFlagBit>();
}
// visibility database of phase
export class VisibilityDB {
public getBlock (index: VisibilityIndex): VisibilityBlock {
const key = JSON.stringify(index);
let block = this.blocks.get(key);
if (block === undefined) {
block = new VisibilityBlock();
this.blocks.set(key, block);
}
return block;
}
blocks = new Map<string, VisibilityBlock>();
}
// visibility database of pass
export class VisibilityPass {
public getPhase (phaseName: string): VisibilityDB {
const phase = this.phases.get(phaseName);
if (phase === undefined) {
const newPhase = new VisibilityDB();
this.phases.set(phaseName, newPhase);
return newPhase;
}
return phase;
}
phases = new Map<string, VisibilityDB>();
}
export class VisibilityGraph {
public getPass (passName: string): VisibilityPass {
const pass = this.passes.get(passName);
if (pass === undefined) {
const newPass = new VisibilityPass();
this.passes.set(passName, newPass);
return newPass;
}
return pass;
}
private merge (
rate: UpdateFrequency,
order: DescriptorTypeOrder,
infoArray: EffectAsset.IBlockInfo[] |
EffectAsset.IBufferInfo[] |
EffectAsset.ISamplerInfo[] |
EffectAsset.IInputAttachmentInfo[] |
EffectAsset.IImageInfo[] |
EffectAsset.ISamplerTextureInfo[] |
EffectAsset.ITextureInfo[],
db: VisibilityDB,
): void {
const blockIndex = new VisibilityIndex(
rate,
ParameterType.TABLE,
order,
);
const block = db.getBlock(blockIndex);
for (const info of infoArray) {
block.mergeVisibility(info.name, info.stageFlags);
}
}
public mergeEffect (asset: EffectAsset): void {
for (const tech of asset.techniques) {
for (const pass of tech.passes) {
const programName = pass.program;
let shader: EffectAsset.IShaderInfo | null = null;
for (const shaderInfo of asset.shaders) {
if (shaderInfo.name === programName) {
shader = shaderInfo;
}
}
if (!shader) {
continue;
}
if (shader.descriptors === undefined) {
warn(`No descriptors in shader: ${programName}, please reimport ALL effects`);
continue;
}
const passName = getPassName(pass);
const passData = this.getPass(passName);
const phaseName = getPhaseName(pass);
const phaseData = passData.getPhase(phaseName);
for (const list of shader.descriptors) {
if (list.rate < UpdateFrequency.PER_PHASE) {
// do not merger PER_BATCH, PER_INSTANCE descriptors
continue;
}
this.merge(list.rate, DescriptorTypeOrder.UNIFORM_BUFFER, list.blocks, phaseData);
this.merge(list.rate, DescriptorTypeOrder.STORAGE_BUFFER, list.buffers, phaseData);
this.merge(list.rate, DescriptorTypeOrder.TEXTURE, list.textures, phaseData);
this.merge(list.rate, DescriptorTypeOrder.SAMPLER_TEXTURE, list.samplerTextures, phaseData);
this.merge(list.rate, DescriptorTypeOrder.SAMPLER, list.samplers, phaseData);
this.merge(list.rate, DescriptorTypeOrder.STORAGE_IMAGE, list.images, phaseData);
this.merge(list.rate, DescriptorTypeOrder.INPUT_ATTACHMENT, list.subpassInputs, phaseData);
}
}
}
}
passes = new Map<string, VisibilityPass>();
}
// graph coloring help class
class VectorGraphColorMap implements MutableVertexPropertyMap<GraphColor> {
constructor (sz: number) {
this.colors = new Array<GraphColor>(sz);
}
get (u: number): GraphColor {
return this.colors[u];
}
put (u: number, value: GraphColor): void {
this.colors[u] = value;
}
readonly colors: Array<GraphColor>;
}
// class to layout all descriptors
export class LayoutGraphInfo {
constructor (visg: VisibilityGraph) {
this.visg = visg;
}
lg = new LayoutGraph();
visg: VisibilityGraph;
readonly enableDebug = false;
private getPassID (passName: string, type: RenderPassType): number {
const lg = this.lg;
let passID = lg.locateChild(lg.nullVertex(), passName);
if (passID === lg.nullVertex()) {
passID = lg.addVertex<LayoutGraphValue.RenderStage>(
LayoutGraphValue.RenderStage, type,
passName, new DescriptorDB(),
lg.nullVertex(),
);
}
assert(passID !== lg.nullVertex());
return passID;
}
private getSubpassID (subpassName: string, passID: number): number {
const lg = this.lg;
let subpassID = lg.locateChild(passID, subpassName);
if (subpassID === lg.nullVertex()) {
subpassID = lg.addVertex<LayoutGraphValue.RenderStage>(
LayoutGraphValue.RenderStage, RenderPassType.RENDER_SUBPASS,
subpassName, new DescriptorDB(),
passID,
);
}
assert(subpassID !== lg.nullVertex());
return subpassID;
}
private getPhaseID (phaseName: string, subpassOrPassID: number): number {
const lg = this.lg;
let phaseID = lg.locateChild(subpassOrPassID, phaseName);
if (phaseID === lg.nullVertex()) {
phaseID = lg.addVertex<LayoutGraphValue.RenderPhase>(
LayoutGraphValue.RenderPhase, new RenderPhase(),
phaseName, new DescriptorDB(),
subpassOrPassID,
);
}
assert(phaseID !== lg.nullVertex());
return phaseID;
}
private getDescriptorBlock (key: string, descriptorDB: DescriptorDB): DescriptorBlock {
const value = descriptorDB.blocks.get(key);
if (value === undefined) {
const uniformBlock: DescriptorBlock = new DescriptorBlock();
descriptorDB.blocks.set(key, uniformBlock);
return uniformBlock;
}
return value;
}
private checkConsistency (lhs: UniformBlock, rhs: UniformBlock): boolean {
if (lhs.count !== 1) {
return false;
}
if (lhs.members.length !== rhs.members.length) {
return false;
}
for (let i = 0; i < lhs.members.length; ++i) {
if (lhs.members[i].name !== rhs.members[i].name) {
return false;
}
if (lhs.members[i].type !== rhs.members[i].type) {
return false;
}
if (lhs.members[i].count !== rhs.members[i].count) {
return false;
}
}
return true;
}
private makeUniformBlock (info: EffectAsset.IBlockInfo): UniformBlock {
const uniformBlock = new UniformBlock(0, 0, info.name);
uniformBlock.count = 1;
for (const member of info.members) {
uniformBlock.members.push(new Uniform(member.name, member.type, member.count));
}
return uniformBlock;
}
private addDescriptor (block: DescriptorBlock, name: string, type = Type.UNKNOWN): void {
const value = block.descriptors.get(name);
if (value === undefined) {
block.descriptors.set(name, new Descriptor(type));
++block.capacity;
++block.count;
return;
}
if (value.type !== type) {
warn(`Type mismatch for descriptor ${name}`);
}
}
private addUniformBlock (block: DescriptorBlock,
name: string, gfxBlock: UniformBlock): void {
const value = block.uniformBlocks.get(name);
if (value === undefined) {
block.uniformBlocks.set(name, gfxBlock);
return;
}
if (!this.checkConsistency(value, gfxBlock)) {
warn(`Uniform block ${name} is inconsistent in the same block`);
}
}
private buildBlocks (visDB: VisibilityDB, rate: UpdateFrequency, blocks: EffectAsset.IBlockInfo[], db: DescriptorDB, counter: DescriptorCounter): void {
const visBlock = visDB.getBlock({
updateFrequency: rate,
parameterType: ParameterType.TABLE,
descriptorType: DescriptorTypeOrder.UNIFORM_BUFFER,
});
for (const info of blocks) {
const blockIndex = new DescriptorBlockIndex(
rate,
ParameterType.TABLE,
DescriptorTypeOrder.UNIFORM_BUFFER,
rate >= UpdateFrequency.PER_PHASE ? visBlock.getVisibility(info.name) : info.stageFlags,
);
const key = JSON.stringify(blockIndex);
const block = this.getDescriptorBlock(key, db);
if (blockIndex.updateFrequency > UpdateFrequency.PER_BATCH) {
this.addDescriptor(block, info.name);
this.addUniformBlock(block, info.name, this.makeUniformBlock(info));
} else {
counter.addDescriptor(key, info.name, 1);
}
}
}
private buildBuffers (
visDB: VisibilityDB,
rate: UpdateFrequency,
infoArray: EffectAsset.IBufferInfo[],
type: Type, db: DescriptorDB, counter: DescriptorCounter,
): void {
const visBlock = visDB.getBlock({
updateFrequency: rate,
parameterType: ParameterType.TABLE,
descriptorType: DescriptorTypeOrder.STORAGE_BUFFER,
});
for (const info of infoArray) {
const blockIndex = new DescriptorBlockIndex(
rate,
ParameterType.TABLE,
DescriptorTypeOrder.STORAGE_BUFFER,
rate >= UpdateFrequency.PER_PHASE ? visBlock.getVisibility(info.name) : info.stageFlags,
);
const key = JSON.stringify(blockIndex);
const block = this.getDescriptorBlock(key, db);
if (blockIndex.updateFrequency > UpdateFrequency.PER_BATCH) {
this.addDescriptor(block, info.name, type);
} else {
counter.addDescriptor(key, info.name, 1);
}
}
}
private buildNonTextures (
visDB: VisibilityDB,
rate: UpdateFrequency,
order: DescriptorTypeOrder,
infoArray: EffectAsset.ISamplerInfo[] | EffectAsset.IInputAttachmentInfo[],
type: Type, db: DescriptorDB, counter: DescriptorCounter,
): void {
const visBlock = visDB.getBlock({
updateFrequency: rate,
parameterType: ParameterType.TABLE,
descriptorType: order,
});
for (const info of infoArray) {
const blockIndex = new DescriptorBlockIndex(
rate,
ParameterType.TABLE,
order,
rate >= UpdateFrequency.PER_PHASE ? visBlock.getVisibility(info.name) : info.stageFlags,
);
const key = JSON.stringify(blockIndex);
const block = this.getDescriptorBlock(key, db);
if (blockIndex.updateFrequency > UpdateFrequency.PER_BATCH) {
this.addDescriptor(block, info.name, type);
} else {
counter.addDescriptor(key, info.name, info.count);
}
}
}
private buildTextures (
visDB: VisibilityDB,
rate: UpdateFrequency,
order: DescriptorTypeOrder,
infoArray: EffectAsset.IImageInfo[] | EffectAsset.ISamplerTextureInfo[] | EffectAsset.ITextureInfo[],
db: DescriptorDB, counter: DescriptorCounter,
): void {
const visBlock = visDB.getBlock({
updateFrequency: rate,
parameterType: ParameterType.TABLE,
descriptorType: order,
});
for (const info of infoArray) {
const blockIndex = new DescriptorBlockIndex(
rate,
ParameterType.TABLE,
order,
rate >= UpdateFrequency.PER_PHASE ? visBlock.getVisibility(info.name) : info.stageFlags,
);
const key = JSON.stringify(blockIndex);
const block = this.getDescriptorBlock(key, db);
if (blockIndex.updateFrequency > UpdateFrequency.PER_BATCH) {
this.addDescriptor(block, info.name, info.type);
} else {
counter.addDescriptor(key, info.name, info.count);
}
}
}
public addEffect (asset: EffectAsset): void {
const lg = this.lg;
for (const tech of asset.techniques) {
for (const pass of tech.passes) {
const programName = pass.program;
let shader: EffectAsset.IShaderInfo | null = null;
for (const shaderInfo of asset.shaders) {
if (shaderInfo.name === programName) {
shader = shaderInfo;
}
}
if (!shader) {
warn(`program: ${programName} not found`);
continue;
}
if (shader.descriptors === undefined) {
warn(`No descriptors in shader: ${programName}, please reimport ALL effects`);
continue;
}
// get database
const passName = getPassName(pass);
const phaseName = getPhaseName(pass);
const enableSubpass = pass.subpass && pass.subpass !== '' && ENABLE_SUBPASS;
const passID = this.getPassID(passName, enableSubpass
? RenderPassType.RENDER_PASS : RenderPassType.SINGLE_RENDER_PASS);
const subpassID: number | undefined = enableSubpass
? this.getSubpassID(pass.subpass!, passID) : undefined;
const phaseID = this.getPhaseID(phaseName, subpassID || passID);
const passVis = this.visg.getPass(passName);
const visDB = passVis.getPhase(phaseName);
const db = lg.getDescriptors(phaseID);
const counter = new DescriptorCounter();
// merge descriptors and reserve capacity
for (const list of shader.descriptors) {
this.buildBlocks(visDB, list.rate, list.blocks, db, counter);
this.buildBuffers(visDB, list.rate, list.buffers, Type.UNKNOWN, db, counter);
this.buildNonTextures(visDB, list.rate, DescriptorTypeOrder.SAMPLER, list.samplers, Type.SAMPLER, db, counter);
this.buildNonTextures(visDB, list.rate, DescriptorTypeOrder.INPUT_ATTACHMENT, list.subpassInputs, Type.SAMPLER, db, counter);
this.buildTextures(visDB, list.rate, DescriptorTypeOrder.TEXTURE, list.textures, db, counter);
this.buildTextures(visDB, list.rate, DescriptorTypeOrder.SAMPLER_TEXTURE, list.samplerTextures, db, counter);
this.buildTextures(visDB, list.rate, DescriptorTypeOrder.STORAGE_IMAGE, list.images, db, counter);
}
// update max capacity and debug info
counter.counter.forEach((v: number, key: string): void => {
const block = this.getDescriptorBlock(key, db);
if (v > block.capacity) {
block.capacity = Math.max(block.capacity, v);
if (this.enableDebug) {
const names = counter.inspector.get(key);
if (names === undefined) {
return;
}
block.descriptors.clear();
for (const name of names) {
block.descriptors.set(name, new Descriptor());
}
}
}
});
}
}
}
public build (): number {
const lg = this.lg;
const visMap = new Map<number, VisibilityDB>();
// merge phase to pass
for (const v of lg.vertices()) {
if (lg.id(v) === LayoutGraphValue.RenderStage) {
// create visibility database
visMap.set(v, new VisibilityDB());
continue;
}
const phaseID = v;
const parentID = lg.getParent(phaseID);
if (lg.id(parentID) !== LayoutGraphValue.RenderStage) {
error(`phase: ${lg.getName(phaseID)} has no parent stage`);
return 1;
}
const phaseDB = lg.getDescriptors(phaseID);
const passVisDB = visMap.get(parentID);
if (!passVisDB) {
error(`pass: ${lg.getName(parentID)} has no visibility database`);
return 1;
}
// merge phase visibility to pass visibility
for (const [key, block] of phaseDB.blocks) {
const index: DescriptorBlockIndex = JSON.parse(key);
if (index.updateFrequency <= UpdateFrequency.PER_PHASE) {
continue;
}
const visIndex = new VisibilityIndex(index.updateFrequency, index.parameterType, index.descriptorType);
const passVisBlock = passVisDB.getBlock(visIndex);
for (const [name, d] of block.descriptors) {
passVisBlock.mergeVisibility(name, index.visibility);
}
}
}
// build phase decriptors
for (const v of lg.vertices()) {
if (lg.id(v) === LayoutGraphValue.RenderStage) {
continue;
}
const phaseID = v;
const parentID = lg.getParent(phaseID);
if (lg.id(parentID) !== LayoutGraphValue.RenderStage) {
error(`phase: ${lg.getName(phaseID)} has no parent stage`);
return 1;
}
const passDB = lg.getDescriptors(parentID);
const phaseDB = lg.getDescriptors(phaseID);
const passVisDB = visMap.get(parentID);
if (passVisDB === undefined) {
error(`pass: ${lg.getName(parentID)} has no visibility database`);
return 1;
}
for (const [key0, block] of phaseDB.blocks) {
const index0: DescriptorBlockIndex = JSON.parse(key0);
if (index0.updateFrequency <= UpdateFrequency.PER_PHASE) {
continue;
}
const visIndex = new VisibilityIndex(
index0.updateFrequency, index0.parameterType, index0.descriptorType,
);
const passVisBlock = passVisDB.getBlock(visIndex);
for (const [name, d] of block.descriptors) {
const vis = passVisBlock.getVisibility(name);
let passBlock: DescriptorBlock;
if (vis === index0.visibility) {
passBlock = this.getDescriptorBlock(key0, passDB);
} else {
const index = new DescriptorBlockIndex(
index0.updateFrequency,
index0.parameterType,
index0.descriptorType,
vis,
);
const key = JSON.stringify(index);
passBlock = this.getDescriptorBlock(key, passDB);
}
this.addDescriptor(passBlock, name, d.type);
if (index0.descriptorType !== DescriptorTypeOrder.UNIFORM_BUFFER) {
continue;
}
const b = block.uniformBlocks.get(name);
if (!b) {
error(`uniform block: ${name} not found`);
return 1;
}
this.addUniformBlock(passBlock, name, b);
}
}
}
// update pass
for (const passID of lg.vertices()) {
// skip RenderPhase
if (lg.id(passID) !== LayoutGraphValue.RenderStage) {
continue;
}
// skip RENDER_PASS
if (lg.getRenderStage(passID) === RenderPassType.RENDER_PASS) {
continue;
}
// build SINGLE_RENDER_PASS or RENDER_SUBPASS
assert(lg.getRenderStage(passID) === RenderPassType.SINGLE_RENDER_PASS
|| lg.getRenderStage(passID) === RenderPassType.RENDER_SUBPASS);
const passDB = lg.getDescriptors(passID);
// update children phases
for (const e of lg.children(passID)) {
const phaseID = lg.child(e);
if (lg.id(phaseID) !== LayoutGraphValue.RenderPhase) {
error(`pass: ${lg.getName(passID)} is not single_render_pass or render_subpass`);
return 1;
}
const phaseDB = lg.getDescriptors(phaseID);
for (const [key, passBlock] of passDB.blocks) {
const index: DescriptorBlockIndex = JSON.parse(key);
if (index.updateFrequency !== UpdateFrequency.PER_PASS) {
error(`phase: ${lg.getName(phaseID)} update frequency is not PER_PASS`);
return 1;
}
if (passBlock.count === 0) {
error(`pass: ${lg.getName(passID)} count is 0`);
return 1;
}
if (passBlock.capacity !== passBlock.count) {
error(`pass: ${lg.getName(passID)} capacity does not equal count`);
return 1;
}
const phaseBlock = this.getDescriptorBlock(key, phaseDB);
phaseBlock.descriptors.clear();
phaseBlock.uniformBlocks.clear();
phaseBlock.capacity = passBlock.capacity;
phaseBlock.count = passBlock.count;
for (const [name, d] of passBlock.descriptors) {
phaseBlock.descriptors.set(name, d);
}
for (const [name, b] of passBlock.uniformBlocks) {
phaseBlock.uniformBlocks.set(name, b);
}
}
}
}
// debug(this.print());
return 0;
}
public print (): string {
const print = new LayoutGraphPrintVisitor();
const colorMap = new VectorGraphColorMap(this.lg.numVertices());
depthFirstSearch(this.lg, print, colorMap);
return print.oss;
}
}
// sort descriptorBlocks using DescriptorBlockIndex
function sortDescriptorBlocks<T> (lhs: [string, T], rhs: [string, T]): number {
const lhsIndex: DescriptorBlockIndex = JSON.parse(lhs[0]);
const rhsIndex: DescriptorBlockIndex = JSON.parse(rhs[0]);
const lhsValue = lhsIndex.updateFrequency * 10000
+ lhsIndex.parameterType * 1000
+ lhsIndex.descriptorType * 100
+ lhsIndex.visibility;
const rhsValue = rhsIndex.updateFrequency * 10000
+ rhsIndex.parameterType * 1000
+ rhsIndex.descriptorType * 100
+ rhsIndex.visibility;
return lhsValue - rhsValue;
}
// build LayoutGraphData
function buildLayoutGraphDataImpl (graph: LayoutGraph, builder: LayoutGraphBuilder2): void {
for (const v of graph.vertices()) {
const db = graph.getDescriptors(v);
let minLevel = UpdateFrequency.PER_INSTANCE;
let maxLevel = UpdateFrequency.PER_PASS;
let isRenderPass = false;
switch (graph.id(v)) {
case LayoutGraphValue.RenderStage: {
const type = graph.getRenderStage(v);
const parentID = graph.getParent(v);
if (type === RenderPassType.RENDER_SUBPASS) {
assert(parentID !== graph.nullVertex());
} else {
assert(parentID === graph.nullVertex());
}
if (type === RenderPassType.RENDER_PASS) {
isRenderPass = true;
}
const vertID = builder.addRenderStage(graph.getName(v), parentID);
if (vertID !== v) {
error('vertex id mismatch');
}
minLevel = UpdateFrequency.PER_PASS;
maxLevel = UpdateFrequency.PER_PASS;
break;
}
case LayoutGraphValue.RenderPhase: {
const parentID = graph.getParent(v);
const parentType = graph.getRenderStage(parentID);
assert(parentType === RenderPassType.RENDER_SUBPASS || parentType === RenderPassType.SINGLE_RENDER_PASS);
const vertID = builder.addRenderPhase(graph.getName(v), parentID);
if (vertID !== v) {
error('vertex id mismatch');
}
const phase = graph.getRenderPhase(v);
for (const shaderName of phase.shaders) {
builder.addShader(shaderName, v);
}
minLevel = UpdateFrequency.PER_INSTANCE;
maxLevel = UpdateFrequency.PER_PHASE;
break;
}
default:
error('unknown vertex type');
minLevel = UpdateFrequency.PER_INSTANCE;
minLevel = UpdateFrequency.PER_PASS;
break;
}
if (isRenderPass) {
assert(db.blocks.size === 0);
continue;
}
const flattenedBlocks = Array.from(db.blocks).sort(sortDescriptorBlocks);
flattenedBlocks.forEach((value: [string, DescriptorBlock]): void => {
const key = value[0];
const block = value[1];
const index: DescriptorBlockIndex = JSON.parse(key);
if (index.updateFrequency > maxLevel || index.updateFrequency < minLevel) {
return;
}
const flattened = convertDescriptorBlock(block);
if (block.capacity === 0) {
error('block capacity is 0');
return;
}
if (index.updateFrequency > UpdateFrequency.PER_BATCH) {
builder.addDescriptorBlock(v, index, flattened);
for (let i = 0; i < flattened.uniformBlockNames.length; ++i) {
builder.addUniformBlock(v, index, flattened.uniformBlockNames[i], flattened.uniformBlocks[i]);
}
} else {
builder.reserveDescriptorBlock(v, index, flattened);
}
});
}
}
// get descriptor nameID from name
export function getOrCreateDescriptorID (lg: LayoutGraphData, name: string): number {
const nameID = lg.attributeIndex.get(name);
if (nameID === undefined) {
const newID = lg.valueNames.length;
lg.attributeIndex.set(name, newID);
lg.valueNames.push(name);
return newID;
}
return nameID;
}
export function getOrCreateConstantID (lg: LayoutGraphData, name: string): number {
const nameID = lg.constantIndex.get(name);
if (nameID === undefined) {
const newID = lg.valueNames.length;
lg.constantIndex.set(name, newID);
lg.valueNames.push(name);
return newID;
}
return nameID;
}
// LayoutGraphData builder
class LayoutGraphBuilder2 {
public constructor (lg: LayoutGraphData) {
this.lg = lg;
}
clear (): void {
this.lg.clear();
}
addRenderStage (name: string, parentID: number): number {
return this.lg.addVertex<LayoutGraphDataValue.RenderStage>(
LayoutGraphDataValue.RenderStage,
new RenderStageData(), name,
UpdateFrequency.PER_PASS, new PipelineLayoutData(),
parentID,
);
}
addRenderPhase (name: string, parentID: number): number {
return this.lg.addVertex<LayoutGraphDataValue.RenderPhase>(
LayoutGraphDataValue.RenderPhase,
new RenderPhaseData(), name,
UpdateFrequency.PER_PHASE, new PipelineLayoutData(),
parentID,
);
}
addShader (name: string, parentPhaseID: number): void {
const lg = this.lg;
const phaseData = lg.getRenderPhase(parentPhaseID);
// 填充shaderData数据
const shaderData = new ShaderProgramData();
const id = phaseData.shaderPrograms.length;
phaseData.shaderPrograms.push(shaderData);
phaseData.shaderIndex.set(name, id);
// 注册shader所在的phase的ID
lg.shaderLayoutIndex.set(name, parentPhaseID);
}
private getDescriptorSetData (ppl: PipelineLayoutData, rate: UpdateFrequency): DescriptorSetData {
const data = ppl.descriptorSets.get(rate);
if (data === undefined) {
const newData = new DescriptorSetData();
ppl.descriptorSets.set(rate, newData);
return newData;
}
return data;
}
addDescriptorBlock (nodeID: number, index: DescriptorBlockIndex, block: Readonly<DescriptorBlockFlattened>): void {
if (block.capacity <= 0) {
error('empty block');
return;
}
if (block.descriptorNames.length !== block.descriptors.length) {
error('error descriptor');
return;
}
if (block.uniformBlockNames.length !== block.uniformBlocks.length) {
error('error uniform');
return;
}
if (!(index.updateFrequency >= UpdateFrequency.PER_INSTANCE
&& index.updateFrequency <= UpdateFrequency.PER_PASS)) {
error('invalid update frequency');
return;
}
const lg = this.lg;
const ppl: PipelineLayoutData = lg.getLayout(nodeID);
const setData = this.getDescriptorSetData(ppl, index.updateFrequency);
const layout = setData.descriptorSetLayoutData;
const dstBlock = new DescriptorBlockData(index.descriptorType, index.visibility, block.capacity);
dstBlock.offset = layout.capacity;
layout.descriptorBlocks.push(dstBlock);
for (let j = 0; j < block.descriptors.length; ++j) {
const name: string = block.descriptorNames[j];
const d: Descriptor = block.descriptors[j];
const nameID = getOrCreateDescriptorID(lg, name);
const data = new DescriptorData(nameID, d.type, d.count);
dstBlock.descriptors.push(data);
}
layout.capacity += block.capacity;
if (index.descriptorType === DescriptorTypeOrder.UNIFORM_BUFFER
|| index.descriptorType === DescriptorTypeOrder.DYNAMIC_UNIFORM_BUFFER) {
layout.uniformBlockCapacity += block.capacity;
} else if (index.descriptorType === DescriptorTypeOrder.SAMPLER_TEXTURE) {
layout.samplerTextureCapacity += block.capacity;
}
}
addUniformBlock (nodeID: number, index: DescriptorBlockIndex, name: string, uniformBlock: UniformBlock): void {
const g: LayoutGraphData = this.lg;
const ppl: PipelineLayoutData = g.getLayout(nodeID);
const setData = this.getDescriptorSetData(ppl, index.updateFrequency);
const layout = setData.descriptorSetLayoutData;
const nameID = getOrCreateDescriptorID(g, name);
layout.uniformBlocks.set(nameID, uniformBlock);
// register constant names
for (const member of uniformBlock.members) {
getOrCreateConstantID(g, member.name);
}
}
reserveDescriptorBlock (nodeID: number, index: DescriptorBlockIndex, block: DescriptorBlockFlattened): void {
if (block.capacity <= 0) {
error('empty block');
return;
}
const g: LayoutGraphData = this.lg;
const ppl: PipelineLayoutData = g.getLayout(nodeID);
const setData = this.getDescriptorSetData(ppl, index.updateFrequency);
const layout = setData.descriptorSetLayoutData;
const dstBlock = new DescriptorBlockData(index.descriptorType, index.visibility, block.capacity);
dstBlock.offset = layout.capacity;
layout.descriptorBlocks.push(dstBlock);
layout.capacity += block.capacity;
if (index.descriptorType === DescriptorTypeOrder.UNIFORM_BUFFER
|| index.descriptorType === DescriptorTypeOrder.DYNAMIC_UNIFORM_BUFFER) {
layout.uniformBlockCapacity += block.capacity;
} else if (index.descriptorType === DescriptorTypeOrder.SAMPLER_TEXTURE) {
layout.samplerTextureCapacity += block.capacity;
}
}
compile (): number {
// debug(this.print());
return 0;
}
print (): string {
const g: LayoutGraphData = this.lg;
const visitor = new PrintVisitor();
const colorMap = new VectorGraphColorMap(g.numVertices());
depthFirstSearch(g, visitor, colorMap);
return visitor.oss;
}
readonly lg: LayoutGraphData;
}
export function buildLayoutGraphData (lg: LayoutGraph, lgData: LayoutGraphData): void {
const builder = new LayoutGraphBuilder2(lgData);
buildLayoutGraphDataImpl(lg, builder);
builder.compile();
}
function createDescriptorInfo (layoutData: DescriptorSetLayoutData, info: DescriptorSetLayoutInfo): void {
for (let i = 0; i < layoutData.descriptorBlocks.length; ++i) {
const block = layoutData.descriptorBlocks[i];
let slot = block.offset;
for (let j = 0; j < block.descriptors.length; ++j) {
const d = block.descriptors[j];
const binding: DescriptorSetLayoutBinding = new DescriptorSetLayoutBinding();
binding.binding = slot;
binding.descriptorType = getGfxDescriptorType(block.type);
binding.count = d.count;
binding.stageFlags = block.visibility;
binding.immutableSamplers = [];
info.bindings.push(binding);
slot += d.count;
}
}
}
function createDescriptorSetLayout (device: Device | null, layoutData: DescriptorSetLayoutData): DescriptorSetLayout | null {
const info: DescriptorSetLayoutInfo = new DescriptorSetLayoutInfo();
createDescriptorInfo(layoutData, info);
if (device) {
return device.createDescriptorSetLayout(info);
} else {
return null;
}
}
export function createGfxDescriptorSetsAndPipelines (device: Device | null, g: LayoutGraphData): void {
for (let i = 0; i < g._layouts.length; ++i) {
const ppl: PipelineLayoutData = g.getLayout(i);
ppl.descriptorSets.forEach((value, key): void => {
const level = value;
const layoutData = level.descriptorSetLayoutData;
if (device) {
const layout: DescriptorSetLayout | null = createDescriptorSetLayout(device, layoutData);
if (layout) {
level.descriptorSetLayout = (layout);
level.descriptorSet = (device.createDescriptorSet(new DescriptorSetInfo(layout)));
}
} else {
createDescriptorInfo(layoutData, level.descriptorSetLayoutInfo);
}
});
}
}
export function printLayoutGraphData (g: LayoutGraphData): string {
const visitor = new PrintVisitor();
const colorMap = new VectorGraphColorMap(g.numVertices());
depthFirstSearch(g, visitor, colorMap);
return visitor.oss;
}
// lookup DescriptorBlockData from Map
function getDescriptorBlockData (map: Map<string, DescriptorBlockData>, index: DescriptorBlockIndex): DescriptorBlockData {
const key = JSON.stringify(index);
const block = map.get(key);
if (block) {
return block;
}
const newBlock = new DescriptorBlockData(index.descriptorType, index.visibility, 0);
map.set(key, newBlock);
return newBlock;
}
// make DescriptorSetLayoutData from effect directly
export function makeDescriptorSetLayoutData (lg: LayoutGraphData,
rate: UpdateFrequency, set: number,
descriptors: EffectAsset.IDescriptorInfo): DescriptorSetLayoutData {
const map = new Map<string, DescriptorBlockData>();
const uniformBlocks: Map<number, UniformBlock> = new Map<number, UniformBlock>();
for (let i = 0; i < descriptors.blocks.length; i++) {
const cb = descriptors.blocks[i];
const block = getDescriptorBlockData(map, {
updateFrequency: rate,
parameterType: ParameterType.TABLE,
descriptorType: DescriptorTypeOrder.UNIFORM_BUFFER,
visibility: cb.stageFlags,
});
const nameID = getOrCreateDescriptorID(lg, cb.name);
block.descriptors.push(new DescriptorData(nameID, Type.UNKNOWN, 1));
// add uniform buffer
uniformBlocks.set(nameID, new UniformBlock(set, 0xFFFFFFFF, cb.name, cb.members, 1));
}
for (let i = 0; i < descriptors.samplerTextures.length; i++) {
const samplerTexture = descriptors.samplerTextures[i];
const block = getDescriptorBlockData(map, {
updateFrequency: rate,
parameterType: ParameterType.TABLE,
descriptorType: DescriptorTypeOrder.SAMPLER_TEXTURE,
visibility: samplerTexture.stageFlags,
});
const nameID = getOrCreateDescriptorID(lg, samplerTexture.name);
block.descriptors.push(new DescriptorData(nameID, samplerTexture.type, samplerTexture.count));
}
for (let i = 0; i < descriptors.samplers.length; i++) {
const sampler = descriptors.samplers[i];
const block = getDescriptorBlockData(map, {
updateFrequency: rate,
parameterType: ParameterType.TABLE,
descriptorType: DescriptorTypeOrder.SAMPLER,
visibility: sampler.stageFlags,
});
const nameID = getOrCreateDescriptorID(lg, sampler.name);
block.descriptors.push(new DescriptorData(nameID, Type.SAMPLER, sampler.count));
}
for (let i = 0; i < descriptors.textures.length; i++) {
const texture = descriptors.textures[i];
const block = getDescriptorBlockData(map, {
updateFrequency: rate,
parameterType: ParameterType.TABLE,
descriptorType: DescriptorTypeOrder.TEXTURE,
visibility: texture.stageFlags,
});
const nameID = getOrCreateDescriptorID(lg, texture.name);
block.descriptors.push(new DescriptorData(nameID, texture.type, texture.count));
}
for (let i = 0; i < descriptors.buffers.length; i++) {
const buffer = descriptors.buffers[i];
const block = getDescriptorBlockData(map, {
updateFrequency: rate,
parameterType: ParameterType.TABLE,
descriptorType: DescriptorTypeOrder.STORAGE_BUFFER,
visibility: buffer.stageFlags,
});
const nameID = getOrCreateDescriptorID(lg, buffer.name);
block.descriptors.push(new DescriptorData(nameID, Type.UNKNOWN, 1));
}
for (let i = 0; i < descriptors.images.length; i++) {
const image = descriptors.images[i];
const block = getDescriptorBlockData(map, {
updateFrequency: rate,
parameterType: ParameterType.TABLE,
descriptorType: DescriptorTypeOrder.STORAGE_IMAGE,
visibility: image.stageFlags,
});
const nameID = getOrCreateDescriptorID(lg, image.name);
block.descriptors.push(new DescriptorData(nameID, image.type, image.count));
}
for (let i = 0; i < descriptors.subpassInputs.length; i++) {
const subpassInput = descriptors.subpassInputs[i];
const block = getDescriptorBlockData(map, {
updateFrequency: rate,
parameterType: ParameterType.TABLE,
descriptorType: DescriptorTypeOrder.INPUT_ATTACHMENT,
visibility: subpassInput.stageFlags,
});
const nameID = getOrCreateDescriptorID(lg, subpassInput.name);
block.descriptors.push(new DescriptorData(nameID, Type.UNKNOWN, subpassInput.count));
}
// sort blocks
const flattenedBlocks = Array.from(map).sort(sortDescriptorBlocks);
const data = new DescriptorSetLayoutData(set, 0);
// calculate bindings
let capacity = 0;
for (const [key, block] of flattenedBlocks) {
const index = JSON.parse(key) as DescriptorBlockIndex;
block.offset = capacity;
for (const d of block.descriptors) {
if (index.descriptorType === DescriptorTypeOrder.UNIFORM_BUFFER) {
// update uniform buffer binding
const ub = uniformBlocks.get(d.descriptorID);
if (!ub) {
error(`Uniform block not found for ${d.descriptorID}`);
continue;
}
assert(ub.binding === 0xFFFFFFFF);
ub.binding = block.capacity;
// add uniform buffer to output
data.uniformBlocks.set(d.descriptorID, ub);
}
// update block capacity
const binding = data.bindingMap.get(d.descriptorID);
if (binding !== undefined) {
error(`Duplicated descriptor ${d.descriptorID}`);
}
data.bindingMap.set(d.descriptorID, block.offset + block.capacity);
block.capacity += d.count;
}
// increate total capacity
capacity += block.capacity;
data.capacity += block.capacity;
if (index.descriptorType === DescriptorTypeOrder.UNIFORM_BUFFER
|| index.descriptorType === DescriptorTypeOrder.DYNAMIC_UNIFORM_BUFFER) {
data.uniformBlockCapacity += block.capacity;
} else if (index.descriptorType === DescriptorTypeOrder.SAMPLER_TEXTURE) {
data.samplerTextureCapacity += block.capacity;
}
data.descriptorBlocks.push(block);
}
return data;
}
// fill DescriptorSetLayoutInfo from DescriptorSetLayoutData
export function initializeDescriptorSetLayoutInfo (layoutData: DescriptorSetLayoutData,
info: DescriptorSetLayoutInfo): void {
for (let i = 0; i < layoutData.descriptorBlocks.length; ++i) {
const block = layoutData.descriptorBlocks[i];
let slot = block.offset;
for (let j = 0; j < block.descriptors.length; ++j) {
const d = block.descriptors[j];
const binding = new DescriptorSetLayoutBinding();
binding.binding = slot;
binding.descriptorType = getGfxDescriptorType(block.type);
binding.count = d.count;
binding.stageFlags = block.visibility;
binding.immutableSamplers = [];
info.bindings.push(binding);
slot += d.count;
}
}
}
let _emptyDescriptorSetLayout: DescriptorSetLayout;
let _emptyPipelineLayout: PipelineLayout;
function populatePipelineLayoutInfo (layout: PipelineLayoutData,
rate: UpdateFrequency, info: PipelineLayoutInfo): void {
const set = layout.descriptorSets.get(rate);
if (set && set.descriptorSetLayout) {
info.setLayouts.push(set.descriptorSetLayout);
} else {
info.setLayouts.push(_emptyDescriptorSetLayout);
}
}
// initialize layout graph module
export function initializeLayoutGraphData (device: Device, lg: LayoutGraphData): void {
// create descriptor sets
_emptyDescriptorSetLayout = device.createDescriptorSetLayout(new DescriptorSetLayoutInfo());
_emptyPipelineLayout = device.createPipelineLayout(new PipelineLayoutInfo());
for (const v of lg.vertices()) {
const layoutData = lg.getLayout(v);
for (const [_, set] of layoutData.descriptorSets) {
if (set.descriptorSetLayout !== null) {
warn('descriptor set layout already initialized. It will be overwritten');
}
initializeDescriptorSetLayoutInfo(set.descriptorSetLayoutData,
set.descriptorSetLayoutInfo);
set.descriptorSetLayout = device.createDescriptorSetLayout(set.descriptorSetLayoutInfo);
}
}
// create pipeline layouts
for (const v of lg.vertices()) {
if (!lg.holds(LayoutGraphDataValue.RenderPhase, v)) {
continue;
}
const subpassOrPassID = lg.getParent(v);
const phaseID = v;
const passLayout = lg.getLayout(subpassOrPassID);
const phaseLayout = lg.getLayout(phaseID);
const info = new PipelineLayoutInfo();
populatePipelineLayoutInfo(passLayout, UpdateFrequency.PER_PASS, info);
populatePipelineLayoutInfo(phaseLayout, UpdateFrequency.PER_PHASE, info);
populatePipelineLayoutInfo(phaseLayout, UpdateFrequency.PER_BATCH, info);
populatePipelineLayoutInfo(phaseLayout, UpdateFrequency.PER_INSTANCE, info);
const phase = lg.getRenderPhase(phaseID);
phase.pipelineLayout = device.createPipelineLayout(info);
}
}
// terminate layout graph module
export function terminateLayoutGraphData (lg: LayoutGraphData): void {
for (const v of lg.vertices()) {
const layoutData = lg.getLayout(v);
for (const [_, set] of layoutData.descriptorSets) {
if (set.descriptorSetLayout !== null) {
set.descriptorSetLayout.destroy();
}
}
}
_emptyPipelineLayout.destroy();
_emptyDescriptorSetLayout.destroy();
}
// get empty descriptor set layout
export function getEmptyDescriptorSetLayout (): DescriptorSetLayout {
return _emptyDescriptorSetLayout;
}
// get empty pipeline layout
export function getEmptyPipelineLayout (): PipelineLayout {
return _emptyPipelineLayout;
}
// get descriptor set from LayoutGraphData (not from ProgramData)
export function getOrCreateDescriptorSetLayout (lg: LayoutGraphData,
subpassOrPassID: number, phaseID: number, rate: UpdateFrequency): DescriptorSetLayout {
if (rate < UpdateFrequency.PER_PASS) {
const phaseData = lg.getLayout(phaseID);
const data = phaseData.descriptorSets.get(rate);
if (data) {
if (!data.descriptorSetLayout) {
error('descriptor set layout not initialized');
return _emptyDescriptorSetLayout;
}
return data.descriptorSetLayout;
}
return _emptyDescriptorSetLayout;
}
assert(rate === UpdateFrequency.PER_PASS);
assert(subpassOrPassID === lg.getParent(phaseID));
const passData = lg.getLayout(subpassOrPassID);
const data = passData.descriptorSets.get(rate);
if (data) {
if (!data.descriptorSetLayout) {
error('descriptor set layout not initialized');
return _emptyDescriptorSetLayout;
}
return data.descriptorSetLayout;
}
return _emptyDescriptorSetLayout;
}
// getDescriptorSetLayout from LayoutGraphData
export function getDescriptorSetLayout (lg: LayoutGraphData,
subpassOrPassID: number, phaseID: number, rate: UpdateFrequency): DescriptorSetLayout | null {
if (rate < UpdateFrequency.PER_PASS) {
const phaseData = lg.getLayout(phaseID);
const data = phaseData.descriptorSets.get(rate);
if (data) {
if (!data.descriptorSetLayout) {
error('descriptor set layout not initialized');
return null;
}
return data.descriptorSetLayout;
}
return null;
}
assert(rate === UpdateFrequency.PER_PASS);
assert(subpassOrPassID === lg.getParent(phaseID));
const passData = lg.getLayout(subpassOrPassID);
const data = passData.descriptorSets.get(rate);
if (data) {
if (!data.descriptorSetLayout) {
error('descriptor set layout not initialized');
return null;
}
return data.descriptorSetLayout;
}
return null;
}
// get or create DescriptorBlockData from DescriptorSetLayoutData
export function getOrCreateDescriptorBlockData (data: DescriptorSetLayoutData,
type: DescriptorType, vis: ShaderStageFlagBit): DescriptorBlockData {
const order = getDescriptorTypeOrder(type);
for (const block of data.descriptorBlocks) {
if (block.type === order && block.visibility === vis) {
return block;
}
}
const block = new DescriptorBlockData(order, vis);
data.descriptorBlocks.push(block);
return block;
}
export function getProgramID (lg: LayoutGraphData, phaseID: number, programName: string): number {
assert(phaseID !== lg.nullVertex());
const phase = lg.getRenderPhase(phaseID);
const programID = phase.shaderIndex.get(programName);
if (programID === undefined) {
return INVALID_ID;
}
return programID;
}
export function getDescriptorNameID (lg: LayoutGraphData, name: string): number {
const nameID = lg.attributeIndex.get(name);
if (nameID === undefined) {
return INVALID_ID;
}
return nameID;
}
export function getDescriptorName (lg: LayoutGraphData, nameID: number): string {
if (nameID >= lg.valueNames.length) {
return '';
}
return lg.valueNames[nameID];
}
export function getPerPassDescriptorSetLayoutData (lg: LayoutGraphData,
subpassOrPassID: number): DescriptorSetLayoutData | null {
assert(subpassOrPassID !== lg.nullVertex());
const node = lg.getLayout(subpassOrPassID);
const set = node.descriptorSets.get(UpdateFrequency.PER_PASS);
if (set === undefined) {
return null;
}
return set.descriptorSetLayoutData;
}
export function getPerPhaseDescriptorSetLayoutData (lg: LayoutGraphData,
phaseID: number): DescriptorSetLayoutData | null {
assert(phaseID !== lg.nullVertex());
const node = lg.getLayout(phaseID);
const set = node.descriptorSets.get(UpdateFrequency.PER_PHASE);
if (set === undefined) {
return null;
}
return set.descriptorSetLayoutData;
}
export function getPerBatchDescriptorSetLayoutData (lg: LayoutGraphData,
phaseID: number, programID): DescriptorSetLayoutData | null {
assert(phaseID !== lg.nullVertex());
const phase = lg.getRenderPhase(phaseID);
assert(programID < phase.shaderPrograms.length);
const program = phase.shaderPrograms[programID];
const set = program.layout.descriptorSets.get(UpdateFrequency.PER_BATCH);
if (set === undefined) {
return null;
}
return set.descriptorSetLayoutData;
}
export function getPerInstanceDescriptorSetLayoutData (lg: LayoutGraphData,
phaseID: number, programID): DescriptorSetLayoutData | null {
assert(phaseID !== lg.nullVertex());
const phase = lg.getRenderPhase(phaseID);
assert(programID < phase.shaderPrograms.length);
const program = phase.shaderPrograms[programID];
const set = program.layout.descriptorSets.get(UpdateFrequency.PER_INSTANCE);
if (set === undefined) {
return null;
}
return set.descriptorSetLayoutData;
}
export function getBinding (layout: DescriptorSetLayoutData, nameID: number): number {
const binding = layout.bindingMap.get(nameID);
if (binding === undefined) {
return 0xFFFFFFFF;
}
return binding;
}