diff --git a/cocos/core/gfx/buffer.ts b/cocos/core/gfx/buffer.ts index 7affea88ec0..7dbd5ac8183 100644 --- a/cocos/core/gfx/buffer.ts +++ b/cocos/core/gfx/buffer.ts @@ -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; diff --git a/cocos/core/pipeline/instanced-buffer.ts b/cocos/core/pipeline/instanced-buffer.ts index 053237f540b..8a32e19d0a4 100644 --- a/cocos/core/pipeline/instanced-buffer.ts +++ b/cocos/core/pipeline/instanced-buffer.ts @@ -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(subModel.handle, SubModelView.SHADER_0 + passIdx); - const hDescriptorSet = SubModelPool.get(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; } diff --git a/cocos/core/pipeline/pipeline-state-manager.ts b/cocos/core/pipeline/pipeline-state-manager.ts index 9007be2f050..5dbbd30e4fd 100644 --- a/cocos/core/pipeline/pipeline-state-manager.ts +++ b/cocos/core/pipeline/pipeline-state-manager.ts @@ -14,7 +14,7 @@ export class PipelineStateManager { static getOrCreatePipelineState (device: GFXDevice, hPass: PassHandle, shader: GFXShader, renderPass: GFXRenderPass, ia: GFXInputAssembler) { - const hash1 = PassPool.get(hPass, PassView.HASH); + const hash1 = PassPool.get(hPass, PassView.HASH); const hash2 = renderPass.hash; const hash3 = ia.attributesHash; @@ -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, diff --git a/cocos/core/pipeline/render-additive-light-queue.ts b/cocos/core/pipeline/render-additive-light-queue.ts index 52fb6385db9..e7ed52006ca 100644 --- a/cocos/core/pipeline/render-additive-light-queue.ts +++ b/cocos/core/pipeline/render-additive-light-queue.ts @@ -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'; @@ -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); diff --git a/cocos/core/pipeline/render-queue.ts b/cocos/core/pipeline/render-queue.ts index 0fd70e33619..3b8aef923f8 100644 --- a/cocos/core/pipeline/render-queue.ts +++ b/cocos/core/pipeline/render-queue.ts @@ -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. @@ -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(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(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(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); @@ -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(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))); diff --git a/cocos/core/pipeline/render-shadowMap-batched-queue.ts b/cocos/core/pipeline/render-shadowMap-batched-queue.ts index 71e3fbc3d6c..586135d63d2 100644 --- a/cocos/core/pipeline/render-shadowMap-batched-queue.ts +++ b/cocos/core/pipeline/render-shadowMap-batched-queue.ts @@ -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 @@ -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); diff --git a/cocos/core/renderer/core/memory-pools.ts b/cocos/core/renderer/core/memory-pools.ts index b1394613143..9b2f311e365 100644 --- a/cocos/core/renderer/core/memory-pools.ts +++ b/cocos/core/renderer/core/memory-pools.ts @@ -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 { 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 extends Number { m!: T; } +interface IHandle 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; -class BufferPool { +class BufferPool

{ // naming convension: // this._bufferViews[chunk][entry][element] @@ -79,13 +90,13 @@ class BufferPool { + public alloc (): IHandle

{ 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

; + return (i << this._entryBits) + j + this._poolFlag as unknown as IHandle

; } } // add a new chunk @@ -99,21 +110,36 @@ class BufferPool; // guarantees the handle is always not zero + return (i << this._entryBits) + this._poolFlag as unknown as IHandle

; // guarantees the handle is always not zero } - public get> (handle: Handle

, 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(handle, SubModelView.SHADER_0 + passIndex); // option #2 + * ``` + */ + public get (handle: IHandle

, 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

, element: E[keyof E], value: number | Handle) { + public set (handle: IHandle

, 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 || @@ -121,10 +147,10 @@ class BufferPool) { + public free (handle: IHandle

) { 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 || @@ -157,7 +183,7 @@ class ObjectPool { this._nativePool = new NativeObjectPool(dataType, this._array); } - public alloc (...args: any[]): Handle

{ + public alloc (...args: any[]): IHandle

{ const freelist = this._freelist; let i = -1; if (freelist.length) { @@ -168,13 +194,13 @@ class ObjectPool { if (i < 0) { i = this._array.length; const obj = this._ctor(arguments); - if (!obj) { return 0 as unknown as Handle

; } + if (!obj) { return 0 as unknown as IHandle

; } this._array.push(obj); } - return i + this._poolFlag as unknown as Handle

; // guarantees the handle is always not zero + return i + this._poolFlag as unknown as IHandle

; // guarantees the handle is always not zero } - public get (handle: Handle

) { + public get (handle: IHandle

) { 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'); @@ -183,7 +209,7 @@ class ObjectPool { return this._array[index]; } - public free (handle: Handle

) { + public free (handle: IHandle

) { 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'); @@ -194,6 +220,10 @@ class ObjectPool { } } +interface IBufferTypeManifest { + [key: string]: GeneralBufferElement; +} + enum PoolType { // objects RASTERIZER_STATE, @@ -208,17 +238,17 @@ enum PoolType { SUB_MODEL, } -export const NULL_HANDLE = 0 as unknown as Handle; +export const NULL_HANDLE = 0 as unknown as IHandle; -export type RasterizerStateHandle = Handle; -export type DepthStencilStateHandle = Handle; -export type BlendStateHandle = Handle; -export type DescriptorSetHandle = Handle; -export type ShaderHandle = Handle; -export type IAHandle = Handle; -export type PipelineLayoutHandle = Handle; -export type PassHandle = Handle; -export type SubModelHandle = Handle; +export type RasterizerStateHandle = IHandle; +export type DepthStencilStateHandle = IHandle; +export type BlendStateHandle = IHandle; +export type DescriptorSetHandle = IHandle; +export type ShaderHandle = IHandle; +export type InputAssemblerHandle = IHandle; +export type PipelineLayoutHandle = IHandle; +export type PassHandle = IHandle; +export type SubModelHandle = IHandle; // 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()); @@ -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, PassView); export enum SubModelView { PRIORITY, @@ -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, SubModelView); diff --git a/cocos/core/renderer/core/pass.ts b/cocos/core/renderer/core/pass.ts index 2d6cc33b762..bd6b529512a 100644 --- a/cocos/core/renderer/core/pass.ts +++ b/cocos/core/renderer/core/pass.ts @@ -604,16 +604,16 @@ export class Pass { get blocks () { return this._blocks; } // states get handle () { return this._handle; } - get priority () { return PassPool.get(this._handle, PassView.PRIORITY); } - get primitive () { return PassPool.get(this._handle, PassView.PRIMITIVE); } - get stage () { return PassPool.get(this._handle, PassView.STAGE); } - get phase () { return PassPool.get(this._handle, PassView.PHASE); } + get priority () { return PassPool.get(this._handle, PassView.PRIORITY); } + get primitive () { return PassPool.get(this._handle, PassView.PRIMITIVE); } + get stage () { return PassPool.get(this._handle, PassView.STAGE); } + get phase () { return PassPool.get(this._handle, PassView.PHASE); } get rasterizerState () { return RasterizerStatePool.get(PassPool.get(this._handle, PassView.RASTERIZER_STATE)); } get depthStencilState () { return DepthStencilStatePool.get(PassPool.get(this._handle, PassView.DEPTH_STENCIL_STATE)); } get blendState () { return BlendStatePool.get(PassPool.get(this._handle, PassView.BLEND_STATE)); } - get dynamicStates () { return PassPool.get(this._handle, PassView.DYNAMIC_STATES); } - get batchingScheme () { return PassPool.get(this._handle, PassView.BATCHING_SCHEME); } - get hash () { return PassPool.get(this._handle, PassView.HASH); } + get dynamicStates () { return PassPool.get(this._handle, PassView.DYNAMIC_STATES); } + get batchingScheme () { return PassPool.get(this._handle, PassView.BATCHING_SCHEME); } + get hash () { return PassPool.get(this._handle, PassView.HASH); } } function serializeBlendState (bs: GFXBlendState) { diff --git a/cocos/core/renderer/scene/model.ts b/cocos/core/renderer/scene/model.ts index 202738288a9..b04dd992f62 100644 --- a/cocos/core/renderer/scene/model.ts +++ b/cocos/core/renderer/scene/model.ts @@ -290,7 +290,7 @@ export class Model { this._updateLocalDescriptors(i, ds); } - const shader = ShaderPool.get(SubModelPool.get(subModel.handle, SubModelView.SHADER_0)); + const shader = ShaderPool.get(SubModelPool.get(subModel.handle, SubModelView.SHADER_0)); this._updateInstancedAttributes(shader.attributes, subModel.passes[0]); } diff --git a/cocos/core/renderer/ui/ui-batch-model.ts b/cocos/core/renderer/ui/ui-batch-model.ts index 4050b70a23a..ba4634282dc 100644 --- a/cocos/core/renderer/ui/ui-batch-model.ts +++ b/cocos/core/renderer/ui/ui-batch-model.ts @@ -30,7 +30,7 @@ import { Model, ModelType } from '../scene/model'; import { SubModel } from '../scene/submodel'; import { UIDrawBatch } from './ui-draw-batch'; import { Pass } from '../core/pass'; -import { SubModelPool, IAHandle, DescriptorSetHandle, SubModelView, IAPool, DSPool, NULL_HANDLE } from '../core/memory-pools'; +import { SubModelPool, InputAssemblerHandle, DescriptorSetHandle, SubModelView, IAPool, DSPool, NULL_HANDLE } from '../core/memory-pools'; import { RenderPriority } from '../../pipeline/define'; export class UIBatchModel extends Model { @@ -69,7 +69,7 @@ export class UIBatchModel extends Model { class UISubModel extends SubModel { - public directInitialize (passes: Pass[], iaHandle: IAHandle, dsHandle: DescriptorSetHandle) { + public directInitialize (passes: Pass[], iaHandle: InputAssemblerHandle, dsHandle: DescriptorSetHandle) { this._passes = passes; this._handle = SubModelPool.alloc(); this._flushPassInfo(); diff --git a/cocos/core/renderer/ui/ui-draw-batch.ts b/cocos/core/renderer/ui/ui-draw-batch.ts index fbdfa0dcb46..4a068eab84d 100644 --- a/cocos/core/renderer/ui/ui-draw-batch.ts +++ b/cocos/core/renderer/ui/ui-draw-batch.ts @@ -10,7 +10,7 @@ import { Camera } from '../scene/camera'; import { Model } from '../scene/model'; import { UI } from './ui'; import { GFXInputAssembler } from '../../gfx/input-assembler'; -import { IAHandle, IAPool, DescriptorSetHandle, NULL_HANDLE, DSPool } from '../core/memory-pools'; +import { InputAssemblerHandle, IAPool, DescriptorSetHandle, NULL_HANDLE, DSPool } from '../core/memory-pools'; import { programLib } from '../core/program-lib'; import { SetIndex } from '../../pipeline/define'; import { legacyCC } from '../../global-exports'; @@ -25,7 +25,7 @@ export class UIDrawBatch { public camera: Camera | null = null; public ia: GFXInputAssembler | null = null; - public hIA: IAHandle = NULL_HANDLE; + public hIA: InputAssemblerHandle = NULL_HANDLE; public model: Model | null = null; public material: Material | null = null; public texture: GFXTexture | null = null;