Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reuse webgpu resources #18208

Open
wants to merge 36 commits into
base: v3.8.6
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
1af5121
Reuse webgpu layout
GengineJS Jan 14, 2025
f19da2e
ADAPTS the effect of new binding
GengineJS Jan 16, 2025
2ee3be2
update
GengineJS Jan 16, 2025
3d94289
Merge branch 'v3.8.6' of github.com:cocos/cocos-engine into v3.8.6-0114
GengineJS Jan 16, 2025
3be250f
update
GengineJS Jan 16, 2025
daf906f
Merge branch 'v3.8.6' of github.com:cocos/cocos-engine into v3.8.6-0114
GengineJS Jan 16, 2025
f2cf144
Merge branch 'v3.8.6' of github.com:cocos/cocos-engine into v3.8.6-0114
GengineJS Jan 17, 2025
19039cc
New pipeline opening screen
GengineJS Jan 17, 2025
71028c4
update
GengineJS Jan 17, 2025
9c6c01e
add missing info
star-e Jan 17, 2025
381104e
Merge pull request #2 from star-e/v3.8.6-0114-hyde
GengineJS Jan 17, 2025
c905efb
Fixed 2d texture mapping cubemap error
GengineJS Jan 17, 2025
ebc4fe8
update
GengineJS Jan 20, 2025
46fc002
Fix the new pipeline referencing the meaningless globalDsManager object.
GengineJS Jan 20, 2025
47c10eb
change storage image to sampler texture
star-e Jan 20, 2025
f3c21de
Merge pull request #3 from star-e/v3.8.6-0114-cube
GengineJS Jan 20, 2025
6af7930
Merge branch 'v3.8.6-0114' of github.com:GengineJS/engine into v3.8.6…
GengineJS Jan 20, 2025
0227211
Fixed the issue where the testlist was not displayed.
GengineJS Jan 22, 2025
41d7dc4
fixed buffer storage usage warning
GengineJS Feb 7, 2025
464d3ce
Merge branch 'v3.8.6' of github.com:cocos/cocos-engine into v3.8.6-0114
GengineJS Feb 7, 2025
59dbad8
Merge branch 'v3.8.6' of github.com:cocos/cocos-engine into v3.8.6-0114
GengineJS Feb 10, 2025
5af5597
fixed storage buffer usage warning
GengineJS Feb 10, 2025
e58edbd
update default storage buffer logic
GengineJS Feb 10, 2025
d2c67e8
Merge branch 'v3.8.6' of github.com:cocos/cocos-engine into v3.8.6-0114
GengineJS Feb 12, 2025
382fa03
fixed shadowmap
GengineJS Feb 14, 2025
112f3ea
Merge branch 'v3.8.6' of github.com:cocos/cocos-engine into v3.8.6-0114
GengineJS Feb 14, 2025
b58aad3
fixed texture not copydst
GengineJS Feb 19, 2025
006ca59
Merge branch 'v3.8.6' of github.com:cocos/cocos-engine into v3.8.6-0114
GengineJS Feb 19, 2025
4fc304e
Fix webgpu import incompatibility problem
GengineJS Feb 20, 2025
b25bc55
Support dynamic import for WebGPU wasm
wdzhangchukong Feb 24, 2025
53c7ddb
Merge pull request #4 from wdzhangchukong/v3.8.6-0114
GengineJS Feb 24, 2025
e75d92f
Fix the problem of dynamic buffer offset error.
GengineJS Feb 25, 2025
60f5d04
Merge branch 'v3.8.6' of github.com:cocos/cocos-engine into v3.8.6-0114
GengineJS Feb 25, 2025
ba9df78
Merge branch 'v3.8.6-0114' of github.com:GengineJS/engine into v3.8.6…
GengineJS Feb 25, 2025
6fa7f75
update
GengineJS Feb 26, 2025
cc4917f
Merge branch 'v3.8.6' of github.com:cocos/cocos-engine into v3.8.6-0114
GengineJS Feb 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions cocos/game/splash-screen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -580,7 +580,6 @@ export class SplashScreen {
);

cmdBuff.bindPipelineState(bgPso);
cmdBuff.bindDescriptorSet(SetIndex.GLOBAL, pipeline.descriptorSet);
cmdBuff.bindDescriptorSet(SetIndex.MATERIAL, bgPass.descriptorSet);
cmdBuff.bindInputAssembler(quadAssmebler);
cmdBuff.draw(quadAssmebler);
Expand All @@ -597,7 +596,6 @@ export class SplashScreen {
);

cmdBuff.bindPipelineState(logoPso);
cmdBuff.bindDescriptorSet(SetIndex.GLOBAL, pipeline.descriptorSet);
cmdBuff.bindDescriptorSet(SetIndex.MATERIAL, logoPass.descriptorSet);
cmdBuff.bindInputAssembler(quadAssmebler);
cmdBuff.draw(quadAssmebler);
Expand All @@ -614,7 +612,6 @@ export class SplashScreen {
);

cmdBuff.bindPipelineState(watermarkPso);
cmdBuff.bindDescriptorSet(SetIndex.GLOBAL, pipeline.descriptorSet);
cmdBuff.bindDescriptorSet(SetIndex.MATERIAL, wartermarkPass.descriptorSet);
cmdBuff.bindInputAssembler(quadAssmebler);
cmdBuff.draw(quadAssmebler);
Expand Down
2 changes: 2 additions & 0 deletions cocos/gfx/base/define.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2255,6 +2255,8 @@ export const DESCRIPTOR_SAMPLER_TYPE = DescriptorType.SAMPLER_TEXTURE | Descript

export const DESCRIPTOR_DYNAMIC_TYPE = DescriptorType.DYNAMIC_STORAGE_BUFFER | DescriptorType.DYNAMIC_UNIFORM_BUFFER;

export const DESCRIPTOR_STORAGE_BUFFER_TYPE = DescriptorType.STORAGE_BUFFER | DescriptorType.DYNAMIC_STORAGE_BUFFER;

export const DRAW_INFO_SIZE = 28;

export type BufferSource = ArrayBuffer | IndirectBuffer;
Expand Down
39 changes: 39 additions & 0 deletions cocos/gfx/webgpu/define.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
THE SOFTWARE.
*/

import { BufferFlagBit, BufferInfo, BufferUsageBit, MemoryUsageBit } from '../base/define';
import { DescriptorSet } from '../base/descriptor-set';
import { DescriptorSetLayout } from '../base/descriptor-set-layout';
import { WebGPUBuffer } from './webgpu-buffer';
Expand Down Expand Up @@ -58,16 +59,54 @@ export function hashCombineStr (str: string, currHash: number): number {
return hashCombine(hash, currHash);
}

interface WebGPU {
glslang: any;
twgsl: any;
}

export const webGPU: WebGPU = {
glslang: undefined,
twgsl: undefined,
};
function overrideClass (wasm): void {
if ('compileGLSL' in wasm) {
webGPU.glslang = wasm;
} else if ('convertSpirV2WGSL' in wasm) {
webGPU.twgsl = wasm;
}
}

export function overrideWebGPUDefine (wasm): void {
overrideClass(wasm);
}

export class DefaultResources {
// hash, targetResource
buffersDescLayout: Map<number, WebGPUBuffer> = new Map<number, WebGPUBuffer>();
texturesDescLayout: Map<number, WebGPUTexture> = new Map<number, WebGPUTexture>();
samplersDescLayout: Map<number, WebGPUSampler> = new Map<number, WebGPUSampler>();
buffer!: WebGPUBuffer;
storageBuffers: WebGPUBuffer[] = [];
texture!: WebGPUTexture;
cubeTexture!: WebGPUTexture;
sampler!: WebGPUSampler;
setLayout!: DescriptorSetLayout;
descSet!: DescriptorSet;
getStorageBuffer (idx: number): WebGPUBuffer {
if (this.storageBuffers[idx]) {
return this.storageBuffers[idx];
}
const bufferInfo = new BufferInfo(
BufferUsageBit.STORAGE,
MemoryUsageBit.DEVICE,
16,
16, // in bytes
BufferFlagBit.NONE,
);
const defaultBuff = WebGPUDeviceManager.instance.createBuffer(bufferInfo) as WebGPUBuffer;
this.storageBuffers[idx] = defaultBuff;
return defaultBuff;
}
}

export function isBound (binds: number[], compares: number[]): boolean {
Expand Down
127 changes: 127 additions & 0 deletions cocos/gfx/webgpu/instantiated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
Copyright (c) 2023 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

import { fetchBuffer, ensureWasmModuleReady, fetchUrl } from 'pal/wasm';
import { NATIVE_CODE_BUNDLE_MODE } from 'internal:constants';
import { error, sys } from '../../core';
import { NativeCodeBundleMode } from '../../misc/webassembly-support';
import { WebGPUWasm } from './webgpu-core';
import { overrideWebGPUDefine } from './define';
// import { overrideSpineDefine } from './spine-define';

const PAGESIZE = 65536; // 64KiB

// How many pages of the wasm memory
// TODO: let this can be canfiguable by user.
const PAGECOUNT = 32 * 16;

// How mush memory size of the wasm memory
const MEMORYSIZE = PAGESIZE * PAGECOUNT; // 32 MiB

let wasmInstance: WebGPUWasm.instance = null!;
const registerList: any[] = [];

function initWasm (wasmFactory, wasmUrl: string): Promise<void> {
return new Promise<void>((resolve, reject) => {
const errorMessage = (err: any): string => `[WebGPU]: WebGPU wasm load failed: ${err}`;
// eslint-disable-next-line @typescript-eslint/no-floating-promises
fetchUrl(wasmUrl).then((currUrl) => {
wasmFactory(currUrl).then((Instance: any) => {
wasmInstance = Instance;
registerList.forEach((cb) => {
cb(wasmInstance);
});
}).then(resolve).catch((err: any) => reject(errorMessage(err)));
});
});
}

function initAsmJS (asmFactory, asmJsMemUrl: string): Promise<void> {
return new Promise<void>((resolve, reject) => {
fetchBuffer(asmJsMemUrl).then((arrayBuffer) => {
const wasmMemory: any = {};
wasmMemory.buffer = new ArrayBuffer(MEMORYSIZE);
const module = {
wasmMemory,
memoryInitializerRequest: {
response: arrayBuffer,
status: 200,
} as Partial<XMLHttpRequest>,
};
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return asmFactory(module).then((instance: any) => {
wasmInstance = instance;
registerList.forEach((cb) => {
cb(wasmInstance);
});
});
}).then(resolve).catch(reject);
});
}

function shouldUseWasmModule (): boolean {
if (NATIVE_CODE_BUNDLE_MODE === (NativeCodeBundleMode.BOTH as number)) {
return sys.hasFeature(sys.Feature.WASM);
} else if (NATIVE_CODE_BUNDLE_MODE === (NativeCodeBundleMode.WASM as number)) {
return true;
} else {
return false;
}
}

export async function waitForWebGPUWasmInstantiation (): Promise<void> {
const errorReport = (msg: any): void => {
error('Error during WebGPU Wasm instantiation:', msg);
};
try {
await ensureWasmModuleReady();
if (shouldUseWasmModule()) {
const [
glslModule,
glslWasmModule,
twgslModule,
twgslWasmModule,
] = await Promise.all([
import('external:emscripten/webgpu/glslang.js'),
import('external:emscripten/webgpu/glslang.wasm'),
import('external:emscripten/webgpu/twgsl.js'),
import('external:emscripten/webgpu/twgsl.wasm'),
]);
const glslFactory = glslModule.default;
const glslWasmUrl = glslWasmModule.default;
const twgslFactory = twgslModule.default;
const twgslWasmUrl = twgslWasmModule.default;
await initWasm(glslFactory, glslWasmUrl);
await initWasm(twgslFactory, twgslWasmUrl);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could load glsl and twgsl at the same time. They don't have any dependency, right ?
If so, we could use Promise.all to improve the loading.

await Promise.all([ initWasm(glslFactory, glslWasmUrl), initWasm(twgslFactory, twgslWasmUrl) ]);

} else {
throw new Error('Wasm module is not supported in this environment.');
}
} catch (error) {
errorReport(error);
}
}

registerList.push(overrideWebGPUDefine);

export const WEBGPU_WASM = 1;
85 changes: 33 additions & 52 deletions cocos/gfx/webgpu/webgpu-command-buffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import {
Viewport,
Filter,
TextureBlit,
ShaderStageFlagBit,
DescriptorSetInfo,
} from '../base/define';
import { Framebuffer } from '../base/framebuffer';
import { InputAssembler } from '../base/input-assembler';
Expand Down Expand Up @@ -70,10 +70,9 @@ import { INT_MAX } from '../../core/math/bits';
import { GeneralBarrier } from '../base/states/general-barrier';
import { TextureBarrier } from '../base/states/texture-barrier';
import { BufferBarrier } from '../base/states/buffer-barrier';
import { DescUpdateFrequency, WebGPUDeviceManager } from './define';
import { WebGPUDeviceManager } from './define';
import { WebGPUSwapchain } from './webgpu-swapchain';
import { WebGPUPipelineLayout } from './webgpu-pipeline-layout';
import { WebGPUDescriptorSetLayout } from './webgpu-descriptor-set-layout';
import { error, errorID } from '../../core';

export interface IWebGPUDepthBias {
Expand Down Expand Up @@ -101,6 +100,7 @@ export interface IWebGPUStencilCompareMask {
interface CommandEncoder { commandEncoder: GPUCommandEncoder, renderPassEncoder: GPURenderPassEncoder }
let currPipelineState: WebGPUPipelineState | null = null;
const descriptorSets: WebGPUDescriptorSet[] = [];
const groupSets: number[] = [0, 1, 2];
const renderAreas: Rect[] = [];
export class WebGPUCommandBuffer extends CommandBuffer {
public pipelineBarrier (
Expand Down Expand Up @@ -613,47 +613,23 @@ export class WebGPUCommandBuffer extends CommandBuffer {
if (!this._curGPUPipelineState) {
return;
}
const gpuShader = this._curGPUPipelineState.gpuShader!;
const bindingMaps = gpuShader.bindings;
let vertBinds: number[][] = []; let fragBinds: number[][] = []; let vertAttrs;
for (const stage of gpuShader.gpuStages) {
if (stage.type === ShaderStageFlagBit.VERTEX) {
vertBinds = stage.bindings;
vertAttrs = stage.attrs;
} else if (stage.type === ShaderStageFlagBit.FRAGMENT) {
fragBinds = stage.bindings;
}
}
const gpuPipelineLayout = this._curGPUPipelineState.gpuPipelineLayout as IWebGPUGPUPipelineLayout;
const wgpuPipLayout = (currPipelineState?.pipelineLayout as WebGPUPipelineLayout);
let needFetchPipLayout = false;
const descSize = descriptorSets.length;
for (let i = 0; i < descSize; i++) {
descriptorSets[i].prepare(
i ? DescUpdateFrequency.NORMAL : DescUpdateFrequency.LOW,
bindingMaps.get(i)!,
vertBinds[i] || [],
fragBinds[i] || [],
);
const layout = descriptorSets[i].layout as WebGPUDescriptorSetLayout;
const currGrpLayout = layout.gpuDescriptorSetLayout!.bindGroupLayout;
const notEqualLayout = gpuPipelineLayout.gpuBindGroupLayouts[i] !== currGrpLayout;
if (layout.hasChanged || notEqualLayout) {
if (notEqualLayout) {
wgpuPipLayout.changeSetLayout(i, layout);
}
layout.resetChanged();
needFetchPipLayout = true;
const device = WebGPUDeviceManager.instance;
for (let i = 0; i < groupSets.length; i++) {
const currSetIdx = groupSets[i];
const currDesc = descriptorSets[currSetIdx];
if (currDesc) {
currDesc.prepare();
} else {
const currLayout = wgpuPipLayout.setLayouts[currSetIdx];
const currLayoutInfo = new DescriptorSetInfo(currLayout);
const newDescSet = device.createDescriptorSet(currLayoutInfo) as WebGPUDescriptorSet;
descriptorSets[currSetIdx] = newDescSet;
newDescSet.prepare(true);
}
}

if (needFetchPipLayout || !wgpuPipLayout.gpuPipelineLayout!.nativePipelineLayout
|| this._curGPUPipelineState.pipelineState!.layout !== gpuPipelineLayout.nativePipelineLayout) {
wgpuPipLayout.fetchPipelineLayout(false);
this._curWebGPUPipelineState?.updatePipelineLayout();
needFetchPipLayout = true;
}
this._curWebGPUPipelineState!.prepare(this._curGPUInputAssembler!, needFetchPipLayout);
this._curWebGPUPipelineState!.prepare(this._curGPUInputAssembler!);
const { dynamicOffsetIndices } = gpuPipelineLayout;
// ----------------------------wgpu pipline state-----------------------------
const wgpuPipeline = this._curGPUPipelineState.nativePipeline as GPURenderPipeline;
Expand All @@ -668,20 +644,21 @@ export class WebGPUCommandBuffer extends CommandBuffer {
};
this._renderPassFuncQueue.push(stencilRefFunc);
}
const currGPUDescSize = this._curGPUDescriptorSets.length;
const currGPUDescSize = groupSets.length;
const wgpuBindGroups = new Array<GPUBindGroup>(currGPUDescSize);
const wgpuDynOffsets = new Array<number[]>(currGPUDescSize);
for (let i = 0; i < currGPUDescSize; i++) {
const curGpuDesc = this._curGPUDescriptorSets[i];
wgpuBindGroups[i] = curGpuDesc.bindGroup;
wgpuDynOffsets[i] = [...this._curDynamicOffsets[i]];
if (!descriptorSets[i].dynamicOffsetCount) {
wgpuDynOffsets[i] = [];
} else if (descriptorSets[i] && descriptorSets[i].dynamicOffsetCount !== wgpuDynOffsets[i].length) {
wgpuDynOffsets[i].length = descriptorSets[i].dynamicOffsetCount;
for (let j = 0; j < descriptorSets[i].dynamicOffsetCount; j++) {
if (!wgpuDynOffsets[i][j]) {
wgpuDynOffsets[i][j] = 0;
const currSetIdx = groupSets[i];
const curGpuDesc = descriptorSets[currSetIdx].gpuDescriptorSet;
wgpuBindGroups[currSetIdx] = curGpuDesc.bindGroup;
wgpuDynOffsets[currSetIdx] = [...this._curDynamicOffsets[currSetIdx]];
if (!descriptorSets[currSetIdx].dynamicOffsetCount) {
wgpuDynOffsets[currSetIdx] = [];
} else if (descriptorSets[currSetIdx] && descriptorSets[currSetIdx].dynamicOffsetCount !== wgpuDynOffsets[currSetIdx].length) {
wgpuDynOffsets[currSetIdx].length = descriptorSets[currSetIdx].dynamicOffsetCount;
for (let j = 0; j < descriptorSets[currSetIdx].dynamicOffsetCount; j++) {
if (!wgpuDynOffsets[currSetIdx][j]) {
wgpuDynOffsets[currSetIdx][j] = 0;
}
}
}
Expand All @@ -690,8 +667,12 @@ export class WebGPUCommandBuffer extends CommandBuffer {
const bgfunc = (passEncoder: GPURenderPassEncoder): void => {
const gpuBindGroupSize = wgpuBindGroups.length;
for (let i = 0; i < gpuBindGroupSize; i++) {
let currBindGroup = wgpuBindGroups[i];
if (!currBindGroup) {
currBindGroup = (device.defaultResource.descSet as WebGPUDescriptorSet).gpuDescriptorSet.bindGroup;
}
// FIXME: this is a special sentence that 2 in 3 parameters I'm not certain.
passEncoder.setBindGroup(i, wgpuBindGroups[i], wgpuDynOffsets[i]);
passEncoder.setBindGroup(i, currBindGroup, wgpuDynOffsets[i]);
}
};
this._renderPassFuncQueue.push(bgfunc);
Expand Down
Loading