Skip to content

Commit

Permalink
even more type check for buffer pools (#7214)
Browse files Browse the repository at this point in the history
  • Loading branch information
YunHsiao committed Aug 25, 2020
1 parent 2185a6f commit d0fd7eb
Show file tree
Hide file tree
Showing 11 changed files with 121 additions and 65 deletions.
8 changes: 0 additions & 8 deletions cocos/core/gfx/buffer.ts
Expand Up @@ -108,14 +108,6 @@ export abstract class GFXBuffer extends GFXObject {
return this._bakcupBuffer;
}

/**
* @en Whether this buffer instance is only a view on top of another buffer.
* @zh 此缓冲实例是否只是另一份缓冲的视图。
*/
get isBufferView () {
return this._isBufferView;
}

protected _device: GFXDevice;
protected _usage: GFXBufferUsage = GFXBufferUsageBit.NONE;
protected _memUsage: GFXMemoryUsage = GFXMemoryUsageBit.NONE;
Expand Down
4 changes: 2 additions & 2 deletions cocos/core/pipeline/instanced-buffer.ts
Expand Up @@ -65,8 +65,8 @@ export class InstancedBuffer {
if (!stride) { return; } // we assume per-instance attributes are always present
const sourceIA = subModel.inputAssembler;
const lightingMap = subModel.descriptorSet.getTexture(UniformLightingMapSampler.binding);
const hShader = SubModelPool.get<ShaderHandle>(subModel.handle, SubModelView.SHADER_0 + passIdx);
const hDescriptorSet = SubModelPool.get<DescriptorSetHandle>(subModel.handle, SubModelView.DESCRIPTOR_SET);
const hShader = SubModelPool.get(subModel.handle, SubModelView.SHADER_0 + passIdx) as ShaderHandle;
const hDescriptorSet = SubModelPool.get(subModel.handle, SubModelView.DESCRIPTOR_SET);
for (let i = 0; i < this.instances.length; ++i) {
const instance = this.instances[i];
if (instance.ia.indexBuffer !== sourceIA.indexBuffer || instance.count >= MAX_CAPACITY) { continue; }
Expand Down
4 changes: 2 additions & 2 deletions cocos/core/pipeline/pipeline-state-manager.ts
Expand Up @@ -14,7 +14,7 @@ export class PipelineStateManager {

static getOrCreatePipelineState (device: GFXDevice, hPass: PassHandle, shader: GFXShader, renderPass: GFXRenderPass, ia: GFXInputAssembler) {

const hash1 = PassPool.get<number>(hPass, PassView.HASH);
const hash1 = PassPool.get(hPass, PassView.HASH);
const hash2 = renderPass.hash;
const hash3 = ia.attributesHash;

Expand All @@ -29,7 +29,7 @@ export class PipelineStateManager {
rasterizerState: RasterizerStatePool.get(PassPool.get(hPass, PassView.RASTERIZER_STATE)),
depthStencilState: DepthStencilStatePool.get(PassPool.get(hPass, PassView.DEPTH_STENCIL_STATE)),
blendState: BlendStatePool.get(PassPool.get(hPass, PassView.BLEND_STATE)),
dynamicStates: PassPool.get(hPass, PassView.DYNAMIC_STATES) as GFXDynamicStateFlags,
dynamicStates: PassPool.get(hPass, PassView.DYNAMIC_STATES),
inputState,
renderPass,
shader,
Expand Down
4 changes: 2 additions & 2 deletions cocos/core/pipeline/render-additive-light-queue.ts
Expand Up @@ -7,7 +7,7 @@ import { SubModel } from '../renderer/scene/submodel';
import { IRenderObject, UBOForwardLight, SetIndex } from './define';
import { Light, LightType, SphereLight, SpotLight, BatchingSchemes, Model } from '../renderer';
import { PipelineStateManager } from './pipeline-state-manager';
import { DSPool, ShaderPool, PassView, PassPool, SubModelPool, SubModelView } from '../renderer/core/memory-pools';
import { DSPool, ShaderPool, PassView, PassPool, SubModelPool, SubModelView, ShaderHandle } from '../renderer/core/memory-pools';
import { Vec3, nextPow2 } from '../../core/math';
import { RenderView } from './render-view';
import { sphere, intersect } from '../geometry';
Expand Down Expand Up @@ -214,7 +214,7 @@ export class RenderAdditiveLightQueue {

for (let i = 0; i < this._lightPasses.length; i++) {
const { subModel, passIdx, dynamicOffsets } = this._lightPasses[i];
const shader = ShaderPool.get(SubModelPool.get(subModel.handle, SubModelView.SHADER_0 + passIdx));
const shader = ShaderPool.get(SubModelPool.get(subModel.handle, SubModelView.SHADER_0 + passIdx) as ShaderHandle);
const pass = subModel.passes[passIdx];
const ia = subModel.inputAssembler;
const pso = PipelineStateManager.getOrCreatePipelineState(device, pass.handle, shader, renderPass, ia);
Expand Down
14 changes: 7 additions & 7 deletions cocos/core/pipeline/render-queue.ts
Expand Up @@ -9,7 +9,7 @@ import { IRenderObject, IRenderPass, IRenderQueueDesc, SetIndex } from './define
import { PipelineStateManager } from './pipeline-state-manager';
import { GFXDevice } from '../gfx/device';
import { GFXRenderPass, GFXDescriptorSet } from '../gfx';
import { BlendStatePool, PassPool, PassView, DSPool, SubModelView, SubModelPool, ShaderPool, PassHandle } from '../renderer/core/memory-pools';
import { BlendStatePool, PassPool, PassView, DSPool, SubModelView, SubModelPool, ShaderPool, PassHandle, ShaderHandle } from '../renderer/core/memory-pools';

/**
* @en Comparison sorting function. Opaque objects are sorted by priority -> depth front to back -> shader ID.
Expand Down Expand Up @@ -78,16 +78,16 @@ export class RenderQueue {
*/
public insertRenderPass (renderObj: IRenderObject, subModelIdx: number, passIdx: number): boolean {
const subModel = renderObj.model.subModels[subModelIdx];
const hPass = SubModelPool.get<PassHandle>(subModel.handle, SubModelView.PASS_0 + passIdx);
const hPass = SubModelPool.get(subModel.handle, SubModelView.PASS_0 + passIdx) as PassHandle;
const isTransparent = BlendStatePool.get(PassPool.get(hPass, PassView.BLEND_STATE)).targets[0].blend;
if (isTransparent !== this._passDesc.isTransparent || !(PassPool.get<number>(hPass, PassView.PHASE) & this._passDesc.phases)) {
if (isTransparent !== this._passDesc.isTransparent || !(PassPool.get(hPass, PassView.PHASE) & this._passDesc.phases)) {
return false;
}
const hash = (0 << 30) | PassPool.get<number>(hPass, PassView.PRIORITY) << 16 | subModel.priority << 8 | passIdx;
const hash = (0 << 30) | PassPool.get(hPass, PassView.PRIORITY) << 16 | subModel.priority << 8 | passIdx;
const rp = this._passPool.add();
rp.hash = hash;
rp.depth = renderObj.depth || 0;
rp.shaderId = SubModelPool.get(subModel.handle, SubModelView.SHADER_0 + passIdx);
rp.shaderId = SubModelPool.get(subModel.handle, SubModelView.SHADER_0 + passIdx) as number;
rp.subModel = subModel;
rp.passIdx = passIdx;
this.queue.push(rp);
Expand All @@ -106,8 +106,8 @@ export class RenderQueue {
for (let i = 0; i < this.queue.length; ++i) {
const { subModel, passIdx } = this.queue.array[i];
const { inputAssembler, handle: hSubModel } = subModel;
const hPass = SubModelPool.get<PassHandle>(hSubModel, SubModelView.PASS_0 + passIdx);
const shader = ShaderPool.get(SubModelPool.get(hSubModel, SubModelView.SHADER_0 + passIdx));
const hPass = SubModelPool.get(hSubModel, SubModelView.PASS_0 + passIdx) as PassHandle;
const shader = ShaderPool.get(SubModelPool.get(hSubModel, SubModelView.SHADER_0 + passIdx) as ShaderHandle);
const pso = PipelineStateManager.getOrCreatePipelineState(device, hPass, shader, renderPass, inputAssembler);
cmdBuff.bindPipelineState(pso);
cmdBuff.bindDescriptorSet(SetIndex.MATERIAL, DSPool.get(PassPool.get(hPass, PassView.DESCRIPTOR_SET)));
Expand Down
4 changes: 2 additions & 2 deletions cocos/core/pipeline/render-shadowMap-batched-queue.ts
Expand Up @@ -9,7 +9,7 @@ import { IRenderObject, SetIndex } from './define';
import { GFXDevice, GFXRenderPass, GFXBuffer, GFXShader } from '../gfx';
import { getPhaseID } from './pass-phase';
import { PipelineStateManager } from './pipeline-state-manager';
import { DSPool, ShaderPool, PassHandle, PassPool, PassView, SubModelPool, SubModelView } from '../renderer/core/memory-pools';
import { DSPool, ShaderPool, PassHandle, PassPool, PassView, SubModelPool, SubModelView, ShaderHandle } from '../renderer/core/memory-pools';

/**
* @zh
Expand Down Expand Up @@ -39,7 +39,7 @@ export class RenderShadowMapBatchedQueue {

if (pass.phase === this._phaseID) {
if (this._shadowMapBuffer) {
const shader = ShaderPool.get(SubModelPool.get(subModel.handle, SubModelView.SHADER_0 + passIdx));
const shader = ShaderPool.get(SubModelPool.get(subModel.handle, SubModelView.SHADER_0 + passIdx) as ShaderHandle);
this._subModelsArray.push(subModel);
this._shaderArray.push(shader);
this._passArray.push(pass.handle);
Expand Down
124 changes: 94 additions & 30 deletions cocos/core/renderer/core/memory-pools.ts
Expand Up @@ -30,21 +30,32 @@
import { DEBUG } from 'internal:constants';
import { NativeBufferPool, NativeObjectPool } from './native-pools';
import { GFXRasterizerState, GFXDepthStencilState, GFXBlendState, IGFXDescriptorSetInfo,
GFXDevice, GFXDescriptorSet, GFXShaderInfo, GFXShader, IGFXInputAssemblerInfo, GFXInputAssembler, IGFXPipelineLayoutInfo, GFXPipelineLayout } from '../../gfx';
GFXDevice, GFXDescriptorSet, GFXShaderInfo, GFXShader, IGFXInputAssemblerInfo, GFXInputAssembler, IGFXPipelineLayoutInfo,
GFXPipelineLayout, GFXPrimitiveMode, GFXDynamicStateFlags } from '../../gfx';
import { RenderPassStage } from '../../pipeline/define';
import { BatchingSchemes } from './pass';

interface ITypedArrayConstructor<T> {
new(buffer: ArrayBufferLike, byteOffset: number, length?: number): T;
readonly BYTES_PER_ELEMENT: number;
}

interface IElementEnum {
interface IBufferManifest {
[key: string]: number | string;
COUNT: number;
}

// a little hacky, but works (different specializations should not be assignable to each other)
class Handle<T extends PoolType> extends Number { m!: T; }
interface IHandle<T extends PoolType> extends Number {
// we make this non-optional so that even plain numbers would not be directly assignable to handles.
// this strictness will introduce some casting hassle in the pool implementation itself
// but becomes generally more useful for client code type checking.
_: T;
}

type GeneralBufferElement = number | IHandle<any>;

class BufferPool<T extends TypedArray, E extends IElementEnum, P extends PoolType> {
class BufferPool<P extends PoolType, T extends TypedArray, E extends IBufferManifest, H extends { [key in E[keyof E]]: GeneralBufferElement }> {

// naming convension:
// this._bufferViews[chunk][entry][element]
Expand Down Expand Up @@ -79,13 +90,13 @@ class BufferPool<T extends TypedArray, E extends IElementEnum, P extends PoolTyp
this._nativePool = new NativeBufferPool(dataType, entryBits, this._stride);
}

public alloc (): Handle<P> {
public alloc (): IHandle<P> {
let i = 0;
for (; i < this._freelists.length; i++) {
const list = this._freelists[i];
if (list.length) {
const j = list[list.length - 1]; list.length--;
return (i << this._entryBits) + j + this._poolFlag as unknown as Handle<P>;
return (i << this._entryBits) + j + this._poolFlag as unknown as IHandle<P>;
}
}
// add a new chunk
Expand All @@ -99,32 +110,47 @@ class BufferPool<T extends TypedArray, E extends IElementEnum, P extends PoolTyp
this._arrayBuffers.push(buffer);
this._bufferViews.push(bufferViews);
this._freelists.push(freelist);
return (i << this._entryBits) + this._poolFlag as unknown as Handle<P>; // guarantees the handle is always not zero
return (i << this._entryBits) + this._poolFlag as unknown as IHandle<P>; // guarantees the handle is always not zero
}

public get<H extends Number | Handle<any>> (handle: Handle<P>, element: E[keyof E]): H {
/**
* Get the specified element out from buffer pool.
*
* Note the type inference does not work when `element` is not directly
* an pre-declared enum value: (e.g. when doing arithmetic operations)
* ```ts
* SubModelPool.get(handle, SubModelView.SHADER_0 + passIndex); // the return value will have type GeneralBufferElement
* ```
*
* To properly declare the variable type, you have two options:
* ```ts
* const hShader = SubModelPool.get(handle, SubModelView.SHADER_0 + passIndex) as ShaderHandle; // option #1
* const hShader = SubModelPool.get<SubModelView.SHADER_0>(handle, SubModelView.SHADER_0 + passIndex); // option #2
* ```
*/
public get<V extends E[keyof E]> (handle: IHandle<P>, element: V): H[V] {
const chunk = (this._chunkMask & handle as unknown as number) >> this._entryBits;
const entry = this._entryMask & handle as unknown as number;
if (DEBUG && (!handle || chunk < 0 || chunk >= this._bufferViews.length ||
entry < 0 || entry >= this._entriesPerChunk || this._freelists[chunk].find((n) => n === entry))) {
console.warn('invalid native buffer pool handle');
return 0 as unknown as H;
return 0 as H[V];
}
return this._bufferViews[chunk][entry][element as unknown as number] as unknown as H;
return this._bufferViews[chunk][entry][element as number] as H[V];
}

public set (handle: Handle<P>, element: E[keyof E], value: number | Handle<any>) {
public set<V extends E[keyof E]> (handle: IHandle<P>, element: V, value: H[V]) {
const chunk = (this._chunkMask & handle as unknown as number) >> this._entryBits;
const entry = this._entryMask & handle as unknown as number;
if (DEBUG && (!handle || chunk < 0 || chunk >= this._bufferViews.length ||
entry < 0 || entry >= this._entriesPerChunk || this._freelists[chunk].find((n) => n === entry))) {
console.warn('invalid native buffer pool handle');
return;
}
this._bufferViews[chunk][entry][element as unknown as number] = value as number;
this._bufferViews[chunk][entry][element as number] = value as number;
}

public free (handle: Handle<P>) {
public free (handle: IHandle<P>) {
const chunk = (this._chunkMask & handle as unknown as number) >> this._entryBits;
const entry = this._entryMask & handle as unknown as number;
if (DEBUG && (!handle || chunk < 0 || chunk >= this._freelists.length ||
Expand Down Expand Up @@ -157,7 +183,7 @@ class ObjectPool<T, P extends PoolType> {
this._nativePool = new NativeObjectPool(dataType, this._array);
}

public alloc (...args: any[]): Handle<P> {
public alloc (...args: any[]): IHandle<P> {
const freelist = this._freelist;
let i = -1;
if (freelist.length) {
Expand All @@ -168,13 +194,13 @@ class ObjectPool<T, P extends PoolType> {
if (i < 0) {
i = this._array.length;
const obj = this._ctor(arguments);
if (!obj) { return 0 as unknown as Handle<P>; }
if (!obj) { return 0 as unknown as IHandle<P>; }
this._array.push(obj);
}
return i + this._poolFlag as unknown as Handle<P>; // guarantees the handle is always not zero
return i + this._poolFlag as unknown as IHandle<P>; // guarantees the handle is always not zero
}

public get (handle: Handle<P>) {
public get (handle: IHandle<P>) {
const index = this._indexMask & handle as unknown as number;
if (DEBUG && (!handle || index < 0 || index >= this._array.length || this._freelist.find((n) => n === index))) {
console.warn('invalid native object pool handle');
Expand All @@ -183,7 +209,7 @@ class ObjectPool<T, P extends PoolType> {
return this._array[index];
}

public free (handle: Handle<P>) {
public free (handle: IHandle<P>) {
const index = this._indexMask & handle as unknown as number;
if (DEBUG && (!handle || index < 0 || index >= this._array.length || this._freelist.find((n) => n === index))) {
console.warn('invalid native object pool handle');
Expand All @@ -194,6 +220,10 @@ class ObjectPool<T, P extends PoolType> {
}
}

interface IBufferTypeManifest {
[key: string]: GeneralBufferElement;
}

enum PoolType {
// objects
RASTERIZER_STATE,
Expand All @@ -208,17 +238,17 @@ enum PoolType {
SUB_MODEL,
}

export const NULL_HANDLE = 0 as unknown as Handle<any>;
export const NULL_HANDLE = 0 as unknown as IHandle<any>;

export type RasterizerStateHandle = Handle<PoolType.RASTERIZER_STATE>;
export type DepthStencilStateHandle = Handle<PoolType.DEPTH_STENCIL_STATE>;
export type BlendStateHandle = Handle<PoolType.BLEND_STATE>;
export type DescriptorSetHandle = Handle<PoolType.DESCRIPTOR_SETS>;
export type ShaderHandle = Handle<PoolType.SHADER>;
export type IAHandle = Handle<PoolType.INPUT_ASSEMBLER>;
export type PipelineLayoutHandle = Handle<PoolType.PIPELINE_LAYOUT>;
export type PassHandle = Handle<PoolType.PASS>;
export type SubModelHandle = Handle<PoolType.SUB_MODEL>;
export type RasterizerStateHandle = IHandle<PoolType.RASTERIZER_STATE>;
export type DepthStencilStateHandle = IHandle<PoolType.DEPTH_STENCIL_STATE>;
export type BlendStateHandle = IHandle<PoolType.BLEND_STATE>;
export type DescriptorSetHandle = IHandle<PoolType.DESCRIPTOR_SETS>;
export type ShaderHandle = IHandle<PoolType.SHADER>;
export type InputAssemblerHandle = IHandle<PoolType.INPUT_ASSEMBLER>;
export type PipelineLayoutHandle = IHandle<PoolType.PIPELINE_LAYOUT>;
export type PassHandle = IHandle<PoolType.PASS>;
export type SubModelHandle = IHandle<PoolType.SUB_MODEL>;

// don't reuse any of these data-only structs, for GFX objects may directly reference them
export const RasterizerStatePool = new ObjectPool(PoolType.RASTERIZER_STATE, (_: any) => new GFXRasterizerState());
Expand Down Expand Up @@ -257,7 +287,25 @@ export enum PassView {
PIPELINE_LAYOUT, // handle
COUNT,
}
export const PassPool = new BufferPool(PoolType.PASS, Uint32Array, PassView);
interface IPassViewType extends IBufferTypeManifest {
[PassView.PRIORITY]: number;
[PassView.STAGE]: RenderPassStage;
[PassView.PHASE]: number;
[PassView.BATCHING_SCHEME]: BatchingSchemes;
[PassView.PRIMITIVE]: GFXPrimitiveMode;
[PassView.DYNAMIC_STATES]: GFXDynamicStateFlags;
[PassView.HASH]: number;
[PassView.RASTERIZER_STATE]: RasterizerStateHandle;
[PassView.DEPTH_STENCIL_STATE]: DepthStencilStateHandle;
[PassView.BLEND_STATE]: BlendStateHandle;
[PassView.DESCRIPTOR_SET]: DescriptorSetHandle;
[PassView.PIPELINE_LAYOUT]: PipelineLayoutHandle;
[PassView.COUNT]: number;
}
// Theoretically we only have to declare IPassViewType here while all the other arguments can be inferred.
// but before the official support of Partial Type Argument Inference releases, (microsoft/TypeScript#26349)
// we'll have to explicitly declare all these types.
export const PassPool = new BufferPool<PoolType.PASS, Uint32Array, typeof PassView, IPassViewType>(PoolType.PASS, Uint32Array, PassView);

export enum SubModelView {
PRIORITY,
Expand All @@ -274,4 +322,20 @@ export enum SubModelView {
INPUT_ASSEMBLER, // handle
COUNT,
}
export const SubModelPool = new BufferPool(PoolType.SUB_MODEL, Uint32Array, SubModelView);
interface ISubModelViewType extends IBufferTypeManifest {
[SubModelView.PRIORITY]: number;
[SubModelView.PASS_COUNT]: number;
[SubModelView.PASS_0]: PassHandle;
[SubModelView.PASS_1]: PassHandle;
[SubModelView.PASS_2]: PassHandle;
[SubModelView.PASS_3]: PassHandle;
[SubModelView.SHADER_0]: ShaderHandle;
[SubModelView.SHADER_1]: ShaderHandle;
[SubModelView.SHADER_2]: ShaderHandle;
[SubModelView.SHADER_3]: ShaderHandle;
[SubModelView.DESCRIPTOR_SET]: DescriptorSetHandle;
[SubModelView.INPUT_ASSEMBLER]: InputAssemblerHandle;
[SubModelView.COUNT]: number;
}
export const SubModelPool = new BufferPool<PoolType.SUB_MODEL, Uint32Array, typeof SubModelView, ISubModelViewType>
(PoolType.SUB_MODEL, Uint32Array, SubModelView);

0 comments on commit d0fd7eb

Please sign in to comment.