|
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "WebGLContext.h" |
|
7 #include "GLContext.h" |
|
8 #include "WebGLBuffer.h" |
|
9 #include "WebGLVertexArray.h" |
|
10 |
|
11 using namespace mozilla; |
|
12 using namespace mozilla::dom; |
|
13 |
|
14 void |
|
15 WebGLContext::BindBuffer(GLenum target, WebGLBuffer *buffer) |
|
16 { |
|
17 if (IsContextLost()) |
|
18 return; |
|
19 |
|
20 if (!ValidateObjectAllowDeletedOrNull("bindBuffer", buffer)) |
|
21 return; |
|
22 |
|
23 // silently ignore a deleted buffer |
|
24 if (buffer && buffer->IsDeleted()) |
|
25 return; |
|
26 |
|
27 WebGLRefPtr<WebGLBuffer>* bufferSlot = GetBufferSlotByTarget(target, "bindBuffer"); |
|
28 |
|
29 if (!bufferSlot) { |
|
30 return; |
|
31 } |
|
32 |
|
33 if (buffer) { |
|
34 if (!buffer->Target()) { |
|
35 buffer->SetTarget(target); |
|
36 buffer->SetHasEverBeenBound(true); |
|
37 } else if (target != buffer->Target()) { |
|
38 return ErrorInvalidOperation("bindBuffer: buffer already bound to a different target"); |
|
39 } |
|
40 } |
|
41 |
|
42 *bufferSlot = buffer; |
|
43 |
|
44 MakeContextCurrent(); |
|
45 |
|
46 gl->fBindBuffer(target, buffer ? buffer->GLName() : 0); |
|
47 } |
|
48 |
|
49 void |
|
50 WebGLContext::BindBufferBase(GLenum target, GLuint index, WebGLBuffer* buffer) |
|
51 { |
|
52 if (IsContextLost()) |
|
53 return; |
|
54 |
|
55 if (!ValidateObjectAllowDeletedOrNull("bindBufferBase", buffer)) |
|
56 return; |
|
57 |
|
58 // silently ignore a deleted buffer |
|
59 if (buffer && buffer->IsDeleted()) { |
|
60 return; |
|
61 } |
|
62 |
|
63 WebGLRefPtr<WebGLBuffer>* indexedBufferSlot = GetBufferSlotByTargetIndexed(target, index, "bindBufferBase"); |
|
64 |
|
65 if (!indexedBufferSlot) { |
|
66 return; |
|
67 } |
|
68 |
|
69 if (buffer) { |
|
70 if (!buffer->Target()) { |
|
71 buffer->SetTarget(target); |
|
72 buffer->SetHasEverBeenBound(true); |
|
73 } else if (target != buffer->Target()) { |
|
74 return ErrorInvalidOperation("bindBuffer: buffer already bound to a different target"); |
|
75 } |
|
76 } |
|
77 |
|
78 WebGLRefPtr<WebGLBuffer>* bufferSlot = GetBufferSlotByTarget(target, "bindBuffer"); |
|
79 |
|
80 MOZ_ASSERT(bufferSlot, "GetBufferSlotByTarget(Indexed) mismatch"); |
|
81 |
|
82 *indexedBufferSlot = buffer; |
|
83 *bufferSlot = buffer; |
|
84 |
|
85 MakeContextCurrent(); |
|
86 |
|
87 gl->fBindBufferBase(target, index, buffer ? buffer->GLName() : 0); |
|
88 } |
|
89 |
|
90 void |
|
91 WebGLContext::BindBufferRange(GLenum target, GLuint index, WebGLBuffer* buffer, |
|
92 WebGLintptr offset, WebGLsizeiptr size) |
|
93 { |
|
94 if (IsContextLost()) |
|
95 return; |
|
96 |
|
97 if (!ValidateObjectAllowDeletedOrNull("bindBufferRange", buffer)) |
|
98 return; |
|
99 |
|
100 // silently ignore a deleted buffer |
|
101 if (buffer && buffer->IsDeleted()) |
|
102 return; |
|
103 |
|
104 WebGLRefPtr<WebGLBuffer>* indexedBufferSlot = GetBufferSlotByTargetIndexed(target, index, "bindBufferBase"); |
|
105 |
|
106 if (!indexedBufferSlot) { |
|
107 return; |
|
108 } |
|
109 |
|
110 if (buffer) { |
|
111 if (!buffer->Target()) { |
|
112 buffer->SetTarget(target); |
|
113 buffer->SetHasEverBeenBound(true); |
|
114 } else if (target != buffer->Target()) { |
|
115 return ErrorInvalidOperation("bindBuffer: buffer already bound to a different target"); |
|
116 } |
|
117 CheckedInt<WebGLsizeiptr> checked_neededByteLength = CheckedInt<WebGLsizeiptr>(offset) + size; |
|
118 if (!checked_neededByteLength.isValid() || |
|
119 checked_neededByteLength.value() > buffer->ByteLength()) |
|
120 { |
|
121 return ErrorInvalidValue("bindBufferRange: invalid range"); |
|
122 } |
|
123 } |
|
124 |
|
125 WebGLRefPtr<WebGLBuffer>* bufferSlot = GetBufferSlotByTarget(target, "bindBuffer"); |
|
126 |
|
127 MOZ_ASSERT(bufferSlot, "GetBufferSlotByTarget(Indexed) mismatch"); |
|
128 |
|
129 *indexedBufferSlot = buffer; |
|
130 *bufferSlot = buffer; |
|
131 |
|
132 MakeContextCurrent(); |
|
133 |
|
134 gl->fBindBufferRange(target, index, buffer ? buffer->GLName() : 0, offset, size); |
|
135 } |
|
136 |
|
137 void |
|
138 WebGLContext::BufferData(GLenum target, WebGLsizeiptr size, |
|
139 GLenum usage) |
|
140 { |
|
141 if (IsContextLost()) |
|
142 return; |
|
143 |
|
144 WebGLRefPtr<WebGLBuffer>* bufferSlot = GetBufferSlotByTarget(target, "bufferData"); |
|
145 |
|
146 if (!bufferSlot) { |
|
147 return; |
|
148 } |
|
149 |
|
150 if (size < 0) |
|
151 return ErrorInvalidValue("bufferData: negative size"); |
|
152 |
|
153 if (!ValidateBufferUsageEnum(usage, "bufferData: usage")) |
|
154 return; |
|
155 |
|
156 // careful: WebGLsizeiptr is always 64-bit, but GLsizeiptr is like intptr_t. |
|
157 if (!CheckedInt<GLsizeiptr>(size).isValid()) |
|
158 return ErrorOutOfMemory("bufferData: bad size"); |
|
159 |
|
160 WebGLBuffer* boundBuffer = bufferSlot->get(); |
|
161 |
|
162 if (!boundBuffer) |
|
163 return ErrorInvalidOperation("bufferData: no buffer bound!"); |
|
164 |
|
165 void* zeroBuffer = calloc(size, 1); |
|
166 if (!zeroBuffer) |
|
167 return ErrorOutOfMemory("bufferData: out of memory"); |
|
168 |
|
169 MakeContextCurrent(); |
|
170 InvalidateBufferFetching(); |
|
171 |
|
172 GLenum error = CheckedBufferData(target, size, zeroBuffer, usage); |
|
173 free(zeroBuffer); |
|
174 |
|
175 if (error) { |
|
176 GenerateWarning("bufferData generated error %s", ErrorName(error)); |
|
177 return; |
|
178 } |
|
179 |
|
180 boundBuffer->SetByteLength(size); |
|
181 if (!boundBuffer->ElementArrayCacheBufferData(nullptr, size)) { |
|
182 return ErrorOutOfMemory("bufferData: out of memory"); |
|
183 } |
|
184 } |
|
185 |
|
186 void |
|
187 WebGLContext::BufferData(GLenum target, |
|
188 const Nullable<ArrayBuffer> &maybeData, |
|
189 GLenum usage) |
|
190 { |
|
191 if (IsContextLost()) |
|
192 return; |
|
193 |
|
194 if (maybeData.IsNull()) { |
|
195 // see http://www.khronos.org/bugzilla/show_bug.cgi?id=386 |
|
196 return ErrorInvalidValue("bufferData: null object passed"); |
|
197 } |
|
198 |
|
199 WebGLRefPtr<WebGLBuffer>* bufferSlot = GetBufferSlotByTarget(target, "bufferData"); |
|
200 |
|
201 if (!bufferSlot) { |
|
202 return; |
|
203 } |
|
204 |
|
205 const ArrayBuffer& data = maybeData.Value(); |
|
206 data.ComputeLengthAndData(); |
|
207 |
|
208 // Careful: data.Length() could conceivably be any uint32_t, but GLsizeiptr |
|
209 // is like intptr_t. |
|
210 if (!CheckedInt<GLsizeiptr>(data.Length()).isValid()) |
|
211 return ErrorOutOfMemory("bufferData: bad size"); |
|
212 |
|
213 if (!ValidateBufferUsageEnum(usage, "bufferData: usage")) |
|
214 return; |
|
215 |
|
216 WebGLBuffer* boundBuffer = bufferSlot->get(); |
|
217 |
|
218 if (!boundBuffer) |
|
219 return ErrorInvalidOperation("bufferData: no buffer bound!"); |
|
220 |
|
221 MakeContextCurrent(); |
|
222 InvalidateBufferFetching(); |
|
223 |
|
224 GLenum error = CheckedBufferData(target, data.Length(), data.Data(), usage); |
|
225 |
|
226 if (error) { |
|
227 GenerateWarning("bufferData generated error %s", ErrorName(error)); |
|
228 return; |
|
229 } |
|
230 |
|
231 boundBuffer->SetByteLength(data.Length()); |
|
232 if (!boundBuffer->ElementArrayCacheBufferData(data.Data(), data.Length())) { |
|
233 return ErrorOutOfMemory("bufferData: out of memory"); |
|
234 } |
|
235 } |
|
236 |
|
237 void |
|
238 WebGLContext::BufferData(GLenum target, const ArrayBufferView& data, |
|
239 GLenum usage) |
|
240 { |
|
241 if (IsContextLost()) |
|
242 return; |
|
243 |
|
244 WebGLRefPtr<WebGLBuffer>* bufferSlot = GetBufferSlotByTarget(target, "bufferSubData"); |
|
245 |
|
246 if (!bufferSlot) { |
|
247 return; |
|
248 } |
|
249 |
|
250 if (!ValidateBufferUsageEnum(usage, "bufferData: usage")) |
|
251 return; |
|
252 |
|
253 WebGLBuffer* boundBuffer = bufferSlot->get(); |
|
254 |
|
255 if (!boundBuffer) |
|
256 return ErrorInvalidOperation("bufferData: no buffer bound!"); |
|
257 |
|
258 data.ComputeLengthAndData(); |
|
259 |
|
260 // Careful: data.Length() could conceivably be any uint32_t, but GLsizeiptr |
|
261 // is like intptr_t. |
|
262 if (!CheckedInt<GLsizeiptr>(data.Length()).isValid()) |
|
263 return ErrorOutOfMemory("bufferData: bad size"); |
|
264 |
|
265 InvalidateBufferFetching(); |
|
266 MakeContextCurrent(); |
|
267 |
|
268 GLenum error = CheckedBufferData(target, data.Length(), data.Data(), usage); |
|
269 if (error) { |
|
270 GenerateWarning("bufferData generated error %s", ErrorName(error)); |
|
271 return; |
|
272 } |
|
273 |
|
274 boundBuffer->SetByteLength(data.Length()); |
|
275 if (!boundBuffer->ElementArrayCacheBufferData(data.Data(), data.Length())) { |
|
276 return ErrorOutOfMemory("bufferData: out of memory"); |
|
277 } |
|
278 } |
|
279 |
|
280 void |
|
281 WebGLContext::BufferSubData(GLenum target, WebGLsizeiptr byteOffset, |
|
282 const Nullable<ArrayBuffer> &maybeData) |
|
283 { |
|
284 if (IsContextLost()) |
|
285 return; |
|
286 |
|
287 if (maybeData.IsNull()) { |
|
288 // see http://www.khronos.org/bugzilla/show_bug.cgi?id=386 |
|
289 return; |
|
290 } |
|
291 |
|
292 WebGLRefPtr<WebGLBuffer>* bufferSlot = GetBufferSlotByTarget(target, "bufferSubData"); |
|
293 |
|
294 if (!bufferSlot) { |
|
295 return; |
|
296 } |
|
297 |
|
298 if (byteOffset < 0) |
|
299 return ErrorInvalidValue("bufferSubData: negative offset"); |
|
300 |
|
301 WebGLBuffer* boundBuffer = bufferSlot->get(); |
|
302 |
|
303 if (!boundBuffer) |
|
304 return ErrorInvalidOperation("bufferData: no buffer bound!"); |
|
305 |
|
306 const ArrayBuffer& data = maybeData.Value(); |
|
307 data.ComputeLengthAndData(); |
|
308 |
|
309 CheckedInt<WebGLsizeiptr> checked_neededByteLength = CheckedInt<WebGLsizeiptr>(byteOffset) + data.Length(); |
|
310 if (!checked_neededByteLength.isValid()) |
|
311 return ErrorInvalidValue("bufferSubData: integer overflow computing the needed byte length"); |
|
312 |
|
313 if (checked_neededByteLength.value() > boundBuffer->ByteLength()) |
|
314 return ErrorInvalidValue("bufferSubData: not enough data - operation requires %d bytes, but buffer only has %d bytes", |
|
315 checked_neededByteLength.value(), boundBuffer->ByteLength()); |
|
316 |
|
317 MakeContextCurrent(); |
|
318 |
|
319 boundBuffer->ElementArrayCacheBufferSubData(byteOffset, data.Data(), data.Length()); |
|
320 |
|
321 gl->fBufferSubData(target, byteOffset, data.Length(), data.Data()); |
|
322 } |
|
323 |
|
324 void |
|
325 WebGLContext::BufferSubData(GLenum target, WebGLsizeiptr byteOffset, |
|
326 const ArrayBufferView& data) |
|
327 { |
|
328 if (IsContextLost()) |
|
329 return; |
|
330 |
|
331 WebGLRefPtr<WebGLBuffer>* bufferSlot = GetBufferSlotByTarget(target, "bufferSubData"); |
|
332 |
|
333 if (!bufferSlot) { |
|
334 return; |
|
335 } |
|
336 |
|
337 if (byteOffset < 0) |
|
338 return ErrorInvalidValue("bufferSubData: negative offset"); |
|
339 |
|
340 WebGLBuffer* boundBuffer = bufferSlot->get(); |
|
341 |
|
342 if (!boundBuffer) |
|
343 return ErrorInvalidOperation("bufferSubData: no buffer bound!"); |
|
344 |
|
345 data.ComputeLengthAndData(); |
|
346 |
|
347 CheckedInt<WebGLsizeiptr> checked_neededByteLength = CheckedInt<WebGLsizeiptr>(byteOffset) + data.Length(); |
|
348 if (!checked_neededByteLength.isValid()) |
|
349 return ErrorInvalidValue("bufferSubData: integer overflow computing the needed byte length"); |
|
350 |
|
351 if (checked_neededByteLength.value() > boundBuffer->ByteLength()) |
|
352 return ErrorInvalidValue("bufferSubData: not enough data -- operation requires %d bytes, but buffer only has %d bytes", |
|
353 checked_neededByteLength.value(), boundBuffer->ByteLength()); |
|
354 |
|
355 boundBuffer->ElementArrayCacheBufferSubData(byteOffset, data.Data(), data.Length()); |
|
356 |
|
357 MakeContextCurrent(); |
|
358 gl->fBufferSubData(target, byteOffset, data.Length(), data.Data()); |
|
359 } |
|
360 |
|
361 already_AddRefed<WebGLBuffer> |
|
362 WebGLContext::CreateBuffer() |
|
363 { |
|
364 if (IsContextLost()) |
|
365 return nullptr; |
|
366 |
|
367 nsRefPtr<WebGLBuffer> globj = new WebGLBuffer(this); |
|
368 return globj.forget(); |
|
369 } |
|
370 |
|
371 void |
|
372 WebGLContext::DeleteBuffer(WebGLBuffer *buffer) |
|
373 { |
|
374 if (IsContextLost()) |
|
375 return; |
|
376 |
|
377 if (!ValidateObjectAllowDeletedOrNull("deleteBuffer", buffer)) |
|
378 return; |
|
379 |
|
380 if (!buffer || buffer->IsDeleted()) |
|
381 return; |
|
382 |
|
383 if (mBoundArrayBuffer == buffer) { |
|
384 BindBuffer(LOCAL_GL_ARRAY_BUFFER, |
|
385 static_cast<WebGLBuffer*>(nullptr)); |
|
386 } |
|
387 |
|
388 if (mBoundVertexArray->mBoundElementArrayBuffer == buffer) { |
|
389 BindBuffer(LOCAL_GL_ELEMENT_ARRAY_BUFFER, |
|
390 static_cast<WebGLBuffer*>(nullptr)); |
|
391 } |
|
392 |
|
393 for (int32_t i = 0; i < mGLMaxVertexAttribs; i++) { |
|
394 if (mBoundVertexArray->HasAttrib(i) && mBoundVertexArray->mAttribs[i].buf == buffer) |
|
395 mBoundVertexArray->mAttribs[i].buf = nullptr; |
|
396 } |
|
397 |
|
398 buffer->RequestDelete(); |
|
399 } |
|
400 |
|
401 bool |
|
402 WebGLContext::IsBuffer(WebGLBuffer *buffer) |
|
403 { |
|
404 if (IsContextLost()) |
|
405 return false; |
|
406 |
|
407 return ValidateObjectAllowDeleted("isBuffer", buffer) && |
|
408 !buffer->IsDeleted() && |
|
409 buffer->HasEverBeenBound(); |
|
410 } |
|
411 |
|
412 bool |
|
413 WebGLContext::ValidateBufferUsageEnum(GLenum target, const char *infos) |
|
414 { |
|
415 switch (target) { |
|
416 case LOCAL_GL_STREAM_DRAW: |
|
417 case LOCAL_GL_STATIC_DRAW: |
|
418 case LOCAL_GL_DYNAMIC_DRAW: |
|
419 return true; |
|
420 default: |
|
421 break; |
|
422 } |
|
423 |
|
424 ErrorInvalidEnumInfo(infos, target); |
|
425 return false; |
|
426 } |
|
427 |
|
428 WebGLRefPtr<WebGLBuffer>* |
|
429 WebGLContext::GetBufferSlotByTarget(GLenum target, const char* infos) |
|
430 { |
|
431 switch (target) { |
|
432 case LOCAL_GL_ARRAY_BUFFER: |
|
433 return &mBoundArrayBuffer; |
|
434 |
|
435 case LOCAL_GL_ELEMENT_ARRAY_BUFFER: |
|
436 return &mBoundVertexArray->mBoundElementArrayBuffer; |
|
437 |
|
438 case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER: |
|
439 if (!IsWebGL2()) { |
|
440 break; |
|
441 } |
|
442 return &mBoundTransformFeedbackBuffer; |
|
443 |
|
444 default: |
|
445 break; |
|
446 } |
|
447 |
|
448 ErrorInvalidEnum("%s: target: invalid enum value 0x%x", infos, target); |
|
449 return nullptr; |
|
450 } |
|
451 |
|
452 WebGLRefPtr<WebGLBuffer>* |
|
453 WebGLContext::GetBufferSlotByTargetIndexed(GLenum target, GLuint index, const char* infos) |
|
454 { |
|
455 switch (target) { |
|
456 case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER: |
|
457 if (index >= mGLMaxTransformFeedbackSeparateAttribs) { |
|
458 ErrorInvalidValue("%s: index should be less than MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS", infos, index); |
|
459 return nullptr; |
|
460 } |
|
461 return nullptr; // See bug 903594 |
|
462 |
|
463 default: |
|
464 break; |
|
465 } |
|
466 |
|
467 ErrorInvalidEnum("%s: target: invalid enum value 0x%x", infos, target); |
|
468 return nullptr; |
|
469 } |
|
470 |
|
471 GLenum |
|
472 WebGLContext::CheckedBufferData(GLenum target, |
|
473 GLsizeiptr size, |
|
474 const GLvoid *data, |
|
475 GLenum usage) |
|
476 { |
|
477 #ifdef XP_MACOSX |
|
478 // bug 790879 |
|
479 if (gl->WorkAroundDriverBugs() && |
|
480 int64_t(size) > INT32_MAX) // the cast avoids a potential always-true warning on 32bit |
|
481 { |
|
482 GenerateWarning("Rejecting valid bufferData call with size %lu to avoid a Mac bug", size); |
|
483 return LOCAL_GL_INVALID_VALUE; |
|
484 } |
|
485 #endif |
|
486 WebGLBuffer *boundBuffer = nullptr; |
|
487 if (target == LOCAL_GL_ARRAY_BUFFER) { |
|
488 boundBuffer = mBoundArrayBuffer; |
|
489 } else if (target == LOCAL_GL_ELEMENT_ARRAY_BUFFER) { |
|
490 boundBuffer = mBoundVertexArray->mBoundElementArrayBuffer; |
|
491 } |
|
492 MOZ_ASSERT(boundBuffer != nullptr, "no buffer bound for this target"); |
|
493 |
|
494 bool sizeChanges = uint32_t(size) != boundBuffer->ByteLength(); |
|
495 if (sizeChanges) { |
|
496 GetAndFlushUnderlyingGLErrors(); |
|
497 gl->fBufferData(target, size, data, usage); |
|
498 GLenum error = GetAndFlushUnderlyingGLErrors(); |
|
499 return error; |
|
500 } else { |
|
501 gl->fBufferData(target, size, data, usage); |
|
502 return LOCAL_GL_NO_ERROR; |
|
503 } |
|
504 } |