bool WebGLContext::ValidateAttribPointer(bool integerMode, GLuint index, GLint size, GLenum type, WebGLboolean normalized, GLsizei stride, WebGLintptr byteOffset, const char* info) { WebGLBuffer* buffer = mBoundArrayBuffer; if (!buffer) { ErrorInvalidOperation("%s: must have valid GL_ARRAY_BUFFER binding", info); return false; } GLsizei requiredAlignment = 0; if (!ValidateAttribPointerType(integerMode, type, &requiredAlignment, info)) return false; // requiredAlignment should always be a power of two MOZ_ASSERT(IsPowerOfTwo(requiredAlignment)); GLsizei requiredAlignmentMask = requiredAlignment - 1; if (size < 1 || size > 4) { ErrorInvalidValue("%s: invalid element size", info); return false; } // see WebGL spec section 6.6 "Vertex Attribute Data Stride" if (stride < 0 || stride > 255) { ErrorInvalidValue("%s: negative or too large stride", info); return false; } if (byteOffset < 0) { ErrorInvalidValue("%s: negative offset", info); return false; } if (stride & requiredAlignmentMask) { ErrorInvalidOperation("%s: stride doesn't satisfy the alignment " "requirement of given type", info); return false; } if (byteOffset & requiredAlignmentMask) { ErrorInvalidOperation("%s: byteOffset doesn't satisfy the alignment " "requirement of given type", info); return false; } return true; }
IndexedBufferBinding* WebGLContext::ValidateIndexedBufferSlot(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: ErrorInvalidEnumInfo("target", target); return nullptr; } if (index >= bindings->size()) { ErrorInvalidValue("`index` >= %s.", maxIndexEnum); return nullptr; } return &(*bindings)[index]; }
void WebGLContext::GetParameterIndexed(JSContext* cx, GLenum pname, GLuint index, JS::MutableHandle<JS::Value> retval) { if (IsContextLost()) { retval.setNull(); return; } MakeContextCurrent(); switch (pname) { case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_BINDING: { if (index >= mGLMaxTransformFeedbackSeparateAttribs) { ErrorInvalidValue("getParameterIndexed: index should be less than MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS", index); retval.setNull(); return; } retval.setNull(); // See bug 903594 return; } default: break; } ErrorInvalidEnumInfo("getParameterIndexed: parameter", pname); retval.setNull(); }
void WebGL2Context::GetIndexedParameter(JSContext* cx, GLenum target, GLuint index, JS::MutableHandleValue retval, ErrorResult& out_error) { const char funcName[] = "getIndexedParameter"; retval.set(JS::NullValue()); if (IsContextLost()) return; const std::vector<IndexedBufferBinding>* bindings; switch (target) { case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_BINDING: case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_START: case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_SIZE: bindings = &(mBoundTransformFeedback->mIndexedBindings); break; case LOCAL_GL_UNIFORM_BUFFER_BINDING: case LOCAL_GL_UNIFORM_BUFFER_START: case LOCAL_GL_UNIFORM_BUFFER_SIZE: bindings = &mIndexedUniformBufferBindings; break; default: ErrorInvalidEnumInfo("getIndexedParameter: target", target); return; } if (index >= bindings->size()) { ErrorInvalidValue("%s: `index` must be < %s.", funcName, "MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS"); return; } const auto& binding = (*bindings)[index]; JS::Value ret = JS::NullValue(); switch (target) { case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_BINDING: case LOCAL_GL_UNIFORM_BUFFER_BINDING: if (binding.mBufferBinding) { ret = WebGLObjectAsJSValue(cx, binding.mBufferBinding.get(), out_error); } break; case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_START: case LOCAL_GL_UNIFORM_BUFFER_START: ret = JS::NumberValue(binding.mRangeStart); break; case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_SIZE: case LOCAL_GL_UNIFORM_BUFFER_SIZE: ret = JS::NumberValue(binding.mRangeSize); break; } retval.set(ret); }
void WebGLContext::BufferSubDataT(GLenum target, WebGLsizeiptr byteOffset, const BufferT& data) { if (IsContextLost()) return; if (!ValidateBufferTarget(target, "bufferSubData")) return; WebGLRefPtr<WebGLBuffer>& 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<WebGLsizeiptr> checked_neededByteLength = CheckedInt<WebGLsizeiptr>(byteOffset) + data.Length(); 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; } boundBuffer->ElementArrayCacheBufferSubData(byteOffset, data.Data(), data.Length()); MakeContextCurrent(); gl->fBufferSubData(target, byteOffset, data.Length(), data.Data()); }
void WebGL2Context::GetBufferSubData(GLenum target, GLintptr offset, const dom::Nullable<dom::ArrayBuffer>& maybeData) { // If returnedData is null then an INVALID_VALUE error is // generated. if (maybeData.IsNull()) return ErrorInvalidValue("getBufferSubData: returnedData is null"); const dom::ArrayBuffer& data = maybeData.Value(); GetBufferSubDataT(target, offset, data); }
bool WebGL1Context::ValidateUniformMatrixTranspose(bool transpose, const char* info) { if (transpose) { ErrorInvalidValue("%s: transpose must be FALSE as per the " "OpenGL ES 2.0 spec", info); return false; } return true; }
void WebGLContext::BufferData(GLenum target, const dom::Nullable<dom::ArrayBuffer>& 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); }
/* This doesn't belong here. It's part of state querying */ void WebGL2Context::GetIndexedParameter(GLenum target, GLuint index, dom::Nullable<dom::OwningWebGLBufferOrLongLong>& retval) { retval.SetNull(); if (IsContextLost()) return; GLint64 data = 0; MakeContextCurrent(); switch (target) { case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_BINDING: if (index >= mGLMaxTransformFeedbackSeparateAttribs) return ErrorInvalidValue("getIndexedParameter: index should be less than " "MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS"); retval.SetValue().SetAsWebGLBuffer() = mBoundTransformFeedbackBuffers[index].get(); return; case LOCAL_GL_UNIFORM_BUFFER_BINDING: if (index >= mGLMaxUniformBufferBindings) return ErrorInvalidValue("getIndexedParameter: index should be than " "MAX_UNIFORM_BUFFER_BINDINGS"); retval.SetValue().SetAsWebGLBuffer() = mBoundUniformBuffers[index].get(); return; case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_START: case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_SIZE: case LOCAL_GL_UNIFORM_BUFFER_START: case LOCAL_GL_UNIFORM_BUFFER_SIZE: gl->fGetInteger64i_v(target, index, &data); retval.SetValue().SetAsLongLong() = data; return; } ErrorInvalidEnumInfo("getIndexedParameter: target", target); }
bool WebGLContext::ValidateAttribIndex(GLuint index, const char* info) { bool valid = (index < MaxVertexAttribs()); if (!valid) { if (index == GLuint(-1)) { ErrorInvalidValue("%s: -1 is not a valid `index`. This value" " probably comes from a getAttribLocation()" " call, where this return value -1 means" " that the passed name didn't correspond to" " an active attribute in the specified" " program.", info); } else { ErrorInvalidValue("%s: `index` must be less than" " MAX_VERTEX_ATTRIBS.", info); } } return valid; }
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); }
bool WebGL2Context::ValidateClearBuffer(const char* info, GLenum buffer, GLint drawbuffer, size_t elemCount) { size_t requiredElements = -1; GLint maxDrawbuffer = -1; switch (buffer) { case LOCAL_GL_COLOR: case LOCAL_GL_FRONT: case LOCAL_GL_BACK: case LOCAL_GL_LEFT: case LOCAL_GL_RIGHT: case LOCAL_GL_FRONT_AND_BACK: requiredElements = 4; maxDrawbuffer = mGLMaxDrawBuffers - 1; break; case LOCAL_GL_DEPTH: case LOCAL_GL_STENCIL: requiredElements = 1; maxDrawbuffer = 0; break; default: ErrorInvalidEnumInfo(info, buffer); return false; } if (drawbuffer < 0 || drawbuffer > maxDrawbuffer) { ErrorInvalidValue("%s: invalid drawbuffer %d. This buffer only supports drawbuffer values between 0 and %d", info, drawbuffer, maxDrawbuffer); return false; } if (elemCount < requiredElements) { ErrorInvalidValue("%s: Not enough elements. Require %u. Given %u.", info, requiredElements, elemCount); return false; } return true; }
bool WebGLContext::ValidateDataOffsetSize(WebGLintptr offset, WebGLsizeiptr size, WebGLsizeiptr bufferSize, const char* info) { if (offset < 0) { ErrorInvalidValue("%s: offset must be positive", info); return false; } if (size < 0) { ErrorInvalidValue("%s: size must be positive", info); return false; } // *** Careful *** WebGLsizeiptr is always 64-bits but GLsizeiptr // is like intptr_t. On some platforms it is 32-bits. CheckedInt<GLsizeiptr> neededBytes = CheckedInt<GLsizeiptr>(offset) + size; if (!neededBytes.isValid() || neededBytes.value() > bufferSize) { ErrorInvalidValue("%s: invalid range", info); return false; } return true; }
/** * Check data ranges [readOffset, readOffset + size] and [writeOffset, * writeOffset + size] for overlap. * * It is assumed that offset and size have already been validated with * ValidateDataOffsetSize(). */ bool WebGLContext::ValidateDataRanges(WebGLintptr readOffset, WebGLintptr writeOffset, WebGLsizeiptr size, const char* info) { MOZ_ASSERT((CheckedInt<WebGLsizeiptr>(readOffset) + size).isValid()); MOZ_ASSERT((CheckedInt<WebGLsizeiptr>(writeOffset) + size).isValid()); bool separate = (readOffset + size < writeOffset || writeOffset + size < readOffset); if (!separate) { ErrorInvalidValue("%s: ranges [readOffset, readOffset + size) and [writeOffset, " "writeOffset + size) overlap", info); } return separate; }
bool WebGLContext::ValidateAttribArraySetter(const char* name, uint32_t setterElemSize, uint32_t arrayLength) { if (IsContextLost()) return false; if (arrayLength < setterElemSize) { ErrorInvalidValue("%s: Array must have >= %d elements.", name, setterElemSize); return false; } return true; }
void WebGLContext::BufferData(GLenum target, WebGLsizeiptr size, GLenum usage) { if (IsContextLost()) return; if (!ValidateBufferTarget(target, "bufferData")) return; WebGLRefPtr<WebGLBuffer>& 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<GLsizeiptr>(size).isValid()) return ErrorOutOfMemory("bufferData: bad size"); WebGLBuffer* boundBuffer = bufferSlot.get(); if (!boundBuffer) return ErrorInvalidOperation("bufferData: no buffer bound!"); UniquePtr<uint8_t> 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)) { return ErrorOutOfMemory("bufferData: out of memory"); } }
void WebGL2Context::BindSampler(GLuint unit, WebGLSampler* sampler) { if (IsContextLost()) return; if (!ValidateObjectAllowDeletedOrNull("bindSampler", sampler)) return; if (GLint(unit) >= mGLMaxTextureUnits) return ErrorInvalidValue("bindSampler: unit must be < %d", mGLMaxTextureUnits); if (sampler && sampler->IsDeleted()) return ErrorInvalidOperation("bindSampler: binding deleted sampler"); WebGLContextUnchecked::BindSampler(unit, sampler); mBoundSamplers[unit] = sampler; }
void WebGLContext::Clear(GLbitfield mask) { const char funcName[] = "clear"; if (IsContextLost()) return; MakeContextCurrent(); uint32_t m = mask & (LOCAL_GL_COLOR_BUFFER_BIT | LOCAL_GL_DEPTH_BUFFER_BIT | LOCAL_GL_STENCIL_BUFFER_BIT); if (mask != m) return ErrorInvalidValue("%s: invalid mask bits", funcName); if (mask == 0) { GenerateWarning("Calling gl.clear(0) has no effect."); } else if (mRasterizerDiscardEnabled) { GenerateWarning("Calling gl.clear() with RASTERIZER_DISCARD enabled has no effects."); } if (mBoundDrawFramebuffer) { if (!mBoundDrawFramebuffer->ValidateAndInitAttachments(funcName)) return; gl->fClear(mask); return; } else { ClearBackbufferIfNeeded(); } // Ok, we're clearing the default framebuffer/screen. { ScopedMaskWorkaround autoMask(*this); gl->fClear(mask); } Invalidate(); mShouldPresent = true; }
bool WebGLContext::DrawArrays_check(GLint first, GLsizei count, GLsizei primcount, const char* info) { if (first < 0 || count < 0) { ErrorInvalidValue("%s: negative first or count", info); return false; } if (primcount < 0) { ErrorInvalidValue("%s: negative primcount", info); return false; } if (!ValidateStencilParamsForDrawCall()) { return false; } // If count is 0, there's nothing to do. if (count == 0 || primcount == 0) { return false; } // Any checks below this depend on a program being available. if (!mCurrentProgram) { ErrorInvalidOperation("%s: null CURRENT_PROGRAM", info); return false; } if (!ValidateBufferFetching(info)) { return false; } CheckedInt<GLsizei> checked_firstPlusCount = CheckedInt<GLsizei>(first) + count; if (!checked_firstPlusCount.isValid()) { ErrorInvalidOperation("%s: overflow in first+count", info); return false; } if (uint32_t(checked_firstPlusCount.value()) > mMaxFetchedVertices) { ErrorInvalidOperation("%s: bound vertex attribute buffers do not have sufficient size for given first and count", info); return false; } if (uint32_t(primcount) > mMaxFetchedInstances) { ErrorInvalidOperation("%s: bound instance attribute buffers do not have sufficient size for given primcount", info); return false; } MOZ_ASSERT(gl->IsCurrent()); if (mBoundDrawFramebuffer) { if (!mBoundDrawFramebuffer->ValidateAndInitAttachments(info)) return false; } else { ClearBackbufferIfNeeded(); } if (!DoFakeVertexAttrib0(checked_firstPlusCount.value())) { return false; } return true; }
void WebGL2Context::GetBufferSubDataT(GLenum target, GLintptr offset, const BufferT& data) { if (IsContextLost()) return; // For the WebGLBuffer bound to the passed target, read // returnedData.byteLength bytes from the buffer starting at byte // offset offset and write them to returnedData. // If zero is bound to target, an INVALID_OPERATION error is // generated. if (!ValidateBufferTarget(target, "getBufferSubData")) return; // If offset is less than zero, an INVALID_VALUE error is // generated. if (offset < 0) return ErrorInvalidValue("getBufferSubData: negative offset"); WebGLRefPtr<WebGLBuffer>& bufferSlot = GetBufferSlotByTarget(target); WebGLBuffer* boundBuffer = bufferSlot.get(); if (!boundBuffer) return ErrorInvalidOperation("getBufferSubData: no buffer bound"); // If offset + returnedData.byteLength would extend beyond the end // of the buffer an INVALID_VALUE error is generated. data.ComputeLengthAndData(); CheckedInt<WebGLsizeiptr> neededByteLength = CheckedInt<WebGLsizeiptr>(offset) + data.LengthAllowShared(); if (!neededByteLength.isValid()) { ErrorInvalidValue("getBufferSubData: Integer overflow computing the needed" " byte length."); return; } if (neededByteLength.value() > boundBuffer->ByteLength()) { ErrorInvalidValue("getBufferSubData: Not enough data. Operation requires" " %d bytes, but buffer only has %d bytes.", neededByteLength.value(), boundBuffer->ByteLength()); return; } // If target is TRANSFORM_FEEDBACK_BUFFER, and any transform // feedback object is currently active, an INVALID_OPERATION error // is generated. WebGLTransformFeedback* currentTF = mBoundTransformFeedback; if (target == LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER && currentTF) { if (currentTF->mIsActive) return ErrorInvalidOperation("getBufferSubData: Currently bound transform" " feedback is active"); // https://github.com/NVIDIA/WebGL/commit/63aff5e58c1d79825a596f0f4aa46174b9a5f72c // Performing reads and writes on a buffer that is currently // bound for transform feedback causes undefined results in // GLES3.0 and OpenGL 4.5. In practice results of reads and // writes might be consistent as long as transform feedback // objects are not active, but neither GLES3.0 nor OpenGL 4.5 // spec guarantees this - just being bound for transform // feedback is sufficient to cause undefined results. BindTransformFeedback(LOCAL_GL_TRANSFORM_FEEDBACK, nullptr); } /* If the buffer is written and read sequentially by other * operations and getBufferSubData, it is the responsibility of * the WebGL API to ensure that data are access * consistently. This applies even if the buffer is currently * bound to a transform feedback binding point. */ void* ptr = gl->fMapBufferRange(target, offset, data.LengthAllowShared(), LOCAL_GL_MAP_READ_BIT); // Warning: Possibly shared memory. See bug 1225033. memcpy(data.DataAllowShared(), ptr, data.LengthAllowShared()); gl->fUnmapBuffer(target); if (target == LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER && currentTF) { BindTransformFeedback(LOCAL_GL_TRANSFORM_FEEDBACK, currentTF); } }
void WebGLContext::BindBufferRange(GLenum target, GLuint index, WebGLBuffer* buffer, WebGLintptr offset, WebGLsizeiptr size) { const char funcName[] = "bindBufferRange"; if (IsContextLost()) return; if (!ValidateObjectAllowDeletedOrNull(funcName, buffer)) return; if (buffer && buffer->IsDeleted()) return ErrorInvalidOperation("%s: Cannot bind a deleted object.", funcName); if (!ValidateNonNegative(funcName, "offset", offset) || !ValidateNonNegative(funcName, "size", size)) { return; } WebGLRefPtr<WebGLBuffer>* genericBinding; IndexedBufferBinding* indexedBinding; if (!ValidateIndexedBufferBinding(funcName, target, index, &genericBinding, &indexedBinding)) { return; } if (buffer && !buffer->ValidateCanBindToTarget(funcName, target)) 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); //// *genericBinding = buffer; indexedBinding->mBufferBinding = buffer; indexedBinding->mRangeStart = offset; indexedBinding->mRangeSize = size; if (buffer) { buffer->SetContentAfterBind(target); } switch (target) { case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER: mBoundTransformFeedback->OnIndexedBindingsChanged(); break; case LOCAL_GL_UNIFORM: OnUBIndexedBindingsChanged(); break; } }
bool WebGLContext::DrawElements_check(GLsizei count, GLenum type, WebGLintptr byteOffset, GLsizei primcount, const char* info, GLuint* out_upperBound) { if (count < 0 || byteOffset < 0) { ErrorInvalidValue("%s: negative count or offset", info); return false; } if (primcount < 0) { ErrorInvalidValue("%s: negative primcount", info); return false; } if (!ValidateStencilParamsForDrawCall()) { return false; } // If count is 0, there's nothing to do. if (count == 0 || primcount == 0) return false; uint8_t bytesPerElem = 0; switch (type) { case LOCAL_GL_UNSIGNED_BYTE: bytesPerElem = 1; break; case LOCAL_GL_UNSIGNED_SHORT: bytesPerElem = 2; break; case LOCAL_GL_UNSIGNED_INT: if (IsWebGL2() || IsExtensionEnabled(WebGLExtensionID::OES_element_index_uint)) { bytesPerElem = 4; } break; } if (!bytesPerElem) { ErrorInvalidEnum("%s: Invalid `type`: 0x%04x", info, type); return false; } if (byteOffset % bytesPerElem != 0) { ErrorInvalidOperation("%s: `byteOffset` must be a multiple of the size of `type`", info); return false; } const GLsizei first = byteOffset / bytesPerElem; const CheckedUint32 checked_byteCount = bytesPerElem * CheckedUint32(count); if (!checked_byteCount.isValid()) { ErrorInvalidValue("%s: overflow in byteCount", info); return false; } // Any checks below this depend on a program being available. if (!mCurrentProgram) { ErrorInvalidOperation("%s: null CURRENT_PROGRAM", info); return false; } if (!mBoundVertexArray->mElementArrayBuffer) { ErrorInvalidOperation("%s: must have element array buffer binding", info); return false; } WebGLBuffer& elemArrayBuffer = *mBoundVertexArray->mElementArrayBuffer; if (!elemArrayBuffer.ByteLength()) { ErrorInvalidOperation("%s: bound element array buffer doesn't have any data", info); return false; } CheckedInt<GLsizei> checked_neededByteCount = checked_byteCount.toChecked<GLsizei>() + byteOffset; if (!checked_neededByteCount.isValid()) { ErrorInvalidOperation("%s: overflow in byteOffset+byteCount", info); return false; } if (uint32_t(checked_neededByteCount.value()) > elemArrayBuffer.ByteLength()) { ErrorInvalidOperation("%s: bound element array buffer is too small for given count and offset", info); return false; } if (!ValidateBufferFetching(info)) return false; if (!mMaxFetchedVertices || !elemArrayBuffer.Validate(type, mMaxFetchedVertices - 1, first, count, out_upperBound)) { ErrorInvalidOperation( "%s: bound vertex attribute buffers do not have sufficient " "size for given indices from the bound element array", info); return false; } if (uint32_t(primcount) > mMaxFetchedInstances) { ErrorInvalidOperation("%s: bound instance attribute buffers do not have sufficient size for given primcount", info); return false; } // Bug 1008310 - Check if buffer has been used with a different previous type if (elemArrayBuffer.IsElementArrayUsedWithMultipleTypes()) { GenerateWarning("%s: bound element array buffer previously used with a type other than " "%s, this will affect performance.", info, WebGLContext::EnumName(type)); } MOZ_ASSERT(gl->IsCurrent()); if (mBoundDrawFramebuffer) { if (!mBoundDrawFramebuffer->ValidateAndInitAttachments(info)) return false; } else { ClearBackbufferIfNeeded(); } if (!DoFakeVertexAttrib0(mMaxFetchedVertices)) { return false; } return true; }
bool WebGL2Context::ValidateClearBuffer(const char* funcName, GLenum buffer, GLint drawBuffer, size_t availElemCount, GLuint elemOffset, GLenum funcType) { if (elemOffset > availElemCount) { ErrorInvalidValue("%s: Offset too big for list.", funcName); return false; } availElemCount -= elemOffset; //// size_t requiredElements; GLint maxDrawBuffer; switch (buffer) { case LOCAL_GL_COLOR: requiredElements = 4; maxDrawBuffer = mGLMaxDrawBuffers - 1; break; case LOCAL_GL_DEPTH: case LOCAL_GL_STENCIL: requiredElements = 1; maxDrawBuffer = 0; break; case LOCAL_GL_DEPTH_STENCIL: requiredElements = 2; maxDrawBuffer = 0; break; default: ErrorInvalidEnumInfo(funcName, buffer); return false; } if (drawBuffer < 0 || drawBuffer > maxDrawBuffer) { ErrorInvalidValue("%s: Invalid drawbuffer %d. This buffer only supports" " `drawbuffer` values between 0 and %u.", funcName, drawBuffer, maxDrawBuffer); return false; } if (availElemCount < requiredElements) { ErrorInvalidValue("%s: Not enough elements. Require %u. Given %u.", funcName, requiredElements, availElemCount); return false; } //// MakeContextCurrent(); const auto& fb = mBoundDrawFramebuffer; if (fb) { if (!fb->ValidateAndInitAttachments(funcName)) return false; if (!fb->ValidateClearBufferType(funcName, buffer, drawBuffer, funcType)) return false; } else if (buffer == LOCAL_GL_COLOR) { if (drawBuffer != 0) return true; if (mDefaultFB_DrawBuffer0 == LOCAL_GL_NONE) return true; if (funcType != LOCAL_GL_FLOAT) { ErrorInvalidOperation("%s: For default framebuffer, COLOR is always of type" " FLOAT.", funcName); return false; } } return true; }
void WebGLContext::DrawBuffers(const dom::Sequence<GLenum>& buffers) { if (IsContextLost()) return; const size_t buffersLength = buffers.Length(); if (!buffersLength) { return ErrorInvalidValue("drawBuffers: invalid <buffers> (buffers must not be empty)"); } if (!mBoundDrawFramebuffer) { // OK: we are rendering in the default framebuffer /* EXT_draw_buffers : If the GL is bound to the default framebuffer, then <buffersLength> must be 1 and the constant must be BACK or NONE. When draw buffer zero is BACK, color values are written into the sole buffer for single- buffered contexts, or into the back buffer for double-buffered contexts. If DrawBuffersEXT is supplied with a constant other than BACK and NONE, the error INVALID_OPERATION is generated. */ if (buffersLength != 1) { return ErrorInvalidValue("drawBuffers: invalid <buffers> (main framebuffer: buffers.length must be 1)"); } if (buffers[0] == LOCAL_GL_NONE || buffers[0] == LOCAL_GL_BACK) { gl->Screen()->SetDrawBuffer(buffers[0]); return; } return ErrorInvalidOperation("drawBuffers: invalid operation (main framebuffer: buffers[0] must be GL_NONE or GL_BACK)"); } // OK: we are rendering in a framebuffer object if (buffersLength > size_t(mGLMaxDrawBuffers)) { /* EXT_draw_buffers : The maximum number of draw buffers is implementation-dependent. The number of draw buffers supported can be queried by calling GetIntegerv with the symbolic constant MAX_DRAW_BUFFERS_EXT. An INVALID_VALUE error is generated if <buffersLength> is greater than MAX_DRAW_BUFFERS_EXT. */ return ErrorInvalidValue("drawBuffers: invalid <buffers> (buffers.length > GL_MAX_DRAW_BUFFERS)"); } for (uint32_t i = 0; i < buffersLength; i++) { /* EXT_draw_buffers : If the GL is bound to a draw framebuffer object, the <i>th buffer listed in <bufs> must be COLOR_ATTACHMENT<i>_EXT or NONE. Specifying a buffer out of order, BACK, or COLOR_ATTACHMENT<m>_EXT where <m> is greater than or equal to the value of MAX_COLOR_ATTACHMENTS_EXT, will generate the error INVALID_OPERATION. */ /* WEBGL_draw_buffers : The value of the MAX_COLOR_ATTACHMENTS_WEBGL parameter must be greater than or equal to that of the MAX_DRAW_BUFFERS_WEBGL parameter. */ if (buffers[i] != LOCAL_GL_NONE && buffers[i] != GLenum(LOCAL_GL_COLOR_ATTACHMENT0 + i)) { return ErrorInvalidOperation("drawBuffers: invalid operation (buffers[i] must be GL_NONE or GL_COLOR_ATTACHMENTi)"); } } MakeContextCurrent(); gl->fDrawBuffers(buffersLength, buffers.Elements()); }
void WebGL2Context::BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) { if (IsContextLost()) return; const GLbitfield validBits = LOCAL_GL_COLOR_BUFFER_BIT | LOCAL_GL_DEPTH_BUFFER_BIT | LOCAL_GL_STENCIL_BUFFER_BIT; if ((mask | validBits) != validBits) { ErrorInvalidValue("blitFramebuffer: Invalid bit set in mask."); return; } switch (filter) { case LOCAL_GL_NEAREST: case LOCAL_GL_LINEAR: break; default: ErrorInvalidEnumInfo("blitFramebuffer: Bad `filter`:", filter); return; } const GLbitfield depthAndStencilBits = LOCAL_GL_DEPTH_BUFFER_BIT | LOCAL_GL_STENCIL_BUFFER_BIT; if (mask & depthAndStencilBits && filter != LOCAL_GL_NEAREST) { ErrorInvalidOperation("blitFramebuffer: DEPTH_BUFFER_BIT and" " STENCIL_BUFFER_BIT can only be used with" " NEAREST filtering."); return; } if (mBoundReadFramebuffer == mBoundDrawFramebuffer) { // TODO: It's actually more complicated than this. We need to check that // the underlying buffers are not the same, not the framebuffers // themselves. ErrorInvalidOperation("blitFramebuffer: Source and destination must" " differ."); return; } GLsizei srcSamples; const webgl::FormatInfo* srcColorFormat = nullptr; const webgl::FormatInfo* srcDepthFormat = nullptr; const webgl::FormatInfo* srcStencilFormat = nullptr; if (mBoundReadFramebuffer) { if (!mBoundReadFramebuffer->ValidateAndInitAttachments("blitFramebuffer's READ_FRAMEBUFFER")) return; if (!GetFBInfoForBlit(mBoundReadFramebuffer, "READ_FRAMEBUFFER", &srcSamples, &srcColorFormat, &srcDepthFormat, &srcStencilFormat)) { return; } } else { srcSamples = 0; // Always 0. GetBackbufferFormats(mOptions, &srcColorFormat, &srcDepthFormat, &srcStencilFormat); } GLsizei dstSamples; const webgl::FormatInfo* dstColorFormat = nullptr; const webgl::FormatInfo* dstDepthFormat = nullptr; const webgl::FormatInfo* dstStencilFormat = nullptr; if (mBoundDrawFramebuffer) { if (!mBoundDrawFramebuffer->ValidateAndInitAttachments("blitFramebuffer's DRAW_FRAMEBUFFER")) return; if (!GetFBInfoForBlit(mBoundDrawFramebuffer, "DRAW_FRAMEBUFFER", &dstSamples, &dstColorFormat, &dstDepthFormat, &dstStencilFormat)) { return; } } else { dstSamples = gl->Screen()->Samples(); GetBackbufferFormats(mOptions, &dstColorFormat, &dstDepthFormat, &dstStencilFormat); } if (mask & LOCAL_GL_COLOR_BUFFER_BIT) { const auto fnSignlessType = [](const webgl::FormatInfo* format) -> webgl::ComponentType { if (!format) return webgl::ComponentType::None; switch (format->componentType) { case webgl::ComponentType::UInt: return webgl::ComponentType::Int; case webgl::ComponentType::NormUInt: return webgl::ComponentType::NormInt; default: return format->componentType; } }; const auto srcType = fnSignlessType(srcColorFormat); const auto dstType = fnSignlessType(dstColorFormat); if (srcType != dstType) { ErrorInvalidOperation("blitFramebuffer: Color buffer format component type" " mismatch."); return; } const bool srcIsInt = (srcType == webgl::ComponentType::Int); if (srcIsInt && filter != LOCAL_GL_NEAREST) { ErrorInvalidOperation("blitFramebuffer: Integer read buffers can only" " be filtered with NEAREST."); return; } } /* GLES 3.0.4, p199: * Calling BlitFramebuffer will result in an INVALID_OPERATION error if * mask includes DEPTH_BUFFER_BIT or STENCIL_BUFFER_BIT, and the source * and destination depth and stencil buffer formats do not match. * * jgilbert: The wording is such that if only DEPTH_BUFFER_BIT is specified, * the stencil formats must match. This seems wrong. It could be a spec bug, * or I could be missing an interaction in one of the earlier paragraphs. */ if (mask & LOCAL_GL_DEPTH_BUFFER_BIT && dstDepthFormat != srcDepthFormat) { ErrorInvalidOperation("blitFramebuffer: Depth buffer formats must match" " if selected."); return; } if (mask & LOCAL_GL_STENCIL_BUFFER_BIT && dstStencilFormat != srcStencilFormat) { ErrorInvalidOperation("blitFramebuffer: Stencil buffer formats must" " match if selected."); return; } if (dstSamples != 0) { ErrorInvalidOperation("blitFramebuffer: DRAW_FRAMEBUFFER may not have" " multiple samples."); return; } if (srcSamples != 0) { if (mask & LOCAL_GL_COLOR_BUFFER_BIT && dstColorFormat != srcColorFormat) { ErrorInvalidOperation("blitFramebuffer: Color buffer formats must" " match if selected, when reading from a" " multisampled source."); return; } if (dstX0 != srcX0 || dstX1 != srcX1 || dstY0 != srcY0 || dstY1 != srcY1) { ErrorInvalidOperation("blitFramebuffer: If the source is" " multisampled, then the source and dest" " regions must match exactly."); return; } } MakeContextCurrent(); gl->fBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); }
void WebGL2Context::CopyBufferSubData(GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size) { const char funcName[] = "copyBufferSubData"; if (IsContextLost()) return; const auto& readBuffer = ValidateBufferSelection(funcName, readTarget); if (!readBuffer) return; const auto& writeBuffer = ValidateBufferSelection(funcName, writeTarget); if (!writeBuffer) return; if (readBuffer->mNumActiveTFOs || writeBuffer->mNumActiveTFOs) { ErrorInvalidOperation("%s: Buffer is bound to an active transform feedback" " object.", funcName); return; } if (!ValidateNonNegative(funcName, "readOffset", readOffset) || !ValidateNonNegative(funcName, "writeOffset", writeOffset) || !ValidateNonNegative(funcName, "size", size)) { return; } const auto fnValidateOffsetSize = [&](const char* info, GLintptr offset, const WebGLBuffer* buffer) { const auto neededBytes = CheckedInt<size_t>(offset) + size; if (!neededBytes.isValid() || neededBytes.value() > buffer->ByteLength()) { ErrorInvalidValue("%s: Invalid %s range.", funcName, info); return false; } return true; }; if (!fnValidateOffsetSize("read", readOffset, readBuffer) || !fnValidateOffsetSize("write", writeOffset, writeBuffer)) { return; } if (readBuffer == writeBuffer && !ValidateDataRanges(readOffset, writeOffset, size, funcName)) { return; } const auto& readType = readBuffer->Content(); const auto& writeType = writeBuffer->Content(); MOZ_ASSERT(readType != WebGLBuffer::Kind::Undefined); MOZ_ASSERT(writeType != WebGLBuffer::Kind::Undefined); if (writeType != readType) { ErrorInvalidOperation("%s: Can't copy %s data to %s data.", funcName, (readType == WebGLBuffer::Kind::OtherData) ? "other" : "element", (writeType == WebGLBuffer::Kind::OtherData) ? "other" : "element"); return; } gl->MakeCurrent(); gl->fCopyBufferSubData(readTarget, writeTarget, readOffset, writeOffset, size); }
void WebGL2Context::GetBufferSubData(GLenum target, GLintptr offset, const dom::ArrayBufferView& data) { const char funcName[] = "getBufferSubData"; if (IsContextLost()) return; if (!ValidateNonNegative(funcName, "offset", offset)) return; const auto& buffer = ValidateBufferSelection(funcName, target); if (!buffer) return; //// // If offset + returnedData.byteLength would extend beyond the end // of the buffer an INVALID_VALUE error is generated. data.ComputeLengthAndData(); const auto neededByteLength = CheckedInt<size_t>(offset) + data.LengthAllowShared(); if (!neededByteLength.isValid()) { ErrorInvalidValue("%s: Integer overflow computing the needed byte length.", funcName); return; } if (neededByteLength.value() > buffer->ByteLength()) { ErrorInvalidValue("%s: Not enough data. Operation requires %d bytes, but buffer" " only has %d bytes.", funcName, neededByteLength.value(), buffer->ByteLength()); return; } //// if (buffer->mNumActiveTFOs) { ErrorInvalidOperation("%s: Buffer is bound to an active transform feedback" " object.", funcName); return; } if (target == LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER && mBoundTransformFeedback->mIsActive) { ErrorInvalidOperation("%s: Currently bound transform feedback is active.", funcName); return; } //// gl->MakeCurrent(); const auto ptr = gl->fMapBufferRange(target, offset, data.LengthAllowShared(), LOCAL_GL_MAP_READ_BIT); // Warning: Possibly shared memory. See bug 1225033. memcpy(data.DataAllowShared(), ptr, data.LengthAllowShared()); gl->fUnmapBuffer(target); }
void WebGL2Context::GetActiveUniforms(JSContext* cx, const WebGLProgram& program, const dom::Sequence<GLuint>& uniformIndices, GLenum pname, JS::MutableHandleValue retval) { const char funcName[] = "getActiveUniforms"; retval.setNull(); if (IsContextLost()) return; if (!ValidateUniformEnum(this, pname, funcName)) return; if (!ValidateObject("getActiveUniforms: program", program)) return; const auto& numActiveUniforms = program.LinkInfo()->uniforms.size(); for (const auto& curIndex : uniformIndices) { if (curIndex >= numActiveUniforms) { ErrorInvalidValue("%s: Too-large active uniform index queried.", funcName); return; } } const auto& count = uniformIndices.Length(); JS::Rooted<JSObject*> array(cx, JS_NewArrayObject(cx, count)); UniquePtr<GLint[]> samples(new GLint[count]); if (!array || !samples) { ErrorOutOfMemory("%s: Failed to allocate buffers.", funcName); return; } retval.setObject(*array); MakeContextCurrent(); gl->fGetActiveUniformsiv(program.mGLName, count, uniformIndices.Elements(), pname, samples.get()); switch (pname) { case LOCAL_GL_UNIFORM_TYPE: case LOCAL_GL_UNIFORM_SIZE: case LOCAL_GL_UNIFORM_BLOCK_INDEX: case LOCAL_GL_UNIFORM_OFFSET: case LOCAL_GL_UNIFORM_ARRAY_STRIDE: case LOCAL_GL_UNIFORM_MATRIX_STRIDE: for (size_t i = 0; i < count; ++i) { JS::RootedValue value(cx); value = JS::Int32Value(samples[i]); if (!JS_DefineElement(cx, array, i, value, JSPROP_ENUMERATE)) return; } break; case LOCAL_GL_UNIFORM_IS_ROW_MAJOR: for (size_t i = 0; i < count; ++i) { JS::RootedValue value(cx); value = JS::BooleanValue(samples[i]); if (!JS_DefineElement(cx, array, i, value, JSPROP_ENUMERATE)) return; } break; default: MOZ_CRASH("Invalid pname"); } }
void WebGL2Context::InvalidateSubFramebuffer(GLenum target, const dom::Sequence<GLenum>& attachments, GLint x, GLint y, GLsizei width, GLsizei height, ErrorResult& rv) { const char funcName[] = "invalidateSubFramebuffer"; if (IsContextLost()) return; MakeContextCurrent(); if (!ValidateFramebufferTarget(target, funcName)) return; if (width < 0 || height < 0) { ErrorInvalidValue("%s: width and height must be >= 0.", funcName); return; } const WebGLFramebuffer* fb; bool isDefaultFB; switch (target) { case LOCAL_GL_FRAMEBUFFER: case LOCAL_GL_DRAW_FRAMEBUFFER: fb = mBoundDrawFramebuffer; isDefaultFB = gl->Screen()->IsDrawFramebufferDefault(); break; case LOCAL_GL_READ_FRAMEBUFFER: fb = mBoundReadFramebuffer; isDefaultFB = gl->Screen()->IsReadFramebufferDefault(); break; default: MOZ_CRASH("Bad target."); } const bool badColorAttachmentIsInvalidOp = true; for (size_t i = 0; i < attachments.Length(); i++) { if (!ValidateFramebufferAttachment(fb, attachments[i], funcName, badColorAttachmentIsInvalidOp)) { return; } } // InvalidateFramebuffer is a hint to the driver. Should be OK to // skip calls if not supported, for example by OSX 10.9 GL // drivers. if (!gl->IsSupported(gl::GLFeature::invalidate_framebuffer)) return; if (!fb && !isDefaultFB) { dom::Sequence<GLenum> tmpAttachments; if (!TranslateDefaultAttachments(attachments, &tmpAttachments)) { rv.Throw(NS_ERROR_OUT_OF_MEMORY); return; } gl->fInvalidateSubFramebuffer(target, tmpAttachments.Length(), tmpAttachments.Elements(), x, y, width, height); } else { gl->fInvalidateSubFramebuffer(target, attachments.Length(), attachments.Elements(), x, y, width, height); } }
void WebGL2Context::FramebufferTextureLayer(GLenum target, GLenum attachment, WebGLTexture* texture, GLint level, GLint layer) { if (IsContextLost()) return; if (!ValidateFramebufferTarget(target, "framebufferTextureLayer")) return; if (!ValidateTextureLayerAttachment(attachment)) return ErrorInvalidEnumInfo("framebufferTextureLayer: attachment:", attachment); if (texture) { if (texture->IsDeleted()) { return ErrorInvalidValue("framebufferTextureLayer: texture must be a valid " "texture object."); } if (layer < 0) return ErrorInvalidValue("framebufferTextureLayer: layer must be >= 0."); if (level < 0) return ErrorInvalidValue("framebufferTextureLayer: level must be >= 0."); switch (texture->Target().get()) { case LOCAL_GL_TEXTURE_3D: if (uint32_t(layer) >= mImplMax3DTextureSize) { return ErrorInvalidValue("framebufferTextureLayer: layer must be < " "MAX_3D_TEXTURE_SIZE"); } if (uint32_t(level) > FloorLog2(mImplMax3DTextureSize)) { return ErrorInvalidValue("framebufferTextureLayer: layer mube be <= " "log2(MAX_3D_TEXTURE_SIZE"); } break; case LOCAL_GL_TEXTURE_2D_ARRAY: if (uint32_t(layer) >= mImplMaxArrayTextureLayers) { return ErrorInvalidValue("framebufferTextureLayer: layer must be < " "MAX_ARRAY_TEXTURE_LAYERS"); } if (uint32_t(level) > FloorLog2(mImplMaxTextureSize)) { return ErrorInvalidValue("framebufferTextureLayer: layer mube be <= " "log2(MAX_TEXTURE_SIZE"); } break; default: return ErrorInvalidOperation("framebufferTextureLayer: texture must be an " "existing 3D texture, or a 2D texture array."); } } WebGLFramebuffer* fb; switch (target) { case LOCAL_GL_FRAMEBUFFER: case LOCAL_GL_DRAW_FRAMEBUFFER: fb = mBoundDrawFramebuffer; break; case LOCAL_GL_READ_FRAMEBUFFER: fb = mBoundReadFramebuffer; break; default: MOZ_CRASH("Bad target."); } if (!fb) { return ErrorInvalidOperation("framebufferTextureLayer: cannot modify" " framebuffer 0."); } fb->FramebufferTextureLayer(attachment, texture, level, layer); }