mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-08 20:47:44 +00:00
477 lines
18 KiB
C++
477 lines
18 KiB
C++
/* -*- 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 "WebGL2Context.h"
|
|
#include "WebGLContextUtils.h"
|
|
#include "GLContext.h"
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::dom;
|
|
|
|
bool
|
|
WebGL2Context::ValidateSizedInternalFormat(GLenum internalformat, const char* info)
|
|
{
|
|
switch (internalformat) {
|
|
// Sized Internal Formats
|
|
// https://www.khronos.org/opengles/sdk/docs/man3/html/glTexStorage2D.xhtml
|
|
case LOCAL_GL_R8:
|
|
case LOCAL_GL_R8_SNORM:
|
|
case LOCAL_GL_R16F:
|
|
case LOCAL_GL_R32F:
|
|
case LOCAL_GL_R8UI:
|
|
case LOCAL_GL_R8I:
|
|
case LOCAL_GL_R16UI:
|
|
case LOCAL_GL_R16I:
|
|
case LOCAL_GL_R32UI:
|
|
case LOCAL_GL_R32I:
|
|
case LOCAL_GL_RG8:
|
|
case LOCAL_GL_RG8_SNORM:
|
|
case LOCAL_GL_RG16F:
|
|
case LOCAL_GL_RG32F:
|
|
case LOCAL_GL_RG8UI:
|
|
case LOCAL_GL_RG8I:
|
|
case LOCAL_GL_RG16UI:
|
|
case LOCAL_GL_RG16I:
|
|
case LOCAL_GL_RG32UI:
|
|
case LOCAL_GL_RG32I:
|
|
case LOCAL_GL_RGB8:
|
|
case LOCAL_GL_SRGB8:
|
|
case LOCAL_GL_RGB565:
|
|
case LOCAL_GL_RGB8_SNORM:
|
|
case LOCAL_GL_R11F_G11F_B10F:
|
|
case LOCAL_GL_RGB9_E5:
|
|
case LOCAL_GL_RGB16F:
|
|
case LOCAL_GL_RGB32F:
|
|
case LOCAL_GL_RGB8UI:
|
|
case LOCAL_GL_RGB8I:
|
|
case LOCAL_GL_RGB16UI:
|
|
case LOCAL_GL_RGB16I:
|
|
case LOCAL_GL_RGB32UI:
|
|
case LOCAL_GL_RGB32I:
|
|
case LOCAL_GL_RGBA8:
|
|
case LOCAL_GL_SRGB8_ALPHA8:
|
|
case LOCAL_GL_RGBA8_SNORM:
|
|
case LOCAL_GL_RGB5_A1:
|
|
case LOCAL_GL_RGBA4:
|
|
case LOCAL_GL_RGB10_A2:
|
|
case LOCAL_GL_RGBA16F:
|
|
case LOCAL_GL_RGBA32F:
|
|
case LOCAL_GL_RGBA8UI:
|
|
case LOCAL_GL_RGBA8I:
|
|
case LOCAL_GL_RGB10_A2UI:
|
|
case LOCAL_GL_RGBA16UI:
|
|
case LOCAL_GL_RGBA16I:
|
|
case LOCAL_GL_RGBA32I:
|
|
case LOCAL_GL_RGBA32UI:
|
|
case LOCAL_GL_DEPTH_COMPONENT16:
|
|
case LOCAL_GL_DEPTH_COMPONENT24:
|
|
case LOCAL_GL_DEPTH_COMPONENT32F:
|
|
case LOCAL_GL_DEPTH24_STENCIL8:
|
|
case LOCAL_GL_DEPTH32F_STENCIL8:
|
|
return true;
|
|
}
|
|
|
|
if (IsCompressedTextureFormat(internalformat)) {
|
|
return true;
|
|
}
|
|
|
|
const char* name = EnumName(internalformat);
|
|
if (name && name[0] != '[')
|
|
ErrorInvalidEnum("%s: invalid internal format %s", info, name);
|
|
else
|
|
ErrorInvalidEnum("%s: invalid internal format 0x%04X", info, internalformat);
|
|
|
|
return false;
|
|
}
|
|
|
|
/** Validates parameters to texStorage{2D,3D} */
|
|
bool
|
|
WebGL2Context::ValidateTexStorage(GLenum target, GLsizei levels, GLenum internalformat,
|
|
GLsizei width, GLsizei height, GLsizei depth,
|
|
const char* info)
|
|
{
|
|
// GL_INVALID_OPERATION is generated if the default texture object is curently bound to target.
|
|
WebGLTexture* tex = activeBoundTextureForTarget(target);
|
|
if (!tex) {
|
|
ErrorInvalidOperation("%s: no texture is bound to target %s", info, EnumName(target));
|
|
return false;
|
|
}
|
|
|
|
// GL_INVALID_OPERATION is generated if the texture object currently bound to target already has
|
|
// GL_TEXTURE_IMMUTABLE_FORMAT set to GL_TRUE.
|
|
if (tex->IsImmutable()) {
|
|
ErrorInvalidOperation("%s: texture bound to target %s is already immutable", info, EnumName(target));
|
|
return false;
|
|
}
|
|
|
|
// GL_INVALID_ENUM is generated if internalformat is not a valid sized internal format.
|
|
if (!ValidateSizedInternalFormat(internalformat, info))
|
|
return false;
|
|
|
|
// GL_INVALID_VALUE is generated if width, height or levels are less than 1.
|
|
if (width < 1) { ErrorInvalidValue("%s: width is < 1", info); return false; }
|
|
if (height < 1) { ErrorInvalidValue("%s: height is < 1", info); return false; }
|
|
if (depth < 1) { ErrorInvalidValue("%s: depth is < 1", info); return false; }
|
|
if (levels < 1) { ErrorInvalidValue("%s: levels is < 1", info); return false; }
|
|
|
|
// GL_INVALID_OPERATION is generated if levels is greater than floor(log2(max(width, height, depth)))+1.
|
|
if (FloorLog2(std::max(std::max(width, height), depth)) + 1 < levels) {
|
|
ErrorInvalidOperation("%s: too many levels for given texture dimensions", info);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Texture objects
|
|
|
|
void
|
|
WebGL2Context::TexStorage2D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height)
|
|
{
|
|
if (IsContextLost())
|
|
return;
|
|
|
|
// GL_INVALID_ENUM is generated if target is not one of the accepted target enumerants.
|
|
if (target != LOCAL_GL_TEXTURE_2D && target != LOCAL_GL_TEXTURE_CUBE_MAP)
|
|
return ErrorInvalidEnum("texStorage2D: target is not TEXTURE_2D or TEXTURE_CUBE_MAP");
|
|
|
|
if (!ValidateTexStorage(target, levels, internalformat, width, height, 1, "texStorage2D"))
|
|
return;
|
|
|
|
GetAndFlushUnderlyingGLErrors();
|
|
gl->fTexStorage2D(target, levels, internalformat, width, height);
|
|
GLenum error = GetAndFlushUnderlyingGLErrors();
|
|
if (error) {
|
|
return GenerateWarning("texStorage2D generated error %s", ErrorName(error));
|
|
}
|
|
|
|
WebGLTexture* tex = activeBoundTextureForTarget(target);
|
|
tex->SetImmutable();
|
|
|
|
const size_t facesCount = (target == LOCAL_GL_TEXTURE_2D) ? 1 : 6;
|
|
GLsizei w = width;
|
|
GLsizei h = height;
|
|
for (size_t l = 0; l < size_t(levels); l++) {
|
|
for (size_t f = 0; f < facesCount; f++) {
|
|
tex->SetImageInfo(TexImageTargetForTargetAndFace(target, f),
|
|
l, w, h, 1,
|
|
internalformat,
|
|
WebGLImageDataStatus::UninitializedImageData);
|
|
}
|
|
w = std::max(1, w / 2);
|
|
h = std::max(1, h / 2);
|
|
}
|
|
}
|
|
|
|
void
|
|
WebGL2Context::TexStorage3D(GLenum target, GLsizei levels, GLenum internalformat,
|
|
GLsizei width, GLsizei height, GLsizei depth)
|
|
{
|
|
if (IsContextLost())
|
|
return;
|
|
|
|
// GL_INVALID_ENUM is generated if target is not one of the accepted target enumerants.
|
|
if (target != LOCAL_GL_TEXTURE_3D)
|
|
return ErrorInvalidEnum("texStorage3D: target is not TEXTURE_3D");
|
|
|
|
if (!ValidateTexStorage(target, levels, internalformat, width, height, depth, "texStorage3D"))
|
|
return;
|
|
|
|
GetAndFlushUnderlyingGLErrors();
|
|
gl->fTexStorage3D(target, levels, internalformat, width, height, depth);
|
|
GLenum error = GetAndFlushUnderlyingGLErrors();
|
|
if (error) {
|
|
return GenerateWarning("texStorage3D generated error %s", ErrorName(error));
|
|
}
|
|
|
|
WebGLTexture* tex = activeBoundTextureForTarget(target);
|
|
tex->SetImmutable();
|
|
|
|
GLsizei w = width;
|
|
GLsizei h = height;
|
|
GLsizei d = depth;
|
|
for (size_t l = 0; l < size_t(levels); l++) {
|
|
tex->SetImageInfo(TexImageTargetForTargetAndFace(target, 0),
|
|
l, w, h, d,
|
|
internalformat,
|
|
WebGLImageDataStatus::UninitializedImageData);
|
|
w = std::max(1, w >> 1);
|
|
h = std::max(1, h >> 1);
|
|
d = std::max(1, d >> 1);
|
|
}
|
|
}
|
|
|
|
void
|
|
WebGL2Context::TexImage3D(GLenum target, GLint level, GLenum internalformat,
|
|
GLsizei width, GLsizei height, GLsizei depth,
|
|
GLint border, GLenum format, GLenum type,
|
|
const Nullable<dom::ArrayBufferView> &pixels,
|
|
ErrorResult& rv)
|
|
{
|
|
if (IsContextLost())
|
|
return;
|
|
|
|
void* data;
|
|
size_t dataLength;
|
|
js::Scalar::Type jsArrayType;
|
|
if (pixels.IsNull()) {
|
|
data = nullptr;
|
|
dataLength = 0;
|
|
jsArrayType = js::Scalar::TypeMax;
|
|
} else {
|
|
const ArrayBufferView& view = pixels.Value();
|
|
view.ComputeLengthAndData();
|
|
|
|
data = view.Data();
|
|
dataLength = view.Length();
|
|
jsArrayType = JS_GetArrayBufferViewType(view.Obj());
|
|
}
|
|
|
|
const WebGLTexImageFunc func = WebGLTexImageFunc::TexImage;
|
|
const WebGLTexDimensions dims = WebGLTexDimensions::Tex3D;
|
|
|
|
if (!ValidateTexImageTarget(target, func, dims))
|
|
return;
|
|
|
|
TexImageTarget texImageTarget = target;
|
|
|
|
if (!ValidateTexImage(texImageTarget, level, internalformat,
|
|
0, 0, 0,
|
|
width, height, depth,
|
|
border, format, type, func, dims))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!ValidateTexInputData(type, jsArrayType, func, dims))
|
|
return;
|
|
|
|
TexInternalFormat effectiveInternalFormat =
|
|
EffectiveInternalFormatFromInternalFormatAndType(internalformat, type);
|
|
|
|
if (effectiveInternalFormat == LOCAL_GL_NONE) {
|
|
return ErrorInvalidOperation("texImage3D: bad combination of internalformat and type");
|
|
}
|
|
|
|
// we need to find the exact sized format of the source data. Slightly abusing
|
|
// EffectiveInternalFormatFromInternalFormatAndType for that purpose. Really, an unsized source format
|
|
// is the same thing as an unsized internalformat.
|
|
TexInternalFormat effectiveSourceFormat =
|
|
EffectiveInternalFormatFromInternalFormatAndType(format, type);
|
|
MOZ_ASSERT(effectiveSourceFormat != LOCAL_GL_NONE); // should have validated format/type combo earlier
|
|
const size_t srcbitsPerTexel = GetBitsPerTexel(effectiveSourceFormat);
|
|
MOZ_ASSERT((srcbitsPerTexel % 8) == 0); // should not have compressed formats here.
|
|
size_t srcTexelSize = srcbitsPerTexel / 8;
|
|
|
|
CheckedUint32 checked_neededByteLength =
|
|
GetImageSize(height, width, depth, srcTexelSize, mPixelStoreUnpackAlignment);
|
|
|
|
if (!checked_neededByteLength.isValid())
|
|
return ErrorInvalidOperation("texSubImage2D: integer overflow computing the needed buffer size");
|
|
|
|
uint32_t bytesNeeded = checked_neededByteLength.value();
|
|
|
|
if (dataLength && dataLength < bytesNeeded)
|
|
return ErrorInvalidOperation("texImage3D: not enough data for operation (need %d, have %d)",
|
|
bytesNeeded, dataLength);
|
|
|
|
WebGLTexture* tex = activeBoundTextureForTexImageTarget(texImageTarget);
|
|
|
|
if (!tex)
|
|
return ErrorInvalidOperation("texImage3D: no texture is bound to this target");
|
|
|
|
if (tex->IsImmutable()) {
|
|
return ErrorInvalidOperation(
|
|
"texImage3D: disallowed because the texture "
|
|
"bound to this target has already been made immutable by texStorage3D");
|
|
}
|
|
|
|
GLenum driverType = LOCAL_GL_NONE;
|
|
GLenum driverInternalFormat = LOCAL_GL_NONE;
|
|
GLenum driverFormat = LOCAL_GL_NONE;
|
|
DriverFormatsFromEffectiveInternalFormat(gl,
|
|
effectiveInternalFormat,
|
|
&driverInternalFormat,
|
|
&driverFormat,
|
|
&driverType);
|
|
|
|
MakeContextCurrent();
|
|
GetAndFlushUnderlyingGLErrors();
|
|
gl->fTexImage3D(texImageTarget.get(), level,
|
|
driverInternalFormat,
|
|
width, height, depth,
|
|
0, driverFormat, driverType,
|
|
data);
|
|
GLenum error = GetAndFlushUnderlyingGLErrors();
|
|
if (error) {
|
|
return GenerateWarning("texImage3D generated error %s", ErrorName(error));
|
|
}
|
|
|
|
tex->SetImageInfo(texImageTarget, level,
|
|
width, height, depth,
|
|
effectiveInternalFormat,
|
|
data ? WebGLImageDataStatus::InitializedImageData
|
|
: WebGLImageDataStatus::UninitializedImageData);
|
|
}
|
|
|
|
void
|
|
WebGL2Context::TexSubImage3D(GLenum rawTarget, GLint level,
|
|
GLint xoffset, GLint yoffset, GLint zoffset,
|
|
GLsizei width, GLsizei height, GLsizei depth,
|
|
GLenum format, GLenum type, const Nullable<dom::ArrayBufferView>& pixels,
|
|
ErrorResult& rv)
|
|
{
|
|
if (IsContextLost())
|
|
return;
|
|
|
|
if (pixels.IsNull())
|
|
return ErrorInvalidValue("texSubImage3D: pixels must not be null!");
|
|
|
|
const ArrayBufferView& view = pixels.Value();
|
|
view.ComputeLengthAndData();
|
|
|
|
const WebGLTexImageFunc func = WebGLTexImageFunc::TexSubImage;
|
|
const WebGLTexDimensions dims = WebGLTexDimensions::Tex3D;
|
|
|
|
if (!ValidateTexImageTarget(rawTarget, func, dims))
|
|
return;
|
|
|
|
TexImageTarget texImageTarget(rawTarget);
|
|
|
|
WebGLTexture* tex = activeBoundTextureForTexImageTarget(texImageTarget);
|
|
if (!tex) {
|
|
return ErrorInvalidOperation("texSubImage3D: no texture bound on active texture unit");
|
|
}
|
|
|
|
if (!tex->HasImageInfoAt(texImageTarget, level)) {
|
|
return ErrorInvalidOperation("texSubImage3D: no previously defined texture image");
|
|
}
|
|
|
|
const WebGLTexture::ImageInfo& imageInfo = tex->ImageInfoAt(texImageTarget, level);
|
|
const TexInternalFormat existingEffectiveInternalFormat = imageInfo.EffectiveInternalFormat();
|
|
TexInternalFormat existingUnsizedInternalFormat = LOCAL_GL_NONE;
|
|
TexType existingType = LOCAL_GL_NONE;
|
|
UnsizedInternalFormatAndTypeFromEffectiveInternalFormat(existingEffectiveInternalFormat,
|
|
&existingUnsizedInternalFormat,
|
|
&existingType);
|
|
|
|
if (!ValidateTexImage(texImageTarget, level, existingEffectiveInternalFormat.get(),
|
|
xoffset, yoffset, zoffset,
|
|
width, height, depth,
|
|
0, format, type, func, dims))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (type != existingType) {
|
|
return ErrorInvalidOperation("texSubImage3D: type differs from that of the existing image");
|
|
}
|
|
|
|
js::Scalar::Type jsArrayType = JS_GetArrayBufferViewType(view.Obj());
|
|
void* data = view.Data();
|
|
size_t dataLength = view.Length();
|
|
|
|
if (!ValidateTexInputData(type, jsArrayType, func, dims))
|
|
return;
|
|
|
|
const size_t bitsPerTexel = GetBitsPerTexel(existingEffectiveInternalFormat);
|
|
MOZ_ASSERT((bitsPerTexel % 8) == 0); // should not have compressed formats here.
|
|
size_t srcTexelSize = bitsPerTexel / 8;
|
|
|
|
if (width == 0 || height == 0 || depth == 0)
|
|
return; // no effect, we better return right now
|
|
|
|
CheckedUint32 checked_neededByteLength =
|
|
GetImageSize(height, width, depth, srcTexelSize, mPixelStoreUnpackAlignment);
|
|
|
|
if (!checked_neededByteLength.isValid())
|
|
return ErrorInvalidOperation("texSubImage2D: integer overflow computing the needed buffer size");
|
|
|
|
uint32_t bytesNeeded = checked_neededByteLength.value();
|
|
|
|
if (dataLength < bytesNeeded)
|
|
return ErrorInvalidOperation("texSubImage2D: not enough data for operation (need %d, have %d)", bytesNeeded, dataLength);
|
|
|
|
if (imageInfo.HasUninitializedImageData()) {
|
|
bool coversWholeImage = xoffset == 0 &&
|
|
yoffset == 0 &&
|
|
zoffset == 0 &&
|
|
width == imageInfo.Width() &&
|
|
height == imageInfo.Height() &&
|
|
depth == imageInfo.Depth();
|
|
if (coversWholeImage) {
|
|
tex->SetImageDataStatus(texImageTarget, level, WebGLImageDataStatus::InitializedImageData);
|
|
} else {
|
|
tex->EnsureNoUninitializedImageData(texImageTarget, level);
|
|
}
|
|
}
|
|
|
|
GLenum driverType = LOCAL_GL_NONE;
|
|
GLenum driverInternalFormat = LOCAL_GL_NONE;
|
|
GLenum driverFormat = LOCAL_GL_NONE;
|
|
DriverFormatsFromEffectiveInternalFormat(gl,
|
|
existingEffectiveInternalFormat,
|
|
&driverInternalFormat,
|
|
&driverFormat,
|
|
&driverType);
|
|
|
|
MakeContextCurrent();
|
|
gl->fTexSubImage3D(texImageTarget.get(), level,
|
|
xoffset, yoffset, zoffset,
|
|
width, height, depth,
|
|
driverFormat, driverType, data);
|
|
|
|
}
|
|
|
|
void
|
|
WebGL2Context::TexSubImage3D(GLenum target, GLint level,
|
|
GLint xoffset, GLint yoffset, GLint zoffset,
|
|
GLenum format, GLenum type, dom::ImageData* data,
|
|
ErrorResult& rv)
|
|
{
|
|
MOZ_CRASH("Not Implemented.");
|
|
}
|
|
|
|
void
|
|
WebGL2Context::CopyTexSubImage3D(GLenum target, GLint level,
|
|
GLint xoffset, GLint yoffset, GLint zoffset,
|
|
GLint x, GLint y, GLsizei width, GLsizei height)
|
|
{
|
|
MOZ_CRASH("Not Implemented.");
|
|
}
|
|
|
|
void
|
|
WebGL2Context::CompressedTexImage3D(GLenum target, GLint level, GLenum internalformat,
|
|
GLsizei width, GLsizei height, GLsizei depth,
|
|
GLint border, GLsizei imageSize, const dom::ArrayBufferView& data)
|
|
{
|
|
MOZ_CRASH("Not Implemented.");
|
|
}
|
|
|
|
void
|
|
WebGL2Context::CompressedTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset,
|
|
GLsizei width, GLsizei height, GLsizei depth,
|
|
GLenum format, GLsizei imageSize, const dom::ArrayBufferView& data)
|
|
{
|
|
MOZ_CRASH("Not Implemented.");
|
|
}
|
|
|
|
JS::Value
|
|
WebGL2Context::GetTexParameterInternal(const TexTarget& target, GLenum pname)
|
|
{
|
|
switch (pname) {
|
|
case LOCAL_GL_TEXTURE_IMMUTABLE_FORMAT:
|
|
case LOCAL_GL_TEXTURE_BASE_LEVEL:
|
|
case LOCAL_GL_TEXTURE_MAX_LEVEL:
|
|
{
|
|
GLint i = 0;
|
|
gl->fGetTexParameteriv(target.get(), pname, &i);
|
|
return JS::NumberValue(uint32_t(i));
|
|
}
|
|
}
|
|
return WebGLContext::GetTexParameterInternal(target, pname);
|
|
}
|