From 8a6c212c82a7ffb548ad81460125ce01672d9107 Mon Sep 17 00:00:00 2001 From: Zeqiang Li Date: Mon, 11 Sep 2023 17:31:43 +0800 Subject: [PATCH] update web pipeline for webgpu (#16207) --- cocos/gfx/webgpu/webgpu-define.ts | 507 +++++++----------- cocos/rendering/custom/compiler.ts | 72 ++- cocos/rendering/custom/define.ts | 10 +- cocos/rendering/custom/executor.ts | 499 +++++++++++++++-- cocos/rendering/custom/render-graph.ts | 48 +- cocos/rendering/custom/web-pipeline.ts | 72 ++- cocos/rendering/custom/web-program-library.ts | 401 +++++++++----- cocos/webgpu/instantiated.ts | 14 +- .../chunks/common/math/coordinates.chunk | 2 +- .../legacy/shading-cluster-additive.chunk | 2 +- .../effects/pipeline/cluster-culling.effect | 14 +- native/cocos/renderer/gfx-wgpu/CMakeLists.txt | 2 +- native/cocos/renderer/gfx-wgpu/WGPUExports.h | 3 +- .../renderer/pipeline/ClusterLightCulling.h | 2 +- 14 files changed, 1114 insertions(+), 534 deletions(-) diff --git a/cocos/gfx/webgpu/webgpu-define.ts b/cocos/gfx/webgpu/webgpu-define.ts index 73283fd0868..155206bef17 100644 --- a/cocos/gfx/webgpu/webgpu-define.ts +++ b/cocos/gfx/webgpu/webgpu-define.ts @@ -27,7 +27,7 @@ */ import { WEBGPU } from 'internal:constants'; -import { gfx, webgpuAdapter, glslalgWasmModule, promiseForWebGPUInstantiation } from '../../webgpu/instantiated'; +import { gfx, webgpuAdapter, glslangWasmModule, promiseForWebGPUInstantiation, spirvOptModule, twgslModule } from '../../webgpu/instantiated'; import { Texture, CommandBuffer, DescriptorSet, Device, InputAssembler, Buffer, Shader } from './override'; @@ -197,339 +197,157 @@ WEBGPU && promiseForWebGPUInstantiation.then(() => { oldDeviceCopyBuffersToTexture.call(this, buffers, texture, regions); }; - function seperateCombinedSamplerTexture (shaderSource: string) { - // sampler and texture - const samplerTexturArr = shaderSource.match(/(.*?)\(set = \d+, binding = \d+\) uniform(.*?)sampler\w* \w+;/g); - const count = samplerTexturArr?.length ? samplerTexturArr?.length : 0; - let code = shaderSource; + const SEPARATE_SAMPLER_BINDING_OFFSET = 16; + function seperateCombinedSamplerTexture(shaderSource: string) { + // gather + let samplerReg = /.*?(\(set = \d+, binding = )(\d+)\) uniform[^;]+sampler(\w*) (\w+);/g; + let iter = samplerReg.exec(shaderSource); + // samplerName, samplerType + const referredMap = new Map(); + while (iter) { + const samplerName = iter[4]; + const samplerType = iter[3]; + referredMap.set(samplerName, samplerType); + iter = samplerReg.exec(shaderSource); + } - const referredFuncMap = new Map(); - const samplerSet = new Set(); - samplerTexturArr?.every((str) => { - // `(?<=)` not compatible with str.match on safari. - // let textureName = str.match(/(?<=uniform(.*?)sampler\w* )(\w+)(?=;)/g)!.toString(); - const textureNameRegExpStr = '(?<=uniform(.*?)sampler\\w* )(\\w+)(?=;)'; - let textureName = (new RegExp(textureNameRegExpStr, 'g')).exec(str)![0]; - - let samplerStr = str.replace(textureName, `${textureName}Sampler`); - - // let samplerFunc = samplerStr.match(/(?<=uniform(.*?))sampler(\w*)/g)!.toString(); - const samplerRegExpStr = '(?<=uniform(.*?))sampler(\\w*)'; - let samplerFunc = (new RegExp(samplerRegExpStr, 'g')).exec(str)![0]; - samplerFunc = samplerFunc.replace('sampler', ''); - if (samplerFunc === '') { - textureName = textureName.replace('Sampler', ''); - } else { - const samplerReplaceReg = new RegExp('(?<=uniform(.*?))(sampler\\w*)', 'g'); - samplerStr = samplerStr.replace(samplerReplaceReg, 'sampler'); - - // layout (set = a, binding = b) uniform sampler2D cctex; - // to: - // layout (set = a, binding = b) uniform sampler cctexSampler; - // layout (set = a, binding = b + maxTextureNum) uniform texture2D cctex; - const samplerReg = new RegExp('(?<=binding = )(\\d+)(?=\\))', 'g'); - const samplerBindingStr = samplerReg.exec(str)![0]; - const samplerBinding = Number(samplerBindingStr) + 16; - samplerStr = samplerStr.replace(samplerReg, samplerBinding.toString()); - - const textureReg = new RegExp('(?<=uniform(.*?))(sampler)(?=\\w*)', 'g'); - const textureStr = str.replace(textureReg, 'texture'); - code = code.replace(str, `${textureStr}\n${samplerStr}`); - } + // replaceAll --> es 2021 required + let code = shaderSource; + // referredMap.forEach((value, key)=> { + // const samplerName = key; + // const samplerType = value; + // const exp = new RegExp(`\\b${samplerName}\\b([^;])`); + // let it = exp.exec(code); + // while (it) { + // code = code.replace(exp, `sampler${samplerType}(_${samplerName}, _${samplerName}_sampler)${it[1]}`); + // it = exp.exec(code); + // } + // }); + let sampReg = /.*?(\(set = \d+, binding = )(\d+)\) uniform[^;]+sampler(\w*) (\w+);/g; + let it = sampReg.exec(code); + while (it) { + code = code.replace(sampReg, `layout$1 $2) uniform texture$3 $4;\nlayout$1 $2 + ${SEPARATE_SAMPLER_BINDING_OFFSET}) uniform sampler $4_sampler;\n`); + it = sampReg.exec(code); + } - if (!samplerSet.has(`${textureName}Sampler`)) { - samplerSet.add(`${textureName}Sampler`); - // gathering referred func - let referredFuncStr = `([\\w]+)[\\s]*\\([0-9a-zA-Z_\\s,]*?sampler${samplerFunc}[^\\)]+\\)[\\s]*{`; - if (samplerFunc === '') { - referredFuncStr = `([\\w]+)[\\s]*\\([0-9a-zA-Z_\\s,]*?sampler([\\S]+)[^\\)]+\\)[\\s]*{`; + const builtinSample = ['texture', 'textureSize', 'texelFetch', 'textureLod']; + const replaceBultin = function (samplerName: string, samplerType: string, target: string) { + builtinSample.forEach((sampleFunc) => { + const builtinSampleReg = new RegExp(`${sampleFunc}\\s*\\(\\s*${samplerName}\\s*,`); + let builtinFuncIter = builtinSampleReg.exec(target); + while (builtinFuncIter) { + target = target.replace(builtinFuncIter[0], `${sampleFunc}(sampler${samplerType}(${samplerName}, ${samplerName}_sampler),`); + builtinFuncIter = builtinSampleReg.exec(target); } - const referredFuncRe = new RegExp(referredFuncStr, 'g'); - let reArr = referredFuncRe.exec(code); - while (reArr) { - // first to see if it's wrapped by #if 0 \n ... \n #ndif - let smpFunc = samplerFunc; - if (smpFunc === '') { - smpFunc = reArr[2]; - } - const searchTarget = code.slice(0, reArr.index); - const defValQueue: { str: string, b: boolean, condition: RegExpExecArray }[] = []; - let searchIndex = 1; - - while (searchIndex > 0) { - let ifIndex = searchTarget.indexOf('#if', searchIndex); - let elseIndex = searchTarget.indexOf('#else', searchIndex); - let endIndex = searchTarget.indexOf('#endif', searchIndex); - - if (ifIndex === -1 && elseIndex === -1 && endIndex === -1) { - break; - } - - if (ifIndex === -1) ifIndex = Number.MAX_SAFE_INTEGER; - if (elseIndex === -1) elseIndex = Number.MAX_SAFE_INTEGER; - if (endIndex === -1) endIndex = Number.MAX_SAFE_INTEGER; - - // next start with '#' is #if(def) - if (ifIndex < elseIndex && ifIndex < endIndex) { - const ifdef = (new RegExp(`#if(def)?[\\s]+(!)?([\\S]+)([\\s+]?(==)?(!=)[\\s+]?([\\S]+))?`, 'gm')).exec(searchTarget.slice(ifIndex))!; - defValQueue[defValQueue.length] = { str: ifdef[3], b: true, condition: ifdef }; - searchIndex = ifIndex + 1; - continue; - } - - if (elseIndex < endIndex && elseIndex < ifIndex) { - defValQueue[defValQueue.length - 1].b = false; - searchIndex = elseIndex + 1; - continue; - } + }); + return target; + } - if (endIndex < elseIndex && endIndex < ifIndex) { - defValQueue.pop(); - searchIndex = endIndex + 1; - continue; - } + let funcReg = /\s([\S]+)\s*\(([\w\s,]+)\)[\s|\\|n]*{/g; + let funcIter = funcReg.exec(code); + const funcSet = new Set(); + const paramTypeMap = new Map(); + while (funcIter) { + paramTypeMap.clear(); + + const params = funcIter[2]; + let paramsRes = params.slice(); + if (params.includes('sampler')) { + const paramIndexSet = new Set(); + const paramArr = params.split(','); + + for (let i = 0; i < paramArr.length; ++i) { + const paramDecl = paramArr[i].split(' '); + // assert(paramDecl.length >= 2) + const typeDecl = paramDecl[paramDecl.length - 2]; + if (typeDecl.includes('sampler') && typeDecl !== 'sampler') { + const samplerType = typeDecl.replace('sampler', ''); + const paramName = paramDecl[paramDecl.length - 1]; + paramsRes = paramsRes.replace(paramArr[i], ` texture${samplerType} ${paramName}, sampler ${paramName}_sampler`); + paramIndexSet.add(i); + paramTypeMap.set(paramName, samplerType); } - - let defCheck = true; - for (let i = 0; i < defValQueue.length; i++) { - const ifdef = defValQueue[i].condition; - let evalRes = false; - if (ifdef[1]) { - evalRes = !!(new RegExp(`#define[\\s]+${ifdef[3]}`, 'gm')).exec(searchTarget); - } else { - const defVal = (new RegExp(`#define[\\s]+${ifdef[3]}[\\s]+([\\S]+).*`, 'gm')).exec(searchTarget)![1]; - if (ifdef[4]) { - const conditionVal = ifdef[7]; - evalRes = ifdef[5] ? defVal === conditionVal : defVal !== conditionVal; - } else if (ifdef[3]) { - evalRes = (defVal !== '0' && !ifdef[2]); + } + // let singleParamReg = new RegExp(`(\\W?)(\\w+)\\s+\\b([^,)]+)\\b`); + + code = code.replace(params, paramsRes); + + const funcName = funcIter[1]; + // function may overload + if (!funcSet.has(funcName)) { + // const samplerTypePrefix = '1D|2D|3D|Cube|Buffer'; + const funcSamplerReg = new RegExp(`${funcName}\\s*?\\((\\s*[^;\\{]+)`, 'g'); + const matches = code.matchAll(funcSamplerReg); + for (let matched of matches) { + if (!matched[1].match(/\b\w+\b\s*\b\w+\b/g)) { + const stripStr = matched[1][matched[1].length - 1] === ')' ? matched[1].slice(0, -1) : matched[1]; + let params = stripStr.split(','); + let queued = 0; // '(' + let paramIndex = 0; + for (let i = 0; i < params.length; ++i) { + if (params[i].includes('(')) { + ++queued; + } + if (params[i].includes(')')) { + --queued; + } + + if (!queued || i === params.length - 1) { + if (paramIndexSet.has(paramIndex)) { + params[i] += `, ${params[i]}_sampler`; + } + ++paramIndex; + } } + const newParams = params.join(','); + const newInvokeStr = matched[0].replace(stripStr, newParams); + code = code.replace(matched[0], newInvokeStr); } - - if (defValQueue[i].b !== evalRes) { - defCheck = false; - break; - } + // else: function declare } - - const key = `${reArr[1]}_${smpFunc}_${textureName}Sampler`; - if (!referredFuncMap.has(key) && defCheck) { - referredFuncMap.set(key, [reArr[1], smpFunc, `${textureName}Sampler`]); - } - reArr = referredFuncRe.exec(code); } - } - // cctex in main() called directly - // .*?texture\( - const regStr = `texture\\(\\b(${textureName})\\b`; - const re = new RegExp(regStr); - let reArr = re.exec(code); - while (reArr) { - code = code.replace(re, `texture(sampler${samplerFunc}(${textureName},${textureName}Sampler)`); - reArr = re.exec(code); - } - return true; - }); - - const functionTemplates = new Map(); - const functionDeps = new Map(); - let forwardDecls = ''; - // function - referredFuncMap.forEach((pair) => { - //pre: if existed, replace - //getVec3DisplacementFromTexture\(cc_TangentDisplacements[^\\n]+ - const textureStr = pair[2].slice(0, -7); - const codePieceStr = `${pair[0]}\\((.*?)${textureStr}[^\\n]+`; - const codePieceRe = new RegExp(codePieceStr); - let res = codePieceRe.exec(code); - while (res) { - let replaceStr = res[0].replace(`${pair[0]}(`, `${pair[0]}_${pair[1]}_${pair[2]}_specialized(`); - replaceStr = replaceStr.replace(`${textureStr},`, ''); - code = code.replace(codePieceRe, replaceStr); - res = codePieceRe.exec(code); - } - - // 1. fn definition - const fnDeclReStr = `[\\n|\\W][\\w]+[\\W]+${pair[0]}[\\s]*\\((.*?)sampler${pair[1]}[^{]+`; - const fnDeclRe = new RegExp(fnDeclReStr); - const fnDecl = fnDeclRe.exec(code); - - let redefFunc = ''; - if (!functionTemplates.has(`${pair[0]}_${pair[1]}`)) { - const funcBodyStart = code.slice(fnDecl!.index); - - const funcRedefine = (funcStr: string) => { - const samplerType = `sampler${pair[1]}`; - const textureRe = (new RegExp(`.*?${samplerType}[\\s]+([\\S]+)[,\\)]`)).exec(funcStr)!; - const textureName = textureRe[1]; - const paramReStr = `${samplerType}[\\s]+${textureName}`; - let funcDef = funcStr.replace(new RegExp(paramReStr), `texture${pair[1]} ${textureName}`); - funcDef = funcDef.replace(pair[0], `${pair[0]}SAMPLER_SPEC`); - // 2. texture(...) inside, builtin funcs - const textureOpArr = ['texture', 'textureSize', 'texelFetch', 'textureLod']; - for (let i = 0; i < textureOpArr.length; i++) { - const texFuncReStr = `(${textureOpArr[i]})\\(${textureName},`; - const texFuncRe = new RegExp(texFuncReStr, 'g'); - funcDef = funcDef.replace(texFuncRe, `$1(${samplerType}(${textureName}TEXTURE_HOLDER, ${textureName}SAMPLER_HOLDER),`); - } - return funcDef; - }; - - const firstIfStatement = funcBodyStart.indexOf('#if'); - const firstElseStatement = funcBodyStart.indexOf('#e'); //#endif, #else, #elif maybe? - if ((firstElseStatement !== -1 && firstIfStatement > firstElseStatement) || (firstElseStatement === -1 && firstElseStatement !== -1)) { // ooops, now func body starts in a #if statement. - let startIndex = 0; - let count = 1; // already in #if - while (count > 0 && startIndex < funcBodyStart.length) { - const nextSymbolIdx = funcBodyStart.indexOf('#', startIndex); - const startSliceIdx = startIndex === 0 ? startIndex : startIndex - 1; - if (funcBodyStart[nextSymbolIdx + 1] === 'i') { // #if - count++; - redefFunc += funcBodyStart.slice(startSliceIdx, nextSymbolIdx); - } else if (funcBodyStart[nextSymbolIdx + 1] === 'e' && funcBodyStart[nextSymbolIdx + 2] === 'l') { //#elif, #else - if (count === 1) { - const tempFuncStr = funcBodyStart.slice(startSliceIdx, nextSymbolIdx - 1); - const funcDefStr = funcRedefine(tempFuncStr); - redefFunc += `\n${funcDefStr}`; - } else { - redefFunc += `\n${funcBodyStart.slice(startSliceIdx, nextSymbolIdx)}`; - } - } else if (funcBodyStart[nextSymbolIdx + 1] === 'e' && funcBodyStart[nextSymbolIdx + 2] === 'n') { //#endif - count--; - if (count === 0) { - const tempFuncStr = funcBodyStart.slice(startSliceIdx, nextSymbolIdx - 1); - const funcDefStr = funcRedefine(tempFuncStr); - redefFunc += `\n${funcDefStr}`; - } else { - redefFunc += `\n${funcBodyStart.slice(startSliceIdx, nextSymbolIdx)}`; - } - } else { // #define, dont care - redefFunc += funcBodyStart.slice(startSliceIdx, nextSymbolIdx); - } - startIndex = nextSymbolIdx + 1; + let count = 1; + let startIndex = code.indexOf(funcIter[1], funcIter.index); + startIndex = code.indexOf('{', startIndex) + 1; + let endIndex = 0; + while (count) { + if (code.at(startIndex) === '{') { + ++count; + } else if (code.at(startIndex) === '}') { + --count; } - //`(?:.(?!layout))+${pair[2]};` - const searchTarget = code.slice(0, fnDecl!.index); - const res = (new RegExp(`#if.+[\\s]*$`)).exec(searchTarget); - redefFunc = `${res![0]}${redefFunc}\n#endif`; - } else { - let count = 0; - let matchBegin = false; - let startIndex = 0; - let endIndex = 0; - for (let i = 0; i < funcBodyStart.length; ++i) { - if (funcBodyStart[i] === '{') { - ++count; - if (!matchBegin) { - matchBegin = true; - startIndex = i; - } - } else if (funcBodyStart[i] === '}') { - --count; - } - - if (matchBegin && count === 0) { - endIndex = i; - break; - } + if (count === 0) { + endIndex = startIndex; + break; } - const rawFunc = `${fnDecl![0]}${funcBodyStart.slice(startIndex, endIndex + 1)}`; - redefFunc = funcRedefine(rawFunc); - } - - functionTemplates.set(`${pair[0]}_${pair[1]}`, redefFunc); - } else { - redefFunc = functionTemplates.get(`${pair[0]}_${pair[1]}`)!; - } - - const depsFuncs: string[] = []; - const iterator = referredFuncMap.values(); - let val = iterator.next().value; - while (val) { - const funcDepReStr = `\\b(${val[0] as string})\\b`; - if (redefFunc.search(funcDepReStr) !== -1) { - depsFuncs[depsFuncs.length] = val[0]; + const nextLeft = code.indexOf('{', startIndex + 1); + const nextRight = code.indexOf('}', startIndex + 1); + startIndex = nextLeft === -1 ? nextRight : Math.min(nextLeft, nextRight); } - val = iterator.next().value; - } - // for (let i = 0; i < referredFuncMap.values.length; ++i) { - // const funcDepReStr = `\\b(${referredFuncMap.values[i].fnName as string})\\b`; - // if (redefFunc.search(funcDepReStr) !== -1) { - // depsFuncs[depsFuncs.length] = referredFuncMap.values[i].fnName; - // } - // } - functionDeps.set(`${pair[0]}_${pair[1]}`, depsFuncs); - - const specializedFuncs = new Map(); - const specialize = (funcs: string[]) => { - funcs.every((str) => { - if (!specializedFuncs.has(`${pair[0]}_${pair[2]}_specialized`)) { - const samplerReStr = `(\\w+)SAMPLER_HOLDER`; - const textureName = pair[2].slice(0, pair[2].length - 7); // xxxxSampler - const textureStr = `(\\w+)TEXTURE_HOLDER`; - let funcTemplate = functionTemplates.get(str); - funcTemplate = funcTemplate!.replace(new RegExp(samplerReStr, 'g'), pair[2]); - funcTemplate = funcTemplate.replace(new RegExp(textureStr, 'g'), textureName); - funcTemplate = funcTemplate.replace(new RegExp('SAMPLER_SPEC', 'g'), `_${pair[1]}_${pair[2]}_specialized`); - funcTemplate = funcTemplate.replace(new RegExp(`texture${pair[1]}\\s+\\w+,`, 'g'), ''); - // funcTemplate = funcTemplate.replace('SAMPLER_SPEC', `_${pair[2]}_specialized`); - - for (let i = 0; i < depsFuncs.length; ++i) { - const depFuncStr = `${depsFuncs[i]}([\\W]?)*\\([^,)]+(,)?`; - funcTemplate = funcTemplate.replace(new RegExp(depFuncStr, 'g'), `${depsFuncs[i]}_${pair[1]}_${pair[2]}_specialized(`); - } - - let declStr = fnDecl![0].replace(pair[0], `${str}_${pair[2]}_specialized`); - declStr = declStr.replace(new RegExp(`sampler${pair[1]}[^,)]+(,)?`, 'g'), ``); - declStr += `;`; - specializedFuncs.set(declStr, funcTemplate); - } else { - return specialize(functionDeps.get(str)!); - } - return true; + const funcBody = code.slice(funcIter.index, endIndex); + let newFunc = funcBody; + paramTypeMap.forEach((type, name) => { + newFunc = replaceBultin(name, type, newFunc); }); - return true; - }; - specialize([`${pair[0]}_${pair[1]}`]); - //(?:.(?!layout))+cc_PositionDisplacementsSampler; - const samplerDefReStr = `(?:.(?!layout))+${pair[2]};`; - const samplerDef = (new RegExp(samplerDefReStr)).exec(code); - - let funcImpls = ''; - for (const [key, value] of specializedFuncs) { - forwardDecls += `\n${key}\n`; - funcImpls += `\n${value}\n`; + + code = code.replace(funcBody, newFunc); + funcSet.add(funcIter[1]); } + funcIter = funcReg.exec(code); + } - code = code.replace(samplerDef![0], `${samplerDef![0]}\n${funcImpls}`); + referredMap.forEach((type, name) => { + code = replaceBultin(name, type, code); }); - // some function appears before it's defined so forward declaration is needed - forwardDecls += '\nvec3 SRGBToLinear (vec3 gamma);\nfloat getDisplacementWeight(int index);\n'; - - const highpIdx = code.indexOf('precision highp'); - const mediumpIdx = code.indexOf('precision mediump'); - const lowpIdx = code.indexOf('precision lowp'); - /////////////////////////////////////////////////////////// // isNan, isInf has been removed in dawn:tint let functionDefs = ''; const precisionKeyWord = 'highp'; - - // const getPrecision = (idx: number) => { - // if (highpIdx !== -1 && highpIdx < idx) { - // precisionKeyWord = 'highp'; - // } else if (mediumpIdx !== -1 && mediumpIdx < idx && highpIdx < mediumpIdx) { - // precisionKeyWord = 'mediump'; - // } else if (lowpIdx !== -1 && lowpIdx < idx && mediumpIdx < lowpIdx && highpIdx < lowpIdx) { - // precisionKeyWord = 'lowp'; - // } - // }; - const isNanIndex = code.indexOf('isnan'); if (isNanIndex !== -1) { // getPrecision(isNanIndex); @@ -557,31 +375,84 @@ WEBGPU && promiseForWebGPUInstantiation.then(() => { let firstPrecisionIdx = code.indexOf('precision'); firstPrecisionIdx = code.indexOf(';', firstPrecisionIdx); firstPrecisionIdx += 1; - code = `${code.slice(0, firstPrecisionIdx)}\n${forwardDecls}\n${functionDefs}\n${code.slice(firstPrecisionIdx)}`; + code = `${code.slice(0, firstPrecisionIdx)}\n${functionDefs}\n${code.slice(firstPrecisionIdx)}`; return code; } + function reflect(wgsl: string[]) { + const bindingList: number[][] = []; + for (let wgslStr of wgsl) { + // @group(1) @binding(0) var x_78 : Constants; + // @group(1) @binding(1) var albedoMap : texture_2d; + const reg = new RegExp(/@group\((\d)\)\s+@binding\((\d+)\)/g); + let iter = reg.exec(wgslStr); + while (iter) { + const set = +iter[1]; + const binding = +iter[2]; + while (bindingList.length <= set) { + bindingList.push([]); + } + bindingList[set][bindingList[set].length] = binding; + iter = reg.exec(wgslStr); + } + } + return bindingList; + } + + function overwriteBlock(info: ShaderInfo, code: string): string { + const regexp = new RegExp(/layout\(([^\)]+)\)\s+uniform\s+\b(\w+)\b/g); + let src = code; + let iter = regexp.exec(src); + if (iter) { + const blockName = iter[2]; + const block = info.blocks.find((ele) => { return ele.name === blockName; }); + const binding = block?.binding; + const overwriteStr = iter[0].replace(iter[1], `${iter[1]}, set = 0, binding = ${binding}`); + src = src.replace(iter[0], overwriteStr); + iter = regexp.exec(src); + } + return src; + } + const createShader = Device.prototype.createShader; Device.prototype.createShader = function (shaderInfo: ShaderInfo) { - const spvDatas: any = []; + const wgslStages: string[] = []; for (let i = 0; i < shaderInfo.stages.length; ++i) { - shaderInfo.stages[i].source = seperateCombinedSamplerTexture(shaderInfo.stages[i].source); + let glslSource = seperateCombinedSamplerTexture(shaderInfo.stages[i].source); const stageStr = shaderInfo.stages[i].stage === ShaderStageFlagBit.VERTEX ? 'vertex' : shaderInfo.stages[i].stage === ShaderStageFlagBit.FRAGMENT ? 'fragment' : 'compute'; - const sourceCode = `#version 450\n${shaderInfo.stages[i].source}`; - const spv = glslalgWasmModule.glslang.compileGLSL(sourceCode, stageStr, true, '1.3'); - spvDatas.push(spv); + // if (stageStr === 'compute') { + // glslSource = overwriteBlock(shaderInfo, glslSource); + // } + const sourceCode = `#version 450\n#define CC_USE_WGPU 1\n${glslSource}`; + const spv = glslangWasmModule.glslang.compileGLSL(sourceCode, stageStr, false, '1.3'); + + const twgsl = twgslModule.twgsl; + const wgsl = twgsl.convertSpirV2WGSL(spv); + if (wgsl === '') { + console.error("empty wgsl"); + } + shaderInfo.stages[i].source = wgsl; + wgslStages.push(wgsl); } - const shader = this.createShaderNative(shaderInfo, spvDatas); + const shader = this.createShaderNative(shaderInfo); + // optioanl : reflect bindings in shader + { + const bindingList = reflect(wgslStages); + for (let bindings of bindingList) { + const u8Array = new Uint8Array(bindings); + shader.reflectBinding(u8Array); + } + } return shader; }; // if property being frequently get in TS, try cache it // attention: invalid if got this object from a native object, // eg. inputAssembler.indexBuffer.objectID - function cacheReadOnlyWGPUProperties (type: T, props: string[]) { + function cacheReadOnlyWGPUProperties(type: T, props: string[]) { const descriptor = { writable: true }; props.map((prop) => { return Object.defineProperty(type['prototype'], `_${prop}`, descriptor); @@ -595,7 +466,7 @@ WEBGPU && promiseForWebGPUInstantiation.then(() => { for (let prop of props) { res[`_${prop}`] = res[`${prop}`]; Object.defineProperty(res, `${prop}`, { - get () { + get() { return this[`_${prop}`]; } }); diff --git a/cocos/rendering/custom/compiler.ts b/cocos/rendering/custom/compiler.ts index da170462e25..7ed6779bba5 100644 --- a/cocos/rendering/custom/compiler.ts +++ b/cocos/rendering/custom/compiler.ts @@ -28,9 +28,11 @@ import { VectorGraphColorMap } from './effect'; import { DefaultVisitor, depthFirstSearch, ReferenceGraphView } from './graph'; import { LayoutGraphData } from './layout-graph'; import { BasicPipeline } from './pipeline'; -import { Blit, ClearView, ComputePass, ComputeSubpass, CopyPass, Dispatch, FormatView, ManagedBuffer, ManagedResource, ManagedTexture, MovePass, +import { + Blit, ClearView, ComputePass, ComputeSubpass, CopyPass, Dispatch, FormatView, ManagedBuffer, ManagedResource, ManagedTexture, MovePass, RasterPass, RasterSubpass, RaytracePass, RenderGraph, RenderGraphVisitor, RasterView, ComputeView, - RenderQueue, RenderSwapchain, ResolvePass, ResourceGraph, ResourceGraphVisitor, SceneData, SubresourceView } from './render-graph'; + RenderQueue, RenderSwapchain, ResolvePass, ResourceGraph, ResourceGraphVisitor, SceneData, SubresourceView, PersistentBuffer, PersistentTexture, +} from './render-graph'; import { AccessType, ResourceResidency, SceneFlags } from './types'; import { hashCombineNum, hashCombineStr } from './define'; @@ -83,10 +85,11 @@ class PassVisitor implements RenderGraphVisitor { public queueID = 0xFFFFFFFF; public sceneID = 0xFFFFFFFF; public passID = 0xFFFFFFFF; + public dispatchID = 0xFFFFFFFF; // output resourcetexture id public resID = 0xFFFFFFFF; public context: CompilerContext; - private _currPass: RasterPass | CopyPass | null = null; + private _currPass: RasterPass | CopyPass | ComputePass | null = null; private _resVisitor: ResourceVisitor; constructor (context: CompilerContext) { this.context = context; @@ -98,6 +101,12 @@ class PassVisitor implements RenderGraphVisitor { protected _isCopyPass (u: number): boolean { return !!this.context.renderGraph.tryGetCopy(u); } + protected _isCompute (u: number): boolean { + return !!this.context.renderGraph.tryGetCompute(u); + } + protected _isDispatch (u: number): boolean { + return !!this.context.renderGraph.tryGetDispatch(u); + } protected _isQueue (u: number): boolean { return !!this.context.renderGraph.tryGetQueue(u); } @@ -242,12 +251,14 @@ class PassVisitor implements RenderGraphVisitor { } applyID (id: number, resId: number): void { this.resID = resId; - if (this._isRasterPass(id) || this._isCopyPass(id)) { + if (this._isRasterPass(id) || this._isCopyPass(id) || this._isCompute(id)) { this.passID = id; } else if (this._isQueue(id)) { this.queueID = id; } else if (this._isScene(id) || this._isBlit(id)) { this.sceneID = id; + } else if (this._isDispatch(id)) { + this.dispatchID = id; } } rasterPass (pass: RasterPass): void { @@ -258,10 +269,20 @@ class PassVisitor implements RenderGraphVisitor { // } this._currPass = pass; } - rasterSubpass (value: RasterSubpass): void {} - computeSubpass (value: ComputeSubpass): void {} - compute (value: ComputePass): void {} - resolve (value: ResolvePass): void {} + rasterSubpass (value: RasterSubpass): void { + // noop + } + computeSubpass (value: ComputeSubpass): void { + // noop + } + compute (value: ComputePass): void { + this._currPass = value; + const rg = context.renderGraph; + rg.setValid(this.passID, true); + } + resolve (value: ResolvePass): void { + // noop + } copy (value: CopyPass): void { const rg = context.renderGraph; if (rg.getValid(this.passID)) { @@ -283,18 +304,32 @@ class PassVisitor implements RenderGraphVisitor { } } } - move (value: MovePass): void {} - raytrace (value: RaytracePass): void {} - queue (value: RenderQueue): void {} + move (value: MovePass): void { + // noop + } + raytrace (value: RaytracePass): void { + // noop + } + queue (value: RenderQueue): void { + // noop + } scene (value: SceneData): void { this._fetchValidPass(); } blit (value: Blit): void { this._fetchValidPass(); } - dispatch (value: Dispatch): void {} - clear (value: ClearView[]): void {} - viewport (value: Viewport): void {} + dispatch (value: Dispatch): void { + const rg = this.context.renderGraph; + rg.setValid(this.queueID, true); + rg.setValid(this.dispatchID, true); + } + clear (value: ClearView[]): void { + // noop + } + viewport (value: Viewport): void { + // noop + } } class PassManagerVisitor extends DefaultVisitor { @@ -342,19 +377,20 @@ class ResourceVisitor implements ResourceGraphVisitor { managed (value: ManagedResource): void { this.dependency(); } - persistentBuffer (value: Buffer): void { + persistentBuffer (value: Buffer | PersistentBuffer): void { + // noop } dependency (): void { if (!this._passManagerVis) { - this._passManagerVis = new PassManagerVisitor(this._context, this.resID); + this._passManagerVis = new PassManagerVisitor(this._context, this.resID); } else { this._passManagerVis.resId = this.resID; } depthFirstSearch(this._passManagerVis.graphView, this._passManagerVis, this._passManagerVis.colorMap); } - persistentTexture (value: Texture): void { + persistentTexture (value: Texture | PersistentTexture): void { this.dependency(); } framebuffer (value: Framebuffer): void { @@ -364,8 +400,10 @@ class ResourceVisitor implements ResourceGraphVisitor { this.dependency(); } formatView (value: FormatView): void { + // noop } subresourceView (value: SubresourceView): void { + // noop } } diff --git a/cocos/rendering/custom/define.ts b/cocos/rendering/custom/define.ts index b131e820357..3704f56ac7d 100644 --- a/cocos/rendering/custom/define.ts +++ b/cocos/rendering/custom/define.ts @@ -45,7 +45,7 @@ import { ImageAsset, Material, Texture2D } from '../../asset/assets'; import { getProfilerCamera, SRGBToLinear } from '../pipeline-funcs'; import { RenderWindow } from '../../render-scene/core/render-window'; import { RenderData, RenderGraph } from './render-graph'; -import { WebPipeline } from './web-pipeline'; +import { WebComputePassBuilder, WebPipeline } from './web-pipeline'; import { DescriptorSetData, LayoutGraph, LayoutGraphData } from './layout-graph'; import { AABB } from '../../core/geometry'; import { DebugViewCompositeType, DebugViewSingleType } from '../debug-view'; @@ -2178,7 +2178,7 @@ export function buildHBAOPasses ( return { rtName: haboCombined.rtName, dsName: inputDS }; } -export const MAX_LIGHTS_PER_CLUSTER = 100; +export const MAX_LIGHTS_PER_CLUSTER = 200; export const CLUSTERS_X = 16; export const CLUSTERS_Y = 8; export const CLUSTERS_Z = 24; @@ -2240,6 +2240,9 @@ export function buildLightClusterBuildPass ( const width = camera.width * ppl.pipelineSceneData.shadingScale; const height = camera.height * ppl.pipelineSceneData.shadingScale; + if ('setCurrConstant' in clusterPass) { // web-pipeline + (clusterPass as WebComputePassBuilder).addConstant('CCConst', 'cluster-build-cs'); + } clusterPass.setVec4('cc_nearFar', new Vec4(camera.nearClip, camera.farClip, camera.getClipSpaceMinz(), 0)); clusterPass.setVec4('cc_viewPort', new Vec4(0, 0, width, height)); clusterPass.setVec4('cc_workGroup', new Vec4(CLUSTERS_X, CLUSTERS_Y, CLUSTERS_Z, 0)); @@ -2280,6 +2283,9 @@ export function buildLightClusterCullingPass ( const width = camera.width * ppl.pipelineSceneData.shadingScale; const height = camera.height * ppl.pipelineSceneData.shadingScale; + if ('setCurrConstant' in clusterPass) { // web-pipeline + (clusterPass as WebComputePassBuilder).addConstant('CCConst', 'cluster-build-cs'); + } clusterPass.setVec4('cc_nearFar', new Vec4(camera.nearClip, camera.farClip, camera.getClipSpaceMinz(), 0)); clusterPass.setVec4('cc_viewPort', new Vec4(width, height, width, height)); clusterPass.setVec4('cc_workGroup', new Vec4(CLUSTERS_X, CLUSTERS_Y, CLUSTERS_Z, 0)); diff --git a/cocos/rendering/custom/executor.ts b/cocos/rendering/custom/executor.ts index 5c1191dc03d..95f9cbae566 100644 --- a/cocos/rendering/custom/executor.ts +++ b/cocos/rendering/custom/executor.ts @@ -36,6 +36,7 @@ import { AccessFlagBit, Attribute, Buffer, + BufferFlagBit, BufferInfo, BufferUsageBit, BufferViewInfo, @@ -47,6 +48,7 @@ import { DescriptorSetInfo, Device, deviceManager, + DispatchInfo, Format, Framebuffer, FramebufferInfo, @@ -55,7 +57,9 @@ import { InputAssemblerInfo, LoadOp, MemoryUsageBit, + PipelineBindPoint, PipelineState, + PipelineStateInfo, Rect, RenderPass, RenderPassInfo, @@ -95,6 +99,8 @@ import { ManagedResource, ManagedTexture, MovePass, + PersistentBuffer, + PersistentTexture, RasterPass, RasterSubpass, RasterView, @@ -113,6 +119,7 @@ import { SubresourceView, } from './render-graph'; import { + AccessType, AttachmentType, QueueHint, ResourceDimension, @@ -137,32 +144,74 @@ import { } from './define'; import { RenderReflectionProbeQueue } from '../render-reflection-probe-queue'; import { SceneCulling } from './scene-culling'; +import { WebPipeline } from './web-pipeline'; class ResourceVisitor implements ResourceGraphVisitor { name: string; constructor (resName = '') { this.name = resName; + if (context) { + const ppl = context.pipeline as any; + ppl.resourceUses.push(resName); + } } set resName (value: string) { this.name = value; } - createDeviceTex (value: Texture | Framebuffer | ManagedResource | RenderSwapchain): void { - const deviceTex = new DeviceTexture(this.name, value); - context.deviceTextures.set(this.name, deviceTex); + checkTexture (name: string): boolean { + const dTex = context.deviceTextures.get(name)!; + const resID = context.resourceGraph.vertex(this.name); + const desc = context.resourceGraph.getDesc(resID); + let res = false; + if (dTex.texture) { + res = dTex.texture.width === desc.width && dTex.texture.height === desc.height; + } else if (dTex.swapchain) { + res = dTex.swapchain.width === desc.width && dTex.swapchain.height === desc.height; + } + return res; + } + createDeviceTex (value: PersistentTexture | Framebuffer | ManagedResource | RenderSwapchain): void { + if (!context.deviceTextures.get(this.name)) { + const deviceTex = new DeviceTexture(this.name, value); + context.deviceTextures.set(this.name, deviceTex); + } else if (!this.checkTexture(this.name)) { + const dTex = context.deviceTextures.get(this.name)!; + dTex.texture?.destroy(); + const deviceTex = new DeviceTexture(this.name, value); + context.deviceTextures.set(this.name, deviceTex); + } + } + checkBuffer (name: string): boolean { + const dBuf = context.deviceBuffers.get(name)!; + const resID = context.resourceGraph.vertex(this.name); + const desc = context.resourceGraph.getDesc(resID); + return dBuf.buffer!.size >= desc.width; + } + createDeviceBuf (value: ManagedBuffer | PersistentBuffer): void { + const mount: boolean = !!context.deviceBuffers.get(this.name); + if (!mount) { + const deviceBuf = new DeviceBuffer(this.name, value); + context.deviceBuffers.set(this.name, deviceBuf); + } else if (!this.checkBuffer(this.name)) { + const dBuf = context.deviceBuffers.get(this.name)!; + dBuf.buffer?.destroy(); + const deviceBuf = new DeviceBuffer(this.name, value); + context.deviceBuffers.set(this.name, deviceBuf); + } } managed (value: ManagedResource): void { this.createDeviceTex(value); } managedBuffer (value: ManagedBuffer): void { - // noop + this.createDeviceBuf(value); } managedTexture (value: ManagedTexture): void { // noop } - persistentBuffer (value: Buffer): void { - // noop + persistentBuffer (value: PersistentBuffer): void { + this.createDeviceBuf(value); } - persistentTexture (value: Texture): void { + persistentTexture (value: PersistentTexture): void { this.createDeviceTex(value); } framebuffer (value: Framebuffer): void { @@ -214,7 +263,7 @@ class DeviceTexture extends DeviceResource { this._desc.textureFlags = desc.textureFlags; this._desc.width = desc.width; } - constructor (name: string, tex: Texture | Framebuffer | RenderSwapchain | ManagedResource) { + constructor (name: string, tex: PersistentTexture | Framebuffer | RenderSwapchain | ManagedResource) { super(name); const resGraph = context.resourceGraph; const verID = resGraph.vertex(name); @@ -287,8 +336,35 @@ function isShadowMap (graphScene: GraphScene): boolean | null { } class DeviceBuffer extends DeviceResource { - constructor (name: string) { + private _buffer: Buffer | null; + + get buffer (): Buffer | null { + return this._buffer; + } + + constructor (name: string, buffer: ManagedBuffer | PersistentBuffer) { super(name); + const resGraph = context.resourceGraph; + const verID = resGraph.vertex(name); + const desc = resGraph.getDesc(verID); + const bufferInfo = new BufferInfo(); + bufferInfo.size = desc.width; + bufferInfo.memUsage = MemoryUsageBit.DEVICE; + + if (desc.flags & ResourceFlags.INDIRECT) bufferInfo.usage |= BufferUsageBit.INDIRECT; + if (desc.flags & ResourceFlags.UNIFORM) bufferInfo.usage |= BufferUsageBit.UNIFORM; + if (desc.flags & ResourceFlags.STORAGE) bufferInfo.usage |= BufferUsageBit.STORAGE; + if (desc.flags & ResourceFlags.TRANSFER_SRC) bufferInfo.usage |= BufferUsageBit.TRANSFER_SRC; + if (desc.flags & ResourceFlags.TRANSFER_DST) bufferInfo.usage |= BufferUsageBit.TRANSFER_DST; + + this._buffer = context.device.createBuffer(bufferInfo); + } + + release (): void { + if (this._buffer) { + this._buffer.destroy(); + this._buffer = null; + } } } @@ -454,12 +530,67 @@ class BlitDesc { } } +class DeviceComputeQueue { + private _devicePass: DeviceComputePass | undefined; + private _hint: QueueHint = QueueHint.NONE; + private _phaseID: number = getPhaseID('default'); + private _renderPhase: RenderPhaseData | null = null; + private _descSetData: DescriptorSetData | null = null; + private _layoutID = -1; + private _isUpdateUBO = false; + private _isUploadInstance = false; + private _isUploadBatched = false; + private _queueId = -1; + init (devicePass: DeviceComputePass, renderQueue: RenderQueue, id: number): void { + this.reset(); + this.queueHint = renderQueue.hint; + this.queueId = id; + this._devicePass = devicePass; + this._phaseID = cclegacy.rendering.getPhaseID(devicePass.passID, context.renderGraph.getLayout(id)); + } + get phaseID (): number { return this._phaseID; } + set layoutID (value: number) { + this._layoutID = value; + const layoutGraph = context.layoutGraph; + this._renderPhase = layoutGraph.tryGetRenderPhase(value); + const layout = layoutGraph.getLayout(value); + this._descSetData = layout.descriptorSets.get(UpdateFrequency.PER_PHASE)!; + } + get layoutID (): number { return this._layoutID; } + get descSetData (): DescriptorSetData | null { return this._descSetData; } + get renderPhase (): RenderPhaseData | null { return this._renderPhase; } + set queueId (val) { this._queueId = val; } + get queueId (): number { return this._queueId; } + set isUpdateUBO (update: boolean) { this._isUpdateUBO = update; } + get isUpdateUBO (): boolean { return this._isUpdateUBO; } + set isUploadInstance (value: boolean) { this._isUploadInstance = value; } + get isUploadInstance (): boolean { return this._isUploadInstance; } + set isUploadBatched (value: boolean) { this._isUploadBatched = value; } + get isUploadBatched (): boolean { return this._isUploadBatched; } + + reset (): void { + this._isUpdateUBO = false; + this._isUploadInstance = false; + this._isUploadBatched = false; + } + set queueHint (value: QueueHint) { this._hint = value; } + get queueHint (): QueueHint { return this._hint; } + get devicePass (): DeviceComputePass { return this._devicePass!; } + + record (): void { + if (this._descSetData && this._descSetData.descriptorSet) { + context.commandBuffer + .bindDescriptorSet(SetIndex.COUNT, this._descSetData.descriptorSet); + } + } +} + class DeviceRenderQueue { private _preSceneTasks: DevicePreSceneTask[] = []; private _sceneTasks: DeviceSceneTask[] = []; private _postSceneTasks: DevicePostSceneTask[] = []; private _devicePass: DeviceRenderPass | undefined; - private _hint: QueueHint = QueueHint.NONE; + private _hint: QueueHint = QueueHint.NONE; private _graphQueue!: RenderQueue; private _phaseID: number = getPhaseID('default'); private _renderPhase: RenderPhaseData | null = null; @@ -522,7 +653,7 @@ class DeviceRenderQueue { } addSceneTask (scene: GraphScene): void { if (!this._transversal) { - this._transversal = new DeviceSceneTransversal(this, context.pipelineSceneData, scene); + this._transversal = new DeviceSceneTransversal(this, context.pipelineSceneData, scene); } this._transversal.graphScene = scene; this._preSceneTasks.push(this._transversal.preRenderPass(this._sceneVisitor)); @@ -594,14 +725,17 @@ class RenderPassLayoutInfo { const lg = context.layoutGraph; this._stage = lg.getRenderStage(layoutId); this._layout = lg.getLayout(layoutId); - const layoutData = this._layout.descriptorSets.get(UpdateFrequency.PER_PASS); - const globalDesc = context.pipeline.descriptorSet; + const layoutData = this._layout.descriptorSets.get(UpdateFrequency.PER_PASS); + // const globalDesc = context.descriptorSet; if (layoutData) { + const layoutDesc = layoutData.descriptorSet!; // find resource const deviceTex = context.deviceTextures.get(this._inputName); const gfxTex = deviceTex?.texture; - const layoutDesc = layoutData.descriptorSet!; - if (!gfxTex) { + + const deviceBuf = context.deviceBuffers.get(this._inputName); + const gfxBuf = deviceBuf?.buffer; + if (!gfxTex && !gfxBuf) { throw Error(`Could not find texture with resource name ${this._inputName}`); } const resId = context.resourceGraph.vertex(this._inputName); @@ -616,8 +750,19 @@ class RenderPassLayoutInfo { // const buffer = layoutDesc.getBuffer(block.offset + i); // const texture = layoutDesc.getTexture(block.offset + i); if (descriptorID === block.descriptors[i].descriptorID) { - layoutDesc.bindTexture(block.offset + i, gfxTex); - layoutDesc.bindSampler(block.offset + i, context.device.getSampler(samplerInfo)); + if (gfxTex) { + layoutDesc.bindTexture(block.offset + i, gfxTex); + layoutDesc.bindSampler(block.offset + i, context.device.getSampler(samplerInfo)); + } else { + const desc = context.resourceGraph.getDesc(resId); + if (desc.flags & ResourceFlags.STORAGE) { + const access = input[1][0].accessType !== AccessType.READ ? AccessFlagBit.COMPUTE_SHADER_WRITE + : AccessFlagBit.COMPUTE_SHADER_READ_OTHER; + (layoutDesc as any).bindBuffer(block.offset + i, gfxBuf!, 0, access); + } else { + layoutDesc.bindBuffer(block.offset + i, gfxBuf!); + } + } if (!this._descriptorSet) this._descriptorSet = layoutDesc; continue; } @@ -806,6 +951,10 @@ class DeviceRenderPass { // colorTexs[0].height, // )); // } + const depth = swapchain ? swapchain.depthStencilTexture : depthTex; + if (!depth) { + depthStencilAttachment.format = Format.UNKNOWN; + } this._renderPass = device.createRenderPass(new RenderPassInfo(colors, depthStencilAttachment)); this._framebuffer = framebuffer || device.createFramebuffer(new FramebufferInfo( this._renderPass, @@ -832,6 +981,7 @@ class DeviceRenderPass { } addQueue (queue: DeviceRenderQueue): void { this._deviceQueues.push(queue); } prePass (): void { + context.descriptorSet = getDescriptorSetDataFromLayout(this.layoutName)!.descriptorSet; for (const queue of this._deviceQueues) { queue.preRecord(); } @@ -884,7 +1034,7 @@ class DeviceRenderPass { ia, ); const descData = getDescriptorSetDataFromLayoutId(pass.passID)!; - mergeSrcToTargetDesc(descData.descriptorSet, context.pipeline.descriptorSet, true); + mergeSrcToTargetDesc(descData.descriptorSet, context.descriptorSet, true); profilerViewport.width = rect.width; profilerViewport.height = rect.height; cmdBuff.setViewport(profilerViewport); @@ -921,7 +1071,7 @@ class DeviceRenderPass { ); cmdBuff.bindDescriptorSet( SetIndex.GLOBAL, - context.pipeline.descriptorSet, + context.descriptorSet!, ); for (const queue of this._deviceQueues) { queue.record(); @@ -1013,6 +1163,135 @@ class DeviceRenderPass { } } +class ComputePassInfo { + protected _id!: number; + protected _pass!: ComputePass; + get id (): number { return this._id; } + get pass (): ComputePass { return this._pass; } + private _copyPass (pass: ComputePass): void { + const computePass = this._pass || new ComputePass(); + for (const val of pass.computeViews) { + const currComputeViews = val[1]; + const currComputeKey = val[0]; + const computeViews: ComputeView[] = computePass.computeViews.get(currComputeKey) || []; + if (computeViews.length) computeViews.length = currComputeViews.length; + let idx = 0; + for (const currComputeView of currComputeViews) { + const computeView = computeViews[idx] || new ComputeView(); + computeView.name = currComputeView.name; + computeView.accessType = currComputeView.accessType; + computeView.clearFlags = currComputeView.clearFlags; + computeView.clearValue.x = currComputeView.clearValue.x; + computeView.clearValue.y = currComputeView.clearValue.y; + computeView.clearValue.z = currComputeView.clearValue.z; + computeView.clearValue.w = currComputeView.clearValue.w; + computeView.clearValueType = currComputeView.clearValueType; + computeViews[idx] = computeView; + idx++; + } + computePass.computeViews.set(currComputeKey, computeViews); + } + this._pass = computePass; + } + applyInfo (id: number, pass: ComputePass): void { + this._id = id; + this._copyPass(pass); + } +} + +class DeviceComputePass { + protected _deviceQueues: DeviceComputeQueue[] = []; + protected _passID: number; + protected _layoutName: string; + protected _viewport: Viewport | null = null; + private _computeInfo: ComputePassInfo; + private _layout: RenderPassLayoutInfo | null = null; + constructor (passInfo: ComputePassInfo) { + this._computeInfo = passInfo; + this._layoutName = context.renderGraph.getLayout(passInfo.id); + this._passID = cclegacy.rendering.getPassID(this._layoutName); + + for (const cv of passInfo.pass.computeViews) { + let resTex = context.deviceTextures.get(cv[0]); + if (!resTex) { + this.visitResource(cv[0]); + resTex = context.deviceTextures.get(cv[0])!; + } + this._applyRenderLayout(cv); + } + // update the layout descriptorSet + if (this.renderLayout && this.renderLayout.descriptorSet) { + this.renderLayout.descriptorSet.update(); + } + } + get layoutName (): string { return this._layoutName; } + get passID (): number { return this._passID; } + get renderLayout (): RenderPassLayoutInfo | null { return this._layout; } + + get deviceQueues (): DeviceComputeQueue[] { return this._deviceQueues; } + get computePassInfo (): ComputePassInfo { return this._computeInfo; } + visitResource (resName: string): void { + const resourceGraph = context.resourceGraph; + const vertId = resourceGraph.vertex(resName); + resourceVisitor.resName = resName; + resourceGraph.visitVertex(resourceVisitor, vertId); + } + addQueue (queue: DeviceComputeQueue): void { this._deviceQueues.push(queue); } + prePass (): void { + // noop + } + protected _applyRenderLayout (input: [string, ComputeView[]]): void { + const stageName = context.renderGraph.getLayout(this._computeInfo.id); + if (stageName) { + const layoutGraph = context.layoutGraph; + const stageId = layoutGraph.locateChild(layoutGraph.nullVertex(), stageName); + if (stageId !== 0xFFFFFFFF) { + this._layout = new RenderPassLayoutInfo(stageId, input); + } + } + } + getGlobalDescData (): DescriptorSetData { + const stageId = context.layoutGraph.locateChild(context.layoutGraph.nullVertex(), 'default'); + assert(stageId !== 0xFFFFFFFF); + const layout = context.layoutGraph.getLayout(stageId); + const layoutData = layout.descriptorSets.get(UpdateFrequency.PER_PASS)!; + return layoutData; + } + + // record common buffer + record (): void { + const cmdBuff = context.commandBuffer; + + cmdBuff.bindDescriptorSet( + SetIndex.GLOBAL, + context.descriptorSet!, + ); + for (const queue of this._deviceQueues) { + queue.record(); + } + const renderData = context.renderGraph.getData(this._computeInfo.id); + updateGlobalDescBinding(renderData, context.renderGraph.getLayout(this._computeInfo.id)); + } + + postPass (): void { + // noop + } + resetResource (id: number, pass: ComputePass): void { + this._computeInfo.applyInfo(id, pass); + this._layoutName = context.renderGraph.getLayout(id); + this._passID = cclegacy.rendering.getPassID(this._layoutName); + this._deviceQueues.length = 0; + const colTextures: Texture[] = []; + for (const cv of this._computeInfo.pass.computeViews) { + this._applyRenderLayout(cv); + } + // update the layout descriptorSet + if (this.renderLayout && this.renderLayout.descriptorSet) { + this.renderLayout.descriptorSet.update(); + } + } +} + class DeviceSceneTransversal extends WebSceneTransversal { protected _currentQueue: DeviceRenderQueue; protected _graphScene: GraphScene; @@ -1180,6 +1459,23 @@ class DeviceSceneTask extends WebSceneTask { } protected _recordUI (): void { + const devicePass = this._currentQueue.devicePass; + const rasterId = devicePass.rasterPassInfo.id; + const passRenderData = context.renderGraph.getData(rasterId); + // CCGlobal + this._updateGlobal(passRenderData); + // CCCamera, CCShadow, CCCSM + const queueId = this._currentQueue.queueId; + const queueRenderData = context.renderGraph.getData(queueId)!; + this._updateGlobal(queueRenderData); + + const layoutName = context.renderGraph.getLayout(rasterId); + const descSetData = getDescriptorSetDataFromLayout(layoutName); + if (context.descriptorSet) { + mergeSrcToTargetDesc(descSetData!.descriptorSet, context.descriptorSet, true); + } + this._currentQueue.isUpdateUBO = true; + const batches = this.camera!.scene!.batches; for (let i = 0; i < batches.length; i++) { const batch = batches[i]; @@ -1243,7 +1539,7 @@ class DeviceSceneTask extends WebSceneTask { const shader = pass.getShaderVariant(); const devicePass = this._currentQueue.devicePass; const screenIa: InputAssembler = this._currentQueue.blitDesc!.screenQuad!.quadIA!; - const globalDesc = context.pipeline.descriptorSet; + const globalDesc = context.descriptorSet; let pso: PipelineState | null = null; if (pass !== null && shader !== null && screenIa !== null) { pso = PipelineStateManager.getOrCreatePipelineState( @@ -1288,7 +1584,7 @@ class DeviceSceneTask extends WebSceneTask { const layoutName = context.renderGraph.getLayout(rasterId); const descSetData = getDescriptorSetDataFromLayout(layoutName); - mergeSrcToTargetDesc(descSetData!.descriptorSet, context.pipeline.descriptorSet, true); + mergeSrcToTargetDesc(descSetData!.descriptorSet, context.descriptorSet, true); this._currentQueue.isUpdateUBO = true; } @@ -1332,7 +1628,7 @@ class DeviceSceneTask extends WebSceneTask { return; } const renderQueueDesc = sceneCulling.sceneQueryIndex.get(this.graphScene.sceneID)!; - const renderQueue = sceneCulling.renderQueues[renderQueueDesc.renderQueueTarget]; + const renderQueue = sceneCulling.renderQueues[renderQueueDesc.renderQueueTarget]; const graphSceneData = this.graphScene.scene!; renderQueue.opaqueQueue.recordCommandBuffer(deviceManager.gfxDevice, this._renderPass, context.commandBuffer); renderQueue.opaqueInstancingQueue.recordCommandBuffer(this._renderPass, context.commandBuffer); @@ -1340,7 +1636,7 @@ class DeviceSceneTask extends WebSceneTask { this._recordAdditiveLights(); this.visitor.bindDescriptorSet( SetIndex.GLOBAL, - context.pipeline.descriptorSet, + context.descriptorSet!, ); } @@ -1362,13 +1658,15 @@ class DeviceSceneTask extends WebSceneTask { } } -class DevicePostSceneTask extends WebSceneTask {} +class DevicePostSceneTask extends WebSceneTask { } class ExecutorPools { constructor (context: ExecutorContext) { this.deviceQueuePool = new RecyclePool((): DeviceRenderQueue => new DeviceRenderQueue(), 16); + this.computeQueuePool = new RecyclePool((): DeviceComputeQueue => new DeviceComputeQueue(), 16); this.graphScenePool = new RecyclePool((): GraphScene => new GraphScene(), 16); this.rasterPassInfoPool = new RecyclePool((): RasterPassInfo => new RasterPassInfo(), 16); + this.computePassInfoPool = new RecyclePool((): ComputePassInfo => new ComputePassInfo(), 16); this.reflectionProbe = new RecyclePool((): RenderReflectionProbeQueue => new RenderReflectionProbeQueue(context.pipeline), 8); this.passPool = new RecyclePool((): { priority: number; hash: number; depth: number; shaderId: number; subModel: any; passIdx: number; } => ({ priority: 0, @@ -1388,6 +1686,9 @@ class ExecutorPools { addDeviceQueue (): DeviceRenderQueue { return this.deviceQueuePool.add(); } + addComputeQueue (): DeviceComputeQueue { + return this.computeQueuePool.add(); + } addGraphScene (): GraphScene { return this.graphScenePool.add(); } @@ -1397,17 +1698,24 @@ class ExecutorPools { addRasterPassInfo (): RasterPassInfo { return this.rasterPassInfoPool.add(); } + addComputePassInfo (): ComputePassInfo { + return this.computePassInfoPool.add(); + } reset (): void { this.deviceQueuePool.reset(); + this.computeQueuePool.reset(); this.graphScenePool.reset(); this.reflectionProbe.reset(); this.resetPassInfo(); + this.computePassInfoPool.reset(); } readonly deviceQueuePool: RecyclePool; + readonly computeQueuePool: RecyclePool; readonly graphScenePool: RecyclePool; readonly reflectionProbe: RecyclePool; readonly passPool: RecyclePool; readonly rasterPassInfoPool: RecyclePool; + readonly computePassInfoPool: RecyclePool; } const vbData = new Float32Array(4 * 4); @@ -1492,8 +1800,8 @@ class BlitInfo { let maxY = (renderArea.y + renderArea.height) / this._context.height; if (this._context.root.device.capabilities.screenSpaceSignY > 0) { const temp = maxY; - maxY = minY; - minY = temp; + maxY = minY; + minY = temp; } let n = 0; switch (surfaceTransform) { @@ -1550,7 +1858,7 @@ class BlitInfo { } // create index buffer - const ibStride = Uint8Array.BYTES_PER_ELEMENT; + const ibStride = Uint16Array.BYTES_PER_ELEMENT; const ibSize = ibStride * 6; const quadIB: Buffer = device.createBuffer(new BufferInfo( @@ -1564,11 +1872,11 @@ class BlitInfo { return inputAssemblerData; } - const indices = new Uint8Array(6); + const indices = new Uint16Array(6); indices[0] = 0; indices[1] = 1; indices[2] = 2; indices[3] = 1; indices[4] = 3; indices[5] = 2; - quadIB.update(indices); + quadIB.update(indices.buffer); // create input assembler @@ -1599,6 +1907,7 @@ class ExecutorContext { layoutGraph: LayoutGraphData, width: number, height: number, + descriptorSet = null, ) { this.pipeline = pipeline; this.device = device; @@ -1615,6 +1924,7 @@ class ExecutorContext { this.pools = new ExecutorPools(this); this.blit = new BlitInfo(this); this.culling = new SceneCulling(); + this.descriptorSet = descriptorSet; } reset (): void { this.culling.clear(); @@ -1636,6 +1946,7 @@ class ExecutorContext { readonly resourceGraph: ResourceGraph; readonly devicePasses: Map = new Map(); readonly deviceTextures: Map = new Map(); + readonly deviceBuffers: Map = new Map(); readonly layoutGraph: LayoutGraphData; readonly root: Root; readonly ubo: PipelineUBO; @@ -1648,6 +1959,7 @@ class ExecutorContext { width: number; height: number; cullCamera; + descriptorSet: DescriptorSet | null; } export class Executor { @@ -1697,6 +2009,26 @@ export class Executor { deviceTexs.get(name)!.release(); deviceTexs.delete(name); } + + const deletesBuff: string[] = []; + const deviceBuffs = context.deviceBuffers; + for (const [name, dBuff] of deviceBuffs) { + const resId = context.resourceGraph.vertex(name); + const trait = context.resourceGraph.getTraits(resId); + if (!resourceUses.includes(name)) { + switch (trait.residency) { + case ResourceResidency.MANAGED: + deletesBuff.push(name); + break; + default: + } + } + } + for (const name of deletesBuff) { + deviceBuffs.get(name)!.release(); + deviceBuffs.delete(name); + } + resourceUses.length = 0; } execute (rg: RenderGraph): void { @@ -1719,6 +2051,11 @@ export class Executor { v.release(); } context.deviceTextures.clear(); + + for (const [k, v] of context.deviceBuffers) { + v.release(); + } + context.deviceBuffers.clear(); } readonly _context: ExecutorContext; private _visitor: RenderVisitor | undefined; @@ -1728,8 +2065,9 @@ class BaseRenderVisitor { public queueID = 0xFFFFFFFF; public sceneID = 0xFFFFFFFF; public passID = 0xFFFFFFFF; - public currPass: DeviceRenderPass | undefined; - public currQueue: DeviceRenderQueue | undefined; + public dispatchID = 0xFFFFFFFF; + public currPass: DeviceRenderPass | DeviceComputePass | undefined; + public currQueue: DeviceRenderQueue |DeviceComputeQueue | undefined; public rg: RenderGraph; constructor () { this.rg = context.renderGraph; @@ -1737,6 +2075,12 @@ class BaseRenderVisitor { protected _isRasterPass (u: number): boolean { return !!context.renderGraph.tryGetRasterPass(u); } + protected isComputePass (u: number): boolean { + return !!context.renderGraph.tryGetCompute(u); + } + protected isDispatch (u: number): boolean { + return !!context.renderGraph.tryGetDispatch(u); + } protected _isQueue (u: number): boolean { return !!context.renderGraph.tryGetQueue(u); } @@ -1747,7 +2091,17 @@ class BaseRenderVisitor { return !!context.renderGraph.tryGetBlit(u); } applyID (id: number): void { - if (this._isRasterPass(id)) { this.passID = id; } else if (this._isQueue(id)) { this.queueID = id; } else if (this._isScene(id) || this._isBlit(id)) { this.sceneID = id; } + if (this._isRasterPass(id)) { + this.passID = id; + } else if (this._isQueue(id)) { + this.queueID = id; + } else if (this._isScene(id) || this._isBlit(id)) { + this.sceneID = id; + } else if (this.isComputePass(id)) { + this.passID = id; + } else if (this.isDispatch(id)) { + this.dispatchID = id; + } } } @@ -1781,27 +2135,54 @@ class PreRenderVisitor extends BaseRenderVisitor implements RenderGraphVisitor { computeSubpass (value: ComputeSubpass): void { // noop } - compute (value: ComputePass): void { - // noop - } resolve (value: ResolvePass): void { // noop } - copy (value: CopyPass): void { - // noop - } move (value: MovePass): void { // noop } raytrace (value: RaytracePass): void { // noop } + compute (pass: ComputePass): void { + if (!this.rg.getValid(this.passID)) return; + const devicePasses = context.devicePasses; + const computeInfo = new ComputePassInfo(); + computeInfo.applyInfo(this.passID, pass); + this.currPass = new DeviceComputePass(computeInfo); + + this.currPass.prePass(); + this.currPass.record(); + this.currPass.postPass(); + } + copy (value: CopyPass): void { + if (value.uploadPairs.length) { + for (const upload of value.uploadPairs) { + const resBuffers = context.deviceBuffers; + const resourceGraph = context.resourceGraph; + const vertId = resourceGraph.vertex(upload.target); + resourceVisitor.resName = upload.target; + resourceGraph.visitVertex(resourceVisitor, vertId); + + const gfxBuffer = resBuffers.get(upload.target); + context.device.commandBuffer.updateBuffer(gfxBuffer!.buffer!, upload.source, upload.source.byteLength); + } + } + } queue (value: RenderQueue): void { if (!this.rg.getValid(this.queueID)) return; - const deviceQueue = context.pools.addDeviceQueue(); - deviceQueue.init(this.currPass!, value, this.queueID); - this.currQueue = deviceQueue; - this.currPass!.addQueue(deviceQueue); + let deviceQueue: DeviceComputeQueue | DeviceRenderQueue; + if ('rasterPassInfo' in this.currPass!) { + deviceQueue = context.pools.addDeviceQueue(); + deviceQueue.init(this.currPass, value, this.queueID); + this.currQueue = deviceQueue; + this.currPass.addQueue(deviceQueue); + } else { + deviceQueue = context.pools.addComputeQueue(); + deviceQueue.init(this.currPass!, value, this.queueID); + this.currQueue = deviceQueue; + this.currPass!.addQueue(deviceQueue); + } const layoutName = this.rg.getLayout(this.queueID); if (layoutName) { const layoutGraph = context.layoutGraph; @@ -1813,18 +2194,46 @@ class PreRenderVisitor extends BaseRenderVisitor implements RenderGraphVisitor { } scene (value: SceneData): void { if (!this.rg.getValid(this.sceneID)) return; + const renderQueue = this.currQueue as DeviceRenderQueue; const graphScene = context.pools.addGraphScene(); graphScene.init(value, null, this.sceneID); - this.currQueue!.addSceneTask(graphScene); + renderQueue.addSceneTask(graphScene); } blit (value: Blit): void { if (!this.rg.getValid(this.sceneID)) return; + const renderQueue = this.currQueue as DeviceRenderQueue; const graphScene = context.pools.addGraphScene(); graphScene.init(null, value, -1); - this.currQueue!.addSceneTask(graphScene); + renderQueue.addSceneTask(graphScene); } dispatch (value: Dispatch): void { - // noop + let pso: PipelineState | null = null; + const devicePass = this.currPass as DeviceComputePass; + const pass = value.material?.passes[value.passID]; + pass?.update(); + const shader = pass?.getShaderVariant(); + + if (pass !== null && shader !== null) { + const psoInfo = new PipelineStateInfo( + shader, + pass?.pipelineLayout, + ); + psoInfo.bindPoint = PipelineBindPoint.COMPUTE; + pso = deviceManager.gfxDevice.createPipelineState(psoInfo); + } + const cmdBuff = context.commandBuffer; + if (pso) { + cmdBuff.bindPipelineState(pso); + const layoutStage = devicePass.renderLayout; + const layoutDesc = layoutStage!.descriptorSet!; + const extResId: number[] = []; + cmdBuff.bindDescriptorSet(SetIndex.GLOBAL, layoutDesc); + } + + const gx = value.threadGroupCountX; + const gy = value.threadGroupCountY; + const gz = value.threadGroupCountZ; + (cmdBuff as any).dispatch(new DispatchInfo(gx, gy, gz)); } } diff --git a/cocos/rendering/custom/render-graph.ts b/cocos/rendering/custom/render-graph.ts index fa3f241f839..e2b609289da 100644 --- a/cocos/rendering/custom/render-graph.ts +++ b/cocos/rendering/custom/render-graph.ts @@ -150,6 +150,14 @@ export class ManagedBuffer { fenceValue = 0; } +export class PersistentBuffer { + constructor (buffer: Buffer | null = null) { + this.buffer = buffer; + } + /*refcount*/ buffer: Buffer | null; + fenceValue = 0; +} + export class ManagedTexture { constructor (texture: Texture | null = null) { this.texture = texture; @@ -158,6 +166,14 @@ export class ManagedTexture { fenceValue = 0; } +export class PersistentTexture { + constructor (texture: Texture | null = null) { + this.texture = texture; + } + /*refcount*/ texture: Texture | null; + fenceValue = 0; +} + export class ManagedResource { unused = 0; } @@ -573,8 +589,8 @@ export interface ResourceGraphValueType { [ResourceGraphValue.Managed]: ManagedResource [ResourceGraphValue.ManagedBuffer]: ManagedBuffer [ResourceGraphValue.ManagedTexture]: ManagedTexture - [ResourceGraphValue.PersistentBuffer]: Buffer - [ResourceGraphValue.PersistentTexture]: Texture + [ResourceGraphValue.PersistentBuffer]: PersistentBuffer + [ResourceGraphValue.PersistentTexture]: PersistentTexture [ResourceGraphValue.Framebuffer]: Framebuffer [ResourceGraphValue.Swapchain]: RenderSwapchain [ResourceGraphValue.FormatView]: FormatView @@ -585,8 +601,8 @@ export interface ResourceGraphVisitor { managed(value: ManagedResource): unknown; managedBuffer(value: ManagedBuffer): unknown; managedTexture(value: ManagedTexture): unknown; - persistentBuffer(value: Buffer): unknown; - persistentTexture(value: Texture): unknown; + persistentBuffer(value: PersistentBuffer): unknown; + persistentTexture(value: PersistentTexture): unknown; framebuffer(value: Framebuffer): unknown; swapchain(value: RenderSwapchain): unknown; formatView(value: FormatView): unknown; @@ -601,7 +617,9 @@ export type ResourceGraphObject = ManagedResource | Framebuffer | RenderSwapchain | FormatView -| SubresourceView; +| SubresourceView +| PersistentBuffer +| PersistentTexture; //----------------------------------------------------------------- // Graph Concept @@ -1046,9 +1064,9 @@ export class ResourceGraph implements BidirectionalGraph case ResourceGraphValue.ManagedTexture: return visitor.managedTexture(vert._object as ManagedTexture); case ResourceGraphValue.PersistentBuffer: - return visitor.persistentBuffer(vert._object as Buffer); + return visitor.persistentBuffer(vert._object as PersistentBuffer); case ResourceGraphValue.PersistentTexture: - return visitor.persistentTexture(vert._object as Texture); + return visitor.persistentTexture(vert._object as PersistentTexture); case ResourceGraphValue.Framebuffer: return visitor.framebuffer(vert._object as Framebuffer); case ResourceGraphValue.Swapchain: @@ -1082,16 +1100,16 @@ export class ResourceGraph implements BidirectionalGraph throw Error('value id not match'); } } - getPersistentBuffer (v: number): Buffer { + getPersistentBuffer (v: number): PersistentBuffer { if (this._vertices[v]._id === ResourceGraphValue.PersistentBuffer) { - return this._vertices[v]._object as Buffer; + return this._vertices[v]._object as PersistentBuffer; } else { throw Error('value id not match'); } } - getPersistentTexture (v: number): Texture { + getPersistentTexture (v: number): PersistentTexture { if (this._vertices[v]._id === ResourceGraphValue.PersistentTexture) { - return this._vertices[v]._object as Texture; + return this._vertices[v]._object as PersistentTexture; } else { throw Error('value id not match'); } @@ -1145,16 +1163,16 @@ export class ResourceGraph implements BidirectionalGraph return null; } } - tryGetPersistentBuffer (v: number): Buffer | null { + tryGetPersistentBuffer (v: number): PersistentBuffer | null { if (this._vertices[v]._id === ResourceGraphValue.PersistentBuffer) { - return this._vertices[v]._object as Buffer; + return this._vertices[v]._object as PersistentBuffer; } else { return null; } } - tryGetPersistentTexture (v: number): Texture | null { + tryGetPersistentTexture (v: number): PersistentTexture | null { if (this._vertices[v]._id === ResourceGraphValue.PersistentTexture) { - return this._vertices[v]._object as Texture; + return this._vertices[v]._object as PersistentTexture; } else { return null; } diff --git a/cocos/rendering/custom/web-pipeline.ts b/cocos/rendering/custom/web-pipeline.ts index 316e90a2431..d322396155a 100644 --- a/cocos/rendering/custom/web-pipeline.ts +++ b/cocos/rendering/custom/web-pipeline.ts @@ -27,8 +27,8 @@ import { systemInfo } from 'pal/system-info'; import { DEBUG } from 'internal:constants'; import { Buffer, DescriptorSetLayout, Device, Feature, Format, FormatFeatureBit, Sampler, Swapchain, Texture, ClearFlagBit, DescriptorSet, deviceManager, Viewport, API, CommandBuffer, Type, SamplerInfo, Filter, Address, DescriptorSetInfo, LoadOp, StoreOp, ShaderStageFlagBit, BufferInfo, TextureInfo, TextureType, UniformBlock, ResolveMode, SampleCount, Color } from '../../gfx'; import { Mat4, Quat, toRadian, Vec2, Vec3, Vec4, assert, macro, cclegacy, IVec4Like, IMat4Like, IVec2Like, Color as CoreColor } from '../../core'; -import { AccessType, AttachmentType, CopyPair, LightInfo, LightingMode, MovePair, QueueHint, ResolvePair, ResourceDimension, ResourceFlags, ResourceResidency, SceneFlags, UpdateFrequency } from './types'; -import { ComputeView, RasterView, Blit, ClearView, ComputePass, CopyPass, Dispatch, ManagedBuffer, ManagedResource, MovePass, RasterPass, RasterSubpass, RenderData, RenderGraph, RenderGraphComponent, RenderGraphValue, RenderQueue, RenderSwapchain, ResourceDesc, ResourceGraph, ResourceGraphValue, ResourceStates, ResourceTraits, SceneData, Subpass } from './render-graph'; +import { AccessType, AttachmentType, CopyPair, LightInfo, LightingMode, MovePair, QueueHint, ResolvePair, ResourceDimension, ResourceFlags, ResourceResidency, SceneFlags, UpdateFrequency, UploadPair } from './types'; +import { ComputeView, RasterView, Blit, ClearView, ComputePass, CopyPass, Dispatch, ManagedBuffer, ManagedResource, MovePass, RasterPass, RasterSubpass, RenderData, RenderGraph, RenderGraphComponent, RenderGraphValue, RenderQueue, RenderSwapchain, ResourceDesc, ResourceGraph, ResourceGraphValue, ResourceStates, ResourceTraits, SceneData, Subpass, PersistentBuffer } from './render-graph'; import { ComputePassBuilder, ComputeQueueBuilder, ComputeSubpassBuilder, BasicPipeline, PipelineBuilder, RenderPassBuilder, RenderQueueBuilder, RenderSubpassBuilder, PipelineType, BasicRenderPassBuilder, PipelineCapabilities, BasicMultisampleRenderPassBuilder } from './pipeline'; import { PipelineSceneData } from '../pipeline-scene-data'; import { Model, Camera, ShadowType, CSMLevel, DirectionalLight, SpotLight, PCFType, Shadows, SphereLight, PointLight, RangedDirectionalLight } from '../../render-scene/scene'; @@ -198,7 +198,7 @@ export class WebSetter { value.fill(0); this._data.constants.set(num, value); } - this.setCurrConstant(block); + this.setCurrConstant(block, stage); return true; } public setMat4 (name: string, mat: Mat4, idx = 0): void { @@ -632,6 +632,13 @@ function setShadowUBOView (setter: WebSetter, camera: Camera | null, layout = 'd } } +function setComputeConstants (setter: WebSetter, layoutName: string): void { + const director = cclegacy.director; + const root = director.root; + const pipeline = root.pipeline as WebPipeline; + setter.addConstant('CCConst', layoutName); +} + function setCameraUBOValues ( setter: WebSetter, camera: Readonly | null, @@ -1334,7 +1341,7 @@ export class WebComputePassBuilder extends WebSetter implements ComputePassBuild throw new Error('Method not implemented.'); } addStorageBuffer (name: string, accessType: AccessType, slotName: string): void { - throw new Error('Method not implemented.'); + this._addComputeResource(name, accessType, slotName); } addStorageImage (name: string, accessType: AccessType, slotName: string): void { throw new Error('Method not implemented.'); @@ -1353,6 +1360,24 @@ export class WebComputePassBuilder extends WebSetter implements ComputePassBuild const queueID = this._renderGraph.addVertex(RenderGraphValue.Queue, queue, '', layoutName, data, false, this._vertID); return new WebComputeQueueBuilder(data, this._renderGraph, this._layoutGraph, queueID, queue, this._pipeline); } + + private _addComputeResource (name: string, accessType: AccessType, slotName: string): void { + const view = new ComputeView(slotName); + view.accessType = accessType; + if (DEBUG) { + assert(Boolean(view.name)); + assert(Boolean(name && this._resourceGraph.contains(name))); + const descriptorName = view.name; + const descriptorID = this._layoutGraph.attributeIndex.get(descriptorName); + assert(descriptorID !== undefined); + } + if (this._pass.computeViews.has(name)) { + this._pass.computeViews.get(name)?.push(view); + } else { + this._pass.computeViews.set(name, [view]); + } + } + private readonly _renderGraph: RenderGraph; private readonly _layoutGraph: LayoutGraphData; private readonly _resourceGraph: ResourceGraph; @@ -1593,6 +1618,30 @@ export class WebPipeline implements BasicPipeline { // TODO: implement resolve pass throw new Error('Method not implemented.'); } + + public addComputePass (passName: string): ComputePassBuilder { + const name = 'Compute'; + const pass = new ComputePass(); + + const data = new RenderData(); + const vertID = this._renderGraph!.addVertex(RenderGraphValue.Compute, pass, name, passName, data, false); + const result = new WebComputePassBuilder(data, this._renderGraph!, this._layoutGraph, this._resourceGraph, vertID, pass, this._pipelineSceneData); + setComputeConstants(result, passName); + initGlobalDescBinding(data, passName); + return result; + } + + public addUploadPass (uploadPairs: UploadPair[]): void { + const name = 'UploadPass'; + const pass = new CopyPass(); + for (const up of uploadPairs) { + pass.uploadPairs.push(up); + } + + const vertID = this._renderGraph!.addVertex(RenderGraphValue.Copy, pass, name, '', new RenderData(), false); + // const result = new WebCopyPassBuilder(this._renderGraph!, vertID, pass); + } + public addCopyPass (copyPairs: CopyPair[]): void { // const renderData = new RenderData(); // const vertID = this._renderGraph!.addVertex( @@ -1621,7 +1670,7 @@ export class WebPipeline implements BasicPipeline { let str = ''; str += `#define CC_DEVICE_SUPPORT_FLOAT_TEXTURE ${this._device.getFormatFeatures(Format.RGBA32F) & (FormatFeatureBit.RENDER_TARGET | FormatFeatureBit.SAMPLED_TEXTURE) ? 1 : 0}\n`; - str += `#define CC_ENABLE_CLUSTERED_LIGHT_CULLING ${clusterEnabled ? 1 : 0}\n`; + // str += `#define CC_ENABLE_CLUSTERED_LIGHT_CULLING ${clusterEnabled ? 1 : 0}\n`; // defined in material str += `#define CC_DEVICE_MAX_VERTEX_UNIFORM_VECTORS ${this._device.capabilities.maxVertexUniformVectors}\n`; str += `#define CC_DEVICE_MAX_FRAGMENT_UNIFORM_VECTORS ${this._device.capabilities.maxFragmentUniformVectors}\n`; str += `#define CC_DEVICE_CAN_BENEFIT_FROM_INPUT_ATTACHMENT ${this._device.hasFeature(Feature.INPUT_ATTACHMENT_BENEFIT) ? 1 : 0}\n`; @@ -1851,11 +1900,22 @@ export class WebPipeline implements BasicPipeline { desc.format = format; desc.flags = ResourceFlags.STORAGE; + if (residency === ResourceResidency.PERSISTENT) { + return this._resourceGraph.addVertex( + ResourceGraphValue.PersistentBuffer, + new PersistentBuffer(), + name, + desc, + new ResourceTraits(ResourceResidency.PERSISTENT), + new ResourceStates(), + new SamplerInfo(), + ); + } + return this._resourceGraph.addVertex( ResourceGraphValue.ManagedBuffer, new ManagedBuffer(), name, - desc, new ResourceTraits(residency), new ResourceStates(), diff --git a/cocos/rendering/custom/web-program-library.ts b/cocos/rendering/custom/web-program-library.ts index 35b9f691675..ba0e5e7ee51 100644 --- a/cocos/rendering/custom/web-program-library.ts +++ b/cocos/rendering/custom/web-program-library.ts @@ -24,7 +24,7 @@ /* eslint-disable max-len */ 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 } from '../../gfx'; +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'; @@ -49,8 +49,89 @@ function makeProgramInfo (effectName: string, shader: EffectAsset.IShaderInfo): 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; @@ -74,7 +155,9 @@ function overwriteProgramBlockInfo (shaderInfo: ShaderInfo, programInfo: IProgra function populateGroupedShaderInfo ( layout: DescriptorSetLayoutData, descriptorInfo: EffectAsset.IDescriptorInfo, - set: number, shaderInfo: ShaderInfo, blockSizes: number[], + set: number, + shaderInfo: ShaderInfo, + blockSizes: number[], ): void { for (const descriptorBlock of layout.descriptorBlocks) { const visibility = descriptorBlock.visibility; @@ -88,9 +171,13 @@ function populateGroupedShaderInfo ( } blockSizes.push(getSize(block.members)); shaderInfo.blocks.push( - new UniformBlock(set, binding, block.name, + 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 + 1, + ), // count is always 1 for UniformBlock ); ++binding; } @@ -103,9 +190,7 @@ function populateGroupedShaderInfo ( if (tex.stageFlags !== visibility) { continue; } - shaderInfo.samplerTextures.push(new UniformSamplerTexture( - set, binding, tex.name, tex.type, tex.count, - )); + shaderInfo.samplerTextures.push(new UniformSamplerTexture(set, binding, tex.name, tex.type, tex.count)); ++binding; } break; @@ -114,9 +199,7 @@ function populateGroupedShaderInfo ( if (sampler.stageFlags !== visibility) { continue; } - shaderInfo.samplers.push(new UniformSampler( - set, binding, sampler.name, sampler.count, - )); + shaderInfo.samplers.push(new UniformSampler(set, binding, sampler.name, sampler.count)); ++binding; } break; @@ -125,9 +208,7 @@ function populateGroupedShaderInfo ( if (texture.stageFlags !== visibility) { continue; } - shaderInfo.textures.push(new UniformTexture( - set, binding, texture.name, texture.type, texture.count, - )); + shaderInfo.textures.push(new UniformTexture(set, binding, texture.name, texture.type, texture.count)); ++binding; } break; @@ -136,9 +217,7 @@ function populateGroupedShaderInfo ( if (buffer.stageFlags !== visibility) { continue; } - shaderInfo.buffers.push(new UniformStorageBuffer( - set, binding, buffer.name, 1, buffer.memoryAccess, - )); // effect compiler guarantees buffer count = 1 + shaderInfo.buffers.push(new UniformStorageBuffer(set, binding, buffer.name, 1, buffer.memoryAccess)); // effect compiler guarantees buffer count = 1 ++binding; } break; @@ -150,9 +229,7 @@ function populateGroupedShaderInfo ( if (image.stageFlags !== visibility) { continue; } - shaderInfo.images.push(new UniformStorageImage( - set, binding, image.name, image.type, image.count, image.memoryAccess, - )); + shaderInfo.images.push(new UniformStorageImage(set, binding, image.name, image.type, image.count, image.memoryAccess)); ++binding; } break; @@ -161,9 +238,7 @@ function populateGroupedShaderInfo ( if (subpassInput.stageFlags !== visibility) { continue; } - shaderInfo.subpassInputs.push(new UniformInputAttachment( - set, subpassInput.binding, subpassInput.name, subpassInput.count, - )); + shaderInfo.subpassInputs.push(new UniformInputAttachment(set, subpassInput.binding, subpassInput.name, subpassInput.count)); ++binding; } break; @@ -173,9 +248,13 @@ function populateGroupedShaderInfo ( } // add merged descriptor to gfx.ShaderInfo -function populateMergedShaderInfo (valueNames: string[], +function populateMergedShaderInfo ( + valueNames: string[], layout: DescriptorSetLayoutData, - set: number, shaderInfo: ShaderInfo, blockSizes: number[]): void { + set: number, + shaderInfo: ShaderInfo, + blockSizes: number[], +): void { for (const descriptorBlock of layout.descriptorBlocks) { let binding = descriptorBlock.offset; switch (descriptorBlock.type) { @@ -188,9 +267,13 @@ function populateMergedShaderInfo (valueNames: string[], } blockSizes.push(getSize(uniformBlock.members)); shaderInfo.blocks.push( - new UniformBlock(set, binding, valueNames[block.descriptorID], + 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 + 1, + ), // count is always 1 for UniformBlock ); ++binding; } @@ -203,32 +286,29 @@ function populateMergedShaderInfo (valueNames: string[], 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, - )); + 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, - )); + 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, - )); + 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, + set, + binding, + valueNames[buffer.descriptorID], + 1, MemoryAccessBit.READ_WRITE/*buffer.memoryAccess*/, )); // effect compiler guarantees buffer count = 1 ++binding; @@ -240,7 +320,11 @@ function populateMergedShaderInfo (valueNames: string[], case DescriptorTypeOrder.STORAGE_IMAGE: for (const image of descriptorBlock.descriptors) { shaderInfo.images.push(new UniformStorageImage( - set, binding, valueNames[image.descriptorID], image.type, image.count, + set, + binding, + valueNames[image.descriptorID], + image.type, + image.count, MemoryAccessBit.READ_WRITE/*image.memoryAccess*/, )); ++binding; @@ -248,9 +332,7 @@ function populateMergedShaderInfo (valueNames: string[], break; case DescriptorTypeOrder.INPUT_ATTACHMENT: for (const subpassInput of descriptorBlock.descriptors) { - shaderInfo.subpassInputs.push(new UniformInputAttachment( - set, binding, valueNames[subpassInput.descriptorID], subpassInput.count, - )); + shaderInfo.subpassInputs.push(new UniformInputAttachment(set, binding, valueNames[subpassInput.descriptorID], subpassInput.count)); ++binding; } break; @@ -262,56 +344,53 @@ function populateMergedShaderInfo (valueNames: string[], // add descriptor from effect to gfx.ShaderInfo function populateShaderInfo ( descriptorInfo: EffectAsset.IDescriptorInfo, - set: number, shaderInfo: ShaderInfo, blockSizes: number[], + 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 + 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, - )); + 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, - )); + 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, - )); + 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 + 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, - )); + 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, - )); + 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[], + source: IDescriptorSetLayoutInfo, + shaderInfo: ShaderInfo, + blockSizes: number[], ): void { const set = _setIndex[UpdateFrequency.PER_INSTANCE]; for (let i = 0; i < target.blocks.length; i++) { @@ -323,8 +402,13 @@ function populateLocalShaderInfo ( 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 + 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]; @@ -334,9 +418,7 @@ function populateLocalShaderInfo ( console.warn(`builtin samplerTexture '${samplerTexture.name}' not available!`); continue; } - shaderInfo.samplerTextures.push(new UniformSamplerTexture( - set, binding.binding, samplerTexture.name, samplerTexture.type, samplerTexture.count, - )); + shaderInfo.samplerTextures.push(new UniformSamplerTexture(set, binding.binding, samplerTexture.name, samplerTexture.type, samplerTexture.count)); } } @@ -362,21 +444,25 @@ function getIDescriptorSetLayoutInfoSamplerTextureCapacity (info: IDescriptorSet return capacity; } -function setFlattenedUniformBlockBinding (setOffsets: number[], - descriptors: UniformBlock[]): void { +function setFlattenedUniformBlockBinding ( + setOffsets: number[], + descriptors: UniformBlock[], +): void { for (const d of descriptors) { d.flattened = setOffsets[d.set] + d.binding; } } -function setFlattenedSamplerTextureBinding (setOffsets: number[], +function setFlattenedSamplerTextureBinding ( + setOffsets: number[], uniformBlockCapacities: number[], descriptors: UniformSamplerTexture[] | UniformSampler[] | UniformTexture[] | UniformStorageBuffer[] | UniformStorageImage[] - | UniformInputAttachment[]): void { + | UniformInputAttachment[], +): void { for (const d of descriptors) { d.flattened = setOffsets[d.set] + d.binding - uniformBlockCapacities[d.set]; } @@ -463,16 +549,26 @@ function makeShaderInfo ( 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); + 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); + populateMergedShaderInfo( + lg.valueNames, + phaseLayout.descriptorSetLayoutData, + _setIndex[UpdateFrequency.PER_PHASE], + shaderInfo, + blockSizes, + ); } } { // batch @@ -481,16 +577,27 @@ function makeShaderInfo ( 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); + 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); + populateGroupedShaderInfo( + batchLayout.descriptorSetLayoutData, + batchInfo, + + _setIndex[UpdateFrequency.PER_BATCH], + shaderInfo, + + blockSizes, + ); } } } @@ -504,17 +611,28 @@ function makeShaderInfo ( 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); + 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); + populateGroupedShaderInfo( + instanceLayout.descriptorSetLayoutData, + instanceInfo, + + _setIndex[UpdateFrequency.PER_INSTANCE], + shaderInfo, + + blockSizes, + ); } } } @@ -554,8 +672,10 @@ function getDescriptorNameAndType (source: IDescriptorSetLayoutInfo, binding: nu } // make DescriptorSetLayoutData from local descriptor set info -function makeLocalDescriptorSetLayoutData (lg: LayoutGraphData, - source: IDescriptorSetLayoutInfo): DescriptorSetLayoutData { +function makeLocalDescriptorSetLayoutData ( + lg: LayoutGraphData, + source: IDescriptorSetLayoutInfo, +): DescriptorSetLayoutData { const data = new DescriptorSetLayoutData(); for (const b of source.bindings) { const [name, type] = getDescriptorNameAndType(source, b.binding); @@ -582,24 +702,32 @@ function makeLocalDescriptorSetLayoutData (lg: LayoutGraphData, function buildProgramData ( programName: string, srcShaderInfo: EffectAsset.IShaderInfo, - lg: LayoutGraphData, phase: RenderPhaseData, programData: ShaderProgramData, + lg: LayoutGraphData, + phase: RenderPhaseData, + programData: ShaderProgramData, fixedLocal: boolean, ): void { { - const perBatch = makeDescriptorSetLayoutData(lg, + const perBatch = makeDescriptorSetLayoutData( + lg, UpdateFrequency.PER_BATCH, _setIndex[UpdateFrequency.PER_BATCH], - srcShaderInfo.descriptors[UpdateFrequency.PER_BATCH]); + srcShaderInfo.descriptors[UpdateFrequency.PER_BATCH], + ); const setData = new DescriptorSetData(perBatch); - initializeDescriptorSetLayoutInfo(setData.descriptorSetLayoutData, - setData.descriptorSetLayoutInfo); + 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); + initializeDescriptorSetLayoutInfo( + setData.descriptorSetLayoutData, + setData.descriptorSetLayoutInfo, + ); if (localDescriptorSetLayout.bindings.length !== setData.descriptorSetLayoutInfo.bindings.length) { console.error('local descriptor set layout inconsistent'); } else { @@ -616,13 +744,17 @@ function buildProgramData ( } programData.layout.descriptorSets.set(UpdateFrequency.PER_INSTANCE, setData); } else { - const perInstance = makeDescriptorSetLayoutData(lg, + const perInstance = makeDescriptorSetLayoutData( + lg, UpdateFrequency.PER_INSTANCE, _setIndex[UpdateFrequency.PER_INSTANCE], - srcShaderInfo.descriptors[UpdateFrequency.PER_INSTANCE]); + srcShaderInfo.descriptors[UpdateFrequency.PER_INSTANCE], + ); const setData = new DescriptorSetData(perInstance); - initializeDescriptorSetLayoutInfo(setData.descriptorSetLayoutData, - setData.descriptorSetLayoutInfo); + initializeDescriptorSetLayoutInfo( + setData.descriptorSetLayoutData, + setData.descriptorSetLayoutInfo, + ); programData.layout.descriptorSets.set(UpdateFrequency.PER_INSTANCE, setData); } const shaderID = phase.shaderPrograms.length; @@ -631,9 +763,13 @@ function buildProgramData ( } // get or create PerProgram gfx.DescriptorSetLayout -function getOrCreateProgramDescriptorSetLayout (device: Device, - lg: LayoutGraphData, phaseID: number, - programName: string, rate: UpdateFrequency): 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); @@ -654,9 +790,13 @@ function getOrCreateProgramDescriptorSetLayout (device: Device, } // get PerProgram gfx.DescriptorSetLayout -function getProgramDescriptorSetLayout (device: Device, - lg: LayoutGraphData, phaseID: number, - programName: string, rate: UpdateFrequency): DescriptorSetLayout | null { +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); @@ -677,8 +817,11 @@ function getProgramDescriptorSetLayout (device: Device, } // find shader program in LayoutGraphData -function getEffectShader (lg: LayoutGraphData, effect: EffectAsset, - pass: EffectAsset.IPassInfo): [number, number, number, EffectAsset.IShaderInfo | null, number] { +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) { @@ -766,8 +909,14 @@ export class WebProgramLibrary implements ProgramLibrary { } // shaderInfo and blockSizes - const [shaderInfo, blockSizes] = makeShaderInfo(lg, passLayout, phaseLayout, - srcShaderInfo, programData, this.fixedLocal); + const [shaderInfo, blockSizes] = makeShaderInfo( + lg, + passLayout, + phaseLayout, + srcShaderInfo, + programData, + this.fixedLocal, + ); // overwrite programInfo overwriteProgramBlockInfo(shaderInfo, programInfo); @@ -777,9 +926,7 @@ export class WebProgramLibrary implements ProgramLibrary { // attributes const attributes = new Array(); for (const attr of programInfo.attributes) { - attributes.push(new Attribute( - attr.name, attr.format, attr.isNormalized, 0, attr.isInstanced, attr.location, - )); + 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); @@ -805,9 +952,7 @@ export class WebProgramLibrary implements ProgramLibrary { } const defines = getCombinationDefines(combination); defines.forEach( - (defines) => this.getProgramVariant( - device, phaseID, programName, defines, - ), + (defines) => this.getProgramVariant(device, phaseID, programName, defines), ); } } @@ -887,8 +1032,14 @@ export class WebProgramLibrary implements ProgramLibrary { // prepare shader info const shaderInfo = info.shaderInfo; - shaderInfo.stages[0].source = prefix + src.vert; - shaderInfo.stages[1].source = prefix + src.frag; + 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); @@ -909,8 +1060,13 @@ export class WebProgramLibrary implements ProgramLibrary { 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); + return getOrCreateProgramDescriptorSetLayout( + device, + this.layoutGraph, + phaseID, + programName, + UpdateFrequency.PER_BATCH, + ); } // get local descriptor set layout getLocalDescriptorSetLayout (device: Device, phaseID: number, programName: string): DescriptorSetLayout { @@ -919,8 +1075,13 @@ export class WebProgramLibrary implements ProgramLibrary { 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); + return getOrCreateProgramDescriptorSetLayout( + device, + this.layoutGraph, + phaseID, + programName, + UpdateFrequency.PER_INSTANCE, + ); } // get related uniform block sizes getBlockSizes (phaseID: number, programName: string): number[] { diff --git a/cocos/webgpu/instantiated.ts b/cocos/webgpu/instantiated.ts index baef8832445..92d548d1fae 100644 --- a/cocos/webgpu/instantiated.ts +++ b/cocos/webgpu/instantiated.ts @@ -30,15 +30,22 @@ import { WASM_SUPPORT_MODE, WEBGPU } from 'internal:constants'; import webgpuUrl from 'external:emscripten/webgpu/webgpu_wasm.wasm'; import glslangUrl from 'external:emscripten/webgpu/glslang.wasm'; +import twgslUrl from 'external:emscripten/webgpu/twgsl.wasm' + import wasmDevice from 'external:emscripten/webgpu/webgpu_wasm.js'; import glslangLoader from 'external:emscripten/webgpu/glslang.js'; +import twgslLoader from 'external:emscripten/webgpu/twgsl.js' import { legacyCC } from '../core/global-exports'; import { WebAssemblySupportMode } from '../misc/webassembly-support'; -export const glslalgWasmModule: any = { +export const glslangWasmModule: any = { glslang: null, }; +export const twgslModule: any = { + twgsl: null, +}; + export const gfx: any = legacyCC.gfx = { wasmBinary: null, nativeDevice: null, @@ -54,7 +61,10 @@ export const promiseForWebGPUInstantiation = (() => { // TODO: we need to support AsmJS fallback option return Promise.all([ glslangLoader(new URL(glslangUrl, import.meta.url).href).then((res) => { - glslalgWasmModule.glslang = res; + glslangWasmModule.glslang = res; + }), + twgslLoader(new URL(twgslUrl, import.meta.url).href).then((data) => { + twgslModule.twgsl = data; }), new Promise((resolve) => { fetch(new URL(webgpuUrl, import.meta.url).href).then((response) => { diff --git a/editor/assets/chunks/common/math/coordinates.chunk b/editor/assets/chunks/common/math/coordinates.chunk index c994cfc699d..1628ab0bb9e 100644 --- a/editor/assets/chunks/common/math/coordinates.chunk +++ b/editor/assets/chunks/common/math/coordinates.chunk @@ -2,7 +2,7 @@ // cc_cameraPos.w is flipNDCSign #pragma define CC_HANDLE_NDC_SAMPLE_FLIP(uv, flipNDCSign) uv = flipNDCSign == 1.0 ? vec2(uv.x, 1.0 - uv.y) : uv -#ifdef CC_USE_METAL +#if defined(CC_USE_METAL) || defined(CC_USE_WGPU) #define CC_HANDLE_SAMPLE_NDC_FLIP_STATIC(y) y = -y #else #define CC_HANDLE_SAMPLE_NDC_FLIP_STATIC(y) diff --git a/editor/assets/chunks/legacy/shading-cluster-additive.chunk b/editor/assets/chunks/legacy/shading-cluster-additive.chunk index 0bf35248e78..ba4c16be3a7 100644 --- a/editor/assets/chunks/legacy/shading-cluster-additive.chunk +++ b/editor/assets/chunks/legacy/shading-cluster-additive.chunk @@ -2,7 +2,7 @@ #pragma define CLUSTERS_X 16u #pragma define CLUSTERS_Y 8u #pragma define CLUSTERS_Z 24u -#pragma define MAX_LIGHTS_PER_CLUSTER 100u +#pragma define MAX_LIGHTS_PER_CLUSTER 200u #pragma rate b_ccLightsBuffer pass #pragma glBinding(0) diff --git a/editor/assets/effects/pipeline/cluster-culling.effect b/editor/assets/effects/pipeline/cluster-culling.effect index 9c418b01383..510d4ad9bfe 100644 --- a/editor/assets/effects/pipeline/cluster-culling.effect +++ b/editor/assets/effects/pipeline/cluster-culling.effect @@ -88,10 +88,18 @@ CCProgram cluster-main %{ vec3 center = (cluster.minBounds + cluster.maxBounds) * 0.5; float sphereRadius = sqrt(dot(halfExtents, halfExtents)); light.cc_lightDir = ((cc_matView) * (vec4(light.cc_lightDir.xyz, 1.0))); - light.cc_lightDir.xyz = normalize((light.cc_lightDir - ((cc_matView) * (vec4(0,0,0, 1.0)))).xyz).xyz; + light.cc_lightDir.xyz = (light.cc_lightDir - ((cc_matView) * (vec4(0,0,0, 1.0)))).xyz; + if (length(light.cc_lightDir.xyz) > 0.1) { + light.cc_lightDir.xyz = normalize(light.cc_lightDir.xyz); + } vec3 v = center - light.cc_lightPos.xyz; float lenSq = dot(v, v); float v1Len = dot(v, light.cc_lightDir.xyz); + if(light.cc_lightDir.w == 1.0) { + v1Len = sqrt(lenSq); + return (v1Len <= sphereRadius + light.cc_lightSizeRangeAngle.y); + } + float cosAngle = light.cc_lightSizeRangeAngle.z; float sinAngle = sqrt(1.0 - cosAngle * cosAngle); float distanceClosestPoint = cosAngle * sqrt(lenSq - v1Len * v1Len) - v1Len * sinAngle; @@ -108,7 +116,7 @@ CCProgram cluster-main %{ layout(local_size_x = LOCAL_SIZE_X, local_size_y = LOCAL_SIZE_Y, local_size_z = LOCAL_SIZE_Z) in; void main() { - uint visibleLights[100]; + uint visibleLights[200]; uint visibleCount = 0u; uint clusterIndex = gl_GlobalInvocationID.z * gl_WorkGroupSize.x * gl_WorkGroupSize.y + gl_GlobalInvocationID.y * gl_WorkGroupSize.x + @@ -127,7 +135,7 @@ CCProgram cluster-main %{ } barrier(); for (uint i = 0u; i < batchSize; i++) { - if (visibleCount < 100u && ccLightIntersectsCluster(lights[i], cluster)) { + if (visibleCount < 200u && ccLightIntersectsCluster(lights[i], cluster)) { visibleLights[visibleCount] = lightOffset + i; visibleCount++; } diff --git a/native/cocos/renderer/gfx-wgpu/CMakeLists.txt b/native/cocos/renderer/gfx-wgpu/CMakeLists.txt index f828214a099..0c1b3e6a052 100644 --- a/native/cocos/renderer/gfx-wgpu/CMakeLists.txt +++ b/native/cocos/renderer/gfx-wgpu/CMakeLists.txt @@ -128,7 +128,7 @@ set(EMS_LINK_FLAGS ) if(CMAKE_BUILD_TYPE STREQUAL "Debug") - string(APPEND EMS_LINK_FLAGS " -s ASSERTIONS=2") + string(APPEND EMS_LINK_FLAGS " -g -s ASSERTIONS=2") endif() set_target_properties(${APP_NAME}_wasm PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${ENGINE_ROOT_DIR}/external/emscripten/webgpu) diff --git a/native/cocos/renderer/gfx-wgpu/WGPUExports.h b/native/cocos/renderer/gfx-wgpu/WGPUExports.h index a9547026a0f..6bfdc38f290 100644 --- a/native/cocos/renderer/gfx-wgpu/WGPUExports.h +++ b/native/cocos/renderer/gfx-wgpu/WGPUExports.h @@ -305,8 +305,7 @@ EMSCRIPTEN_BINDINGS(WEBGPU_DEVICE_WASM_EXPORT) { .property("layout", &DescriptorSet::getLayout) .property("objectID", select_overload(&DescriptorSet::getObjectID)); class_>("CCWGPUDescriptorSet") - // .property("gpuDescriptorSet", &CCWGPUDescriptorSet::gpuDescriptors) - .function("updateFrom", &CCWGPUDescriptorSet::setGpuDescriptors) + .property("gpuDescriptorSet", &CCWGPUDescriptorSet::gpuDescriptors, &CCWGPUDescriptorSet::setGpuDescriptors) .constructor<>(); class_("PipelineLayout") diff --git a/native/cocos/renderer/pipeline/ClusterLightCulling.h b/native/cocos/renderer/pipeline/ClusterLightCulling.h index bfff4b5ad2b..0c01dbd51b6 100644 --- a/native/cocos/renderer/pipeline/ClusterLightCulling.h +++ b/native/cocos/renderer/pipeline/ClusterLightCulling.h @@ -54,7 +54,7 @@ class ClusterLightCulling { static constexpr uint32_t CLUSTER_COUNT = CLUSTERS_X * CLUSTERS_Y * CLUSTERS_Z; - static constexpr uint32_t MAX_LIGHTS_PER_CLUSTER = 100; + static constexpr uint32_t MAX_LIGHTS_PER_CLUSTER = 200; static constexpr uint32_t MAX_LIGHTS_GLOBAL = 1000;