1 | import { ENV, BUFFER_TYPE } from '@pixi/constants';
|
2 | import { ExtensionType, extensions } from '@pixi/extensions';
|
3 | import { settings } from '@pixi/settings';
|
4 |
|
5 | const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 };
|
6 | class GeometrySystem {
|
7 | constructor(renderer) {
|
8 | this.renderer = renderer;
|
9 | this._activeGeometry = null;
|
10 | this._activeVao = null;
|
11 | this.hasVao = true;
|
12 | this.hasInstance = true;
|
13 | this.canUseUInt32ElementIndex = false;
|
14 | this.managedGeometries = {};
|
15 | }
|
16 | contextChange() {
|
17 | this.disposeAll(true);
|
18 | const gl = this.gl = this.renderer.gl;
|
19 | const context = this.renderer.context;
|
20 | this.CONTEXT_UID = this.renderer.CONTEXT_UID;
|
21 | if (context.webGLVersion !== 2) {
|
22 | let nativeVaoExtension = this.renderer.context.extensions.vertexArrayObject;
|
23 | if (settings.PREFER_ENV === ENV.WEBGL_LEGACY) {
|
24 | nativeVaoExtension = null;
|
25 | }
|
26 | if (nativeVaoExtension) {
|
27 | gl.createVertexArray = () => nativeVaoExtension.createVertexArrayOES();
|
28 | gl.bindVertexArray = (vao) => nativeVaoExtension.bindVertexArrayOES(vao);
|
29 | gl.deleteVertexArray = (vao) => nativeVaoExtension.deleteVertexArrayOES(vao);
|
30 | } else {
|
31 | this.hasVao = false;
|
32 | gl.createVertexArray = () => null;
|
33 | gl.bindVertexArray = () => null;
|
34 | gl.deleteVertexArray = () => null;
|
35 | }
|
36 | }
|
37 | if (context.webGLVersion !== 2) {
|
38 | const instanceExt = gl.getExtension("ANGLE_instanced_arrays");
|
39 | if (instanceExt) {
|
40 | gl.vertexAttribDivisor = (a, b) => instanceExt.vertexAttribDivisorANGLE(a, b);
|
41 | gl.drawElementsInstanced = (a, b, c, d, e) => instanceExt.drawElementsInstancedANGLE(a, b, c, d, e);
|
42 | gl.drawArraysInstanced = (a, b, c, d) => instanceExt.drawArraysInstancedANGLE(a, b, c, d);
|
43 | } else {
|
44 | this.hasInstance = false;
|
45 | }
|
46 | }
|
47 | this.canUseUInt32ElementIndex = context.webGLVersion === 2 || !!context.extensions.uint32ElementIndex;
|
48 | }
|
49 | bind(geometry, shader) {
|
50 | shader = shader || this.renderer.shader.shader;
|
51 | const { gl } = this;
|
52 | let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID];
|
53 | let incRefCount = false;
|
54 | if (!vaos) {
|
55 | this.managedGeometries[geometry.id] = geometry;
|
56 | geometry.disposeRunner.add(this);
|
57 | geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {};
|
58 | incRefCount = true;
|
59 | }
|
60 | const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader, incRefCount);
|
61 | this._activeGeometry = geometry;
|
62 | if (this._activeVao !== vao) {
|
63 | this._activeVao = vao;
|
64 | if (this.hasVao) {
|
65 | gl.bindVertexArray(vao);
|
66 | } else {
|
67 | this.activateVao(geometry, shader.program);
|
68 | }
|
69 | }
|
70 | this.updateBuffers();
|
71 | }
|
72 | reset() {
|
73 | this.unbind();
|
74 | }
|
75 | updateBuffers() {
|
76 | const geometry = this._activeGeometry;
|
77 | const bufferSystem = this.renderer.buffer;
|
78 | for (let i = 0; i < geometry.buffers.length; i++) {
|
79 | const buffer = geometry.buffers[i];
|
80 | bufferSystem.update(buffer);
|
81 | }
|
82 | }
|
83 | checkCompatibility(geometry, program) {
|
84 | const geometryAttributes = geometry.attributes;
|
85 | const shaderAttributes = program.attributeData;
|
86 | for (const j in shaderAttributes) {
|
87 | if (!geometryAttributes[j]) {
|
88 | throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`);
|
89 | }
|
90 | }
|
91 | }
|
92 | getSignature(geometry, program) {
|
93 | const attribs = geometry.attributes;
|
94 | const shaderAttributes = program.attributeData;
|
95 | const strings = ["g", geometry.id];
|
96 | for (const i in attribs) {
|
97 | if (shaderAttributes[i]) {
|
98 | strings.push(i, shaderAttributes[i].location);
|
99 | }
|
100 | }
|
101 | return strings.join("-");
|
102 | }
|
103 | initGeometryVao(geometry, shader, incRefCount = true) {
|
104 | const gl = this.gl;
|
105 | const CONTEXT_UID = this.CONTEXT_UID;
|
106 | const bufferSystem = this.renderer.buffer;
|
107 | const program = shader.program;
|
108 | if (!program.glPrograms[CONTEXT_UID]) {
|
109 | this.renderer.shader.generateProgram(shader);
|
110 | }
|
111 | this.checkCompatibility(geometry, program);
|
112 | const signature = this.getSignature(geometry, program);
|
113 | const vaoObjectHash = geometry.glVertexArrayObjects[this.CONTEXT_UID];
|
114 | let vao = vaoObjectHash[signature];
|
115 | if (vao) {
|
116 | vaoObjectHash[program.id] = vao;
|
117 | return vao;
|
118 | }
|
119 | const buffers = geometry.buffers;
|
120 | const attributes = geometry.attributes;
|
121 | const tempStride = {};
|
122 | const tempStart = {};
|
123 | for (const j in buffers) {
|
124 | tempStride[j] = 0;
|
125 | tempStart[j] = 0;
|
126 | }
|
127 | for (const j in attributes) {
|
128 | if (!attributes[j].size && program.attributeData[j]) {
|
129 | attributes[j].size = program.attributeData[j].size;
|
130 | } else if (!attributes[j].size) {
|
131 | console.warn(`PIXI Geometry attribute '${j}' size cannot be determined (likely the bound shader does not have the attribute)`);
|
132 | }
|
133 | tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type];
|
134 | }
|
135 | for (const j in attributes) {
|
136 | const attribute = attributes[j];
|
137 | const attribSize = attribute.size;
|
138 | if (attribute.stride === void 0) {
|
139 | if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) {
|
140 | attribute.stride = 0;
|
141 | } else {
|
142 | attribute.stride = tempStride[attribute.buffer];
|
143 | }
|
144 | }
|
145 | if (attribute.start === void 0) {
|
146 | attribute.start = tempStart[attribute.buffer];
|
147 | tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type];
|
148 | }
|
149 | }
|
150 | vao = gl.createVertexArray();
|
151 | gl.bindVertexArray(vao);
|
152 | for (let i = 0; i < buffers.length; i++) {
|
153 | const buffer = buffers[i];
|
154 | bufferSystem.bind(buffer);
|
155 | if (incRefCount) {
|
156 | buffer._glBuffers[CONTEXT_UID].refCount++;
|
157 | }
|
158 | }
|
159 | this.activateVao(geometry, program);
|
160 | vaoObjectHash[program.id] = vao;
|
161 | vaoObjectHash[signature] = vao;
|
162 | gl.bindVertexArray(null);
|
163 | bufferSystem.unbind(BUFFER_TYPE.ARRAY_BUFFER);
|
164 | return vao;
|
165 | }
|
166 | disposeGeometry(geometry, contextLost) {
|
167 | if (!this.managedGeometries[geometry.id]) {
|
168 | return;
|
169 | }
|
170 | delete this.managedGeometries[geometry.id];
|
171 | const vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID];
|
172 | const gl = this.gl;
|
173 | const buffers = geometry.buffers;
|
174 | const bufferSystem = this.renderer?.buffer;
|
175 | geometry.disposeRunner.remove(this);
|
176 | if (!vaos) {
|
177 | return;
|
178 | }
|
179 | if (bufferSystem) {
|
180 | for (let i = 0; i < buffers.length; i++) {
|
181 | const buf = buffers[i]._glBuffers[this.CONTEXT_UID];
|
182 | if (buf) {
|
183 | buf.refCount--;
|
184 | if (buf.refCount === 0 && !contextLost) {
|
185 | bufferSystem.dispose(buffers[i], contextLost);
|
186 | }
|
187 | }
|
188 | }
|
189 | }
|
190 | if (!contextLost) {
|
191 | for (const vaoId in vaos) {
|
192 | if (vaoId[0] === "g") {
|
193 | const vao = vaos[vaoId];
|
194 | if (this._activeVao === vao) {
|
195 | this.unbind();
|
196 | }
|
197 | gl.deleteVertexArray(vao);
|
198 | }
|
199 | }
|
200 | }
|
201 | delete geometry.glVertexArrayObjects[this.CONTEXT_UID];
|
202 | }
|
203 | disposeAll(contextLost) {
|
204 | const all = Object.keys(this.managedGeometries);
|
205 | for (let i = 0; i < all.length; i++) {
|
206 | this.disposeGeometry(this.managedGeometries[all[i]], contextLost);
|
207 | }
|
208 | }
|
209 | activateVao(geometry, program) {
|
210 | const gl = this.gl;
|
211 | const CONTEXT_UID = this.CONTEXT_UID;
|
212 | const bufferSystem = this.renderer.buffer;
|
213 | const buffers = geometry.buffers;
|
214 | const attributes = geometry.attributes;
|
215 | if (geometry.indexBuffer) {
|
216 | bufferSystem.bind(geometry.indexBuffer);
|
217 | }
|
218 | let lastBuffer = null;
|
219 | for (const j in attributes) {
|
220 | const attribute = attributes[j];
|
221 | const buffer = buffers[attribute.buffer];
|
222 | const glBuffer = buffer._glBuffers[CONTEXT_UID];
|
223 | if (program.attributeData[j]) {
|
224 | if (lastBuffer !== glBuffer) {
|
225 | bufferSystem.bind(buffer);
|
226 | lastBuffer = glBuffer;
|
227 | }
|
228 | const location = program.attributeData[j].location;
|
229 | gl.enableVertexAttribArray(location);
|
230 | gl.vertexAttribPointer(location, attribute.size, attribute.type || gl.FLOAT, attribute.normalized, attribute.stride, attribute.start);
|
231 | if (attribute.instance) {
|
232 | if (this.hasInstance) {
|
233 | gl.vertexAttribDivisor(location, attribute.divisor);
|
234 | } else {
|
235 | throw new Error("geometry error, GPU Instancing is not supported on this device");
|
236 | }
|
237 | }
|
238 | }
|
239 | }
|
240 | }
|
241 | draw(type, size, start, instanceCount) {
|
242 | const { gl } = this;
|
243 | const geometry = this._activeGeometry;
|
244 | if (geometry.indexBuffer) {
|
245 | const byteSize = geometry.indexBuffer.data.BYTES_PER_ELEMENT;
|
246 | const glType = byteSize === 2 ? gl.UNSIGNED_SHORT : gl.UNSIGNED_INT;
|
247 | if (byteSize === 2 || byteSize === 4 && this.canUseUInt32ElementIndex) {
|
248 | if (geometry.instanced) {
|
249 | gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, glType, (start || 0) * byteSize, instanceCount || 1);
|
250 | } else {
|
251 | gl.drawElements(type, size || geometry.indexBuffer.data.length, glType, (start || 0) * byteSize);
|
252 | }
|
253 | } else {
|
254 | console.warn("unsupported index buffer type: uint32");
|
255 | }
|
256 | } else if (geometry.instanced) {
|
257 | gl.drawArraysInstanced(type, start, size || geometry.getSize(), instanceCount || 1);
|
258 | } else {
|
259 | gl.drawArrays(type, start, size || geometry.getSize());
|
260 | }
|
261 | return this;
|
262 | }
|
263 | unbind() {
|
264 | this.gl.bindVertexArray(null);
|
265 | this._activeVao = null;
|
266 | this._activeGeometry = null;
|
267 | }
|
268 | destroy() {
|
269 | this.renderer = null;
|
270 | }
|
271 | }
|
272 | GeometrySystem.extension = {
|
273 | type: ExtensionType.RendererSystem,
|
274 | name: "geometry"
|
275 | };
|
276 | extensions.add(GeometrySystem);
|
277 |
|
278 | export { GeometrySystem };
|
279 |
|