diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
commit | 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch) | |
tree | a31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /dom/canvas/WebGLProgram.cpp | |
parent | Initial commit. (diff) | |
download | firefox-esr-37a0381f8351b370577b65028ba1f6563ae23fdf.tar.xz firefox-esr-37a0381f8351b370577b65028ba1f6563ae23fdf.zip |
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/canvas/WebGLProgram.cpp')
-rw-r--r-- | dom/canvas/WebGLProgram.cpp | 1213 |
1 files changed, 1213 insertions, 0 deletions
diff --git a/dom/canvas/WebGLProgram.cpp b/dom/canvas/WebGLProgram.cpp new file mode 100644 index 0000000000..f29d3d1c14 --- /dev/null +++ b/dom/canvas/WebGLProgram.cpp @@ -0,0 +1,1213 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "WebGLProgram.h" + +#include "GLContext.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/dom/WebGL2RenderingContextBinding.h" +#include "mozilla/dom/WebGLRenderingContextBinding.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/RefPtr.h" +#include "nsPrintfCString.h" +#include "WebGLBuffer.h" +#include "WebGLContext.h" +#include "WebGLShader.h" +#include "WebGLShaderValidator.h" +#include "WebGLTransformFeedback.h" +#include "WebGLValidateStrings.h" +#include "WebGLVertexArray.h" + +namespace mozilla { + +static bool IsShadowSampler(const GLenum elemType) { + switch (elemType) { + case LOCAL_GL_SAMPLER_2D_SHADOW: + case LOCAL_GL_SAMPLER_CUBE_SHADOW: + case LOCAL_GL_SAMPLER_2D_ARRAY_SHADOW: + return true; + default: + return false; + } +} + +static Maybe<webgl::TextureBaseType> SamplerBaseType(const GLenum elemType) { + switch (elemType) { + case LOCAL_GL_SAMPLER_2D: + case LOCAL_GL_SAMPLER_3D: + case LOCAL_GL_SAMPLER_CUBE: + case LOCAL_GL_SAMPLER_2D_ARRAY: + case LOCAL_GL_SAMPLER_2D_SHADOW: + case LOCAL_GL_SAMPLER_CUBE_SHADOW: + case LOCAL_GL_SAMPLER_2D_ARRAY_SHADOW: + return Some(webgl::TextureBaseType::Float); + + case LOCAL_GL_INT_SAMPLER_2D: + case LOCAL_GL_INT_SAMPLER_3D: + case LOCAL_GL_INT_SAMPLER_CUBE: + case LOCAL_GL_INT_SAMPLER_2D_ARRAY: + return Some(webgl::TextureBaseType::Int); + + case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D: + case LOCAL_GL_UNSIGNED_INT_SAMPLER_3D: + case LOCAL_GL_UNSIGNED_INT_SAMPLER_CUBE: + case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: + return Some(webgl::TextureBaseType::UInt); + + default: + return {}; + } +} + +////////// + +static webgl::TextureBaseType FragOutputBaseType(const GLenum type) { + switch (type) { + case LOCAL_GL_FLOAT: + case LOCAL_GL_FLOAT_VEC2: + case LOCAL_GL_FLOAT_VEC3: + case LOCAL_GL_FLOAT_VEC4: + return webgl::TextureBaseType::Float; + + case LOCAL_GL_INT: + case LOCAL_GL_INT_VEC2: + case LOCAL_GL_INT_VEC3: + case LOCAL_GL_INT_VEC4: + return webgl::TextureBaseType::Int; + + case LOCAL_GL_UNSIGNED_INT: + case LOCAL_GL_UNSIGNED_INT_VEC2: + case LOCAL_GL_UNSIGNED_INT_VEC3: + case LOCAL_GL_UNSIGNED_INT_VEC4: + return webgl::TextureBaseType::UInt; + + default: + break; + } + + const auto& str = EnumString(type); + gfxCriticalError() << "Unhandled enum for FragOutputBaseType: " + << str.c_str(); + return webgl::TextureBaseType::Float; +} + +// ----------------------------------------- + +namespace webgl { + +void UniformAs1fv(gl::GLContext& gl, GLint location, GLsizei count, + bool transpose, const void* any) { + gl.fUniform1fv(location, count, static_cast<const float*>(any)); +} +void UniformAs2fv(gl::GLContext& gl, GLint location, GLsizei count, + bool transpose, const void* any) { + gl.fUniform2fv(location, count, static_cast<const float*>(any)); +} +void UniformAs3fv(gl::GLContext& gl, GLint location, GLsizei count, + bool transpose, const void* any) { + gl.fUniform3fv(location, count, static_cast<const float*>(any)); +} +void UniformAs4fv(gl::GLContext& gl, GLint location, GLsizei count, + bool transpose, const void* any) { + gl.fUniform4fv(location, count, static_cast<const float*>(any)); +} + +void UniformAs1iv(gl::GLContext& gl, GLint location, GLsizei count, + bool transpose, const void* any) { + gl.fUniform1iv(location, count, static_cast<const int32_t*>(any)); +} +void UniformAs2iv(gl::GLContext& gl, GLint location, GLsizei count, + bool transpose, const void* any) { + gl.fUniform2iv(location, count, static_cast<const int32_t*>(any)); +} +void UniformAs3iv(gl::GLContext& gl, GLint location, GLsizei count, + bool transpose, const void* any) { + gl.fUniform3iv(location, count, static_cast<const int32_t*>(any)); +} +void UniformAs4iv(gl::GLContext& gl, GLint location, GLsizei count, + bool transpose, const void* any) { + gl.fUniform4iv(location, count, static_cast<const int32_t*>(any)); +} + +void UniformAs1uiv(gl::GLContext& gl, GLint location, GLsizei count, + bool transpose, const void* any) { + gl.fUniform1uiv(location, count, static_cast<const uint32_t*>(any)); +} +void UniformAs2uiv(gl::GLContext& gl, GLint location, GLsizei count, + bool transpose, const void* any) { + gl.fUniform2uiv(location, count, static_cast<const uint32_t*>(any)); +} +void UniformAs3uiv(gl::GLContext& gl, GLint location, GLsizei count, + bool transpose, const void* any) { + gl.fUniform3uiv(location, count, static_cast<const uint32_t*>(any)); +} +void UniformAs4uiv(gl::GLContext& gl, GLint location, GLsizei count, + bool transpose, const void* any) { + gl.fUniform4uiv(location, count, static_cast<const uint32_t*>(any)); +} + +void UniformAsMatrix2x2fv(gl::GLContext& gl, GLint location, GLsizei count, + bool transpose, const void* any) { + gl.fUniformMatrix2fv(location, count, transpose, + static_cast<const float*>(any)); +} +void UniformAsMatrix2x3fv(gl::GLContext& gl, GLint location, GLsizei count, + bool transpose, const void* any) { + gl.fUniformMatrix2x3fv(location, count, transpose, + static_cast<const float*>(any)); +} +void UniformAsMatrix2x4fv(gl::GLContext& gl, GLint location, GLsizei count, + bool transpose, const void* any) { + gl.fUniformMatrix2x4fv(location, count, transpose, + static_cast<const float*>(any)); +} + +void UniformAsMatrix3x2fv(gl::GLContext& gl, GLint location, GLsizei count, + bool transpose, const void* any) { + gl.fUniformMatrix3x2fv(location, count, transpose, + static_cast<const float*>(any)); +} +void UniformAsMatrix3x3fv(gl::GLContext& gl, GLint location, GLsizei count, + bool transpose, const void* any) { + gl.fUniformMatrix3fv(location, count, transpose, + static_cast<const float*>(any)); +} +void UniformAsMatrix3x4fv(gl::GLContext& gl, GLint location, GLsizei count, + bool transpose, const void* any) { + gl.fUniformMatrix3x4fv(location, count, transpose, + static_cast<const float*>(any)); +} + +void UniformAsMatrix4x2fv(gl::GLContext& gl, GLint location, GLsizei count, + bool transpose, const void* any) { + gl.fUniformMatrix4x2fv(location, count, transpose, + static_cast<const float*>(any)); +} +void UniformAsMatrix4x3fv(gl::GLContext& gl, GLint location, GLsizei count, + bool transpose, const void* any) { + gl.fUniformMatrix4x3fv(location, count, transpose, + static_cast<const float*>(any)); +} +void UniformAsMatrix4x4fv(gl::GLContext& gl, GLint location, GLsizei count, + bool transpose, const void* any) { + gl.fUniformMatrix4fv(location, count, transpose, + static_cast<const float*>(any)); +} + +// - + +static bool EndsWith(const std::string& str, const std::string& needle) { + if (str.length() < needle.length()) return false; + return str.compare(str.length() - needle.length(), needle.length(), needle) == + 0; +} + +webgl::ActiveUniformValidationInfo webgl::ActiveUniformValidationInfo::Make( + const webgl::ActiveUniformInfo& info) { + auto ret = webgl::ActiveUniformValidationInfo{info}; + ret.isArray = EndsWith(info.name, "[0]"); + + switch (info.elemType) { + case LOCAL_GL_FLOAT: + ret.channelsPerElem = 1; + ret.pfn = &UniformAs1fv; + break; + case LOCAL_GL_FLOAT_VEC2: + ret.channelsPerElem = 2; + ret.pfn = &UniformAs2fv; + break; + case LOCAL_GL_FLOAT_VEC3: + ret.channelsPerElem = 3; + ret.pfn = &UniformAs3fv; + break; + case LOCAL_GL_FLOAT_VEC4: + ret.channelsPerElem = 4; + ret.pfn = &UniformAs4fv; + break; + + case LOCAL_GL_SAMPLER_2D: + case LOCAL_GL_SAMPLER_3D: + case LOCAL_GL_SAMPLER_CUBE: + case LOCAL_GL_SAMPLER_2D_SHADOW: + case LOCAL_GL_SAMPLER_2D_ARRAY: + case LOCAL_GL_SAMPLER_2D_ARRAY_SHADOW: + case LOCAL_GL_SAMPLER_CUBE_SHADOW: + case LOCAL_GL_INT_SAMPLER_2D: + case LOCAL_GL_INT_SAMPLER_3D: + case LOCAL_GL_INT_SAMPLER_CUBE: + case LOCAL_GL_INT_SAMPLER_2D_ARRAY: + case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D: + case LOCAL_GL_UNSIGNED_INT_SAMPLER_3D: + case LOCAL_GL_UNSIGNED_INT_SAMPLER_CUBE: + case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: + case LOCAL_GL_BOOL: + case LOCAL_GL_INT: + ret.channelsPerElem = 1; + ret.pfn = &UniformAs1iv; + break; + case LOCAL_GL_BOOL_VEC2: + case LOCAL_GL_INT_VEC2: + ret.channelsPerElem = 2; + ret.pfn = &UniformAs2iv; + break; + case LOCAL_GL_BOOL_VEC3: + case LOCAL_GL_INT_VEC3: + ret.channelsPerElem = 3; + ret.pfn = &UniformAs3iv; + break; + case LOCAL_GL_BOOL_VEC4: + case LOCAL_GL_INT_VEC4: + ret.channelsPerElem = 4; + ret.pfn = &UniformAs4iv; + break; + + case LOCAL_GL_UNSIGNED_INT: + ret.channelsPerElem = 1; + ret.pfn = &UniformAs1uiv; + break; + case LOCAL_GL_UNSIGNED_INT_VEC2: + ret.channelsPerElem = 2; + ret.pfn = &UniformAs2uiv; + break; + case LOCAL_GL_UNSIGNED_INT_VEC3: + ret.channelsPerElem = 3; + ret.pfn = &UniformAs3uiv; + break; + case LOCAL_GL_UNSIGNED_INT_VEC4: + ret.channelsPerElem = 4; + ret.pfn = &UniformAs4uiv; + break; + + // - + + case LOCAL_GL_FLOAT_MAT2: + ret.channelsPerElem = 2 * 2; + ret.pfn = &UniformAsMatrix2x2fv; + break; + case LOCAL_GL_FLOAT_MAT2x3: + ret.channelsPerElem = 2 * 3; + ret.pfn = &UniformAsMatrix2x3fv; + break; + case LOCAL_GL_FLOAT_MAT2x4: + ret.channelsPerElem = 2 * 4; + ret.pfn = &UniformAsMatrix2x4fv; + break; + + case LOCAL_GL_FLOAT_MAT3x2: + ret.channelsPerElem = 3 * 2; + ret.pfn = &UniformAsMatrix3x2fv; + break; + case LOCAL_GL_FLOAT_MAT3: + ret.channelsPerElem = 3 * 3; + ret.pfn = &UniformAsMatrix3x3fv; + break; + case LOCAL_GL_FLOAT_MAT3x4: + ret.channelsPerElem = 3 * 4; + ret.pfn = &UniformAsMatrix3x4fv; + break; + + case LOCAL_GL_FLOAT_MAT4x2: + ret.channelsPerElem = 4 * 2; + ret.pfn = &UniformAsMatrix4x2fv; + break; + case LOCAL_GL_FLOAT_MAT4x3: + ret.channelsPerElem = 4 * 3; + ret.pfn = &UniformAsMatrix4x3fv; + break; + case LOCAL_GL_FLOAT_MAT4: + ret.channelsPerElem = 4 * 4; + ret.pfn = &UniformAsMatrix4x4fv; + break; + + default: + gfxCriticalError() << "Bad `elemType`: " << EnumString(info.elemType); + MOZ_CRASH("`elemType`"); + } + return ret; +} + +} // namespace webgl + +// ------------------------- + +// #define DUMP_SHADERVAR_MAPPINGS + +RefPtr<const webgl::LinkedProgramInfo> QueryProgramInfo(WebGLProgram* prog, + gl::GLContext* gl) { + WebGLContext* const webgl = prog->mContext; + + RefPtr<webgl::LinkedProgramInfo> info(new webgl::LinkedProgramInfo(prog)); + + // Frag outputs + + { + const auto& fragShader = prog->FragShader(); + const auto& compileResults = fragShader->CompileResults(); + const auto version = compileResults->mShaderVersion; + + const auto fnAddInfo = [&](const webgl::FragOutputInfo& x) { + info->hasOutput[x.loc] = true; + info->fragOutputs.insert({x.loc, x}); + }; + + if (version == 300) { + for (const auto& cur : compileResults->mOutputVariables) { + auto loc = cur.location; + if (loc == -1) loc = 0; + + const auto info = + webgl::FragOutputInfo{uint8_t(loc), cur.name, cur.mappedName, + FragOutputBaseType(cur.type)}; + if (!cur.isArray()) { + fnAddInfo(info); + continue; + } + MOZ_ASSERT(cur.arraySizes.size() == 1); + for (uint32_t i = 0; i < cur.arraySizes[0]; ++i) { + const auto indexStr = std::string("[") + std::to_string(i) + "]"; + + const auto userName = info.userName + indexStr; + const auto mappedName = info.mappedName + indexStr; + + const auto indexedInfo = webgl::FragOutputInfo{ + uint8_t(info.loc + i), userName, mappedName, info.baseType}; + fnAddInfo(indexedInfo); + } + } + } else { + // ANGLE's translator doesn't tell us about non-user frag outputs. :( + + const auto& translatedSource = compileResults->mObjectCode; + uint32_t drawBuffers = 1; + if (translatedSource.find("(gl_FragData[1]") != std::string::npos || + translatedSource.find("(webgl_FragData[1]") != std::string::npos) { + // The matching with the leading '(' prevents cleverly-named user vars + // breaking this. Since ANGLE initializes all outputs, if this is an MRT + // shader, FragData[1] will be present. FragData[0] is valid for non-MRT + // shaders. + drawBuffers = webgl->GLMaxDrawBuffers(); + } else if (translatedSource.find("(gl_FragColor") == std::string::npos && + translatedSource.find("(webgl_FragColor") == + std::string::npos && + translatedSource.find("(gl_FragData") == std::string::npos && + translatedSource.find("(webgl_FragData") == + std::string::npos) { + // We have to support no-color-output shaders? + drawBuffers = 0; + } + + for (uint32_t i = 0; i < drawBuffers; ++i) { + const auto name = std::string("gl_FragData[") + std::to_string(i) + "]"; + const auto info = webgl::FragOutputInfo{uint8_t(i), name, name, + webgl::TextureBaseType::Float}; + fnAddInfo(info); + } + } + } + + const auto& vertShader = prog->VertShader(); + const auto& vertCompileResults = vertShader->CompileResults(); + const auto numViews = vertCompileResults->mVertexShaderNumViews; + if (numViews != -1) { + info->zLayerCount = AssertedCast<uint8_t>(numViews); + } + + // - + + auto& nameMap = info->nameMap; + + const auto fnAccum = [&](WebGLShader& shader) { + const auto& compRes = shader.CompileResults(); + for (const auto& pair : compRes->mNameMap) { + nameMap.insert(pair); + } + }; + fnAccum(*prog->VertShader()); + fnAccum(*prog->FragShader()); + + // - + + std::unordered_map<std::string, std::string> nameUnmap; + for (const auto& pair : nameMap) { + nameUnmap.insert({pair.second, pair.first}); + } + + info->active = + GetLinkActiveInfo(*gl, prog->mGLName, webgl->IsWebGL2(), nameUnmap); + + // - + + for (const auto& attrib : info->active.activeAttribs) { + if (attrib.location == 0) { + info->attrib0Active = true; + break; + } + } + + info->webgl_gl_VertexID_Offset = + gl->fGetUniformLocation(prog->mGLName, "webgl_gl_VertexID_Offset"); + + // - + + for (const auto& uniform : info->active.activeUniforms) { + const auto& elemType = uniform.elemType; + webgl::SamplerUniformInfo* samplerInfo = nullptr; + const auto baseType = SamplerBaseType(elemType); + if (baseType) { + const bool isShadowSampler = IsShadowSampler(elemType); + + auto* texList = &webgl->mBound2DTextures; + + switch (elemType) { + case LOCAL_GL_SAMPLER_2D: + case LOCAL_GL_SAMPLER_2D_SHADOW: + case LOCAL_GL_INT_SAMPLER_2D: + case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D: + break; + + case LOCAL_GL_SAMPLER_CUBE: + case LOCAL_GL_SAMPLER_CUBE_SHADOW: + case LOCAL_GL_INT_SAMPLER_CUBE: + case LOCAL_GL_UNSIGNED_INT_SAMPLER_CUBE: + texList = &webgl->mBoundCubeMapTextures; + break; + + case LOCAL_GL_SAMPLER_3D: + case LOCAL_GL_INT_SAMPLER_3D: + case LOCAL_GL_UNSIGNED_INT_SAMPLER_3D: + texList = &webgl->mBound3DTextures; + break; + + case LOCAL_GL_SAMPLER_2D_ARRAY: + case LOCAL_GL_SAMPLER_2D_ARRAY_SHADOW: + case LOCAL_GL_INT_SAMPLER_2D_ARRAY: + case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: + texList = &webgl->mBound2DArrayTextures; + break; + } + + auto curInfo = std::unique_ptr<webgl::SamplerUniformInfo>( + new webgl::SamplerUniformInfo{*texList, *baseType, isShadowSampler}); + MOZ_RELEASE_ASSERT(curInfo->texUnits.resize(uniform.elemCount)); + samplerInfo = curInfo.get(); + info->samplerUniforms.push_back(std::move(curInfo)); + } + + const auto valInfo = webgl::ActiveUniformValidationInfo::Make(uniform); + + for (const auto& pair : uniform.locByIndex) { + info->locationMap.insert( + {pair.second, {valInfo, pair.first, samplerInfo}}); + } + } + + // - + + { + const auto& activeBlocks = info->active.activeUniformBlocks; + info->uniformBlocks.reserve(activeBlocks.size()); + for (const auto& cur : activeBlocks) { + const auto curInfo = webgl::UniformBlockInfo{ + cur, &webgl->mIndexedUniformBufferBindings[0]}; + info->uniformBlocks.push_back(curInfo); + } + } + + return info; +} + +//////////////////////////////////////////////////////////////////////////////// + +webgl::LinkedProgramInfo::LinkedProgramInfo(WebGLProgram* prog) + : prog(prog), + transformFeedbackBufferMode(prog->mNextLink_TransformFeedbackBufferMode) { +} + +webgl::LinkedProgramInfo::~LinkedProgramInfo() = default; + +webgl::AttribBaseType webgl::ToAttribBaseType(const GLenum elemType) { + switch (elemType) { + case LOCAL_GL_BOOL: + case LOCAL_GL_BOOL_VEC2: + case LOCAL_GL_BOOL_VEC3: + case LOCAL_GL_BOOL_VEC4: + return webgl::AttribBaseType::Boolean; + + case LOCAL_GL_FLOAT: + case LOCAL_GL_FLOAT_VEC2: + case LOCAL_GL_FLOAT_VEC3: + case LOCAL_GL_FLOAT_VEC4: + case LOCAL_GL_FLOAT_MAT2: + case LOCAL_GL_FLOAT_MAT2x3: + case LOCAL_GL_FLOAT_MAT3x2: + case LOCAL_GL_FLOAT_MAT2x4: + case LOCAL_GL_FLOAT_MAT4x2: + case LOCAL_GL_FLOAT_MAT3: + case LOCAL_GL_FLOAT_MAT3x4: + case LOCAL_GL_FLOAT_MAT4x3: + case LOCAL_GL_FLOAT_MAT4: + return webgl::AttribBaseType::Float; + + case LOCAL_GL_INT: + case LOCAL_GL_INT_VEC2: + case LOCAL_GL_INT_VEC3: + case LOCAL_GL_INT_VEC4: + case LOCAL_GL_SAMPLER_2D: + case LOCAL_GL_SAMPLER_3D: + case LOCAL_GL_SAMPLER_CUBE: + case LOCAL_GL_SAMPLER_2D_SHADOW: + case LOCAL_GL_SAMPLER_2D_ARRAY: + case LOCAL_GL_SAMPLER_2D_ARRAY_SHADOW: + case LOCAL_GL_SAMPLER_CUBE_SHADOW: + case LOCAL_GL_INT_SAMPLER_2D: + case LOCAL_GL_INT_SAMPLER_3D: + case LOCAL_GL_INT_SAMPLER_CUBE: + case LOCAL_GL_INT_SAMPLER_2D_ARRAY: + case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D: + case LOCAL_GL_UNSIGNED_INT_SAMPLER_3D: + case LOCAL_GL_UNSIGNED_INT_SAMPLER_CUBE: + case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: + return webgl::AttribBaseType::Int; + + case LOCAL_GL_UNSIGNED_INT: + case LOCAL_GL_UNSIGNED_INT_VEC2: + case LOCAL_GL_UNSIGNED_INT_VEC3: + case LOCAL_GL_UNSIGNED_INT_VEC4: + return webgl::AttribBaseType::Uint; + + default: + gfxCriticalError() << "Bad `elemType`: " << EnumString(elemType); + MOZ_CRASH("`elemType`"); + } +} + +const char* webgl::ToString(const webgl::AttribBaseType x) { + switch (x) { + case webgl::AttribBaseType::Float: + return "FLOAT"; + case webgl::AttribBaseType::Int: + return "INT"; + case webgl::AttribBaseType::Uint: + return "UINT"; + case webgl::AttribBaseType::Boolean: + return "BOOL"; + } + MOZ_CRASH("pacify gcc6 warning"); +} + +const char* webgl::ToString(const webgl::UniformBaseType x) { + switch (x) { + case webgl::UniformBaseType::Float: + return "FLOAT"; + case webgl::UniformBaseType::Int: + return "INT"; + case webgl::UniformBaseType::Uint: + return "UINT"; + } + MOZ_CRASH("pacify gcc6 warning"); +} + +const webgl::CachedDrawFetchLimits* +webgl::LinkedProgramInfo::GetDrawFetchLimits() const { + const auto& webgl = prog->mContext; + const auto& vao = webgl->mBoundVertexArray; + + { + // We have to ensure that every enabled attrib array (not just the active + // ones) has a non-null buffer. + const auto badIndex = vao->GetAttribIsArrayWithNullBuffer(); + if (badIndex) { + webgl->ErrorInvalidOperation( + "Vertex attrib array %u is enabled but" + " has no buffer bound.", + *badIndex); + return nullptr; + } + } + + const auto& activeAttribs = active.activeAttribs; + + webgl::CachedDrawFetchLimits fetchLimits; + fetchLimits.usedBuffers = + std::move(mScratchFetchLimits.usedBuffers); // Avoid realloc. + fetchLimits.usedBuffers.clear(); + fetchLimits.usedBuffers.reserve(activeAttribs.size()); + + bool hasActiveAttrib = false; + bool hasActiveDivisor0 = false; + + for (const auto& progAttrib : activeAttribs) { + const auto& loc = progAttrib.location; + if (loc == -1) continue; + hasActiveAttrib |= true; + + const auto& binding = vao->AttribBinding(loc); + const auto& buffer = binding.buffer; + const auto& layout = binding.layout; + hasActiveDivisor0 |= (layout.divisor == 0); + + webgl::AttribBaseType attribDataBaseType; + if (layout.isArray) { + MOZ_ASSERT(buffer); + fetchLimits.usedBuffers.push_back( + {buffer.get(), static_cast<uint32_t>(loc)}); + + attribDataBaseType = layout.baseType; + + const auto availBytes = buffer->ByteLength(); + const auto availElems = AvailGroups(availBytes, layout.byteOffset, + layout.byteSize, layout.byteStride); + if (layout.divisor) { + const auto availInstances = + CheckedInt<uint64_t>(availElems) * layout.divisor; + if (availInstances.isValid()) { + fetchLimits.maxInstances = + std::min(fetchLimits.maxInstances, availInstances.value()); + } // If not valid, it overflowed too large, so we're super safe. + } else { + fetchLimits.maxVerts = std::min(fetchLimits.maxVerts, availElems); + } + } else { + attribDataBaseType = webgl->mGenericVertexAttribTypes[loc]; + } + + const auto& progBaseType = progAttrib.baseType; + if ((attribDataBaseType != progBaseType) && + (progBaseType != webgl::AttribBaseType::Boolean)) { + const auto& dataType = ToString(attribDataBaseType); + const auto& progType = ToString(progBaseType); + webgl->ErrorInvalidOperation( + "Vertex attrib %u requires data of type %s," + " but is being supplied with type %s.", + loc, progType, dataType); + return nullptr; + } + } + + if (!webgl->IsWebGL2() && hasActiveAttrib && !hasActiveDivisor0) { + webgl->ErrorInvalidOperation( + "One active vertex attrib (if any are active)" + " must have a divisor of 0."); + return nullptr; + } + + // - + + mScratchFetchLimits = std::move(fetchLimits); + return &mScratchFetchLimits; +} + +//////////////////////////////////////////////////////////////////////////////// +// WebGLProgram + +WebGLProgram::WebGLProgram(WebGLContext* webgl) + : WebGLContextBoundObject(webgl), + mGLName(webgl->gl->fCreateProgram()), + mNumActiveTFOs(0), + mNextLink_TransformFeedbackBufferMode(LOCAL_GL_INTERLEAVED_ATTRIBS) {} + +WebGLProgram::~WebGLProgram() { + mVertShader = nullptr; + mFragShader = nullptr; + + mMostRecentLinkInfo = nullptr; + + if (!mContext) return; + mContext->gl->fDeleteProgram(mGLName); +} + +//////////////////////////////////////////////////////////////////////////////// +// GL funcs + +void WebGLProgram::AttachShader(WebGLShader& shader) { + RefPtr<WebGLShader>* shaderSlot = nullptr; + switch (shader.mType) { + case LOCAL_GL_VERTEX_SHADER: + shaderSlot = &mVertShader; + break; + case LOCAL_GL_FRAGMENT_SHADER: + shaderSlot = &mFragShader; + break; + } + MOZ_ASSERT(shaderSlot); + + *shaderSlot = &shader; + + mContext->gl->fAttachShader(mGLName, shader.mGLName); +} + +void WebGLProgram::BindAttribLocation(GLuint loc, const std::string& name) { + const auto err = CheckGLSLVariableName(mContext->IsWebGL2(), name); + if (err) { + mContext->GenerateError(err->type, "%s", err->info.c_str()); + return; + } + + if (loc >= mContext->MaxVertexAttribs()) { + mContext->ErrorInvalidValue( + "`location` must be less than" + " MAX_VERTEX_ATTRIBS."); + return; + } + + if (name.find("gl_") == 0) { + mContext->ErrorInvalidOperation( + "Can't set the location of a" + " name that starts with 'gl_'."); + return; + } + + auto res = mNextLink_BoundAttribLocs.insert({name, loc}); + + const auto& wasInserted = res.second; + if (!wasInserted) { + const auto& itr = res.first; + itr->second = loc; + } +} + +void WebGLProgram::DetachShader(const WebGLShader& shader) { + RefPtr<WebGLShader>* shaderSlot = nullptr; + switch (shader.mType) { + case LOCAL_GL_VERTEX_SHADER: + shaderSlot = &mVertShader; + break; + case LOCAL_GL_FRAGMENT_SHADER: + shaderSlot = &mFragShader; + break; + } + MOZ_ASSERT(shaderSlot); + + if (*shaderSlot != &shader) return; + + *shaderSlot = nullptr; + + mContext->gl->fDetachShader(mGLName, shader.mGLName); +} + +void WebGLProgram::UniformBlockBinding(GLuint uniformBlockIndex, + GLuint uniformBlockBinding) const { + if (!IsLinked()) { + mContext->ErrorInvalidOperation("`program` must be linked."); + return; + } + + auto& uniformBlocks = LinkInfo()->uniformBlocks; + if (uniformBlockIndex >= uniformBlocks.size()) { + mContext->ErrorInvalidValue("Index %u invalid.", uniformBlockIndex); + return; + } + auto& uniformBlock = uniformBlocks[uniformBlockIndex]; + + const auto& indexedBindings = mContext->mIndexedUniformBufferBindings; + if (uniformBlockBinding >= indexedBindings.size()) { + mContext->ErrorInvalidValue("Binding %u invalid.", uniformBlockBinding); + return; + } + const auto& indexedBinding = indexedBindings[uniformBlockBinding]; + + //// + + gl::GLContext* gl = mContext->GL(); + gl->fUniformBlockBinding(mGLName, uniformBlockIndex, uniformBlockBinding); + + //// + + uniformBlock.binding = &indexedBinding; +} + +bool WebGLProgram::ValidateForLink() { + const auto AppendCompileLog = [&](const WebGLShader* const shader) { + if (!shader) { + mLinkLog += " Missing shader."; + return; + } + mLinkLog += "\nSHADER_INFO_LOG:\n"; + mLinkLog += shader->CompileLog(); + }; + + if (!mVertShader || !mVertShader->IsCompiled()) { + mLinkLog = "Must have a compiled vertex shader attached:"; + AppendCompileLog(mVertShader); + return false; + } + const auto& vertInfo = *mVertShader->CompileResults(); + + if (!mFragShader || !mFragShader->IsCompiled()) { + mLinkLog = "Must have a compiled fragment shader attached:"; + AppendCompileLog(mFragShader); + return false; + } + const auto& fragInfo = *mFragShader->CompileResults(); + + nsCString errInfo; + if (!fragInfo.CanLinkTo(vertInfo, &errInfo)) { + mLinkLog = errInfo.BeginReading(); + return false; + } + + const auto& gl = mContext->gl; + + if (gl->WorkAroundDriverBugs() && mContext->mIsMesa) { + // Bug 1203135: Mesa crashes internally if we exceed the reported maximum + // attribute count. + if (mVertShader->NumAttributes() > mContext->MaxVertexAttribs()) { + mLinkLog = + "Number of attributes exceeds Mesa's reported max" + " attribute count."; + return false; + } + } + + return true; +} + +void WebGLProgram::LinkProgram() { + if (mNumActiveTFOs) { + mContext->ErrorInvalidOperation( + "Program is in-use by one or more active" + " transform feedback objects."); + return; + } + + // as some of the validation changes program state + + mLinkLog = {}; + mMostRecentLinkInfo = nullptr; + + if (!ValidateForLink()) { + mContext->GenerateWarning("%s", mLinkLog.c_str()); + return; + } + + // Bind the attrib locations. + // This can't be done trivially, because we have to deal with mapped attrib + // names. + for (const auto& pair : mNextLink_BoundAttribLocs) { + const auto& name = pair.first; + const auto& index = pair.second; + + mVertShader->BindAttribLocation(mGLName, name, index); + } + + // Storage for transform feedback varyings before link. + // (Work around for bug seen on nVidia drivers.) + std::vector<std::string> scopedMappedTFVaryings; + + if (mContext->IsWebGL2()) { + mVertShader->MapTransformFeedbackVaryings( + mNextLink_TransformFeedbackVaryings, &scopedMappedTFVaryings); + + std::vector<const char*> driverVaryings; + driverVaryings.reserve(scopedMappedTFVaryings.size()); + for (const auto& cur : scopedMappedTFVaryings) { + driverVaryings.push_back(cur.c_str()); + } + + mContext->gl->fTransformFeedbackVaryings( + mGLName, driverVaryings.size(), driverVaryings.data(), + mNextLink_TransformFeedbackBufferMode); + } + + LinkAndUpdate(); + + if (mMostRecentLinkInfo) { + std::string postLinkLog; + if (ValidateAfterTentativeLink(&postLinkLog)) return; + + mMostRecentLinkInfo = nullptr; + mLinkLog = std::move(postLinkLog); + } + + // Failed link. + if (mContext->ShouldGenerateWarnings()) { + // report shader/program infoLogs as warnings. + // note that shader compilation errors can be deferred to linkProgram, + // which is why we can't do anything in compileShader. In practice we could + // report in compileShader the translation errors generated by ANGLE, + // but it seems saner to keep a single way of obtaining shader infologs. + if (!mLinkLog.empty()) { + mContext->GenerateWarning( + "Failed to link, leaving the following" + " log:\n%s\n", + mLinkLog.c_str()); + } + } +} + +static uint8_t NumUsedLocationsByElemType(GLenum elemType) { + // GLES 3.0.4 p55 + + switch (elemType) { + case LOCAL_GL_FLOAT_MAT2: + case LOCAL_GL_FLOAT_MAT2x3: + case LOCAL_GL_FLOAT_MAT2x4: + return 2; + + case LOCAL_GL_FLOAT_MAT3x2: + case LOCAL_GL_FLOAT_MAT3: + case LOCAL_GL_FLOAT_MAT3x4: + return 3; + + case LOCAL_GL_FLOAT_MAT4x2: + case LOCAL_GL_FLOAT_MAT4x3: + case LOCAL_GL_FLOAT_MAT4: + return 4; + + default: + return 1; + } +} + +uint8_t ElemTypeComponents(const GLenum elemType) { + switch (elemType) { + case LOCAL_GL_BOOL: + case LOCAL_GL_FLOAT: + case LOCAL_GL_INT: + case LOCAL_GL_UNSIGNED_INT: + case LOCAL_GL_SAMPLER_2D: + case LOCAL_GL_SAMPLER_3D: + case LOCAL_GL_SAMPLER_CUBE: + case LOCAL_GL_SAMPLER_2D_SHADOW: + case LOCAL_GL_SAMPLER_2D_ARRAY: + case LOCAL_GL_SAMPLER_2D_ARRAY_SHADOW: + case LOCAL_GL_SAMPLER_CUBE_SHADOW: + case LOCAL_GL_INT_SAMPLER_2D: + case LOCAL_GL_INT_SAMPLER_3D: + case LOCAL_GL_INT_SAMPLER_CUBE: + case LOCAL_GL_INT_SAMPLER_2D_ARRAY: + case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D: + case LOCAL_GL_UNSIGNED_INT_SAMPLER_3D: + case LOCAL_GL_UNSIGNED_INT_SAMPLER_CUBE: + case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: + return 1; + + case LOCAL_GL_BOOL_VEC2: + case LOCAL_GL_FLOAT_VEC2: + case LOCAL_GL_INT_VEC2: + case LOCAL_GL_UNSIGNED_INT_VEC2: + return 2; + + case LOCAL_GL_BOOL_VEC3: + case LOCAL_GL_FLOAT_VEC3: + case LOCAL_GL_INT_VEC3: + case LOCAL_GL_UNSIGNED_INT_VEC3: + return 3; + + case LOCAL_GL_BOOL_VEC4: + case LOCAL_GL_FLOAT_VEC4: + case LOCAL_GL_INT_VEC4: + case LOCAL_GL_UNSIGNED_INT_VEC4: + case LOCAL_GL_FLOAT_MAT2: + return 4; + + case LOCAL_GL_FLOAT_MAT2x3: + case LOCAL_GL_FLOAT_MAT3x2: + return 2 * 3; + + case LOCAL_GL_FLOAT_MAT2x4: + case LOCAL_GL_FLOAT_MAT4x2: + return 2 * 4; + + case LOCAL_GL_FLOAT_MAT3: + return 3 * 3; + + case LOCAL_GL_FLOAT_MAT3x4: + case LOCAL_GL_FLOAT_MAT4x3: + return 3 * 4; + + case LOCAL_GL_FLOAT_MAT4: + return 4 * 4; + + default: + return 0; + } +} + +bool WebGLProgram::ValidateAfterTentativeLink( + std::string* const out_linkLog) const { + const auto& linkInfo = mMostRecentLinkInfo; + const auto& gl = mContext->gl; + + // Check for overlapping attrib locations. + { + std::unordered_map<uint32_t, const std::string&> nameByLoc; + for (const auto& attrib : linkInfo->active.activeAttribs) { + if (attrib.location == -1) continue; + + const auto& elemType = attrib.elemType; + const auto numUsedLocs = NumUsedLocationsByElemType(elemType); + for (uint32_t i = 0; i < numUsedLocs; i++) { + const uint32_t usedLoc = attrib.location + i; + + const auto res = nameByLoc.insert({usedLoc, attrib.name}); + const bool& didInsert = res.second; + if (!didInsert) { + const auto& aliasingName = attrib.name; + const auto& itrExisting = res.first; + const auto& existingName = itrExisting->second; + *out_linkLog = nsPrintfCString( + "Attrib \"%s\" aliases locations used by" + " attrib \"%s\".", + aliasingName.c_str(), existingName.c_str()) + .BeginReading(); + return false; + } + } + } + } + + // Forbid too many components for specified buffer mode + const auto& activeTfVaryings = linkInfo->active.activeTfVaryings; + MOZ_ASSERT(mNextLink_TransformFeedbackVaryings.size() == + activeTfVaryings.size()); + if (!activeTfVaryings.empty()) { + GLuint maxComponentsPerIndex = 0; + switch (linkInfo->transformFeedbackBufferMode) { + case LOCAL_GL_INTERLEAVED_ATTRIBS: + gl->GetUIntegerv(LOCAL_GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS, + &maxComponentsPerIndex); + break; + + case LOCAL_GL_SEPARATE_ATTRIBS: + gl->GetUIntegerv(LOCAL_GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS, + &maxComponentsPerIndex); + break; + + default: + MOZ_CRASH("`bufferMode`"); + } + + std::vector<size_t> componentsPerVert; + for (const auto& cur : activeTfVaryings) { + if (componentsPerVert.empty() || + linkInfo->transformFeedbackBufferMode == LOCAL_GL_SEPARATE_ATTRIBS) { + componentsPerVert.push_back(0); + } + + size_t varyingComponents = ElemTypeComponents(cur.elemType); + MOZ_ASSERT(varyingComponents); + varyingComponents *= cur.elemCount; + + auto& totalComponentsForIndex = *(componentsPerVert.rbegin()); + totalComponentsForIndex += varyingComponents; + + if (totalComponentsForIndex > maxComponentsPerIndex) { + *out_linkLog = nsPrintfCString( + "Transform feedback varying \"%s\"" + " pushed `componentsForIndex` over the" + " limit of %u.", + cur.name.c_str(), maxComponentsPerIndex) + .BeginReading(); + return false; + } + } + + linkInfo->componentsPerTFVert = std::move(componentsPerVert); + } + + return true; +} + +bool WebGLProgram::UseProgram() const { + if (!mMostRecentLinkInfo) { + mContext->ErrorInvalidOperation( + "Program has not been successfully linked."); + return false; + } + + if (mContext->mBoundTransformFeedback && + mContext->mBoundTransformFeedback->mIsActive && + !mContext->mBoundTransformFeedback->mIsPaused) { + mContext->ErrorInvalidOperation( + "Transform feedback active and not paused."); + return false; + } + + mContext->gl->fUseProgram(mGLName); + return true; +} + +bool WebGLProgram::ValidateProgram() const { + gl::GLContext* gl = mContext->gl; + +#ifdef XP_MACOSX + // See bug 593867 for NVIDIA and bug 657201 for ATI. The latter is confirmed + // with Mac OS 10.6.7. + if (gl->WorkAroundDriverBugs()) { + mContext->GenerateWarning( + "Implemented as a no-op on" + " Mac to work around crashes."); + return true; + } +#endif + + gl->fValidateProgram(mGLName); + GLint ok = 0; + gl->fGetProgramiv(mGLName, LOCAL_GL_VALIDATE_STATUS, &ok); + return bool(ok); +} + +//////////////////////////////////////////////////////////////////////////////// + +void WebGLProgram::LinkAndUpdate() { + mMostRecentLinkInfo = nullptr; + + gl::GLContext* gl = mContext->gl; + gl->fLinkProgram(mGLName); + + // Grab the program log. + { + GLuint logLenWithNull = 0; + gl->fGetProgramiv(mGLName, LOCAL_GL_INFO_LOG_LENGTH, + (GLint*)&logLenWithNull); + if (logLenWithNull > 1) { + std::vector<char> buffer(logLenWithNull); + gl->fGetProgramInfoLog(mGLName, buffer.size(), nullptr, buffer.data()); + mLinkLog = buffer.data(); + } else { + mLinkLog.clear(); + } + } + + GLint ok = 0; + gl->fGetProgramiv(mGLName, LOCAL_GL_LINK_STATUS, &ok); + if (!ok) return; + + mMostRecentLinkInfo = + QueryProgramInfo(this, gl); // Fallible after context loss. +} + +void WebGLProgram::TransformFeedbackVaryings( + const std::vector<std::string>& varyings, GLenum bufferMode) { + const auto& gl = mContext->gl; + + switch (bufferMode) { + case LOCAL_GL_INTERLEAVED_ATTRIBS: + break; + + case LOCAL_GL_SEPARATE_ATTRIBS: { + GLuint maxAttribs = 0; + gl->GetUIntegerv(LOCAL_GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS, + &maxAttribs); + if (varyings.size() > maxAttribs) { + mContext->ErrorInvalidValue("Length of `varyings` exceeds %s.", + "TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS"); + return; + } + } break; + + default: + mContext->ErrorInvalidEnumInfo("bufferMode", bufferMode); + return; + } + + //// + + mNextLink_TransformFeedbackVaryings = varyings; + mNextLink_TransformFeedbackBufferMode = bufferMode; +} + +} // namespace mozilla |