-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
instanced-buffer.ts
144 lines (127 loc) · 5.45 KB
/
instanced-buffer.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
/**
* @hidden
*/
import { GFXBufferUsageBit, GFXMemoryUsageBit, GFXDevice, GFXTexture } from '../gfx';
import { GFXBuffer } from '../gfx/buffer';
import { GFXInputAssembler, IGFXAttribute } from '../gfx/input-assembler';
import { IInstancedAttributeBlock, Pass } from '../renderer';
import { SubModel } from '../renderer/scene/submodel';
import { SubModelView, SubModelPool, ShaderHandle, DescriptorSetHandle, PassHandle, NULL_HANDLE } from '../renderer/core/memory-pools';
import { UniformLightingMapSampler } from './define';
export interface IInstancedItem {
count: number;
capacity: number;
vb: GFXBuffer;
data: Uint8Array;
ia: GFXInputAssembler;
stride: number;
hShader: ShaderHandle;
hDescriptorSet: DescriptorSetHandle;
lightingMap: GFXTexture;
}
const INITIAL_CAPACITY = 32;
const MAX_CAPACITY = 1024;
export class InstancedBuffer {
private static _buffers = new Map<Pass | number, InstancedBuffer>();
public static get (pass: Pass, extraKey?: number) {
const hash = extraKey ? pass.hash ^ extraKey : pass.hash;
const buffers = InstancedBuffer._buffers;
if (!buffers.has(hash)) {
const buffer = new InstancedBuffer(pass);
buffers.set(hash, buffer);
return buffer;
}
return buffers.get(hash)!;
}
public instances: IInstancedItem[] = [];
public hPass: PassHandle = NULL_HANDLE;
public hasPendingModels = false;
public dynamicOffsets: number[] = [];
private _device: GFXDevice;
constructor (pass: Pass) {
this._device = pass.device;
this.hPass = pass.handle;
}
public destroy () {
for (let i = 0; i < this.instances.length; ++i) {
const instance = this.instances[i];
instance.vb.destroy();
instance.ia.destroy();
}
this.instances.length = 0;
}
public merge (subModel: SubModel, attrs: IInstancedAttributeBlock, passIdx: number) {
const stride = attrs.buffer.length;
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) 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; }
// check same binding
if (instance.lightingMap !== lightingMap) {
continue;
}
if (instance.stride !== stride) {
// console.error(`instanced buffer stride mismatch! ${stride}/${instance.stride}`);
return;
}
if (instance.count >= instance.capacity) { // resize buffers
instance.capacity <<= 1;
const newSize = instance.stride * instance.capacity;
const oldData = instance.data;
instance.data = new Uint8Array(newSize);
instance.data.set(oldData);
instance.vb.resize(newSize);
}
if (instance.hShader !== hShader) { instance.hShader = hShader; }
if (instance.hDescriptorSet !== hDescriptorSet) { instance.hDescriptorSet = hDescriptorSet; }
instance.data.set(attrs.buffer, instance.stride * instance.count++);
this.hasPendingModels = true;
return;
}
// Create a new instance
const vb = this._device.createBuffer({
usage: GFXBufferUsageBit.VERTEX | GFXBufferUsageBit.TRANSFER_DST,
memUsage: GFXMemoryUsageBit.HOST | GFXMemoryUsageBit.DEVICE,
size: stride * INITIAL_CAPACITY, stride,
});
const data = new Uint8Array(stride * INITIAL_CAPACITY);
const vertexBuffers = sourceIA.vertexBuffers.slice();
const attributes = sourceIA.attributes.slice();
const indexBuffer = sourceIA.indexBuffer || undefined;
for (let i = 0; i < attrs.list.length; i++) {
const attr = attrs.list[i];
const newAttr: IGFXAttribute = {
name: attr.name,
format: attr.format,
stream: vertexBuffers.length,
isInstanced: true,
};
if (attr.isNormalized !== undefined) { newAttr.isNormalized = attr.isNormalized; }
attributes.push(newAttr);
}
data.set(attrs.buffer);
vertexBuffers.push(vb);
const ia = this._device.createInputAssembler({ attributes, vertexBuffers, indexBuffer });
this.instances.push({ count: 1, capacity: INITIAL_CAPACITY, vb, data, ia, stride, hShader, hDescriptorSet, lightingMap});
this.hasPendingModels = true;
}
public uploadBuffers () {
for (let i = 0; i < this.instances.length; ++i) {
const instance = this.instances[i];
if (!instance.count) { continue; }
instance.ia.instanceCount = instance.count;
instance.vb.update(instance.data);
}
}
public clear () {
for (let i = 0; i < this.instances.length; ++i) {
const instance = this.instances[i];
instance.count = 0;
}
this.hasPendingModels = false;
}
}