/* -*- 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 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(any)); } void UniformAs2fv(gl::GLContext& gl, GLint location, GLsizei count, bool transpose, const void* any) { gl.fUniform2fv(location, count, static_cast(any)); } void UniformAs3fv(gl::GLContext& gl, GLint location, GLsizei count, bool transpose, const void* any) { gl.fUniform3fv(location, count, static_cast(any)); } void UniformAs4fv(gl::GLContext& gl, GLint location, GLsizei count, bool transpose, const void* any) { gl.fUniform4fv(location, count, static_cast(any)); } void UniformAs1iv(gl::GLContext& gl, GLint location, GLsizei count, bool transpose, const void* any) { gl.fUniform1iv(location, count, static_cast(any)); } void UniformAs2iv(gl::GLContext& gl, GLint location, GLsizei count, bool transpose, const void* any) { gl.fUniform2iv(location, count, static_cast(any)); } void UniformAs3iv(gl::GLContext& gl, GLint location, GLsizei count, bool transpose, const void* any) { gl.fUniform3iv(location, count, static_cast(any)); } void UniformAs4iv(gl::GLContext& gl, GLint location, GLsizei count, bool transpose, const void* any) { gl.fUniform4iv(location, count, static_cast(any)); } void UniformAs1uiv(gl::GLContext& gl, GLint location, GLsizei count, bool transpose, const void* any) { gl.fUniform1uiv(location, count, static_cast(any)); } void UniformAs2uiv(gl::GLContext& gl, GLint location, GLsizei count, bool transpose, const void* any) { gl.fUniform2uiv(location, count, static_cast(any)); } void UniformAs3uiv(gl::GLContext& gl, GLint location, GLsizei count, bool transpose, const void* any) { gl.fUniform3uiv(location, count, static_cast(any)); } void UniformAs4uiv(gl::GLContext& gl, GLint location, GLsizei count, bool transpose, const void* any) { gl.fUniform4uiv(location, count, static_cast(any)); } void UniformAsMatrix2x2fv(gl::GLContext& gl, GLint location, GLsizei count, bool transpose, const void* any) { gl.fUniformMatrix2fv(location, count, transpose, static_cast(any)); } void UniformAsMatrix2x3fv(gl::GLContext& gl, GLint location, GLsizei count, bool transpose, const void* any) { gl.fUniformMatrix2x3fv(location, count, transpose, static_cast(any)); } void UniformAsMatrix2x4fv(gl::GLContext& gl, GLint location, GLsizei count, bool transpose, const void* any) { gl.fUniformMatrix2x4fv(location, count, transpose, static_cast(any)); } void UniformAsMatrix3x2fv(gl::GLContext& gl, GLint location, GLsizei count, bool transpose, const void* any) { gl.fUniformMatrix3x2fv(location, count, transpose, static_cast(any)); } void UniformAsMatrix3x3fv(gl::GLContext& gl, GLint location, GLsizei count, bool transpose, const void* any) { gl.fUniformMatrix3fv(location, count, transpose, static_cast(any)); } void UniformAsMatrix3x4fv(gl::GLContext& gl, GLint location, GLsizei count, bool transpose, const void* any) { gl.fUniformMatrix3x4fv(location, count, transpose, static_cast(any)); } void UniformAsMatrix4x2fv(gl::GLContext& gl, GLint location, GLsizei count, bool transpose, const void* any) { gl.fUniformMatrix4x2fv(location, count, transpose, static_cast(any)); } void UniformAsMatrix4x3fv(gl::GLContext& gl, GLint location, GLsizei count, bool transpose, const void* any) { gl.fUniformMatrix4x3fv(location, count, transpose, static_cast(any)); } void UniformAsMatrix4x4fv(gl::GLContext& gl, GLint location, GLsizei count, bool transpose, const void* any) { gl.fUniformMatrix4fv(location, count, transpose, static_cast(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 QueryProgramInfo(WebGLProgram* prog, gl::GLContext* gl) { WebGLContext* const webgl = prog->mContext; RefPtr 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(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 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( 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(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(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* 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* 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 scopedMappedTFVaryings; if (mContext->IsWebGL2()) { mVertShader->MapTransformFeedbackVaryings( mNextLink_TransformFeedbackVaryings, &scopedMappedTFVaryings); std::vector 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 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 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 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& 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