michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "WebGLContext.h" michael@0: #include "GLContext.h" michael@0: #include "WebGLBuffer.h" michael@0: #include "WebGLVertexArray.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: void michael@0: WebGLContext::BindBuffer(GLenum target, WebGLBuffer *buffer) michael@0: { michael@0: if (IsContextLost()) michael@0: return; michael@0: michael@0: if (!ValidateObjectAllowDeletedOrNull("bindBuffer", buffer)) michael@0: return; michael@0: michael@0: // silently ignore a deleted buffer michael@0: if (buffer && buffer->IsDeleted()) michael@0: return; michael@0: michael@0: WebGLRefPtr* bufferSlot = GetBufferSlotByTarget(target, "bindBuffer"); michael@0: michael@0: if (!bufferSlot) { michael@0: return; michael@0: } michael@0: michael@0: if (buffer) { michael@0: if (!buffer->Target()) { michael@0: buffer->SetTarget(target); michael@0: buffer->SetHasEverBeenBound(true); michael@0: } else if (target != buffer->Target()) { michael@0: return ErrorInvalidOperation("bindBuffer: buffer already bound to a different target"); michael@0: } michael@0: } michael@0: michael@0: *bufferSlot = buffer; michael@0: michael@0: MakeContextCurrent(); michael@0: michael@0: gl->fBindBuffer(target, buffer ? buffer->GLName() : 0); michael@0: } michael@0: michael@0: void michael@0: WebGLContext::BindBufferBase(GLenum target, GLuint index, WebGLBuffer* buffer) michael@0: { michael@0: if (IsContextLost()) michael@0: return; michael@0: michael@0: if (!ValidateObjectAllowDeletedOrNull("bindBufferBase", buffer)) michael@0: return; michael@0: michael@0: // silently ignore a deleted buffer michael@0: if (buffer && buffer->IsDeleted()) { michael@0: return; michael@0: } michael@0: michael@0: WebGLRefPtr* indexedBufferSlot = GetBufferSlotByTargetIndexed(target, index, "bindBufferBase"); michael@0: michael@0: if (!indexedBufferSlot) { michael@0: return; michael@0: } michael@0: michael@0: if (buffer) { michael@0: if (!buffer->Target()) { michael@0: buffer->SetTarget(target); michael@0: buffer->SetHasEverBeenBound(true); michael@0: } else if (target != buffer->Target()) { michael@0: return ErrorInvalidOperation("bindBuffer: buffer already bound to a different target"); michael@0: } michael@0: } michael@0: michael@0: WebGLRefPtr* bufferSlot = GetBufferSlotByTarget(target, "bindBuffer"); michael@0: michael@0: MOZ_ASSERT(bufferSlot, "GetBufferSlotByTarget(Indexed) mismatch"); michael@0: michael@0: *indexedBufferSlot = buffer; michael@0: *bufferSlot = buffer; michael@0: michael@0: MakeContextCurrent(); michael@0: michael@0: gl->fBindBufferBase(target, index, buffer ? buffer->GLName() : 0); michael@0: } michael@0: michael@0: void michael@0: WebGLContext::BindBufferRange(GLenum target, GLuint index, WebGLBuffer* buffer, michael@0: WebGLintptr offset, WebGLsizeiptr size) michael@0: { michael@0: if (IsContextLost()) michael@0: return; michael@0: michael@0: if (!ValidateObjectAllowDeletedOrNull("bindBufferRange", buffer)) michael@0: return; michael@0: michael@0: // silently ignore a deleted buffer michael@0: if (buffer && buffer->IsDeleted()) michael@0: return; michael@0: michael@0: WebGLRefPtr* indexedBufferSlot = GetBufferSlotByTargetIndexed(target, index, "bindBufferBase"); michael@0: michael@0: if (!indexedBufferSlot) { michael@0: return; michael@0: } michael@0: michael@0: if (buffer) { michael@0: if (!buffer->Target()) { michael@0: buffer->SetTarget(target); michael@0: buffer->SetHasEverBeenBound(true); michael@0: } else if (target != buffer->Target()) { michael@0: return ErrorInvalidOperation("bindBuffer: buffer already bound to a different target"); michael@0: } michael@0: CheckedInt checked_neededByteLength = CheckedInt(offset) + size; michael@0: if (!checked_neededByteLength.isValid() || michael@0: checked_neededByteLength.value() > buffer->ByteLength()) michael@0: { michael@0: return ErrorInvalidValue("bindBufferRange: invalid range"); michael@0: } michael@0: } michael@0: michael@0: WebGLRefPtr* bufferSlot = GetBufferSlotByTarget(target, "bindBuffer"); michael@0: michael@0: MOZ_ASSERT(bufferSlot, "GetBufferSlotByTarget(Indexed) mismatch"); michael@0: michael@0: *indexedBufferSlot = buffer; michael@0: *bufferSlot = buffer; michael@0: michael@0: MakeContextCurrent(); michael@0: michael@0: gl->fBindBufferRange(target, index, buffer ? buffer->GLName() : 0, offset, size); michael@0: } michael@0: michael@0: void michael@0: WebGLContext::BufferData(GLenum target, WebGLsizeiptr size, michael@0: GLenum usage) michael@0: { michael@0: if (IsContextLost()) michael@0: return; michael@0: michael@0: WebGLRefPtr* bufferSlot = GetBufferSlotByTarget(target, "bufferData"); michael@0: michael@0: if (!bufferSlot) { michael@0: return; michael@0: } michael@0: michael@0: if (size < 0) michael@0: return ErrorInvalidValue("bufferData: negative size"); michael@0: michael@0: if (!ValidateBufferUsageEnum(usage, "bufferData: usage")) michael@0: return; michael@0: michael@0: // careful: WebGLsizeiptr is always 64-bit, but GLsizeiptr is like intptr_t. michael@0: if (!CheckedInt(size).isValid()) michael@0: return ErrorOutOfMemory("bufferData: bad size"); michael@0: michael@0: WebGLBuffer* boundBuffer = bufferSlot->get(); michael@0: michael@0: if (!boundBuffer) michael@0: return ErrorInvalidOperation("bufferData: no buffer bound!"); michael@0: michael@0: void* zeroBuffer = calloc(size, 1); michael@0: if (!zeroBuffer) michael@0: return ErrorOutOfMemory("bufferData: out of memory"); michael@0: michael@0: MakeContextCurrent(); michael@0: InvalidateBufferFetching(); michael@0: michael@0: GLenum error = CheckedBufferData(target, size, zeroBuffer, usage); michael@0: free(zeroBuffer); michael@0: michael@0: if (error) { michael@0: GenerateWarning("bufferData generated error %s", ErrorName(error)); michael@0: return; michael@0: } michael@0: michael@0: boundBuffer->SetByteLength(size); michael@0: if (!boundBuffer->ElementArrayCacheBufferData(nullptr, size)) { michael@0: return ErrorOutOfMemory("bufferData: out of memory"); michael@0: } michael@0: } michael@0: michael@0: void michael@0: WebGLContext::BufferData(GLenum target, michael@0: const Nullable &maybeData, michael@0: GLenum usage) michael@0: { michael@0: if (IsContextLost()) michael@0: return; michael@0: michael@0: if (maybeData.IsNull()) { michael@0: // see http://www.khronos.org/bugzilla/show_bug.cgi?id=386 michael@0: return ErrorInvalidValue("bufferData: null object passed"); michael@0: } michael@0: michael@0: WebGLRefPtr* bufferSlot = GetBufferSlotByTarget(target, "bufferData"); michael@0: michael@0: if (!bufferSlot) { michael@0: return; michael@0: } michael@0: michael@0: const ArrayBuffer& data = maybeData.Value(); michael@0: data.ComputeLengthAndData(); michael@0: michael@0: // Careful: data.Length() could conceivably be any uint32_t, but GLsizeiptr michael@0: // is like intptr_t. michael@0: if (!CheckedInt(data.Length()).isValid()) michael@0: return ErrorOutOfMemory("bufferData: bad size"); michael@0: michael@0: if (!ValidateBufferUsageEnum(usage, "bufferData: usage")) michael@0: return; michael@0: michael@0: WebGLBuffer* boundBuffer = bufferSlot->get(); michael@0: michael@0: if (!boundBuffer) michael@0: return ErrorInvalidOperation("bufferData: no buffer bound!"); michael@0: michael@0: MakeContextCurrent(); michael@0: InvalidateBufferFetching(); michael@0: michael@0: GLenum error = CheckedBufferData(target, data.Length(), data.Data(), usage); michael@0: michael@0: if (error) { michael@0: GenerateWarning("bufferData generated error %s", ErrorName(error)); michael@0: return; michael@0: } michael@0: michael@0: boundBuffer->SetByteLength(data.Length()); michael@0: if (!boundBuffer->ElementArrayCacheBufferData(data.Data(), data.Length())) { michael@0: return ErrorOutOfMemory("bufferData: out of memory"); michael@0: } michael@0: } michael@0: michael@0: void michael@0: WebGLContext::BufferData(GLenum target, const ArrayBufferView& data, michael@0: GLenum usage) michael@0: { michael@0: if (IsContextLost()) michael@0: return; michael@0: michael@0: WebGLRefPtr* bufferSlot = GetBufferSlotByTarget(target, "bufferSubData"); michael@0: michael@0: if (!bufferSlot) { michael@0: return; michael@0: } michael@0: michael@0: if (!ValidateBufferUsageEnum(usage, "bufferData: usage")) michael@0: return; michael@0: michael@0: WebGLBuffer* boundBuffer = bufferSlot->get(); michael@0: michael@0: if (!boundBuffer) michael@0: return ErrorInvalidOperation("bufferData: no buffer bound!"); michael@0: michael@0: data.ComputeLengthAndData(); michael@0: michael@0: // Careful: data.Length() could conceivably be any uint32_t, but GLsizeiptr michael@0: // is like intptr_t. michael@0: if (!CheckedInt(data.Length()).isValid()) michael@0: return ErrorOutOfMemory("bufferData: bad size"); michael@0: michael@0: InvalidateBufferFetching(); michael@0: MakeContextCurrent(); michael@0: michael@0: GLenum error = CheckedBufferData(target, data.Length(), data.Data(), usage); michael@0: if (error) { michael@0: GenerateWarning("bufferData generated error %s", ErrorName(error)); michael@0: return; michael@0: } michael@0: michael@0: boundBuffer->SetByteLength(data.Length()); michael@0: if (!boundBuffer->ElementArrayCacheBufferData(data.Data(), data.Length())) { michael@0: return ErrorOutOfMemory("bufferData: out of memory"); michael@0: } michael@0: } michael@0: michael@0: void michael@0: WebGLContext::BufferSubData(GLenum target, WebGLsizeiptr byteOffset, michael@0: const Nullable &maybeData) michael@0: { michael@0: if (IsContextLost()) michael@0: return; michael@0: michael@0: if (maybeData.IsNull()) { michael@0: // see http://www.khronos.org/bugzilla/show_bug.cgi?id=386 michael@0: return; michael@0: } michael@0: michael@0: WebGLRefPtr* bufferSlot = GetBufferSlotByTarget(target, "bufferSubData"); michael@0: michael@0: if (!bufferSlot) { michael@0: return; michael@0: } michael@0: michael@0: if (byteOffset < 0) michael@0: return ErrorInvalidValue("bufferSubData: negative offset"); michael@0: michael@0: WebGLBuffer* boundBuffer = bufferSlot->get(); michael@0: michael@0: if (!boundBuffer) michael@0: return ErrorInvalidOperation("bufferData: no buffer bound!"); michael@0: michael@0: const ArrayBuffer& data = maybeData.Value(); michael@0: data.ComputeLengthAndData(); michael@0: michael@0: CheckedInt checked_neededByteLength = CheckedInt(byteOffset) + data.Length(); michael@0: if (!checked_neededByteLength.isValid()) michael@0: return ErrorInvalidValue("bufferSubData: integer overflow computing the needed byte length"); michael@0: michael@0: if (checked_neededByteLength.value() > boundBuffer->ByteLength()) michael@0: return ErrorInvalidValue("bufferSubData: not enough data - operation requires %d bytes, but buffer only has %d bytes", michael@0: checked_neededByteLength.value(), boundBuffer->ByteLength()); michael@0: michael@0: MakeContextCurrent(); michael@0: michael@0: boundBuffer->ElementArrayCacheBufferSubData(byteOffset, data.Data(), data.Length()); michael@0: michael@0: gl->fBufferSubData(target, byteOffset, data.Length(), data.Data()); michael@0: } michael@0: michael@0: void michael@0: WebGLContext::BufferSubData(GLenum target, WebGLsizeiptr byteOffset, michael@0: const ArrayBufferView& data) michael@0: { michael@0: if (IsContextLost()) michael@0: return; michael@0: michael@0: WebGLRefPtr* bufferSlot = GetBufferSlotByTarget(target, "bufferSubData"); michael@0: michael@0: if (!bufferSlot) { michael@0: return; michael@0: } michael@0: michael@0: if (byteOffset < 0) michael@0: return ErrorInvalidValue("bufferSubData: negative offset"); michael@0: michael@0: WebGLBuffer* boundBuffer = bufferSlot->get(); michael@0: michael@0: if (!boundBuffer) michael@0: return ErrorInvalidOperation("bufferSubData: no buffer bound!"); michael@0: michael@0: data.ComputeLengthAndData(); michael@0: michael@0: CheckedInt checked_neededByteLength = CheckedInt(byteOffset) + data.Length(); michael@0: if (!checked_neededByteLength.isValid()) michael@0: return ErrorInvalidValue("bufferSubData: integer overflow computing the needed byte length"); michael@0: michael@0: if (checked_neededByteLength.value() > boundBuffer->ByteLength()) michael@0: return ErrorInvalidValue("bufferSubData: not enough data -- operation requires %d bytes, but buffer only has %d bytes", michael@0: checked_neededByteLength.value(), boundBuffer->ByteLength()); michael@0: michael@0: boundBuffer->ElementArrayCacheBufferSubData(byteOffset, data.Data(), data.Length()); michael@0: michael@0: MakeContextCurrent(); michael@0: gl->fBufferSubData(target, byteOffset, data.Length(), data.Data()); michael@0: } michael@0: michael@0: already_AddRefed michael@0: WebGLContext::CreateBuffer() michael@0: { michael@0: if (IsContextLost()) michael@0: return nullptr; michael@0: michael@0: nsRefPtr globj = new WebGLBuffer(this); michael@0: return globj.forget(); michael@0: } michael@0: michael@0: void michael@0: WebGLContext::DeleteBuffer(WebGLBuffer *buffer) michael@0: { michael@0: if (IsContextLost()) michael@0: return; michael@0: michael@0: if (!ValidateObjectAllowDeletedOrNull("deleteBuffer", buffer)) michael@0: return; michael@0: michael@0: if (!buffer || buffer->IsDeleted()) michael@0: return; michael@0: michael@0: if (mBoundArrayBuffer == buffer) { michael@0: BindBuffer(LOCAL_GL_ARRAY_BUFFER, michael@0: static_cast(nullptr)); michael@0: } michael@0: michael@0: if (mBoundVertexArray->mBoundElementArrayBuffer == buffer) { michael@0: BindBuffer(LOCAL_GL_ELEMENT_ARRAY_BUFFER, michael@0: static_cast(nullptr)); michael@0: } michael@0: michael@0: for (int32_t i = 0; i < mGLMaxVertexAttribs; i++) { michael@0: if (mBoundVertexArray->HasAttrib(i) && mBoundVertexArray->mAttribs[i].buf == buffer) michael@0: mBoundVertexArray->mAttribs[i].buf = nullptr; michael@0: } michael@0: michael@0: buffer->RequestDelete(); michael@0: } michael@0: michael@0: bool michael@0: WebGLContext::IsBuffer(WebGLBuffer *buffer) michael@0: { michael@0: if (IsContextLost()) michael@0: return false; michael@0: michael@0: return ValidateObjectAllowDeleted("isBuffer", buffer) && michael@0: !buffer->IsDeleted() && michael@0: buffer->HasEverBeenBound(); michael@0: } michael@0: michael@0: bool michael@0: WebGLContext::ValidateBufferUsageEnum(GLenum target, const char *infos) michael@0: { michael@0: switch (target) { michael@0: case LOCAL_GL_STREAM_DRAW: michael@0: case LOCAL_GL_STATIC_DRAW: michael@0: case LOCAL_GL_DYNAMIC_DRAW: michael@0: return true; michael@0: default: michael@0: break; michael@0: } michael@0: michael@0: ErrorInvalidEnumInfo(infos, target); michael@0: return false; michael@0: } michael@0: michael@0: WebGLRefPtr* michael@0: WebGLContext::GetBufferSlotByTarget(GLenum target, const char* infos) michael@0: { michael@0: switch (target) { michael@0: case LOCAL_GL_ARRAY_BUFFER: michael@0: return &mBoundArrayBuffer; michael@0: michael@0: case LOCAL_GL_ELEMENT_ARRAY_BUFFER: michael@0: return &mBoundVertexArray->mBoundElementArrayBuffer; michael@0: michael@0: case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER: michael@0: if (!IsWebGL2()) { michael@0: break; michael@0: } michael@0: return &mBoundTransformFeedbackBuffer; michael@0: michael@0: default: michael@0: break; michael@0: } michael@0: michael@0: ErrorInvalidEnum("%s: target: invalid enum value 0x%x", infos, target); michael@0: return nullptr; michael@0: } michael@0: michael@0: WebGLRefPtr* michael@0: WebGLContext::GetBufferSlotByTargetIndexed(GLenum target, GLuint index, const char* infos) michael@0: { michael@0: switch (target) { michael@0: case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER: michael@0: if (index >= mGLMaxTransformFeedbackSeparateAttribs) { michael@0: ErrorInvalidValue("%s: index should be less than MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS", infos, index); michael@0: return nullptr; michael@0: } michael@0: return nullptr; // See bug 903594 michael@0: michael@0: default: michael@0: break; michael@0: } michael@0: michael@0: ErrorInvalidEnum("%s: target: invalid enum value 0x%x", infos, target); michael@0: return nullptr; michael@0: } michael@0: michael@0: GLenum michael@0: WebGLContext::CheckedBufferData(GLenum target, michael@0: GLsizeiptr size, michael@0: const GLvoid *data, michael@0: GLenum usage) michael@0: { michael@0: #ifdef XP_MACOSX michael@0: // bug 790879 michael@0: if (gl->WorkAroundDriverBugs() && michael@0: int64_t(size) > INT32_MAX) // the cast avoids a potential always-true warning on 32bit michael@0: { michael@0: GenerateWarning("Rejecting valid bufferData call with size %lu to avoid a Mac bug", size); michael@0: return LOCAL_GL_INVALID_VALUE; michael@0: } michael@0: #endif michael@0: WebGLBuffer *boundBuffer = nullptr; michael@0: if (target == LOCAL_GL_ARRAY_BUFFER) { michael@0: boundBuffer = mBoundArrayBuffer; michael@0: } else if (target == LOCAL_GL_ELEMENT_ARRAY_BUFFER) { michael@0: boundBuffer = mBoundVertexArray->mBoundElementArrayBuffer; michael@0: } michael@0: MOZ_ASSERT(boundBuffer != nullptr, "no buffer bound for this target"); michael@0: michael@0: bool sizeChanges = uint32_t(size) != boundBuffer->ByteLength(); michael@0: if (sizeChanges) { michael@0: GetAndFlushUnderlyingGLErrors(); michael@0: gl->fBufferData(target, size, data, usage); michael@0: GLenum error = GetAndFlushUnderlyingGLErrors(); michael@0: return error; michael@0: } else { michael@0: gl->fBufferData(target, size, data, usage); michael@0: return LOCAL_GL_NO_ERROR; michael@0: } michael@0: }