/* -*- Mode: C++; tab-width: 20; 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 "WebGLProgram.h" #include "GLContext.h" #include "mozilla/dom/WebGLRenderingContextBinding.h" #include "MurmurHash3.h" #include "WebGLContext.h" #include "WebGLShader.h" namespace mozilla { /** Takes an ASCII string like "foo[i]", turns it into "foo" and returns "[i]" * in bracketPart. * * \param string input/output: The string to split, becomes the string without * the bracket part. * \param bracketPart output: Gets the bracket part. * * Notice that if there are multiple brackets like "foo[i].bar[j]", only the * last bracket is split. */ static bool SplitLastSquareBracket(nsACString& string, nsCString& bracketPart) { MOZ_ASSERT(bracketPart.IsEmpty(), "SplitLastSquareBracket must be called with empty bracketPart" " string."); if (string.IsEmpty()) return false; char* string_start = string.BeginWriting(); char* s = string_start + string.Length() - 1; if (*s != ']') return false; while (*s != '[' && s != string_start) s--; if (*s != '[') return false; bracketPart.Assign(s); *s = 0; string.EndWriting(); string.SetLength(s - string_start); return true; } JSObject* WebGLProgram::WrapObject(JSContext* cx) { return dom::WebGLProgramBinding::Wrap(cx, this); } WebGLProgram::WebGLProgram(WebGLContext* webgl) : WebGLContextBoundObject(webgl) , mLinkStatus(false) , mGeneration(0) , mIdentifierMap(new CStringMap) , mIdentifierReverseMap(new CStringMap) , mUniformInfoMap(new CStringToUniformInfoMap) , mAttribMaxNameLength(0) { mContext->MakeContextCurrent(); mGLName = mContext->gl->fCreateProgram(); mContext->mPrograms.insertBack(this); } void WebGLProgram::Delete() { DetachShaders(); mContext->MakeContextCurrent(); mContext->gl->fDeleteProgram(mGLName); LinkedListElement::removeFrom(mContext->mPrograms); } bool WebGLProgram::AttachShader(WebGLShader* shader) { if (ContainsShader(shader)) return false; mAttachedShaders.AppendElement(shader); mContext->MakeContextCurrent(); mContext->gl->fAttachShader(GLName(), shader->GLName()); return true; } bool WebGLProgram::DetachShader(WebGLShader* shader) { if (!mAttachedShaders.RemoveElement(shader)) return false; mContext->MakeContextCurrent(); mContext->gl->fDetachShader(GLName(), shader->GLName()); return true; } bool WebGLProgram::HasAttachedShaderOfType(GLenum shaderType) { for (uint32_t i = 0; i < mAttachedShaders.Length(); ++i) { if (mAttachedShaders[i] && mAttachedShaders[i]->ShaderType() == shaderType) return true; } return false; } bool WebGLProgram::HasBadShaderAttached() { for (uint32_t i = 0; i < mAttachedShaders.Length(); ++i) { if (mAttachedShaders[i] && !mAttachedShaders[i]->CompileStatus()) return true; } return false; } size_t WebGLProgram::UpperBoundNumSamplerUniforms() { size_t numSamplerUniforms = 0; for (size_t i = 0; i < mAttachedShaders.Length(); ++i) { const WebGLShader* shader = mAttachedShaders[i]; if (!shader) continue; for (size_t j = 0; j < shader->mUniformInfos.Length(); ++j) { WebGLUniformInfo u = shader->mUniformInfos[j]; if (u.type == LOCAL_GL_SAMPLER_2D || u.type == LOCAL_GL_SAMPLER_CUBE) { numSamplerUniforms += u.arraySize; } } } return numSamplerUniforms; } void WebGLProgram::MapIdentifier(const nsACString& name, nsCString* const out_mappedName) { MOZ_ASSERT(mIdentifierMap); nsCString mutableName(name); nsCString bracketPart; bool hadBracketPart = SplitLastSquareBracket(mutableName, bracketPart); if (hadBracketPart) mutableName.AppendLiteral("[0]"); if (mIdentifierMap->Get(mutableName, out_mappedName)) { if (hadBracketPart) { nsCString mappedBracketPart; bool mappedHadBracketPart = SplitLastSquareBracket(*out_mappedName, mappedBracketPart); if (mappedHadBracketPart) out_mappedName->Append(bracketPart); } return; } // Not found? We might be in the situation we have a uniform array name and // the GL's glGetActiveUniform returned its name without [0], as is allowed // by desktop GL but not in ES. Let's then try with [0]. mutableName.AppendLiteral("[0]"); if (mIdentifierMap->Get(mutableName, out_mappedName)) return; /* Not found? Return name unchanged. This case happens e.g. on bad user * input, or when we're not using identifier mapping, or if we didn't store * an identifier in the map because e.g. its mapping is trivial. (as happens * for short identifiers) */ out_mappedName->Assign(name); } void WebGLProgram::ReverseMapIdentifier(const nsACString& name, nsCString* const out_reverseMappedName) { MOZ_ASSERT(mIdentifierReverseMap); nsCString mutableName(name); nsCString bracketPart; bool hadBracketPart = SplitLastSquareBracket(mutableName, bracketPart); if (hadBracketPart) mutableName.AppendLiteral("[0]"); if (mIdentifierReverseMap->Get(mutableName, out_reverseMappedName)) { if (hadBracketPart) { nsCString reverseMappedBracketPart; bool reverseMappedHadBracketPart = SplitLastSquareBracket(*out_reverseMappedName, reverseMappedBracketPart); if (reverseMappedHadBracketPart) out_reverseMappedName->Append(bracketPart); } return; } // Not found? We might be in the situation we have a uniform array name and // the GL's glGetActiveUniform returned its name without [0], as is allowed // by desktop GL but not in ES. Let's then try with [0]. mutableName.AppendLiteral("[0]"); if (mIdentifierReverseMap->Get(mutableName, out_reverseMappedName)) return; /* Not found? Return name unchanged. This case happens e.g. on bad user * input, or when we're not using identifier mapping, or if we didn't store * an identifier in the map because e.g. its mapping is trivial. (as happens * for short identifiers) */ out_reverseMappedName->Assign(name); } WebGLUniformInfo WebGLProgram::GetUniformInfoForMappedIdentifier(const nsACString& name) { MOZ_ASSERT(mUniformInfoMap); nsCString mutableName(name); nsCString bracketPart; bool hadBracketPart = SplitLastSquareBracket(mutableName, bracketPart); // If there is a bracket, we're either an array or an entry in an array. if (hadBracketPart) mutableName.AppendLiteral("[0]"); WebGLUniformInfo info; mUniformInfoMap->Get(mutableName, &info); // We don't check if that Get failed, as if it did, it left info with // default values. return info; } bool WebGLProgram::UpdateInfo() { mAttribMaxNameLength = 0; for (size_t i = 0; i < mAttachedShaders.Length(); i++) { mAttribMaxNameLength = std::max(mAttribMaxNameLength, mAttachedShaders[i]->mAttribMaxNameLength); } GLint attribCount; mContext->gl->fGetProgramiv(mGLName, LOCAL_GL_ACTIVE_ATTRIBUTES, &attribCount); if (!mAttribsInUse.SetLength(mContext->mGLMaxVertexAttribs)) { mContext->ErrorOutOfMemory("updateInfo: Out of memory to allocate %d" " attribs.", mContext->mGLMaxVertexAttribs); return false; } for (size_t i = 0; i < mAttribsInUse.Length(); i++) mAttribsInUse[i] = false; nsAutoArrayPtr nameBuf(new char[mAttribMaxNameLength]); for (int i = 0; i < attribCount; ++i) { GLint attrnamelen; GLint attrsize; GLenum attrtype; mContext->gl->fGetActiveAttrib(mGLName, i, mAttribMaxNameLength, &attrnamelen, &attrsize, &attrtype, nameBuf); if (attrnamelen > 0) { GLint loc = mContext->gl->fGetAttribLocation(mGLName, nameBuf); MOZ_ASSERT(loc >= 0, "Major oops in managing the attributes of a" " WebGL program."); if (loc < mContext->mGLMaxVertexAttribs) { mAttribsInUse[loc] = true; } else { mContext->GenerateWarning("Program exceeds MAX_VERTEX_ATTRIBS."); return false; } } } // nsAutoPtr will delete old version first mIdentifierMap = new CStringMap; mIdentifierReverseMap = new CStringMap; mUniformInfoMap = new CStringToUniformInfoMap; for (size_t i = 0; i < mAttachedShaders.Length(); i++) { // Loop through ATTRIBUTES for (size_t j = 0; j < mAttachedShaders[i]->mAttributes.Length(); j++) { const WebGLMappedIdentifier& attrib = mAttachedShaders[i]->mAttributes[j]; // FORWARD MAPPING mIdentifierMap->Put(attrib.original, attrib.mapped); // REVERSE MAPPING mIdentifierReverseMap->Put(attrib.mapped, attrib.original); } // Loop through UNIFORMS for (size_t j = 0; j < mAttachedShaders[i]->mUniforms.Length(); j++) { // Add the uniforms name mapping to mIdentifier[Reverse]Map const WebGLMappedIdentifier& uniform = mAttachedShaders[i]->mUniforms[j]; // FOWARD MAPPING mIdentifierMap->Put(uniform.original, uniform.mapped); // REVERSE MAPPING mIdentifierReverseMap->Put(uniform.mapped, uniform.original); // Add uniform info to mUniformInfoMap const WebGLUniformInfo& info = mAttachedShaders[i]->mUniformInfos[j]; mUniformInfoMap->Put(uniform.mapped, info); } } mActiveAttribMap.clear(); GLint numActiveAttrs = 0; mContext->gl->fGetProgramiv(mGLName, LOCAL_GL_ACTIVE_ATTRIBUTES, &numActiveAttrs); // Spec says the maximum attrib name length is 256 chars, so this is // sufficient to hold any attrib name. char attrName[257]; GLint dummySize; GLenum dummyType; for (GLint i = 0; i < numActiveAttrs; i++) { mContext->gl->fGetActiveAttrib(mGLName, i, 257, nullptr, &dummySize, &dummyType, attrName); GLint attrLoc = mContext->gl->fGetAttribLocation(mGLName, attrName); MOZ_ASSERT(attrLoc >= 0); mActiveAttribMap.insert(std::make_pair(attrLoc, nsCString(attrName))); } return true; } /*static*/ uint64_t WebGLProgram::IdentifierHashFunction(const char* ident, size_t size) { uint64_t outhash[2]; // NB: we use the x86 function everywhere, even though it's suboptimal perf // on x64. They return different results; not sure if that's a requirement. MurmurHash3_x86_128(ident, size, 0, &outhash[0]); return outhash[0]; } /*static*/ void WebGLProgram::HashMapIdentifier(const nsACString& name, nsCString* const out_hashedName) { uint64_t hash = IdentifierHashFunction(name.BeginReading(), name.Length()); out_hashedName->Truncate(); // This MUST MATCH HASHED_NAME_PREFIX from // angle/src/compiler/translator/HashNames.h . out_hashedName->AppendPrintf("webgl_%llx", hash); } NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLProgram, mAttachedShaders) NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebGLProgram, AddRef) NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WebGLProgram, Release) } // namespace mozilla