/* -*- 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" namespace mozilla { void WebGLContext::UpdateBoundBuffer(GLenum target, WebGLBuffer* buffer) { WebGLRefPtr& bufferSlot = GetBufferSlotByTarget(target); bufferSlot = buffer; if (!buffer) return; buffer->BindTo(target); } void WebGLContext::UpdateBoundBufferIndexed(GLenum target, GLuint index, WebGLBuffer* buffer) { UpdateBoundBuffer(target, buffer); WebGLRefPtr& bufferIndexSlot = GetBufferSlotByTargetIndexed(target, index); bufferIndexSlot = buffer; } void WebGLContext::BindBuffer(GLenum target, WebGLBuffer* buffer) { if (IsContextLost()) return; if (!ValidateObjectAllowDeletedOrNull("bindBuffer", buffer)) return; // silently ignore a deleted buffer if (buffer && buffer->IsDeleted()) return; if (!ValidateBufferTarget(target, "bindBuffer")) return; if (!ValidateBufferForTarget(target, buffer, "bindBuffer")) return; WebGLContextUnchecked::BindBuffer(target, buffer); UpdateBoundBuffer(target, buffer); } void WebGLContext::BindBufferBase(GLenum target, GLuint index, WebGLBuffer* buffer) { if (IsContextLost()) return; if (!ValidateObjectAllowDeletedOrNull("bindBufferBase", buffer)) return; // silently ignore a deleted buffer if (buffer && buffer->IsDeleted()) return; // ValidateBufferTarget switch (target) { case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER: if (index >= mGLMaxTransformFeedbackSeparateAttribs) return ErrorInvalidValue("bindBufferBase: index should be less than " "MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS"); break; case LOCAL_GL_UNIFORM_BUFFER: if (index >= mGLMaxUniformBufferBindings) return ErrorInvalidValue("bindBufferBase: index should be less than " "MAX_UNIFORM_BUFFER_BINDINGS"); break; default: return ErrorInvalidEnumInfo("bindBufferBase: target", target); } if (!ValidateBufferForTarget(target, buffer, "bindBufferBase")) return; WebGLContextUnchecked::BindBufferBase(target, index, buffer); UpdateBoundBufferIndexed(target, index, buffer); } void WebGLContext::BindBufferRange(GLenum target, GLuint index, WebGLBuffer* buffer, WebGLintptr offset, WebGLsizeiptr size) { if (IsContextLost()) return; if (!ValidateObjectAllowDeletedOrNull("bindBufferRange", buffer)) return; // silently ignore a deleted buffer if (buffer && buffer->IsDeleted()) return; // ValidateBufferTarget switch (target) { case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER: if (index >= mGLMaxTransformFeedbackSeparateAttribs) return ErrorInvalidValue("bindBufferRange: index should be less than " "MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS"); break; case LOCAL_GL_UNIFORM_BUFFER: if (index >= mGLMaxUniformBufferBindings) return ErrorInvalidValue("bindBufferRange: index should be less than " "MAX_UNIFORM_BUFFER_BINDINGS"); break; default: return ErrorInvalidEnumInfo("bindBufferRange: target", target); } if (!ValidateBufferForTarget(target, buffer, "bindBufferRange")) return; WebGLContextUnchecked::BindBufferRange(target, index, buffer, offset, size); UpdateBoundBufferIndexed(target, index, buffer); } void WebGLContext::BufferData(GLenum target, WebGLsizeiptr size, GLenum usage) { if (IsContextLost()) return; if (!ValidateBufferTarget(target, "bufferData")) return; WebGLRefPtr& bufferSlot = GetBufferSlotByTarget(target); if (size < 0) return ErrorInvalidValue("bufferData: negative size"); if (!ValidateBufferUsageEnum(usage, "bufferData: usage")) return; // careful: WebGLsizeiptr is always 64-bit, but GLsizeiptr is like intptr_t. if (!CheckedInt(size).isValid()) return ErrorOutOfMemory("bufferData: bad size"); WebGLBuffer* boundBuffer = bufferSlot.get(); if (!boundBuffer) return ErrorInvalidOperation("bufferData: no buffer bound!"); UniquePtr zeroBuffer((uint8_t*)calloc(size, 1)); if (!zeroBuffer) return ErrorOutOfMemory("bufferData: out of memory"); MakeContextCurrent(); InvalidateBufferFetching(); GLenum error = CheckedBufferData(target, size, zeroBuffer.get(), usage); if (error) { GenerateWarning("bufferData generated error %s", ErrorName(error)); return; } boundBuffer->SetByteLength(size); if (!boundBuffer->ElementArrayCacheBufferData(nullptr, size)) { boundBuffer->SetByteLength(0); return ErrorOutOfMemory("bufferData: out of memory"); } } // BufferT may be one of // const dom::ArrayBuffer& // const dom::SharedArrayBuffer& // const dom::ArrayBufferView& template void WebGLContext::BufferDataT(GLenum target, const BufferT& data, GLenum usage) { if (IsContextLost()) return; if (!ValidateBufferTarget(target, "bufferData")) return; const WebGLRefPtr& bufferSlot = GetBufferSlotByTarget(target); data.ComputeLengthAndData(); // Careful: data.Length() could conceivably be any uint32_t, but GLsizeiptr // is like intptr_t. if (!CheckedInt(data.LengthAllowShared()).isValid()) return ErrorOutOfMemory("bufferData: bad size"); if (!ValidateBufferUsageEnum(usage, "bufferData: usage")) return; WebGLBuffer* boundBuffer = bufferSlot.get(); if (!boundBuffer) return ErrorInvalidOperation("bufferData: no buffer bound!"); MakeContextCurrent(); InvalidateBufferFetching(); // Warning: Possibly shared memory. See bug 1225033. GLenum error = CheckedBufferData(target, data.LengthAllowShared(), data.DataAllowShared(), usage); if (error) { GenerateWarning("bufferData generated error %s", ErrorName(error)); return; } boundBuffer->SetByteLength(data.LengthAllowShared()); // Warning: Possibly shared memory. See bug 1225033. if (!boundBuffer->ElementArrayCacheBufferData(data.DataAllowShared(), data.LengthAllowShared())) { boundBuffer->SetByteLength(0); return ErrorOutOfMemory("bufferData: out of memory"); } } void WebGLContext::BufferData(GLenum target, const dom::SharedArrayBuffer& data, GLenum usage) { BufferDataT(target, data, usage); } void WebGLContext::BufferData(GLenum target, const dom::Nullable& maybeData, GLenum usage) { if (maybeData.IsNull()) { // see http://www.khronos.org/bugzilla/show_bug.cgi?id=386 return ErrorInvalidValue("bufferData: null object passed"); } BufferDataT(target, maybeData.Value(), usage); } void WebGLContext::BufferData(GLenum target, const dom::ArrayBufferView& data, GLenum usage) { BufferDataT(target, data, usage); } // BufferT may be one of // const dom::ArrayBuffer& // const dom::SharedArrayBuffer& // const dom::ArrayBufferView& template void WebGLContext::BufferSubDataT(GLenum target, WebGLsizeiptr byteOffset, const BufferT& data) { if (IsContextLost()) return; if (!ValidateBufferTarget(target, "bufferSubData")) return; WebGLRefPtr& bufferSlot = GetBufferSlotByTarget(target); if (byteOffset < 0) return ErrorInvalidValue("bufferSubData: negative offset"); WebGLBuffer* boundBuffer = bufferSlot.get(); if (!boundBuffer) return ErrorInvalidOperation("bufferData: no buffer bound!"); data.ComputeLengthAndData(); CheckedInt checked_neededByteLength = CheckedInt(byteOffset) + data.LengthAllowShared(); if (!checked_neededByteLength.isValid()) { ErrorInvalidValue("bufferSubData: Integer overflow computing the needed" " byte length."); return; } if (checked_neededByteLength.value() > boundBuffer->ByteLength()) { ErrorInvalidValue("bufferSubData: Not enough data. Operation requires" " %d bytes, but buffer only has %d bytes.", checked_neededByteLength.value(), boundBuffer->ByteLength()); return; } // Warning: Possibly shared memory. See bug 1225033. boundBuffer->ElementArrayCacheBufferSubData(byteOffset, data.DataAllowShared(), data.LengthAllowShared()); MakeContextCurrent(); // Warning: Possibly shared memory. See bug 1225033. gl->fBufferSubData(target, byteOffset, data.LengthAllowShared(), data.DataAllowShared()); } void WebGLContext::BufferSubData(GLenum target, WebGLsizeiptr byteOffset, const dom::Nullable& maybeData) { if (maybeData.IsNull()) { // see http://www.khronos.org/bugzilla/show_bug.cgi?id=386 return; } BufferSubDataT(target, byteOffset, maybeData.Value()); } void WebGLContext::BufferSubData(GLenum target, WebGLsizeiptr byteOffset, const dom::SharedArrayBuffer& data) { BufferSubDataT(target, byteOffset, data); } void WebGLContext::BufferSubData(GLenum target, WebGLsizeiptr byteOffset, const dom::ArrayBufferView& data) { BufferSubDataT(target, byteOffset, data); } 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 (IsContextLost()) return; if (!ValidateObjectAllowDeletedOrNull("deleteBuffer", buffer)) return; if (!buffer || buffer->IsDeleted()) return; // TODO: Extract this into a helper function? if (mBoundArrayBuffer == buffer) { WebGLContextUnchecked::BindBuffer(LOCAL_GL_ARRAY_BUFFER, nullptr); mBoundArrayBuffer = nullptr; } if (mBoundVertexArray->mElementArrayBuffer == buffer) { WebGLContextUnchecked::BindBuffer(LOCAL_GL_ELEMENT_ARRAY_BUFFER, nullptr); mBoundVertexArray->mElementArrayBuffer = nullptr; } // WebGL binding points if (IsWebGL2()) { if (mBoundCopyReadBuffer == buffer) mBoundCopyReadBuffer = nullptr; if (mBoundCopyWriteBuffer == buffer) mBoundCopyWriteBuffer = nullptr; if (mBoundPixelPackBuffer == buffer) mBoundPixelPackBuffer = nullptr; if (mBoundPixelUnpackBuffer == buffer) mBoundPixelUnpackBuffer = nullptr; if (mBoundTransformFeedbackBuffer == buffer) mBoundTransformFeedbackBuffer = nullptr; if (mBoundUniformBuffer == buffer) mBoundUniformBuffer = nullptr; const size_t xfBufferCount = mBoundTransformFeedbackBuffers.Length(); for (size_t n = 0; n < xfBufferCount; n++) { if (mBoundTransformFeedbackBuffers[n] == buffer) { mBoundTransformFeedbackBuffers[n] = nullptr; } } const size_t uniformBufferCount = mBoundUniformBuffers.Length(); for (size_t n = 0; n < uniformBufferCount; n++) { if (mBoundUniformBuffers[n] == buffer) { mBoundUniformBuffers[n] = nullptr; } } } for (int32_t i = 0; i < mGLMaxVertexAttribs; i++) { if (mBoundVertexArray->HasAttrib(i) && mBoundVertexArray->mAttribs[i].buf == buffer) { mBoundVertexArray->mAttribs[i].buf = nullptr; } } buffer->RequestDelete(); } bool WebGLContext::IsBuffer(WebGLBuffer* buffer) { if (IsContextLost()) return false; if (!ValidateObjectAllowDeleted("isBuffer", buffer)) return false; if (buffer->IsDeleted()) return false; MakeContextCurrent(); return gl->fIsBuffer(buffer->mGLName); } bool WebGLContext::ValidateBufferForTarget(GLenum target, WebGLBuffer* buffer, const char* info) { if (!buffer) return true; /* https://www.khronos.org/registry/webgl/specs/latest/2.0/#5.1 * * In the WebGL 2 API, buffers have their WebGL buffer type * initially set to undefined. Calling bindBuffer, bindBufferRange * or bindBufferBase with the target argument set to any buffer * binding point except COPY_READ_BUFFER or COPY_WRITE_BUFFER will * then set the WebGL buffer type of the buffer being bound * according to the table above. * * Any call to one of these functions which attempts to bind a * WebGLBuffer that has the element array WebGL buffer type to a * binding point that falls under other data, or bind a * WebGLBuffer which has the other data WebGL buffer type to * ELEMENT_ARRAY_BUFFER will generate an INVALID_OPERATION error, * and the state of the binding point will remain untouched. */ GLenum boundTo = GetCurrentBinding(buffer); if (boundTo != LOCAL_GL_NONE) { if (target == LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER && boundTo != LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER) { ErrorInvalidOperation("Can't bind buffer to TRANSFORM_FEEDBACK_BUFFER as the " "buffer is already bound to another bind point."); return false; } else if (target != LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER && boundTo == LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER) { ErrorInvalidOperation("Can't bind buffer to bind point as it is currently " "bound to TRANSFORM_FEEDBACK_BUFFER."); return false; } } WebGLBuffer::Kind content = buffer->Content(); if (content == WebGLBuffer::Kind::Undefined) return true; switch (target) { case LOCAL_GL_COPY_READ_BUFFER: case LOCAL_GL_COPY_WRITE_BUFFER: return true; case LOCAL_GL_ELEMENT_ARRAY_BUFFER: if (content == WebGLBuffer::Kind::ElementArray) return true; break; case LOCAL_GL_ARRAY_BUFFER: case LOCAL_GL_PIXEL_PACK_BUFFER: case LOCAL_GL_PIXEL_UNPACK_BUFFER: case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER: case LOCAL_GL_UNIFORM_BUFFER: if (content == WebGLBuffer::Kind::OtherData) return true; break; default: MOZ_CRASH(); } ErrorInvalidOperation("%s: buffer already contains %s data.", info, content == WebGLBuffer::Kind::OtherData ? "other" : "element"); return false; } bool WebGLContext::ValidateBufferUsageEnum(GLenum target, const char* info) { switch (target) { case LOCAL_GL_STREAM_DRAW: case LOCAL_GL_STATIC_DRAW: case LOCAL_GL_DYNAMIC_DRAW: return true; default: break; } ErrorInvalidEnumInfo(info, target); return false; } WebGLRefPtr& WebGLContext::GetBufferSlotByTarget(GLenum target) { /* This function assumes that target has been validated for either * WebGL1 or WebGL2. */ switch (target) { case LOCAL_GL_ARRAY_BUFFER: return mBoundArrayBuffer; case LOCAL_GL_COPY_READ_BUFFER: return mBoundCopyReadBuffer; case LOCAL_GL_COPY_WRITE_BUFFER: return mBoundCopyWriteBuffer; case LOCAL_GL_ELEMENT_ARRAY_BUFFER: return mBoundVertexArray->mElementArrayBuffer; case LOCAL_GL_PIXEL_PACK_BUFFER: return mBoundPixelPackBuffer; case LOCAL_GL_PIXEL_UNPACK_BUFFER: return mBoundPixelUnpackBuffer; case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER: return mBoundTransformFeedbackBuffer; case LOCAL_GL_UNIFORM_BUFFER: return mBoundUniformBuffer; default: MOZ_CRASH("Should not get here."); } } WebGLRefPtr& WebGLContext::GetBufferSlotByTargetIndexed(GLenum target, GLuint index) { /* This function assumes that target has been validated for either WebGL1 or WebGL. */ switch (target) { case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER: MOZ_ASSERT(index < mGLMaxTransformFeedbackSeparateAttribs); return mBoundTransformFeedbackBuffers[index]; case LOCAL_GL_UNIFORM_BUFFER: MOZ_ASSERT(index < mGLMaxUniformBufferBindings); return mBoundUniformBuffers[index]; default: MOZ_CRASH("Should not get here."); } } GLenum WebGLContext::GetCurrentBinding(WebGLBuffer* buffer) const { if (mBoundArrayBuffer == buffer) return LOCAL_GL_ARRAY_BUFFER; if (mBoundCopyReadBuffer == buffer) return LOCAL_GL_COPY_READ_BUFFER; if (mBoundCopyWriteBuffer == buffer) return LOCAL_GL_COPY_WRITE_BUFFER; if (mBoundPixelPackBuffer == buffer) return LOCAL_GL_PIXEL_PACK_BUFFER; if (mBoundPixelUnpackBuffer == buffer) return LOCAL_GL_PIXEL_UNPACK_BUFFER; if (mBoundTransformFeedbackBuffer == buffer || mBoundTransformFeedbackBuffers.Contains(buffer)) { return LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER; } if (mBoundUniformBuffer == buffer || mBoundUniformBuffers.Contains(buffer)) { return LOCAL_GL_UNIFORM_BUFFER; } return LOCAL_GL_NONE; } GLenum WebGLContext::CheckedBufferData(GLenum target, GLsizeiptr size, const GLvoid* data, GLenum usage) { #if(0) #ifdef XP_MACOSX // bug 790879 if (gl->WorkAroundDriverBugs() && int64_t(size) > INT32_MAX) // cast avoids a potential always-true warning on 32bit { GenerateWarning("Rejecting valid bufferData call with size %lu to avoid" " a Mac bug", size); return LOCAL_GL_INVALID_VALUE; } #endif #endif WebGLRefPtr& bufferSlot = GetBufferSlotByTarget(target); WebGLBuffer* boundBuffer = bufferSlot.get(); MOZ_ASSERT(boundBuffer, "No buffer bound for this target."); bool sizeChanges = uint32_t(size) != boundBuffer->ByteLength(); if (sizeChanges) { GetAndFlushUnderlyingGLErrors(); gl->fBufferData(target, size, data, usage); GLenum error = GetAndFlushUnderlyingGLErrors(); return error; } else { gl->fBufferData(target, size, data, usage); return LOCAL_GL_NO_ERROR; } } } // namespace mozilla