/* -*- 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" using namespace mozilla; using namespace mozilla::dom; 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; WebGLRefPtr* bufferSlot = GetBufferSlotByTarget(target, "bindBuffer"); if (!bufferSlot) { return; } if (buffer) { if (!buffer->HasEverBeenBound()) { buffer->BindTo(target); } else if (target != buffer->Target()) { return ErrorInvalidOperation("bindBuffer: buffer already bound to a different target"); } } *bufferSlot = buffer; MakeContextCurrent(); gl->fBindBuffer(target, buffer ? buffer->GLName() : 0); } 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; } WebGLRefPtr* indexedBufferSlot = GetBufferSlotByTargetIndexed(target, index, "bindBufferBase"); if (!indexedBufferSlot) { return; } if (buffer) { if (!buffer->HasEverBeenBound()) buffer->BindTo(target); if (target != buffer->Target()) return ErrorInvalidOperation("bindBuffer: buffer already bound to a different target"); } WebGLRefPtr* bufferSlot = GetBufferSlotByTarget(target, "bindBuffer"); MOZ_ASSERT(bufferSlot, "GetBufferSlotByTarget(Indexed) mismatch"); *indexedBufferSlot = buffer; *bufferSlot = buffer; MakeContextCurrent(); gl->fBindBufferBase(target, index, buffer ? buffer->GLName() : 0); } 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; WebGLRefPtr* indexedBufferSlot = GetBufferSlotByTargetIndexed(target, index, "bindBufferBase"); if (!indexedBufferSlot) { return; } if (buffer) { if (!buffer->HasEverBeenBound()) buffer->BindTo(target); if (target != buffer->Target()) return ErrorInvalidOperation("bindBuffer: buffer already bound to a different target"); CheckedInt checked_neededByteLength = CheckedInt(offset) + size; if (!checked_neededByteLength.isValid() || checked_neededByteLength.value() > buffer->ByteLength()) { return ErrorInvalidValue("bindBufferRange: invalid range"); } } WebGLRefPtr* bufferSlot = GetBufferSlotByTarget(target, "bindBuffer"); MOZ_ASSERT(bufferSlot, "GetBufferSlotByTarget(Indexed) mismatch"); *indexedBufferSlot = buffer; *bufferSlot = buffer; MakeContextCurrent(); gl->fBindBufferRange(target, index, buffer ? buffer->GLName() : 0, offset, size); } void WebGLContext::BufferData(GLenum target, WebGLsizeiptr size, GLenum usage) { if (IsContextLost()) return; WebGLRefPtr* bufferSlot = GetBufferSlotByTarget(target, "bufferData"); if (!bufferSlot) { return; } 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*)moz_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)) { return ErrorOutOfMemory("bufferData: out of memory"); } } void WebGLContext::BufferData(GLenum target, const Nullable &maybeData, GLenum usage) { if (IsContextLost()) return; if (maybeData.IsNull()) { // see http://www.khronos.org/bugzilla/show_bug.cgi?id=386 return ErrorInvalidValue("bufferData: null object passed"); } WebGLRefPtr* bufferSlot = GetBufferSlotByTarget(target, "bufferData"); if (!bufferSlot) { return; } const ArrayBuffer& data = maybeData.Value(); data.ComputeLengthAndData(); // Careful: data.Length() could conceivably be any uint32_t, but GLsizeiptr // is like intptr_t. if (!CheckedInt(data.Length()).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(); GLenum error = CheckedBufferData(target, data.Length(), data.Data(), usage); if (error) { GenerateWarning("bufferData generated error %s", ErrorName(error)); return; } boundBuffer->SetByteLength(data.Length()); if (!boundBuffer->ElementArrayCacheBufferData(data.Data(), data.Length())) { return ErrorOutOfMemory("bufferData: out of memory"); } } void WebGLContext::BufferData(GLenum target, const ArrayBufferView& data, GLenum usage) { if (IsContextLost()) return; WebGLRefPtr* bufferSlot = GetBufferSlotByTarget(target, "bufferSubData"); if (!bufferSlot) { return; } if (!ValidateBufferUsageEnum(usage, "bufferData: usage")) return; WebGLBuffer* boundBuffer = bufferSlot->get(); if (!boundBuffer) return ErrorInvalidOperation("bufferData: no buffer bound!"); data.ComputeLengthAndData(); // Careful: data.Length() could conceivably be any uint32_t, but GLsizeiptr // is like intptr_t. if (!CheckedInt(data.Length()).isValid()) return ErrorOutOfMemory("bufferData: bad size"); InvalidateBufferFetching(); MakeContextCurrent(); GLenum error = CheckedBufferData(target, data.Length(), data.Data(), usage); if (error) { GenerateWarning("bufferData generated error %s", ErrorName(error)); return; } boundBuffer->SetByteLength(data.Length()); if (!boundBuffer->ElementArrayCacheBufferData(data.Data(), data.Length())) { return ErrorOutOfMemory("bufferData: out of memory"); } } void WebGLContext::BufferSubData(GLenum target, WebGLsizeiptr byteOffset, const Nullable &maybeData) { if (IsContextLost()) return; if (maybeData.IsNull()) { // see http://www.khronos.org/bugzilla/show_bug.cgi?id=386 return; } WebGLRefPtr* bufferSlot = GetBufferSlotByTarget(target, "bufferSubData"); if (!bufferSlot) { return; } if (byteOffset < 0) return ErrorInvalidValue("bufferSubData: negative offset"); WebGLBuffer* boundBuffer = bufferSlot->get(); if (!boundBuffer) return ErrorInvalidOperation("bufferData: no buffer bound!"); const ArrayBuffer& data = maybeData.Value(); data.ComputeLengthAndData(); CheckedInt checked_neededByteLength = CheckedInt(byteOffset) + data.Length(); if (!checked_neededByteLength.isValid()) return ErrorInvalidValue("bufferSubData: integer overflow computing the needed byte length"); if (checked_neededByteLength.value() > boundBuffer->ByteLength()) return ErrorInvalidValue("bufferSubData: not enough data - operation requires %d bytes, but buffer only has %d bytes", checked_neededByteLength.value(), boundBuffer->ByteLength()); MakeContextCurrent(); boundBuffer->ElementArrayCacheBufferSubData(byteOffset, data.Data(), data.Length()); gl->fBufferSubData(target, byteOffset, data.Length(), data.Data()); } void WebGLContext::BufferSubData(GLenum target, WebGLsizeiptr byteOffset, const ArrayBufferView& data) { if (IsContextLost()) return; WebGLRefPtr* bufferSlot = GetBufferSlotByTarget(target, "bufferSubData"); if (!bufferSlot) { return; } if (byteOffset < 0) return ErrorInvalidValue("bufferSubData: negative offset"); WebGLBuffer* boundBuffer = bufferSlot->get(); if (!boundBuffer) return ErrorInvalidOperation("bufferSubData: no buffer bound!"); data.ComputeLengthAndData(); CheckedInt checked_neededByteLength = CheckedInt(byteOffset) + data.Length(); if (!checked_neededByteLength.isValid()) return ErrorInvalidValue("bufferSubData: integer overflow computing the needed byte length"); if (checked_neededByteLength.value() > boundBuffer->ByteLength()) return ErrorInvalidValue("bufferSubData: not enough data -- operation requires %d bytes, but buffer only has %d bytes", checked_neededByteLength.value(), boundBuffer->ByteLength()); boundBuffer->ElementArrayCacheBufferSubData(byteOffset, data.Data(), data.Length()); MakeContextCurrent(); gl->fBufferSubData(target, byteOffset, data.Length(), data.Data()); } already_AddRefed WebGLContext::CreateBuffer() { if (IsContextLost()) return nullptr; nsRefPtr globj = new WebGLBuffer(this); return globj.forget(); } void WebGLContext::DeleteBuffer(WebGLBuffer *buffer) { if (IsContextLost()) return; if (!ValidateObjectAllowDeletedOrNull("deleteBuffer", buffer)) return; if (!buffer || buffer->IsDeleted()) return; if (mBoundArrayBuffer == buffer) { BindBuffer(LOCAL_GL_ARRAY_BUFFER, static_cast(nullptr)); } if (mBoundVertexArray->mElementArrayBuffer == buffer) { BindBuffer(LOCAL_GL_ELEMENT_ARRAY_BUFFER, static_cast(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; return ValidateObjectAllowDeleted("isBuffer", buffer) && !buffer->IsDeleted() && buffer->HasEverBeenBound(); } bool WebGLContext::ValidateBufferUsageEnum(GLenum target, const char *infos) { switch (target) { case LOCAL_GL_STREAM_DRAW: case LOCAL_GL_STATIC_DRAW: case LOCAL_GL_DYNAMIC_DRAW: return true; default: break; } ErrorInvalidEnumInfo(infos, target); return false; } WebGLRefPtr* WebGLContext::GetBufferSlotByTarget(GLenum target, const char* infos) { switch (target) { case LOCAL_GL_ARRAY_BUFFER: return &mBoundArrayBuffer; case LOCAL_GL_ELEMENT_ARRAY_BUFFER: return &mBoundVertexArray->mElementArrayBuffer; case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER: if (!IsWebGL2()) { break; } return &mBoundTransformFeedbackBuffer; default: break; } ErrorInvalidEnum("%s: target: invalid enum value 0x%x", infos, target); return nullptr; } WebGLRefPtr* WebGLContext::GetBufferSlotByTargetIndexed(GLenum target, GLuint index, const char* infos) { switch (target) { case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER: if (index >= mGLMaxTransformFeedbackSeparateAttribs) { ErrorInvalidValue("%s: index should be less than MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS", infos, index); return nullptr; } return nullptr; // See bug 903594 default: break; } ErrorInvalidEnum("%s: target: invalid enum value 0x%x", infos, target); return nullptr; } GLenum WebGLContext::CheckedBufferData(GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage) { #ifdef XP_MACOSX // bug 790879 if (gl->WorkAroundDriverBugs() && int64_t(size) > INT32_MAX) // the 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 WebGLBuffer *boundBuffer = nullptr; if (target == LOCAL_GL_ARRAY_BUFFER) { boundBuffer = mBoundArrayBuffer; } else if (target == LOCAL_GL_ELEMENT_ARRAY_BUFFER) { boundBuffer = mBoundVertexArray->mElementArrayBuffer; } MOZ_ASSERT(boundBuffer != nullptr, "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; } }