mirror of
https://github.com/cocos/cocos-engine.git
synced 2026-09-06 15:48:37 +08:00
1176 lines
48 KiB
TypeScript
1176 lines
48 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 { Attribute, DescriptorSetLayout, DescriptorType, DESCRIPTOR_BUFFER_TYPE, DESCRIPTOR_SAMPLER_TYPE, Device, MemoryAccessBit, PipelineLayout, PipelineLayoutInfo, Shader, ShaderInfo, ShaderStage, ShaderStageFlagBit, Type, Uniform, UniformBlock, UniformInputAttachment, UniformSampler, UniformSamplerTexture, UniformStorageBuffer, UniformStorageImage, UniformTexture, deviceManager } from '../../gfx';
|
|
import { genHandles, getActiveAttributes, getCombinationDefines, getShaderInstanceName, getSize, getVariantKey, populateMacros, prepareDefines } from '../../render-scene/core/program-utils';
|
|
import { getDeviceShaderVersion, MacroRecord } from '../../render-scene';
|
|
import { IProgramInfo } from '../../render-scene/core/program-lib';
|
|
import { DescriptorBlockData, DescriptorData, DescriptorSetData, DescriptorSetLayoutData, LayoutGraphData, LayoutGraphDataValue, PipelineLayoutData, RenderPhaseData, ShaderProgramData } from './layout-graph';
|
|
import { ProgramLibrary, ProgramProxy } from './private';
|
|
import { DescriptorTypeOrder, UpdateFrequency } from './types';
|
|
import { ProgramGroup, ProgramInfo } from './web-types';
|
|
import { getCustomPassID, getCustomPhaseID, getOrCreateDescriptorSetLayout, getEmptyDescriptorSetLayout, getEmptyPipelineLayout, initializeDescriptorSetLayoutInfo, makeDescriptorSetLayoutData, getDescriptorSetLayout, getOrCreateDescriptorID, getDescriptorTypeOrder, getProgramID, getDescriptorNameID, getDescriptorName, INVALID_ID, ENABLE_SUBPASS, getCustomSubpassID } from './layout-graph-utils';
|
|
import { IDescriptorSetLayoutInfo, localDescriptorSetLayout } from '../define';
|
|
import { PipelineRuntime } from './pipeline';
|
|
|
|
const _setIndex = [2, 1, 3, 0];
|
|
|
|
// make IProgramInfo from IShaderInfo
|
|
function makeProgramInfo (effectName: string, shader: EffectAsset.IShaderInfo): IProgramInfo {
|
|
const programInfo = { ...shader } as IProgramInfo;
|
|
programInfo.effectName = effectName;
|
|
|
|
populateMacros(programInfo);
|
|
|
|
return programInfo;
|
|
}
|
|
|
|
function findBinding (shaderInfo: ShaderInfo, name: string): { set: number, binding: number } {
|
|
for (const v of shaderInfo.blocks) {
|
|
if (v.name === name) {
|
|
return { set: v.set, binding: v.binding };
|
|
}
|
|
}
|
|
for (const v of shaderInfo.buffers) {
|
|
if (v.name === name) {
|
|
return { set: v.set, binding: v.binding };
|
|
}
|
|
}
|
|
for (const v of shaderInfo.samplerTextures) {
|
|
if (v.name === name) {
|
|
return { set: v.set, binding: v.binding };
|
|
}
|
|
}
|
|
for (const v of shaderInfo.samplers) {
|
|
if (v.name === name) {
|
|
return { set: v.set, binding: v.binding };
|
|
}
|
|
}
|
|
for (const v of shaderInfo.textures) {
|
|
if (v.name === name) {
|
|
return { set: v.set, binding: v.binding };
|
|
}
|
|
}
|
|
for (const v of shaderInfo.images) {
|
|
if (v.name === name) {
|
|
return { set: v.set, binding: v.binding };
|
|
}
|
|
}
|
|
for (const v of shaderInfo.subpassInputs) {
|
|
if (v.name === name) {
|
|
return { set: v.set, binding: v.binding };
|
|
}
|
|
}
|
|
// eslint-disable-next-line no-console
|
|
throw console.error('binding not found in shaderInfo!');
|
|
}
|
|
|
|
function overwriteShaderSourceBinding (shaderInfo: ShaderInfo, source: string): string {
|
|
let code = source;
|
|
const samplerExp = /layout\s*\(([^\)])+\)\s+uniform\s+(\b\w+\b\s+)?sampler(\w+)\s+(\b\w+\b)/g;
|
|
let samplerIter = samplerExp.exec(code);
|
|
while (samplerIter) {
|
|
const name = samplerIter[4];
|
|
const { set, binding } = findBinding(shaderInfo, name);
|
|
const precStr = samplerIter[2] ? samplerIter[2] : '';
|
|
const replaceStr = `layout(set = ${set}, binding = ${binding}) uniform ${precStr} sampler${samplerIter[3]} ${samplerIter[4]}`;
|
|
code = code.replace(samplerIter[0], replaceStr);
|
|
samplerIter = samplerExp.exec(code);
|
|
}
|
|
const blockExp = /layout\s*\(([^\)])+\)\s*(readonly)?\s*\b(uniform|buffer)\b\s+(\b\w+\b)\s*[{;]/g;
|
|
let blockIter = blockExp.exec(code);
|
|
while (blockIter) {
|
|
const name = blockIter[4];
|
|
const { set, binding } = findBinding(shaderInfo, name);
|
|
const accessStr = blockIter[2] ? blockIter[2] : '';
|
|
const replaceStr = `layout(set = ${set}, binding = ${binding}) ${accessStr} ${blockIter[3]} ${blockIter[4]} {`;
|
|
code = code.replace(blockIter[0], replaceStr);
|
|
blockIter = blockExp.exec(code);
|
|
}
|
|
return code;
|
|
}
|
|
|
|
function overwriteShaderProgramBinding (shaderInfo: ShaderInfo, programInfo: IProgramInfo): void {
|
|
const version = getDeviceShaderVersion(deviceManager.gfxDevice);
|
|
if (version !== 'glsl4') {
|
|
return;
|
|
}
|
|
if (programInfo.glsl4.vert) {
|
|
programInfo.glsl4.vert = overwriteShaderSourceBinding(shaderInfo, programInfo.glsl4.vert);
|
|
}
|
|
if (programInfo.glsl4.frag) {
|
|
programInfo.glsl4.frag = overwriteShaderSourceBinding(shaderInfo, programInfo.glsl4.frag);
|
|
} if (programInfo.glsl4.compute) {
|
|
programInfo.glsl4.compute = overwriteShaderSourceBinding(shaderInfo, programInfo.glsl4.compute);
|
|
}
|
|
}
|
|
|
|
// overwrite IProgramInfo using gfx.ShaderInfo
|
|
function overwriteProgramBlockInfo (shaderInfo: ShaderInfo, programInfo: IProgramInfo): void {
|
|
overwriteShaderProgramBinding(shaderInfo, programInfo);
|
|
const set = _setIndex[UpdateFrequency.PER_BATCH];
|
|
for (const block of programInfo.blocks) {
|
|
let found = false;
|
|
for (const src of shaderInfo.blocks) {
|
|
if (src.set !== set) {
|
|
continue;
|
|
}
|
|
if (src.name === block.name) {
|
|
block.binding = src.binding;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
error(`Block ${block.name} not found in shader ${shaderInfo.name}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// add descriptor to size-reserved descriptor set
|
|
function populateGroupedShaderInfo (
|
|
layout: DescriptorSetLayoutData,
|
|
descriptorInfo: EffectAsset.IDescriptorInfo,
|
|
set: number,
|
|
shaderInfo: ShaderInfo,
|
|
blockSizes: number[],
|
|
): void {
|
|
for (const descriptorBlock of layout.descriptorBlocks) {
|
|
const visibility = descriptorBlock.visibility;
|
|
let binding = descriptorBlock.offset;
|
|
|
|
switch (descriptorBlock.type) {
|
|
case DescriptorTypeOrder.UNIFORM_BUFFER:
|
|
for (const block of descriptorInfo.blocks) {
|
|
if (block.stageFlags !== visibility) {
|
|
continue;
|
|
}
|
|
blockSizes.push(getSize(block.members));
|
|
shaderInfo.blocks.push(
|
|
new UniformBlock(
|
|
set,
|
|
binding,
|
|
block.name,
|
|
block.members.map((m): Uniform => new Uniform(m.name, m.type, m.count)),
|
|
1,
|
|
), // count is always 1 for UniformBlock
|
|
);
|
|
++binding;
|
|
}
|
|
break;
|
|
case DescriptorTypeOrder.DYNAMIC_UNIFORM_BUFFER:
|
|
// not implemented yet
|
|
break;
|
|
case DescriptorTypeOrder.SAMPLER_TEXTURE:
|
|
for (const tex of descriptorInfo.samplerTextures) {
|
|
if (tex.stageFlags !== visibility) {
|
|
continue;
|
|
}
|
|
shaderInfo.samplerTextures.push(new UniformSamplerTexture(set, binding, tex.name, tex.type, tex.count));
|
|
++binding;
|
|
}
|
|
break;
|
|
case DescriptorTypeOrder.SAMPLER:
|
|
for (const sampler of descriptorInfo.samplers) {
|
|
if (sampler.stageFlags !== visibility) {
|
|
continue;
|
|
}
|
|
shaderInfo.samplers.push(new UniformSampler(set, binding, sampler.name, sampler.count));
|
|
++binding;
|
|
}
|
|
break;
|
|
case DescriptorTypeOrder.TEXTURE:
|
|
for (const texture of descriptorInfo.textures) {
|
|
if (texture.stageFlags !== visibility) {
|
|
continue;
|
|
}
|
|
shaderInfo.textures.push(new UniformTexture(set, binding, texture.name, texture.type, texture.count));
|
|
++binding;
|
|
}
|
|
break;
|
|
case DescriptorTypeOrder.STORAGE_BUFFER:
|
|
for (const buffer of descriptorInfo.buffers) {
|
|
if (buffer.stageFlags !== visibility) {
|
|
continue;
|
|
}
|
|
shaderInfo.buffers.push(new UniformStorageBuffer(set, binding, buffer.name, 1, buffer.memoryAccess)); // effect compiler guarantees buffer count = 1
|
|
++binding;
|
|
}
|
|
break;
|
|
case DescriptorTypeOrder.DYNAMIC_STORAGE_BUFFER:
|
|
// not implemented yet
|
|
break;
|
|
case DescriptorTypeOrder.STORAGE_IMAGE:
|
|
for (const image of descriptorInfo.images) {
|
|
if (image.stageFlags !== visibility) {
|
|
continue;
|
|
}
|
|
shaderInfo.images.push(new UniformStorageImage(set, binding, image.name, image.type, image.count, image.memoryAccess));
|
|
++binding;
|
|
}
|
|
break;
|
|
case DescriptorTypeOrder.INPUT_ATTACHMENT:
|
|
for (const subpassInput of descriptorInfo.subpassInputs) {
|
|
if (subpassInput.stageFlags !== visibility) {
|
|
continue;
|
|
}
|
|
shaderInfo.subpassInputs.push(new UniformInputAttachment(set, subpassInput.binding, subpassInput.name, subpassInput.count));
|
|
++binding;
|
|
}
|
|
break;
|
|
default:
|
|
}
|
|
}
|
|
}
|
|
|
|
// add merged descriptor to gfx.ShaderInfo
|
|
function populateMergedShaderInfo (
|
|
valueNames: string[],
|
|
layout: DescriptorSetLayoutData,
|
|
set: number,
|
|
shaderInfo: ShaderInfo,
|
|
blockSizes: number[],
|
|
): void {
|
|
for (const descriptorBlock of layout.descriptorBlocks) {
|
|
let binding = descriptorBlock.offset;
|
|
switch (descriptorBlock.type) {
|
|
case DescriptorTypeOrder.UNIFORM_BUFFER:
|
|
for (const block of descriptorBlock.descriptors) {
|
|
const uniformBlock = layout.uniformBlocks.get(block.descriptorID);
|
|
if (uniformBlock === undefined) {
|
|
error(`Failed to find uniform block ${block.descriptorID} in layout`);
|
|
continue;
|
|
}
|
|
blockSizes.push(getSize(uniformBlock.members));
|
|
shaderInfo.blocks.push(
|
|
new UniformBlock(
|
|
set,
|
|
binding,
|
|
valueNames[block.descriptorID],
|
|
uniformBlock.members.map((m): Uniform => new Uniform(m.name, m.type, m.count)),
|
|
1,
|
|
), // count is always 1 for UniformBlock
|
|
);
|
|
++binding;
|
|
}
|
|
if (binding !== descriptorBlock.offset + descriptorBlock.capacity) {
|
|
error(`Uniform buffer binding mismatch for set ${set}`);
|
|
}
|
|
break;
|
|
case DescriptorTypeOrder.DYNAMIC_UNIFORM_BUFFER:
|
|
// not implemented yet
|
|
break;
|
|
case DescriptorTypeOrder.SAMPLER_TEXTURE:
|
|
for (const tex of descriptorBlock.descriptors) {
|
|
shaderInfo.samplerTextures.push(new UniformSamplerTexture(set, binding, valueNames[tex.descriptorID], tex.type, tex.count));
|
|
++binding;
|
|
}
|
|
break;
|
|
case DescriptorTypeOrder.SAMPLER:
|
|
for (const sampler of descriptorBlock.descriptors) {
|
|
shaderInfo.samplers.push(new UniformSampler(set, binding, valueNames[sampler.descriptorID], sampler.count));
|
|
++binding;
|
|
}
|
|
break;
|
|
case DescriptorTypeOrder.TEXTURE:
|
|
for (const texture of descriptorBlock.descriptors) {
|
|
shaderInfo.textures.push(new UniformTexture(set, binding, valueNames[texture.descriptorID], texture.type, texture.count));
|
|
++binding;
|
|
}
|
|
break;
|
|
case DescriptorTypeOrder.STORAGE_BUFFER:
|
|
for (const buffer of descriptorBlock.descriptors) {
|
|
shaderInfo.buffers.push(new UniformStorageBuffer(
|
|
set,
|
|
binding,
|
|
valueNames[buffer.descriptorID],
|
|
1,
|
|
MemoryAccessBit.READ_WRITE/*buffer.memoryAccess*/,
|
|
)); // effect compiler guarantees buffer count = 1
|
|
++binding;
|
|
}
|
|
break;
|
|
case DescriptorTypeOrder.DYNAMIC_STORAGE_BUFFER:
|
|
// not implemented yet
|
|
break;
|
|
case DescriptorTypeOrder.STORAGE_IMAGE:
|
|
for (const image of descriptorBlock.descriptors) {
|
|
shaderInfo.images.push(new UniformStorageImage(
|
|
set,
|
|
binding,
|
|
valueNames[image.descriptorID],
|
|
image.type,
|
|
image.count,
|
|
MemoryAccessBit.READ_WRITE/*image.memoryAccess*/,
|
|
));
|
|
++binding;
|
|
}
|
|
break;
|
|
case DescriptorTypeOrder.INPUT_ATTACHMENT:
|
|
for (const subpassInput of descriptorBlock.descriptors) {
|
|
shaderInfo.subpassInputs.push(new UniformInputAttachment(set, binding, valueNames[subpassInput.descriptorID], subpassInput.count));
|
|
++binding;
|
|
}
|
|
break;
|
|
default:
|
|
}
|
|
}
|
|
}
|
|
|
|
// add descriptor from effect to gfx.ShaderInfo
|
|
function populateShaderInfo (
|
|
descriptorInfo: EffectAsset.IDescriptorInfo,
|
|
set: number,
|
|
shaderInfo: ShaderInfo,
|
|
blockSizes: number[],
|
|
): void {
|
|
for (let i = 0; i < descriptorInfo.blocks.length; i++) {
|
|
const block = descriptorInfo.blocks[i];
|
|
blockSizes.push(getSize(block.members));
|
|
shaderInfo.blocks.push(new UniformBlock(
|
|
set,
|
|
block.binding,
|
|
block.name,
|
|
block.members.map((m): Uniform => new Uniform(m.name, m.type, m.count)),
|
|
1,
|
|
)); // effect compiler guarantees block count = 1
|
|
}
|
|
for (let i = 0; i < descriptorInfo.samplerTextures.length; i++) {
|
|
const samplerTexture = descriptorInfo.samplerTextures[i];
|
|
shaderInfo.samplerTextures.push(new UniformSamplerTexture(set, samplerTexture.binding, samplerTexture.name, samplerTexture.type, samplerTexture.count));
|
|
}
|
|
for (let i = 0; i < descriptorInfo.samplers.length; i++) {
|
|
const sampler = descriptorInfo.samplers[i];
|
|
shaderInfo.samplers.push(new UniformSampler(set, sampler.binding, sampler.name, sampler.count));
|
|
}
|
|
for (let i = 0; i < descriptorInfo.textures.length; i++) {
|
|
const texture = descriptorInfo.textures[i];
|
|
shaderInfo.textures.push(new UniformTexture(set, texture.binding, texture.name, texture.type, texture.count));
|
|
}
|
|
for (let i = 0; i < descriptorInfo.buffers.length; i++) {
|
|
const buffer = descriptorInfo.buffers[i];
|
|
shaderInfo.buffers.push(new UniformStorageBuffer(set, buffer.binding, buffer.name, 1, buffer.memoryAccess)); // effect compiler guarantees buffer count = 1
|
|
}
|
|
for (let i = 0; i < descriptorInfo.images.length; i++) {
|
|
const image = descriptorInfo.images[i];
|
|
shaderInfo.images.push(new UniformStorageImage(set, image.binding, image.name, image.type, image.count, image.memoryAccess));
|
|
}
|
|
for (let i = 0; i < descriptorInfo.subpassInputs.length; i++) {
|
|
const subpassInput = descriptorInfo.subpassInputs[i];
|
|
shaderInfo.subpassInputs.push(new UniformInputAttachment(set, subpassInput.binding, subpassInput.name, subpassInput.count));
|
|
}
|
|
}
|
|
|
|
// add fixed local descriptors to gfx.ShaderInfo
|
|
function populateLocalShaderInfo (
|
|
target: EffectAsset.IDescriptorInfo,
|
|
source: IDescriptorSetLayoutInfo,
|
|
shaderInfo: ShaderInfo,
|
|
blockSizes: number[],
|
|
): void {
|
|
const set = _setIndex[UpdateFrequency.PER_INSTANCE];
|
|
for (let i = 0; i < target.blocks.length; i++) {
|
|
const block = target.blocks[i];
|
|
const info = source.layouts[block.name] as UniformBlock | undefined;
|
|
const binding = info && source.bindings.find((bd): boolean => bd.binding === info.binding);
|
|
if (!info || !binding || !(binding.descriptorType & DESCRIPTOR_BUFFER_TYPE)) {
|
|
warn(`builtin UBO '${block.name}' not available!`);
|
|
continue;
|
|
}
|
|
blockSizes.push(getSize(block.members));
|
|
shaderInfo.blocks.push(new UniformBlock(
|
|
set,
|
|
binding.binding,
|
|
block.name,
|
|
block.members.map((m): Uniform => new Uniform(m.name, m.type, m.count)),
|
|
1,
|
|
)); // effect compiler guarantees block count = 1
|
|
}
|
|
for (let i = 0; i < target.samplerTextures.length; i++) {
|
|
const samplerTexture = target.samplerTextures[i];
|
|
const info = source.layouts[samplerTexture.name] as UniformSamplerTexture;
|
|
const binding = info && source.bindings.find((bd): boolean => bd.binding === info.binding);
|
|
if (!info || !binding || !(binding.descriptorType & DESCRIPTOR_SAMPLER_TYPE)) {
|
|
warn(`builtin samplerTexture '${samplerTexture.name}' not available!`);
|
|
continue;
|
|
}
|
|
shaderInfo.samplerTextures.push(new UniformSamplerTexture(set, binding.binding, samplerTexture.name, samplerTexture.type, samplerTexture.count));
|
|
}
|
|
}
|
|
|
|
function getIDescriptorSetLayoutInfoUniformBlockCapacity (info: IDescriptorSetLayoutInfo): number {
|
|
let capacity = 0;
|
|
for (const binding of info.bindings) {
|
|
if (binding.descriptorType === DescriptorType.UNIFORM_BUFFER
|
|
|| binding.descriptorType === DescriptorType.DYNAMIC_UNIFORM_BUFFER) {
|
|
capacity += binding.count;
|
|
}
|
|
}
|
|
return capacity;
|
|
}
|
|
|
|
function getIDescriptorSetLayoutInfoSamplerTextureCapacity (info: IDescriptorSetLayoutInfo): number {
|
|
let capacity = 0;
|
|
for (const binding of info.bindings) {
|
|
if (binding.descriptorType !== DescriptorType.UNIFORM_BUFFER
|
|
&& binding.descriptorType !== DescriptorType.DYNAMIC_UNIFORM_BUFFER) {
|
|
capacity += binding.count;
|
|
}
|
|
}
|
|
return capacity;
|
|
}
|
|
|
|
function setFlattenedUniformBlockBinding (
|
|
setOffsets: number[],
|
|
descriptors: UniformBlock[],
|
|
): void {
|
|
for (const d of descriptors) {
|
|
d.flattened = setOffsets[d.set] + d.binding;
|
|
}
|
|
}
|
|
|
|
function setFlattenedSamplerTextureBinding (
|
|
setOffsets: number[],
|
|
uniformBlockCapacities: number[],
|
|
descriptors: UniformSamplerTexture[]
|
|
| UniformSampler[]
|
|
| UniformTexture[]
|
|
| UniformStorageBuffer[]
|
|
| UniformStorageImage[]
|
|
| UniformInputAttachment[],
|
|
): void {
|
|
for (const d of descriptors) {
|
|
d.flattened = setOffsets[d.set] + d.binding - uniformBlockCapacities[d.set];
|
|
}
|
|
}
|
|
|
|
function calculateFlattenedBinding (
|
|
descriptorSets: (DescriptorSetLayoutData | null)[],
|
|
fixedInstanceDescriptorSetLayout: IDescriptorSetLayoutInfo | null,
|
|
shaderInfo: ShaderInfo,
|
|
): void {
|
|
// Descriptors of UniformBlock starts from 0, and Descriptors of SamplerTexture starts from the end of UniformBlock.
|
|
const uniformBlockCapacities = new Array(4);
|
|
{
|
|
const passCapacity = descriptorSets[UpdateFrequency.PER_PASS]?.uniformBlockCapacity || 0;
|
|
const phaseCapacity = descriptorSets[UpdateFrequency.PER_PHASE]?.uniformBlockCapacity || 0;
|
|
const batchCapacity = descriptorSets[UpdateFrequency.PER_BATCH]?.uniformBlockCapacity || 0; // dynamic size
|
|
const instanceCapacity = fixedInstanceDescriptorSetLayout
|
|
? getIDescriptorSetLayoutInfoUniformBlockCapacity(fixedInstanceDescriptorSetLayout)
|
|
: (descriptorSets[UpdateFrequency.PER_INSTANCE]?.uniformBlockCapacity || 0);
|
|
|
|
// update uniform block capacities
|
|
uniformBlockCapacities[_setIndex[UpdateFrequency.PER_PASS]] = passCapacity;
|
|
uniformBlockCapacities[_setIndex[UpdateFrequency.PER_PHASE]] = phaseCapacity;
|
|
uniformBlockCapacities[_setIndex[UpdateFrequency.PER_BATCH]] = batchCapacity;
|
|
uniformBlockCapacities[_setIndex[UpdateFrequency.PER_INSTANCE]] = instanceCapacity;
|
|
|
|
// calculate uniform block offsets
|
|
const passOffset = 0;
|
|
const phaseOffset = passOffset + passCapacity;
|
|
const instanceOffset = phaseOffset + phaseCapacity;
|
|
const batchOffset = instanceOffset + instanceCapacity;
|
|
|
|
// save uniform block offsets by set index
|
|
const uniformBlockOffsets = new Array(4);
|
|
uniformBlockOffsets[_setIndex[UpdateFrequency.PER_PASS]] = passOffset;
|
|
uniformBlockOffsets[_setIndex[UpdateFrequency.PER_PHASE]] = phaseOffset;
|
|
uniformBlockOffsets[_setIndex[UpdateFrequency.PER_BATCH]] = batchOffset;
|
|
uniformBlockOffsets[_setIndex[UpdateFrequency.PER_INSTANCE]] = instanceOffset;
|
|
|
|
// update flattened uniform block binding
|
|
setFlattenedUniformBlockBinding(uniformBlockOffsets, shaderInfo.blocks);
|
|
}
|
|
{
|
|
// calculate sampler texture capacities
|
|
const passCapacity = descriptorSets[UpdateFrequency.PER_PASS]?.samplerTextureCapacity || 0;
|
|
const phaseCapacity = descriptorSets[UpdateFrequency.PER_PHASE]?.samplerTextureCapacity || 0;
|
|
// const batchCapacity = descriptorSets[UpdateFrequency.PER_BATCH]?.capacity || 0; // dynamic size
|
|
const instanceCapacity = fixedInstanceDescriptorSetLayout
|
|
? getIDescriptorSetLayoutInfoSamplerTextureCapacity(fixedInstanceDescriptorSetLayout)
|
|
: (descriptorSets[UpdateFrequency.PER_INSTANCE]?.samplerTextureCapacity || 0);
|
|
|
|
// calculate sampler texture offsets
|
|
const passOffset = 0;
|
|
const phaseOffset = passOffset + passCapacity;
|
|
const instanceOffset = phaseOffset + phaseCapacity;
|
|
const batchOffset = instanceOffset + instanceCapacity;
|
|
|
|
// save sampler texture offsets by set index
|
|
const samplerTextureOffsets = new Array(4);
|
|
samplerTextureOffsets[_setIndex[UpdateFrequency.PER_PASS]] = passOffset;
|
|
samplerTextureOffsets[_setIndex[UpdateFrequency.PER_PHASE]] = phaseOffset;
|
|
samplerTextureOffsets[_setIndex[UpdateFrequency.PER_BATCH]] = batchOffset;
|
|
samplerTextureOffsets[_setIndex[UpdateFrequency.PER_INSTANCE]] = instanceOffset;
|
|
|
|
// update flattened sampler texture binding
|
|
setFlattenedSamplerTextureBinding(samplerTextureOffsets, uniformBlockCapacities, shaderInfo.samplerTextures);
|
|
}
|
|
}
|
|
|
|
// make gfx.ShaderInfo
|
|
function makeShaderInfo (
|
|
lg: LayoutGraphData,
|
|
passLayouts: PipelineLayoutData,
|
|
phaseLayouts: PipelineLayoutData,
|
|
srcShaderInfo: EffectAsset.IShaderInfo,
|
|
programData: ShaderProgramData | null,
|
|
fixedLocal: boolean,
|
|
): [ShaderInfo, Array<number>] {
|
|
const descriptorSets: Array<DescriptorSetLayoutData | null> = [null, null, null, null];
|
|
let fixedInstanceDescriptorSetLayout: IDescriptorSetLayoutInfo | null = null;
|
|
const shaderInfo = new ShaderInfo();
|
|
const blockSizes = new Array<number>();
|
|
{ // pass
|
|
const passLayout = passLayouts.descriptorSets.get(UpdateFrequency.PER_PASS);
|
|
if (passLayout) {
|
|
descriptorSets[UpdateFrequency.PER_PASS] = passLayout.descriptorSetLayoutData;
|
|
populateMergedShaderInfo(
|
|
lg.valueNames,
|
|
passLayout.descriptorSetLayoutData,
|
|
_setIndex[UpdateFrequency.PER_PASS],
|
|
shaderInfo,
|
|
blockSizes,
|
|
);
|
|
}
|
|
}
|
|
{ // phase
|
|
const phaseLayout = phaseLayouts.descriptorSets.get(UpdateFrequency.PER_PHASE);
|
|
if (phaseLayout) {
|
|
descriptorSets[UpdateFrequency.PER_PHASE] = phaseLayout.descriptorSetLayoutData;
|
|
populateMergedShaderInfo(
|
|
lg.valueNames,
|
|
phaseLayout.descriptorSetLayoutData,
|
|
_setIndex[UpdateFrequency.PER_PHASE],
|
|
shaderInfo,
|
|
blockSizes,
|
|
);
|
|
}
|
|
}
|
|
{ // batch
|
|
const batchInfo = srcShaderInfo.descriptors[UpdateFrequency.PER_BATCH];
|
|
if (programData) {
|
|
const perBatch = programData.layout.descriptorSets.get(UpdateFrequency.PER_BATCH);
|
|
if (perBatch) {
|
|
descriptorSets[UpdateFrequency.PER_BATCH] = perBatch.descriptorSetLayoutData;
|
|
populateMergedShaderInfo(
|
|
lg.valueNames,
|
|
perBatch.descriptorSetLayoutData,
|
|
_setIndex[UpdateFrequency.PER_BATCH],
|
|
shaderInfo,
|
|
blockSizes,
|
|
);
|
|
}
|
|
} else {
|
|
const batchLayout = phaseLayouts.descriptorSets.get(UpdateFrequency.PER_BATCH);
|
|
if (batchLayout) {
|
|
descriptorSets[UpdateFrequency.PER_BATCH] = batchLayout.descriptorSetLayoutData;
|
|
populateGroupedShaderInfo(
|
|
batchLayout.descriptorSetLayoutData,
|
|
batchInfo,
|
|
|
|
_setIndex[UpdateFrequency.PER_BATCH],
|
|
shaderInfo,
|
|
|
|
blockSizes,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
{ // instance
|
|
const instanceInfo = srcShaderInfo.descriptors[UpdateFrequency.PER_INSTANCE];
|
|
if (programData) {
|
|
if (fixedLocal) {
|
|
fixedInstanceDescriptorSetLayout = localDescriptorSetLayout;
|
|
populateLocalShaderInfo(instanceInfo, localDescriptorSetLayout, shaderInfo, blockSizes);
|
|
} else {
|
|
const perInstance = programData.layout.descriptorSets.get(UpdateFrequency.PER_INSTANCE);
|
|
if (perInstance) {
|
|
descriptorSets[UpdateFrequency.PER_INSTANCE] = perInstance.descriptorSetLayoutData;
|
|
populateMergedShaderInfo(
|
|
lg.valueNames,
|
|
perInstance.descriptorSetLayoutData,
|
|
_setIndex[UpdateFrequency.PER_INSTANCE],
|
|
shaderInfo,
|
|
blockSizes,
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
const instanceLayout = phaseLayouts.descriptorSets.get(UpdateFrequency.PER_INSTANCE);
|
|
if (instanceLayout) {
|
|
descriptorSets[UpdateFrequency.PER_INSTANCE] = instanceLayout.descriptorSetLayoutData;
|
|
populateGroupedShaderInfo(
|
|
instanceLayout.descriptorSetLayoutData,
|
|
instanceInfo,
|
|
|
|
_setIndex[UpdateFrequency.PER_INSTANCE],
|
|
shaderInfo,
|
|
|
|
blockSizes,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
calculateFlattenedBinding(descriptorSets, fixedInstanceDescriptorSetLayout, shaderInfo);
|
|
shaderInfo.stages.push(new ShaderStage(ShaderStageFlagBit.VERTEX, ''));
|
|
shaderInfo.stages.push(new ShaderStage(ShaderStageFlagBit.FRAGMENT, ''));
|
|
return [shaderInfo, blockSizes];
|
|
}
|
|
|
|
class WebProgramProxy implements ProgramProxy {
|
|
constructor (shader: Shader) {
|
|
this.shader = shader;
|
|
}
|
|
get name (): string {
|
|
return this.shader.name;
|
|
}
|
|
readonly shader: Shader;
|
|
}
|
|
|
|
// find name and type from local descriptor set info
|
|
function getDescriptorNameAndType (source: IDescriptorSetLayoutInfo, binding: number): [string, Type] {
|
|
for (const name in source.layouts) {
|
|
const v = source.layouts[name];
|
|
if (v.binding === binding) {
|
|
assert(v.name === name);
|
|
let type = Type.UNKNOWN;
|
|
if (v instanceof UniformSamplerTexture) {
|
|
type = v.type;
|
|
} else if (v instanceof UniformStorageImage) {
|
|
type = v.type;
|
|
}
|
|
return [v.name, type];
|
|
}
|
|
}
|
|
error('descriptor not found');
|
|
return ['', Type.UNKNOWN];
|
|
}
|
|
|
|
// make DescriptorSetLayoutData from local descriptor set info
|
|
function makeLocalDescriptorSetLayoutData (
|
|
lg: LayoutGraphData,
|
|
source: IDescriptorSetLayoutInfo,
|
|
): DescriptorSetLayoutData {
|
|
const data = new DescriptorSetLayoutData();
|
|
for (const b of source.bindings) {
|
|
const [name, type] = getDescriptorNameAndType(source, b.binding);
|
|
const nameID = getOrCreateDescriptorID(lg, name);
|
|
const order = getDescriptorTypeOrder(b.descriptorType);
|
|
const block = new DescriptorBlockData(order, b.stageFlags, b.count);
|
|
block.offset = b.binding;
|
|
block.descriptors.push(new DescriptorData(nameID, type, b.count));
|
|
data.descriptorBlocks.push(block);
|
|
const binding = data.bindingMap.get(nameID);
|
|
if (binding !== undefined) {
|
|
error(`duplicate descriptor name '${name}'`);
|
|
}
|
|
data.bindingMap.set(nameID, b.binding);
|
|
const v = source.layouts[name];
|
|
if (v instanceof UniformBlock) {
|
|
data.uniformBlocks.set(nameID, v);
|
|
}
|
|
}
|
|
return data;
|
|
}
|
|
|
|
// make descriptor sets for ShaderProgramData (PerBatch, PerInstance)
|
|
function buildProgramData (
|
|
programName: string,
|
|
srcShaderInfo: EffectAsset.IShaderInfo,
|
|
lg: LayoutGraphData,
|
|
phase: RenderPhaseData,
|
|
programData: ShaderProgramData,
|
|
fixedLocal: boolean,
|
|
): void {
|
|
{
|
|
const perBatch = makeDescriptorSetLayoutData(
|
|
lg,
|
|
UpdateFrequency.PER_BATCH,
|
|
_setIndex[UpdateFrequency.PER_BATCH],
|
|
srcShaderInfo.descriptors[UpdateFrequency.PER_BATCH],
|
|
);
|
|
const setData = new DescriptorSetData(perBatch);
|
|
initializeDescriptorSetLayoutInfo(
|
|
setData.descriptorSetLayoutData,
|
|
setData.descriptorSetLayoutInfo,
|
|
);
|
|
programData.layout.descriptorSets.set(UpdateFrequency.PER_BATCH, setData);
|
|
}
|
|
if (fixedLocal) {
|
|
const perInstance = makeLocalDescriptorSetLayoutData(lg, localDescriptorSetLayout);
|
|
const setData = new DescriptorSetData(perInstance);
|
|
initializeDescriptorSetLayoutInfo(
|
|
setData.descriptorSetLayoutData,
|
|
setData.descriptorSetLayoutInfo,
|
|
);
|
|
if (localDescriptorSetLayout.bindings.length !== setData.descriptorSetLayoutInfo.bindings.length) {
|
|
error('local descriptor set layout inconsistent');
|
|
} else {
|
|
for (let k = 0; k !== localDescriptorSetLayout.bindings.length; ++k) {
|
|
const b = localDescriptorSetLayout.bindings[k];
|
|
const b2 = setData.descriptorSetLayoutInfo.bindings[k];
|
|
if (b.binding !== b2.binding
|
|
|| b.descriptorType !== b2.descriptorType
|
|
|| b.count !== b2.count
|
|
|| b.stageFlags !== b2.stageFlags) {
|
|
error('local descriptor set layout inconsistent');
|
|
}
|
|
}
|
|
}
|
|
programData.layout.descriptorSets.set(UpdateFrequency.PER_INSTANCE, setData);
|
|
} else {
|
|
const perInstance = makeDescriptorSetLayoutData(
|
|
lg,
|
|
UpdateFrequency.PER_INSTANCE,
|
|
_setIndex[UpdateFrequency.PER_INSTANCE],
|
|
srcShaderInfo.descriptors[UpdateFrequency.PER_INSTANCE],
|
|
);
|
|
const setData = new DescriptorSetData(perInstance);
|
|
initializeDescriptorSetLayoutInfo(
|
|
setData.descriptorSetLayoutData,
|
|
setData.descriptorSetLayoutInfo,
|
|
);
|
|
programData.layout.descriptorSets.set(UpdateFrequency.PER_INSTANCE, setData);
|
|
}
|
|
const shaderID = phase.shaderPrograms.length;
|
|
phase.shaderIndex.set(programName, shaderID);
|
|
phase.shaderPrograms.push(programData);
|
|
}
|
|
|
|
// get or create PerProgram gfx.DescriptorSetLayout
|
|
function getOrCreateProgramDescriptorSetLayout (
|
|
device: Device,
|
|
lg: LayoutGraphData,
|
|
phaseID: number,
|
|
programName: string,
|
|
rate: UpdateFrequency,
|
|
): DescriptorSetLayout {
|
|
assert(rate < UpdateFrequency.PER_PHASE);
|
|
const phase = lg.getRenderPhase(phaseID);
|
|
const programID = phase.shaderIndex.get(programName);
|
|
if (programID === undefined) {
|
|
return getEmptyDescriptorSetLayout();
|
|
}
|
|
const programData = phase.shaderPrograms[programID];
|
|
const layout = programData.layout.descriptorSets.get(rate);
|
|
if (layout === undefined) {
|
|
return getEmptyDescriptorSetLayout();
|
|
}
|
|
if (layout.descriptorSetLayout) {
|
|
return layout.descriptorSetLayout;
|
|
}
|
|
layout.descriptorSetLayout = device.createDescriptorSetLayout(layout.descriptorSetLayoutInfo);
|
|
|
|
return layout.descriptorSetLayout;
|
|
}
|
|
|
|
// get PerProgram gfx.DescriptorSetLayout
|
|
function getProgramDescriptorSetLayout (
|
|
device: Device,
|
|
lg: LayoutGraphData,
|
|
phaseID: number,
|
|
programName: string,
|
|
rate: UpdateFrequency,
|
|
): DescriptorSetLayout | null {
|
|
assert(rate < UpdateFrequency.PER_PHASE);
|
|
const phase = lg.getRenderPhase(phaseID);
|
|
const programID = phase.shaderIndex.get(programName);
|
|
if (programID === undefined) {
|
|
return null;
|
|
}
|
|
const programData = phase.shaderPrograms[programID];
|
|
const layout = programData.layout.descriptorSets.get(rate);
|
|
if (layout === undefined) {
|
|
return null;
|
|
}
|
|
if (layout.descriptorSetLayout) {
|
|
return layout.descriptorSetLayout;
|
|
}
|
|
layout.descriptorSetLayout = device.createDescriptorSetLayout(layout.descriptorSetLayoutInfo);
|
|
|
|
return layout.descriptorSetLayout;
|
|
}
|
|
|
|
// find shader program in LayoutGraphData
|
|
function getEffectShader (
|
|
lg: LayoutGraphData,
|
|
effect: EffectAsset,
|
|
pass: EffectAsset.IPassInfo,
|
|
): [number, number, number, EffectAsset.IShaderInfo | null, number] {
|
|
const programName = pass.program;
|
|
const passID = getCustomPassID(lg, pass.pass);
|
|
if (passID === INVALID_ID) {
|
|
error(`Invalid render pass, program: ${programName}`);
|
|
return [INVALID_ID, INVALID_ID, INVALID_ID, null, INVALID_ID];
|
|
}
|
|
|
|
const enableSubpass = pass.subpass && pass.subpass !== '' && ENABLE_SUBPASS;
|
|
const subpassID = enableSubpass ? getCustomSubpassID(lg, passID, pass.subpass!) : INVALID_ID;
|
|
if (enableSubpass && subpassID === INVALID_ID) {
|
|
error(`Invalid render subpass, program: ${programName}`);
|
|
return [INVALID_ID, INVALID_ID, INVALID_ID, null, INVALID_ID];
|
|
}
|
|
|
|
const phaseID = getCustomPhaseID(lg, subpassID === INVALID_ID ? passID : subpassID, pass.phase);
|
|
if (phaseID === INVALID_ID) {
|
|
error(`Invalid render phase, program: ${programName}`);
|
|
return [INVALID_ID, INVALID_ID, INVALID_ID, null, INVALID_ID];
|
|
}
|
|
let srcShaderInfo: EffectAsset.IShaderInfo | null = null;
|
|
let shaderID = INVALID_ID;
|
|
for (let i = 0; i < effect.shaders.length; ++i) {
|
|
const shaderInfo = effect.shaders[i];
|
|
if (shaderInfo.name === programName) {
|
|
srcShaderInfo = shaderInfo;
|
|
shaderID = i;
|
|
break;
|
|
}
|
|
}
|
|
return [passID, subpassID, phaseID, srcShaderInfo, shaderID];
|
|
}
|
|
|
|
// valid IShaderInfo is compatible
|
|
function validateShaderInfo (srcShaderInfo: EffectAsset.IShaderInfo): number {
|
|
// source shader info
|
|
if (srcShaderInfo.descriptors === undefined) {
|
|
error(`No descriptors in shader: ${srcShaderInfo.name}, please reimport ALL effects`);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
export class WebProgramLibrary implements ProgramLibrary {
|
|
constructor (lg: LayoutGraphData) {
|
|
this.layoutGraph = lg;
|
|
for (const v of lg.vertices()) {
|
|
if (lg.holds(LayoutGraphDataValue.RenderPhase, v)) {
|
|
this.phases.set(v, new ProgramGroup());
|
|
}
|
|
}
|
|
}
|
|
// add effect to database
|
|
addEffect (effect: EffectAsset): void {
|
|
const lg = this.layoutGraph;
|
|
for (const tech of effect.techniques) {
|
|
for (const pass of tech.passes) {
|
|
const programName = pass.program;
|
|
const [passID, subpassID, phaseID, srcShaderInfo] = getEffectShader(lg, effect, pass);
|
|
if (srcShaderInfo === null || validateShaderInfo(srcShaderInfo)) {
|
|
error(`program: ${programName} not found`);
|
|
continue;
|
|
}
|
|
assert(passID !== INVALID_ID && phaseID !== INVALID_ID);
|
|
const subpassOrPassID = subpassID === INVALID_ID ? passID : subpassID;
|
|
const passLayout = lg.getLayout(subpassOrPassID);
|
|
const phaseLayout = lg.getLayout(phaseID);
|
|
|
|
// programs
|
|
let group = this.phases.get(phaseID);
|
|
if (group === undefined) {
|
|
group = new ProgramGroup();
|
|
this.phases.set(phaseID, group);
|
|
}
|
|
const phasePrograms = group.programInfos;
|
|
|
|
// build program
|
|
const programInfo = makeProgramInfo(effect.name, srcShaderInfo);
|
|
|
|
// collect program descriptors
|
|
let programData: ShaderProgramData | null = null;
|
|
if (!this.mergeHighFrequency) {
|
|
const phase = lg.getRenderPhase(phaseID);
|
|
programData = new ShaderProgramData();
|
|
buildProgramData(programName, srcShaderInfo, lg, phase, programData, this.fixedLocal);
|
|
}
|
|
|
|
// shaderInfo and blockSizes
|
|
const [shaderInfo, blockSizes] = makeShaderInfo(
|
|
lg,
|
|
passLayout,
|
|
phaseLayout,
|
|
srcShaderInfo,
|
|
programData,
|
|
this.fixedLocal,
|
|
);
|
|
|
|
// overwrite programInfo
|
|
overwriteProgramBlockInfo(shaderInfo, programInfo);
|
|
|
|
// handle map
|
|
const handleMap = genHandles(shaderInfo);
|
|
// attributes
|
|
const attributes = new Array<Attribute>();
|
|
for (const attr of programInfo.attributes) {
|
|
attributes.push(new Attribute(attr.name, attr.format, attr.isNormalized, 0, attr.isInstanced, attr.location));
|
|
}
|
|
// create programInfo
|
|
const info = new ProgramInfo(programInfo, shaderInfo, attributes, blockSizes, handleMap);
|
|
phasePrograms.set(srcShaderInfo.name, info);
|
|
}
|
|
}
|
|
}
|
|
// precompile effect
|
|
precompileEffect (device: Device, effect: EffectAsset): void {
|
|
const lg = this.layoutGraph;
|
|
for (const tech of effect.techniques) {
|
|
for (const pass of tech.passes) {
|
|
const programName = pass.program;
|
|
const [passID, subpassID, phaseID, srcShaderInfo, shaderID] = getEffectShader(lg, effect, pass);
|
|
if (srcShaderInfo === null || validateShaderInfo(srcShaderInfo)) {
|
|
error(`program: ${programName} not valid`);
|
|
continue;
|
|
}
|
|
assert(passID !== INVALID_ID && phaseID !== INVALID_ID && shaderID !== INVALID_ID);
|
|
const combination = effect.combinations[shaderID];
|
|
if (!combination) {
|
|
continue;
|
|
}
|
|
const defines = getCombinationDefines(combination);
|
|
defines.forEach(
|
|
(defines) => this.getProgramVariant(device, phaseID, programName, defines),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
// get IProgramInfo
|
|
getProgramInfo (phaseID: number, programName: string): IProgramInfo {
|
|
assert(phaseID !== INVALID_ID);
|
|
const group = this.phases.get(phaseID)!;
|
|
const info = group.programInfos.get(programName)!;
|
|
return info.programInfo;
|
|
}
|
|
|
|
// get gfx.ShaderInfo
|
|
getShaderInfo (phaseID: number, programName: string): ShaderInfo {
|
|
assert(phaseID !== INVALID_ID);
|
|
const group = this.phases.get(phaseID)!;
|
|
const info = group.programInfos.get(programName)!;
|
|
return info.shaderInfo;
|
|
}
|
|
// get shader key
|
|
getKey (phaseID: number, programName: string, defines: MacroRecord): string {
|
|
assert(phaseID !== INVALID_ID);
|
|
// get phase
|
|
const group = this.phases.get(phaseID);
|
|
if (group === undefined) {
|
|
error(`Invalid render phase, program: ${programName}`);
|
|
return '';
|
|
}
|
|
// get info
|
|
const info = group.programInfos.get(programName);
|
|
if (info === undefined) {
|
|
error(`Invalid program, program: ${programName}`);
|
|
return '';
|
|
}
|
|
return getVariantKey(info.programInfo, defines);
|
|
}
|
|
// get program variant
|
|
getProgramVariant (device: Device, phaseID: number, name: string, defines: MacroRecord, key: string | null = null): ProgramProxy | null {
|
|
Object.assign(defines, this.pipeline?.macros);
|
|
assert(phaseID !== INVALID_ID);
|
|
// get phase
|
|
const group = this.phases.get(phaseID);
|
|
if (group === undefined) {
|
|
error(`Invalid render phase, program: ${name}`);
|
|
return null;
|
|
}
|
|
// get info
|
|
const info = group.programInfos.get(name);
|
|
if (info === undefined) {
|
|
error(`Invalid program, program: ${name}`);
|
|
return null;
|
|
}
|
|
const programInfo = info.programInfo;
|
|
if (key === null) {
|
|
key = getVariantKey(programInfo, defines);
|
|
}
|
|
|
|
// try get program
|
|
const programHosts = group.programProxies;
|
|
const programHost = programHosts.get(key);
|
|
if (programHost !== undefined) {
|
|
return programHost;
|
|
}
|
|
|
|
// prepare variant
|
|
const macroArray = prepareDefines(defines, programInfo.defines);
|
|
const prefix = this.layoutGraph.constantMacros + programInfo.constantMacros
|
|
+ macroArray.reduce((acc, cur): string => `${acc}#define ${cur.name} ${cur.value}\n`, '');
|
|
|
|
let src = programInfo.glsl3;
|
|
const deviceShaderVersion = getDeviceShaderVersion(device);
|
|
if (deviceShaderVersion) {
|
|
src = programInfo[deviceShaderVersion];
|
|
} else {
|
|
error('Invalid GFX API!');
|
|
}
|
|
|
|
// prepare shader info
|
|
const shaderInfo = info.shaderInfo;
|
|
if (src.compute) {
|
|
shaderInfo.stages[0].source = prefix + src.compute;
|
|
shaderInfo.stages[0].stage = ShaderStageFlagBit.COMPUTE;
|
|
shaderInfo.stages.length = 1;
|
|
} else {
|
|
shaderInfo.stages[0].source = prefix + src.vert;
|
|
shaderInfo.stages[1].source = prefix + src.frag;
|
|
}
|
|
shaderInfo.attributes = getActiveAttributes(programInfo, info.attributes, defines);
|
|
shaderInfo.name = getShaderInstanceName(name, macroArray);
|
|
|
|
// create shader
|
|
const shader = device.createShader(shaderInfo);
|
|
|
|
// create program host and register
|
|
const host = new WebProgramProxy(shader);
|
|
programHosts.set(key, host);
|
|
|
|
// create
|
|
return host;
|
|
}
|
|
// get material descriptor set layout
|
|
getMaterialDescriptorSetLayout (device: Device, phaseID: number, programName: string): DescriptorSetLayout {
|
|
if (this.mergeHighFrequency) {
|
|
assert(phaseID !== INVALID_ID);
|
|
const subpassOrPassID = this.layoutGraph.getParent(phaseID);
|
|
return getOrCreateDescriptorSetLayout(this.layoutGraph, subpassOrPassID, phaseID, UpdateFrequency.PER_BATCH);
|
|
}
|
|
return getOrCreateProgramDescriptorSetLayout(
|
|
device,
|
|
this.layoutGraph,
|
|
phaseID,
|
|
programName,
|
|
UpdateFrequency.PER_BATCH,
|
|
);
|
|
}
|
|
// get local descriptor set layout
|
|
getLocalDescriptorSetLayout (device: Device, phaseID: number, programName: string): DescriptorSetLayout {
|
|
if (this.mergeHighFrequency) {
|
|
assert(phaseID !== INVALID_ID);
|
|
const subpassOrPassID = this.layoutGraph.getParent(phaseID);
|
|
return getOrCreateDescriptorSetLayout(this.layoutGraph, subpassOrPassID, phaseID, UpdateFrequency.PER_INSTANCE);
|
|
}
|
|
return getOrCreateProgramDescriptorSetLayout(
|
|
device,
|
|
this.layoutGraph,
|
|
phaseID,
|
|
programName,
|
|
UpdateFrequency.PER_INSTANCE,
|
|
);
|
|
}
|
|
// get related uniform block sizes
|
|
getBlockSizes (phaseID: number, programName: string): number[] {
|
|
assert(phaseID !== INVALID_ID);
|
|
const group = this.phases.get(phaseID);
|
|
if (!group) {
|
|
error(`Invalid render phase, program: ${programName}`);
|
|
return [];
|
|
}
|
|
const info = group.programInfos.get(programName);
|
|
if (!info) {
|
|
error(`Invalid program, program: ${programName}`);
|
|
return [];
|
|
}
|
|
return info.blockSizes;
|
|
}
|
|
// get property handle map
|
|
getHandleMap (phaseID: number, programName: string): Record<string, number> {
|
|
assert(phaseID !== INVALID_ID);
|
|
const group = this.phases.get(phaseID);
|
|
if (!group) {
|
|
error(`Invalid render phase, program: ${programName}`);
|
|
return {};
|
|
}
|
|
const info = group.programInfos.get(programName);
|
|
if (!info) {
|
|
error(`Invalid program, program: ${programName}`);
|
|
return {};
|
|
}
|
|
return info.handleMap;
|
|
}
|
|
// get shader pipeline layout
|
|
getPipelineLayout (device: Device, phaseID: number, programName: string): PipelineLayout {
|
|
if (this.mergeHighFrequency) {
|
|
assert(phaseID !== INVALID_ID);
|
|
const layout = this.layoutGraph.getRenderPhase(phaseID);
|
|
return layout.pipelineLayout!;
|
|
}
|
|
const lg = this.layoutGraph;
|
|
const phase = lg.getRenderPhase(phaseID);
|
|
const programID = phase.shaderIndex.get(programName);
|
|
if (programID === undefined) {
|
|
return getEmptyPipelineLayout();
|
|
}
|
|
const programData = phase.shaderPrograms[programID];
|
|
if (programData.pipelineLayout) {
|
|
return programData.pipelineLayout;
|
|
}
|
|
|
|
// get pass
|
|
const subpassOrPassID = lg.getParent(phaseID);
|
|
if (subpassOrPassID === INVALID_ID) {
|
|
return getEmptyPipelineLayout();
|
|
}
|
|
|
|
// craete pipeline layout
|
|
const info = new PipelineLayoutInfo();
|
|
const passSet = getDescriptorSetLayout(this.layoutGraph, subpassOrPassID, phaseID, UpdateFrequency.PER_PASS);
|
|
if (passSet) {
|
|
info.setLayouts.push(passSet);
|
|
}
|
|
const phaseSet = getDescriptorSetLayout(this.layoutGraph, subpassOrPassID, phaseID, UpdateFrequency.PER_PHASE);
|
|
if (phaseSet) {
|
|
info.setLayouts.push(phaseSet);
|
|
}
|
|
const batchSet = getProgramDescriptorSetLayout(device, lg, phaseID, programName, UpdateFrequency.PER_BATCH);
|
|
if (batchSet) {
|
|
info.setLayouts.push(batchSet);
|
|
}
|
|
const instanceSet = getProgramDescriptorSetLayout(device, lg, phaseID, programName, UpdateFrequency.PER_INSTANCE);
|
|
if (instanceSet) {
|
|
info.setLayouts.push(instanceSet);
|
|
}
|
|
programData.pipelineLayout = device.createPipelineLayout(info);
|
|
return programData.pipelineLayout;
|
|
}
|
|
getProgramID (phaseID: number, programName: string): number {
|
|
return getProgramID(this.layoutGraph, phaseID, programName);
|
|
}
|
|
getDescriptorNameID (name: string): number {
|
|
return getDescriptorNameID(this.layoutGraph, name);
|
|
}
|
|
getDescriptorName (nameID: number): string {
|
|
return getDescriptorName(this.layoutGraph, nameID);
|
|
}
|
|
readonly layoutGraph: LayoutGraphData;
|
|
readonly phases: Map<number, ProgramGroup> = new Map<number, ProgramGroup>();
|
|
mergeHighFrequency = false;
|
|
fixedLocal = true;
|
|
pipeline: PipelineRuntime | null = null;
|
|
}
|