1 /* -*- Mode: C++; tab-width: 20; 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 "WebGLBuffer.h"
7 
8 #include "GLContext.h"
9 #include "mozilla/dom/WebGLRenderingContextBinding.h"
10 #include "WebGLContext.h"
11 #include "WebGLElementArrayCache.h"
12 
13 namespace mozilla {
14 
WebGLBuffer(WebGLContext * webgl,GLuint buf)15 WebGLBuffer::WebGLBuffer(WebGLContext* webgl, GLuint buf)
16     : WebGLRefCountedObject(webgl)
17     , mGLName(buf)
18     , mContent(Kind::Undefined)
19     , mUsage(LOCAL_GL_STATIC_DRAW)
20     , mByteLength(0)
21     , mTFBindCount(0)
22     , mNonTFBindCount(0)
23 {
24     mContext->mBuffers.insertBack(this);
25 }
26 
~WebGLBuffer()27 WebGLBuffer::~WebGLBuffer()
28 {
29     DeleteOnce();
30 }
31 
32 void
SetContentAfterBind(GLenum target)33 WebGLBuffer::SetContentAfterBind(GLenum target)
34 {
35     if (mContent != Kind::Undefined)
36         return;
37 
38     switch (target) {
39     case LOCAL_GL_ELEMENT_ARRAY_BUFFER:
40         mContent = Kind::ElementArray;
41         if (!mCache) {
42             mCache.reset(new WebGLElementArrayCache);
43         }
44         break;
45 
46     case LOCAL_GL_ARRAY_BUFFER:
47     case LOCAL_GL_PIXEL_PACK_BUFFER:
48     case LOCAL_GL_PIXEL_UNPACK_BUFFER:
49     case LOCAL_GL_UNIFORM_BUFFER:
50     case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
51     case LOCAL_GL_COPY_READ_BUFFER:
52     case LOCAL_GL_COPY_WRITE_BUFFER:
53         mContent = Kind::OtherData;
54         break;
55 
56     default:
57         MOZ_CRASH("GFX: invalid target");
58     }
59 }
60 
61 void
Delete()62 WebGLBuffer::Delete()
63 {
64     mContext->MakeContextCurrent();
65     mContext->gl->fDeleteBuffers(1, &mGLName);
66     mByteLength = 0;
67     mCache = nullptr;
68     LinkedListElement<WebGLBuffer>::remove(); // remove from mContext->mBuffers
69 }
70 
71 ////////////////////////////////////////
72 
73 static bool
ValidateBufferUsageEnum(WebGLContext * webgl,const char * funcName,GLenum usage)74 ValidateBufferUsageEnum(WebGLContext* webgl, const char* funcName, GLenum usage)
75 {
76     switch (usage) {
77     case LOCAL_GL_STREAM_DRAW:
78     case LOCAL_GL_STATIC_DRAW:
79     case LOCAL_GL_DYNAMIC_DRAW:
80         return true;
81 
82     case LOCAL_GL_DYNAMIC_COPY:
83     case LOCAL_GL_DYNAMIC_READ:
84     case LOCAL_GL_STATIC_COPY:
85     case LOCAL_GL_STATIC_READ:
86     case LOCAL_GL_STREAM_COPY:
87     case LOCAL_GL_STREAM_READ:
88         if (MOZ_LIKELY(webgl->IsWebGL2()))
89             return true;
90         break;
91 
92     default:
93         break;
94     }
95 
96     webgl->ErrorInvalidEnum("%s: Invalid `usage`: 0x%04x", funcName, usage);
97     return false;
98 }
99 
100 void
BufferData(GLenum target,size_t size,const void * data,GLenum usage)101 WebGLBuffer::BufferData(GLenum target, size_t size, const void* data, GLenum usage)
102 {
103     const char funcName[] = "bufferData";
104 
105     // Careful: data.Length() could conceivably be any uint32_t, but GLsizeiptr
106     // is like intptr_t.
107     if (!CheckedInt<GLsizeiptr>(size).isValid())
108         return mContext->ErrorOutOfMemory("%s: bad size", funcName);
109 
110     if (!ValidateBufferUsageEnum(mContext, funcName, usage))
111         return;
112 
113     const auto& gl = mContext->gl;
114     gl->MakeCurrent();
115     const ScopedLazyBind lazyBind(gl, target, this);
116     mContext->InvalidateBufferFetching();
117 
118 #ifdef XP_MACOSX
119     // bug 790879
120     if (gl->WorkAroundDriverBugs() &&
121         size > INT32_MAX)
122     {
123         mContext->ErrorOutOfMemory("%s: Allocation size too large.", funcName);
124         return;
125     }
126 #endif
127 
128     const bool sizeChanges = (size != ByteLength());
129     if (sizeChanges) {
130         gl::GLContext::LocalErrorScope errorScope(*gl);
131         gl->fBufferData(target, size, data, usage);
132         const auto error = errorScope.GetError();
133 
134         if (error) {
135             MOZ_ASSERT(error == LOCAL_GL_OUT_OF_MEMORY);
136             mContext->ErrorOutOfMemory("%s: Error from driver: 0x%04x", funcName, error);
137             return;
138         }
139     } else {
140         gl->fBufferData(target, size, data, usage);
141     }
142 
143     mUsage = usage;
144     mByteLength = size;
145 
146     // Warning: Possibly shared memory.  See bug 1225033.
147     if (!ElementArrayCacheBufferData(data, size)) {
148         mByteLength = 0;
149         mContext->ErrorOutOfMemory("%s: Failed update index buffer cache.", funcName);
150     }
151 }
152 
153 bool
ValidateRange(const char * funcName,size_t byteOffset,size_t byteLen) const154 WebGLBuffer::ValidateRange(const char* funcName, size_t byteOffset, size_t byteLen) const
155 {
156     auto availLength = mByteLength;
157     if (byteOffset > availLength) {
158         mContext->ErrorInvalidValue("%s: Offset passes the end of the buffer.", funcName);
159         return false;
160     }
161     availLength -= byteOffset;
162 
163     if (byteLen > availLength) {
164         mContext->ErrorInvalidValue("%s: Offset+size passes the end of the buffer.",
165                                     funcName);
166         return false;
167     }
168 
169     return true;
170 }
171 
172 ////////////////////////////////////////
173 
174 bool
ElementArrayCacheBufferData(const void * ptr,size_t bufferSizeInBytes)175 WebGLBuffer::ElementArrayCacheBufferData(const void* ptr,
176                                          size_t bufferSizeInBytes)
177 {
178     if (mContext->IsWebGL2())
179         return true;
180 
181     if (mContent == Kind::ElementArray)
182         return mCache->BufferData(ptr, bufferSizeInBytes);
183 
184     return true;
185 }
186 
187 void
ElementArrayCacheBufferSubData(size_t pos,const void * ptr,size_t updateSizeInBytes)188 WebGLBuffer::ElementArrayCacheBufferSubData(size_t pos, const void* ptr,
189                                             size_t updateSizeInBytes)
190 {
191     if (mContext->IsWebGL2())
192         return;
193 
194     if (mContent == Kind::ElementArray)
195         mCache->BufferSubData(pos, ptr, updateSizeInBytes);
196 }
197 
198 size_t
SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const199 WebGLBuffer::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const
200 {
201     size_t sizeOfCache = mCache ? mCache->SizeOfIncludingThis(mallocSizeOf)
202                                 : 0;
203     return mallocSizeOf(this) + sizeOfCache;
204 }
205 
206 bool
Validate(GLenum type,uint32_t maxAllowed,size_t first,size_t count) const207 WebGLBuffer::Validate(GLenum type, uint32_t maxAllowed, size_t first, size_t count) const
208 {
209     if (mContext->IsWebGL2())
210         return true;
211 
212     return mCache->Validate(type, maxAllowed, first, count);
213 }
214 
215 bool
IsElementArrayUsedWithMultipleTypes() const216 WebGLBuffer::IsElementArrayUsedWithMultipleTypes() const
217 {
218     if (mContext->IsWebGL2())
219         return false;
220 
221     return mCache->BeenUsedWithMultipleTypes();
222 }
223 
224 ////
225 
226 bool
ValidateCanBindToTarget(const char * funcName,GLenum target)227 WebGLBuffer::ValidateCanBindToTarget(const char* funcName, GLenum target)
228 {
229     /* https://www.khronos.org/registry/webgl/specs/latest/2.0/#5.1
230      *
231      * In the WebGL 2 API, buffers have their WebGL buffer type
232      * initially set to undefined. Calling bindBuffer, bindBufferRange
233      * or bindBufferBase with the target argument set to any buffer
234      * binding point except COPY_READ_BUFFER or COPY_WRITE_BUFFER will
235      * then set the WebGL buffer type of the buffer being bound
236      * according to the table above.
237      *
238      * Any call to one of these functions which attempts to bind a
239      * WebGLBuffer that has the element array WebGL buffer type to a
240      * binding point that falls under other data, or bind a
241      * WebGLBuffer which has the other data WebGL buffer type to
242      * ELEMENT_ARRAY_BUFFER will generate an INVALID_OPERATION error,
243      * and the state of the binding point will remain untouched.
244      */
245 
246     if (mContent == WebGLBuffer::Kind::Undefined)
247         return true;
248 
249     switch (target) {
250     case LOCAL_GL_COPY_READ_BUFFER:
251     case LOCAL_GL_COPY_WRITE_BUFFER:
252         return true;
253 
254     case LOCAL_GL_ELEMENT_ARRAY_BUFFER:
255         if (mContent == WebGLBuffer::Kind::ElementArray)
256             return true;
257         break;
258 
259     case LOCAL_GL_ARRAY_BUFFER:
260     case LOCAL_GL_PIXEL_PACK_BUFFER:
261     case LOCAL_GL_PIXEL_UNPACK_BUFFER:
262     case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
263     case LOCAL_GL_UNIFORM_BUFFER:
264         if (mContent == WebGLBuffer::Kind::OtherData)
265             return true;
266         break;
267 
268     default:
269         MOZ_CRASH();
270     }
271 
272     const auto dataType = (mContent == WebGLBuffer::Kind::OtherData) ? "other"
273                                                                      : "element";
274     mContext->ErrorInvalidOperation("%s: Buffer already contains %s data.", funcName,
275                                     dataType);
276     return false;
277 }
278 
279 JSObject*
WrapObject(JSContext * cx,JS::Handle<JSObject * > givenProto)280 WebGLBuffer::WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto)
281 {
282     return dom::WebGLBufferBinding::Wrap(cx, this, givenProto);
283 }
284 
285 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLBuffer)
286 
287 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebGLBuffer, AddRef)
288 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WebGLBuffer, Release)
289 
290 } // namespace mozilla
291