gecko-dev/dom/canvas/WebGLProgram.cpp

1211 строки
36 KiB
C++

/* -*- 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;
}
}
// -
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});
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