From 3cde8a0d966170338907c7eed35327bf0bcb7c4a Mon Sep 17 00:00:00 2001 From: Yun Hsiao Wu Date: Mon, 10 Jan 2022 19:06:01 +0800 Subject: [PATCH] binding mappings --- cocos/core/gfx/base/define.ts | 40 +++++++++++++++---- cocos/core/gfx/empty/empty-device.ts | 2 - cocos/core/gfx/webgl/webgl-command-buffer.ts | 2 +- cocos/core/gfx/webgl/webgl-commands.ts | 8 ++-- cocos/core/gfx/webgl/webgl-device.ts | 32 +++++++++++++-- cocos/core/gfx/webgl/webgl-gpu-objects.ts | 6 +++ .../core/gfx/webgl2/webgl2-command-buffer.ts | 2 +- cocos/core/gfx/webgl2/webgl2-commands.ts | 8 ++-- cocos/core/gfx/webgl2/webgl2-device.ts | 32 +++++++++++++-- cocos/core/gfx/webgl2/webgl2-gpu-objects.ts | 6 +++ cocos/core/pipeline/define.ts | 14 +++++-- 11 files changed, 123 insertions(+), 29 deletions(-) diff --git a/cocos/core/gfx/base/define.ts b/cocos/core/gfx/base/define.ts index d381b87b7c2..6c4513d1009 100644 --- a/cocos/core/gfx/base/define.ts +++ b/cocos/core/gfx/base/define.ts @@ -89,6 +89,7 @@ export enum API { GLES3, METAL, VULKAN, + NVN, WEBGL, WEBGL2, WEBGPU, @@ -953,19 +954,44 @@ export class Color { } } +/** + * For non-vulkan backends, to maintain compatibility and maximize + * descriptor cache-locality, descriptor-set-based binding numbers need + * to be mapped to backend-specific bindings based on maximum limit + * of available descriptor slots in each set. + * + * The GFX layer assumes the binding numbers for each descriptor type inside each set + * are guaranteed to be consecutive, so the mapping procedure is reduced + * to a simple shifting operation. This data structure specifies the + * capacity for each descriptor type in each set. + * + * The `setIndices` field defines the binding ordering between different sets. + * The last set index is treated as the 'flexible set', whose capacity is dynamically + * assigned based on the total available descriptor slots on the runtime device. + */ export class BindingMappingInfo { declare private _token: never; // to make sure all usages must be an instance of this exact class, not assembled from plain object constructor ( - public bufferOffsets: number[] = [], - public samplerOffsets: number[] = [], - public flexibleSet: number = 0, + public maxBlockCounts: number[] = [0], + public maxSamplerTextureCounts: number[] = [0], + public maxSamplerCounts: number[] = [0], + public maxTextureCounts: number[] = [0], + public maxBufferCounts: number[] = [0], + public maxImageCounts: number[] = [0], + public maxSubpassInputCounts: number[] = [0], + public setIndices: number[] = [0], ) {} public copy (info: Readonly) { - this.bufferOffsets = info.bufferOffsets.slice(); - this.samplerOffsets = info.samplerOffsets.slice(); - this.flexibleSet = info.flexibleSet; + this.maxBlockCounts = info.maxBlockCounts.slice(); + this.maxSamplerTextureCounts = info.maxSamplerTextureCounts.slice(); + this.maxSamplerCounts = info.maxSamplerCounts.slice(); + this.maxTextureCounts = info.maxTextureCounts.slice(); + this.maxBufferCounts = info.maxBufferCounts.slice(); + this.maxImageCounts = info.maxImageCounts.slice(); + this.maxSubpassInputCounts = info.maxSubpassInputCounts.slice(); + this.setIndices = info.setIndices.slice(); return this; } } @@ -1712,7 +1738,7 @@ export class QueryPoolInfo { constructor ( public type: QueryType = QueryType.OCCLUSION, - public maxQueryObjects: number = 65536, + public maxQueryObjects: number = 32767, public forceWait: boolean = true, ) {} diff --git a/cocos/core/gfx/empty/empty-device.ts b/cocos/core/gfx/empty/empty-device.ts index c00ef702c6a..b1b3d191864 100644 --- a/cocos/core/gfx/empty/empty-device.ts +++ b/cocos/core/gfx/empty/empty-device.ts @@ -67,8 +67,6 @@ export class EmptyDevice extends Device { this._gfxAPI = API.UNKNOWN; this._bindingMappingInfo = info.bindingMappingInfo; - if (!this._bindingMappingInfo.bufferOffsets.length) this._bindingMappingInfo.bufferOffsets.push(0); - if (!this._bindingMappingInfo.samplerOffsets.length) this._bindingMappingInfo.samplerOffsets.push(0); this._queue = this.createQueue(new QueueInfo(QueueType.GRAPHICS)); this._cmdBuff = this.createCommandBuffer(new CommandBufferInfo(this._queue)); diff --git a/cocos/core/gfx/webgl/webgl-command-buffer.ts b/cocos/core/gfx/webgl/webgl-command-buffer.ts index 7b5687fea8c..2da4c5a1ce2 100644 --- a/cocos/core/gfx/webgl/webgl-command-buffer.ts +++ b/cocos/core/gfx/webgl/webgl-command-buffer.ts @@ -64,7 +64,7 @@ export class WebGLCommandBuffer extends CommandBuffer { this._type = info.type; this._queue = info.queue; - const setCount = WebGLDeviceManager.instance.bindingMappingInfo.bufferOffsets.length; + const setCount = WebGLDeviceManager.instance.bindingMappings.blockOffsets.length; for (let i = 0; i < setCount; i++) { this._curGPUDescriptorSets.push(null!); } diff --git a/cocos/core/gfx/webgl/webgl-commands.ts b/cocos/core/gfx/webgl/webgl-commands.ts index 4ccc5e06bbd..277a8c4dcaa 100644 --- a/cocos/core/gfx/webgl/webgl-commands.ts +++ b/cocos/core/gfx/webgl/webgl-commands.ts @@ -1432,12 +1432,12 @@ export function WebGLCmdFuncCreateShader (device: WebGLDevice, gpuShader: IWebGL // texture unit index mapping optimization const glActiveSamplers: IWebGLGPUUniformSamplerTexture[] = []; const glActiveSamplerLocations: WebGLUniformLocation[] = []; - const { bindingMappingInfo } = device; + const { bindingMappings } = device; const { texUnitCacheMap } = device.stateCache; let flexibleSetBaseOffset = 0; for (let i = 0; i < gpuShader.blocks.length; ++i) { - if (gpuShader.blocks[i].set === bindingMappingInfo.flexibleSet) { + if (gpuShader.blocks[i].set === bindingMappings.flexibleSet) { flexibleSetBaseOffset++; } } @@ -1451,8 +1451,8 @@ export function WebGLCmdFuncCreateShader (device: WebGLDevice, gpuShader: IWebGL glActiveSamplerLocations.push(glLoc); } if (texUnitCacheMap[sampler.name] === undefined) { - let binding = sampler.binding + bindingMappingInfo.samplerOffsets[sampler.set] + arrayOffset; - if (sampler.set === bindingMappingInfo.flexibleSet) { binding -= flexibleSetBaseOffset; } + let binding = sampler.binding + bindingMappings.samplerTextureOffsets[sampler.set] + arrayOffset; + if (sampler.set === bindingMappings.flexibleSet) { binding -= flexibleSetBaseOffset; } texUnitCacheMap[sampler.name] = binding % device.capabilities.maxTextureUnits; arrayOffset += sampler.count - 1; } diff --git a/cocos/core/gfx/webgl/webgl-device.ts b/cocos/core/gfx/webgl/webgl-device.ts index e0c64696c20..2529b299698 100644 --- a/cocos/core/gfx/webgl/webgl-device.ts +++ b/cocos/core/gfx/webgl/webgl-device.ts @@ -64,6 +64,7 @@ import { TextureBarrier } from '../base/states/texture-barrier'; import { debug } from '../../platform/debug'; import { Swapchain } from '../base/swapchain'; import { WebGLDeviceManager } from './webgl-define'; +import { IWebGLBindingMapping } from './webgl-gpu-objects'; export class WebGLDevice extends Device { get gl () { @@ -86,16 +87,41 @@ export class WebGLDevice extends Device { return this._swapchain!.nullTexCube; } + get bindingMappings () { + return this._bindingMappings!; + } + private _swapchain: WebGLSwapchain | null = null; private _context: WebGLRenderingContext | null = null; + private _bindingMappings: IWebGLBindingMapping | null = null; public initialize (info: DeviceInfo): boolean { WebGLDeviceManager.setInstance(this); this._gfxAPI = API.WEBGL; - this._bindingMappingInfo = info.bindingMappingInfo; - if (!this._bindingMappingInfo.bufferOffsets.length) this._bindingMappingInfo.bufferOffsets.push(0); - if (!this._bindingMappingInfo.samplerOffsets.length) this._bindingMappingInfo.samplerOffsets.push(0); + const mapping = this._bindingMappingInfo = info.bindingMappingInfo; + const blockOffsets: number[] = []; + const samplerTextureOffsets: number[] = []; + const firstSet = mapping.setIndices[0]; + blockOffsets[firstSet] = 0; + samplerTextureOffsets[firstSet] = 0; + for (let i = 1; i < mapping.setIndices.length; ++i) { + const curSet = mapping.setIndices[i]; + const prevSet = mapping.setIndices[i - 1]; + // accumulate the per set offset according to the specified capacity + blockOffsets[curSet] = mapping.maxBlockCounts[prevSet] + blockOffsets[prevSet]; + samplerTextureOffsets[curSet] = mapping.maxSamplerTextureCounts[prevSet] + samplerTextureOffsets[prevSet]; + } + for (let i = 0; i < mapping.setIndices.length; ++i) { + const curSet = mapping.setIndices[i]; + // textures always come after UBOs + samplerTextureOffsets[curSet] -= mapping.maxBlockCounts[curSet]; + } + this._bindingMappings = { + blockOffsets, + samplerTextureOffsets, + flexibleSet: mapping.setIndices[mapping.setIndices.length - 1], + }; const gl = this._context = getContext(Device.canvas); diff --git a/cocos/core/gfx/webgl/webgl-gpu-objects.ts b/cocos/core/gfx/webgl/webgl-gpu-objects.ts index 5e9d387124a..f3af82edec4 100644 --- a/cocos/core/gfx/webgl/webgl-gpu-objects.ts +++ b/cocos/core/gfx/webgl/webgl-gpu-objects.ts @@ -102,6 +102,12 @@ export interface IWebGLGPUUniformInfo { isDirty: boolean; } +export interface IWebGLBindingMapping { + blockOffsets: number[]; + samplerTextureOffsets: number[]; + flexibleSet: number; +} + export interface IWebGLGPUBufferView { gpuBuffer: IWebGLGPUBuffer; offset: number; diff --git a/cocos/core/gfx/webgl2/webgl2-command-buffer.ts b/cocos/core/gfx/webgl2/webgl2-command-buffer.ts index e8cf7949e11..ea1330aa000 100644 --- a/cocos/core/gfx/webgl2/webgl2-command-buffer.ts +++ b/cocos/core/gfx/webgl2/webgl2-command-buffer.ts @@ -76,7 +76,7 @@ export class WebGL2CommandBuffer extends CommandBuffer { this._type = info.type; this._queue = info.queue; - const setCount = WebGL2DeviceManager.instance.bindingMappingInfo.bufferOffsets.length; + const setCount = WebGL2DeviceManager.instance.bindingMappings.blockOffsets.length; for (let i = 0; i < setCount; i++) { this._curGPUDescriptorSets.push(null!); } diff --git a/cocos/core/gfx/webgl2/webgl2-commands.ts b/cocos/core/gfx/webgl2/webgl2-commands.ts index 6c53623a752..f4432a09017 100644 --- a/cocos/core/gfx/webgl2/webgl2-commands.ts +++ b/cocos/core/gfx/webgl2/webgl2-commands.ts @@ -1552,7 +1552,7 @@ export function WebGL2CmdFuncCreateShader (device: WebGL2Device, gpuShader: IWeb // blockIdx = gl.getUniformBlockIndex(gpuShader.glProgram, blockName); blockIdx = b; blockSize = gl.getActiveUniformBlockParameter(gpuShader.glProgram, blockIdx, gl.UNIFORM_BLOCK_DATA_SIZE); - const glBinding = block.binding + (device.bindingMappingInfo.bufferOffsets[block.set] || 0); + const glBinding = block.binding + (device.bindingMappings.blockOffsets[block.set] || 0); gl.uniformBlockBinding(gpuShader.glProgram, blockIdx, glBinding); @@ -1603,7 +1603,7 @@ export function WebGL2CmdFuncCreateShader (device: WebGL2Device, gpuShader: IWeb let flexibleSetBaseOffset = 0; for (let i = 0; i < gpuShader.blocks.length; ++i) { - if (gpuShader.blocks[i].set === device.bindingMappingInfo.flexibleSet) { + if (gpuShader.blocks[i].set === device.bindingMappings.flexibleSet) { flexibleSetBaseOffset++; } } @@ -1618,8 +1618,8 @@ export function WebGL2CmdFuncCreateShader (device: WebGL2Device, gpuShader: IWeb glActiveSamplerLocations.push(glLoc); } if (texUnitCacheMap[sampler.name] === undefined) { - let binding = sampler.binding + device.bindingMappingInfo.samplerOffsets[sampler.set] + arrayOffset; - if (sampler.set === device.bindingMappingInfo.flexibleSet) { binding -= flexibleSetBaseOffset; } + let binding = sampler.binding + device.bindingMappings.samplerTextureOffsets[sampler.set] + arrayOffset; + if (sampler.set === device.bindingMappings.flexibleSet) { binding -= flexibleSetBaseOffset; } texUnitCacheMap[sampler.name] = binding % device.capabilities.maxTextureUnits; arrayOffset += sampler.count - 1; } diff --git a/cocos/core/gfx/webgl2/webgl2-device.ts b/cocos/core/gfx/webgl2/webgl2-device.ts index e5cad72e9f7..de2eab9868d 100644 --- a/cocos/core/gfx/webgl2/webgl2-device.ts +++ b/cocos/core/gfx/webgl2/webgl2-device.ts @@ -64,6 +64,7 @@ import { TextureBarrier } from '../base/states/texture-barrier'; import { debug } from '../../platform/debug'; import { Swapchain } from '../base/swapchain'; import { WebGL2DeviceManager } from './webgl2-define'; +import { IWebGL2BindingMapping } from './webgl2-gpu-objects'; export class WebGL2Device extends Device { get gl () { @@ -86,16 +87,41 @@ export class WebGL2Device extends Device { return this._swapchain!.nullTexCube; } + get bindingMappings () { + return this._bindingMappings!; + } + private _swapchain: WebGL2Swapchain | null = null; private _context: WebGL2RenderingContext | null = null; + private _bindingMappings: IWebGL2BindingMapping | null = null; public initialize (info: DeviceInfo): boolean { WebGL2DeviceManager.setInstance(this); this._gfxAPI = API.WEBGL2; - this._bindingMappingInfo = info.bindingMappingInfo; - if (!this._bindingMappingInfo.bufferOffsets.length) this._bindingMappingInfo.bufferOffsets.push(0); - if (!this._bindingMappingInfo.samplerOffsets.length) this._bindingMappingInfo.samplerOffsets.push(0); + const mapping = this._bindingMappingInfo = info.bindingMappingInfo; + const blockOffsets: number[] = []; + const samplerTextureOffsets: number[] = []; + const firstSet = mapping.setIndices[0]; + blockOffsets[firstSet] = 0; + samplerTextureOffsets[firstSet] = 0; + for (let i = 1; i < mapping.setIndices.length; ++i) { + const curSet = mapping.setIndices[i]; + const prevSet = mapping.setIndices[i - 1]; + // accumulate the per set offset according to the specified capacity + blockOffsets[curSet] = mapping.maxBlockCounts[prevSet] + blockOffsets[prevSet]; + samplerTextureOffsets[curSet] = mapping.maxSamplerTextureCounts[prevSet] + samplerTextureOffsets[prevSet]; + } + for (let i = 0; i < mapping.setIndices.length; ++i) { + const curSet = mapping.setIndices[i]; + // textures always come after UBOs + samplerTextureOffsets[curSet] -= mapping.maxBlockCounts[curSet]; + } + this._bindingMappings = { + blockOffsets, + samplerTextureOffsets, + flexibleSet: mapping.setIndices[mapping.setIndices.length - 1], + }; const gl = this._context = getContext(Device.canvas); diff --git a/cocos/core/gfx/webgl2/webgl2-gpu-objects.ts b/cocos/core/gfx/webgl2/webgl2-gpu-objects.ts index 3385a10b4e2..7f516e00d14 100644 --- a/cocos/core/gfx/webgl2/webgl2-gpu-objects.ts +++ b/cocos/core/gfx/webgl2/webgl2-gpu-objects.ts @@ -92,6 +92,12 @@ export class WebGL2IndirectDrawInfos { } } +export interface IWebGL2BindingMapping { + blockOffsets: number[]; + samplerTextureOffsets: number[]; + flexibleSet: number; +} + export interface IWebGL2GPUUniformInfo { name: string; type: Type; diff --git a/cocos/core/pipeline/define.ts b/cocos/core/pipeline/define.ts index 3ddc962a5a6..e62b7d1bfbc 100644 --- a/cocos/core/pipeline/define.ts +++ b/cocos/core/pipeline/define.ts @@ -156,10 +156,16 @@ export enum SetIndex { LOCAL, } // parameters passed to GFX Device -export const bindingMappingInfo = new BindingMappingInfo(); -bindingMappingInfo.bufferOffsets = [0, GLOBAL_UBO_COUNT + LOCAL_UBO_COUNT, GLOBAL_UBO_COUNT]; -bindingMappingInfo.samplerOffsets = [-GLOBAL_UBO_COUNT, GLOBAL_SAMPLER_COUNT + LOCAL_SAMPLER_COUNT, GLOBAL_SAMPLER_COUNT - LOCAL_UBO_COUNT]; -bindingMappingInfo.flexibleSet = 1; +export const bindingMappingInfo = new BindingMappingInfo( + [GLOBAL_UBO_COUNT, 0, LOCAL_UBO_COUNT], // Uniform Buffer Counts + [GLOBAL_SAMPLER_COUNT, 0, LOCAL_SAMPLER_COUNT], // Combined Sampler Texture Counts + [0, 0, 0], // Sampler Counts + [0, 0, 0], // Texture Counts + [0, 0, 0], // Storage Buffer Counts + [0, 0, 0], // Storage Image Counts + [0, 0, 0], // Subpass Input Counts + [0, 2, 1], // Set Order Indices +); /** * @en The global uniform buffer object