/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "WebGLContext.h" #include "GLContext.h" #include "WebGLBuffer.h" #include "WebGLVertexArray.h" #include "mozilla/CheckedInt.h" namespace mozilla { WebGLRefPtr* WebGLContext::ValidateBufferSlot(const char* funcName, GLenum target) { WebGLRefPtr* slot = nullptr; switch (target) { case LOCAL_GL_ARRAY_BUFFER: slot = &mBoundArrayBuffer; break; case LOCAL_GL_ELEMENT_ARRAY_BUFFER: slot = &(mBoundVertexArray->mElementArrayBuffer); break; } if (IsWebGL2()) { switch (target) { case LOCAL_GL_COPY_READ_BUFFER: slot = &mBoundCopyReadBuffer; break; case LOCAL_GL_COPY_WRITE_BUFFER: slot = &mBoundCopyWriteBuffer; break; case LOCAL_GL_PIXEL_PACK_BUFFER: slot = &mBoundPixelPackBuffer; break; case LOCAL_GL_PIXEL_UNPACK_BUFFER: slot = &mBoundPixelUnpackBuffer; break; case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER: slot = &(mBoundTransformFeedback->mGenericBufferBinding); break; case LOCAL_GL_UNIFORM_BUFFER: slot = &mBoundUniformBuffer; break; } } if (!slot) { ErrorInvalidEnum("%s: Bad `target`: 0x%04x", funcName, target); return nullptr; } return slot; } WebGLBuffer* WebGLContext::ValidateBufferSelection(const char* funcName, GLenum target) { const auto& slot = ValidateBufferSlot(funcName, target); if (!slot) return nullptr; const auto& buffer = *slot; if (!buffer) { ErrorInvalidOperation("%s: Buffer for `target` is null.", funcName); return nullptr; } if (target == LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER) { if (mBoundTransformFeedback->IsActiveAndNotPaused()) { ErrorInvalidOperation("%s: Cannot select TRANSFORM_FEEDBACK_BUFFER when" " transform feedback is active and unpaused.", funcName); return nullptr; } if (buffer->IsBoundForNonTF()) { ErrorInvalidOperation("%s: Specified WebGLBuffer is currently bound for" " non-transform-feedback.", funcName); return nullptr; } } else { if (buffer->IsBoundForTF()) { ErrorInvalidOperation("%s: Specified WebGLBuffer is currently bound for" " transform feedback.", funcName); return nullptr; } } return buffer.get(); } IndexedBufferBinding* WebGLContext::ValidateIndexedBufferSlot(const char* funcName, GLenum target, GLuint index) { decltype(mIndexedUniformBufferBindings)* bindings; const char* maxIndexEnum; switch (target) { case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER: bindings = &(mBoundTransformFeedback->mIndexedBindings); maxIndexEnum = "MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS"; break; case LOCAL_GL_UNIFORM_BUFFER: bindings = &mIndexedUniformBufferBindings; maxIndexEnum = "MAX_UNIFORM_BUFFER_BINDINGS"; break; default: ErrorInvalidEnum("%s: Bad `target`: 0x%04x", funcName, target); return nullptr; } if (index >= bindings->size()) { ErrorInvalidValue("%s: `index` >= %s.", funcName, maxIndexEnum); return nullptr; } return &(*bindings)[index]; } //////////////////////////////////////// void WebGLContext::BindBuffer(GLenum target, WebGLBuffer* buffer) { const char funcName[] = "bindBuffer"; if (IsContextLost()) return; if (buffer && !ValidateObject(funcName, *buffer)) return; const auto& slot = ValidateBufferSlot(funcName, target); if (!slot) return; if (buffer && !buffer->ValidateCanBindToTarget(funcName, target)) return; gl->MakeCurrent(); gl->fBindBuffer(target, buffer ? buffer->mGLName : 0); WebGLBuffer::SetSlot(target, buffer, slot); if (buffer) { buffer->SetContentAfterBind(target); } switch (target) { case LOCAL_GL_PIXEL_PACK_BUFFER: case LOCAL_GL_PIXEL_UNPACK_BUFFER: gl->fBindBuffer(target, 0); break; } } //////////////////////////////////////// bool WebGLContext::ValidateIndexedBufferBinding(const char* funcName, GLenum target, GLuint index, WebGLRefPtr** const out_genericBinding, IndexedBufferBinding** const out_indexedBinding) { *out_genericBinding = ValidateBufferSlot(funcName, target); if (!*out_genericBinding) return false; *out_indexedBinding = ValidateIndexedBufferSlot(funcName, target, index); if (!*out_indexedBinding) return false; if (target == LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER && mBoundTransformFeedback->mIsActive) { ErrorInvalidOperation("%s: Cannot update indexed buffer bindings on active" " transform feedback objects.", funcName); return false; } return true; } void WebGLContext::BindBufferBase(GLenum target, GLuint index, WebGLBuffer* buffer) { const char funcName[] = "bindBufferBase"; if (IsContextLost()) return; if (buffer && !ValidateObject(funcName, *buffer)) return; WebGLRefPtr* genericBinding; IndexedBufferBinding* indexedBinding; if (!ValidateIndexedBufferBinding(funcName, target, index, &genericBinding, &indexedBinding)) { return; } if (buffer && !buffer->ValidateCanBindToTarget(funcName, target)) return; //// gl->MakeCurrent(); gl->fBindBufferBase(target, index, buffer ? buffer->mGLName : 0); //// WebGLBuffer::SetSlot(target, buffer, genericBinding); WebGLBuffer::SetSlot(target, buffer, &indexedBinding->mBufferBinding); indexedBinding->mRangeStart = 0; indexedBinding->mRangeSize = 0; if (buffer) { buffer->SetContentAfterBind(target); } } void WebGLContext::BindBufferRange(GLenum target, GLuint index, WebGLBuffer* buffer, WebGLintptr offset, WebGLsizeiptr size) { const char funcName[] = "bindBufferRange"; if (IsContextLost()) return; if (buffer && !ValidateObject(funcName, *buffer)) return; if (!ValidateNonNegative(funcName, "offset", offset) || !ValidateNonNegative(funcName, "size", size)) { return; } WebGLRefPtr* genericBinding; IndexedBufferBinding* indexedBinding; if (!ValidateIndexedBufferBinding(funcName, target, index, &genericBinding, &indexedBinding)) { return; } if (buffer && !buffer->ValidateCanBindToTarget(funcName, target)) return; if (buffer && !size) { ErrorInvalidValue("%s: size must be non-zero for non-null buffer.", funcName); return; } //// gl->MakeCurrent(); switch (target) { case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER: if (offset % 4 != 0 || size % 4 != 0) { ErrorInvalidValue("%s: For %s, `offset` and `size` must be multiples of 4.", funcName, "TRANSFORM_FEEDBACK_BUFFER"); return; } break; case LOCAL_GL_UNIFORM_BUFFER: { GLuint offsetAlignment = 0; gl->GetUIntegerv(LOCAL_GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &offsetAlignment); if (offset % offsetAlignment != 0) { ErrorInvalidValue("%s: For %s, `offset` must be a multiple of %s.", funcName, "UNIFORM_BUFFER", "UNIFORM_BUFFER_OFFSET_ALIGNMENT"); return; } } break; } //// #ifdef XP_MACOSX if (buffer && buffer->Content() == WebGLBuffer::Kind::Undefined && gl->WorkAroundDriverBugs()) { // BindBufferRange will fail if the buffer's contents is undefined. // Bind so driver initializes the buffer. gl->fBindBuffer(target, buffer->mGLName); } #endif gl->fBindBufferRange(target, index, buffer ? buffer->mGLName : 0, offset, size); //// WebGLBuffer::SetSlot(target, buffer, genericBinding); WebGLBuffer::SetSlot(target, buffer, &indexedBinding->mBufferBinding); indexedBinding->mRangeStart = offset; indexedBinding->mRangeSize = size; if (buffer) { buffer->SetContentAfterBind(target); } } //////////////////////////////////////// void WebGLContext::BufferDataImpl(GLenum target, size_t dataLen, const uint8_t* data, GLenum usage) { const char funcName[] = "bufferData"; const auto& buffer = ValidateBufferSelection(funcName, target); if (!buffer) return; buffer->BufferData(target, dataLen, data, usage); } //// void WebGLContext::BufferData(GLenum target, WebGLsizeiptr size, GLenum usage) { const char funcName[] = "bufferData"; if (IsContextLost()) return; if (!ValidateNonNegative(funcName, "size", size)) return; //// const auto checkedSize = CheckedInt(size); if (!checkedSize.isValid()) return ErrorOutOfMemory("%s: Size too large for platform.", funcName); #if defined(XP_MACOSX) if (gl->WorkAroundDriverBugs() && size > 1200000000) { return ErrorOutOfMemory("Allocations larger than 1200000000 fail on MacOS."); } #endif const UniqueBuffer zeroBuffer(calloc(size, 1)); if (!zeroBuffer) return ErrorOutOfMemory("%s: Failed to allocate zeros.", funcName); BufferDataImpl(target, size_t(size), (const uint8_t*)zeroBuffer.get(), usage); } void WebGLContext::BufferData(GLenum target, const dom::Nullable& maybeSrc, GLenum usage) { if (IsContextLost()) return; if (!ValidateNonNull("bufferData", maybeSrc)) return; const auto& src = maybeSrc.Value(); src.ComputeLengthAndData(); BufferDataImpl(target, src.LengthAllowShared(), src.DataAllowShared(), usage); } void WebGLContext::BufferData(GLenum target, const dom::ArrayBufferView& src, GLenum usage, GLuint srcElemOffset, GLuint srcElemCountOverride) { const char funcName[] = "bufferData"; if (IsContextLost()) return; uint8_t* bytes; size_t byteLen; if (!ValidateArrayBufferView(funcName, src, srcElemOffset, srcElemCountOverride, &bytes, &byteLen)) { return; } BufferDataImpl(target, byteLen, bytes, usage); } //////////////////////////////////////// void WebGLContext::BufferSubDataImpl(GLenum target, WebGLsizeiptr dstByteOffset, size_t dataLen, const uint8_t* data) { const char funcName[] = "bufferSubData"; if (!ValidateNonNegative(funcName, "byteOffset", dstByteOffset)) return; const auto& buffer = ValidateBufferSelection(funcName, target); if (!buffer) return; if (!buffer->ValidateRange(funcName, dstByteOffset, dataLen)) return; if (!CheckedInt(dataLen).isValid()) { ErrorOutOfMemory("%s: Size too large.", funcName); return; } const GLintptr glDataLen(dataLen); //// MakeContextCurrent(); const ScopedLazyBind lazyBind(gl, target, buffer); // Warning: Possibly shared memory. See bug 1225033. gl->fBufferSubData(target, dstByteOffset, glDataLen, data); // Warning: Possibly shared memory. See bug 1225033. buffer->ElementArrayCacheBufferSubData(dstByteOffset, data, size_t(glDataLen)); } void WebGLContext::BufferSubData(GLenum target, WebGLsizeiptr dstByteOffset, const dom::ArrayBuffer& src) { if (IsContextLost()) return; src.ComputeLengthAndData(); BufferSubDataImpl(target, dstByteOffset, src.LengthAllowShared(), src.DataAllowShared()); } void WebGLContext::BufferSubData(GLenum target, WebGLsizeiptr dstByteOffset, const dom::ArrayBufferView& src, GLuint srcElemOffset, GLuint srcElemCountOverride) { const char funcName[] = "bufferSubData"; if (IsContextLost()) return; uint8_t* bytes; size_t byteLen; if (!ValidateArrayBufferView(funcName, src, srcElemOffset, srcElemCountOverride, &bytes, &byteLen)) { return; } BufferSubDataImpl(target, dstByteOffset, byteLen, bytes); } //////////////////////////////////////// already_AddRefed WebGLContext::CreateBuffer() { if (IsContextLost()) return nullptr; GLuint buf = 0; MakeContextCurrent(); gl->fGenBuffers(1, &buf); RefPtr globj = new WebGLBuffer(this, buf); return globj.forget(); } void WebGLContext::DeleteBuffer(WebGLBuffer* buffer) { if (!ValidateDeleteObject("deleteBuffer", buffer)) return; //// const auto fnClearIfBuffer = [&](GLenum target, WebGLRefPtr& bindPoint) { if (bindPoint == buffer) { WebGLBuffer::SetSlot(target, nullptr, &bindPoint); } }; fnClearIfBuffer(0, mBoundArrayBuffer); fnClearIfBuffer(0, mBoundVertexArray->mElementArrayBuffer); for (auto& cur : mBoundVertexArray->mAttribs) { fnClearIfBuffer(0, cur.mBuf); } // WebGL binding points if (IsWebGL2()) { fnClearIfBuffer(0, mBoundCopyReadBuffer); fnClearIfBuffer(0, mBoundCopyWriteBuffer); fnClearIfBuffer(0, mBoundPixelPackBuffer); fnClearIfBuffer(0, mBoundPixelUnpackBuffer); fnClearIfBuffer(0, mBoundUniformBuffer); fnClearIfBuffer(LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER, mBoundTransformFeedback->mGenericBufferBinding); if (!mBoundTransformFeedback->mIsActive) { for (auto& binding : mBoundTransformFeedback->mIndexedBindings) { fnClearIfBuffer(LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER, binding.mBufferBinding); } } for (auto& binding : mIndexedUniformBufferBindings) { fnClearIfBuffer(0, binding.mBufferBinding); } } //// buffer->RequestDelete(); } bool WebGLContext::IsBuffer(WebGLBuffer* buffer) { if (!ValidateIsObject("isBuffer", buffer)) return false; MakeContextCurrent(); return gl->fIsBuffer(buffer->mGLName); } } // namespace mozilla