Merge Program/ProgramPipeline::getMergedVaryings().

This merges two very similar pieces of code into one simpler function.
The function doesn't use any maps or indirection to build the merged
varyings list. It also fixes a potential bug with IO blocks and name
matching due to the code bifurcation.

Bug: angleproject:5496
Change-Id: Ibf54faeeb01d1940570b366ed153fff7c9135c52
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2606533
Commit-Queue: Jamie Madill <jmadill@chromium.org>
Reviewed-by: Tim Van Patten <timvp@google.com>
Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>
This commit is contained in:
Jamie Madill 2020-12-30 17:17:17 -05:00 коммит произвёл Commit Bot
Родитель d8ca7d6247
Коммит 3e33db95e4
10 изменённых файлов: 233 добавлений и 287 удалений

Просмотреть файл

@ -48,7 +48,7 @@ class HashStream final : angle::NonCopyable
std::ostringstream mStringStream;
};
HashStream &operator<<(HashStream &stream, const Shader *shader)
HashStream &operator<<(HashStream &stream, Shader *shader)
{
if (shader)
{

Просмотреть файл

@ -1471,7 +1471,7 @@ int Program::getAttachedShadersCount() const
return numAttachedShaders;
}
const Shader *Program::getAttachedShader(ShaderType shaderType) const
Shader *Program::getAttachedShader(ShaderType shaderType) const
{
ASSERT(!mLinkingState);
return mState.getAttachedShader(shaderType);
@ -1715,15 +1715,7 @@ angle::Result Program::linkImpl(const Context *context)
InitUniformBlockLinker(mState, &resources.uniformBlockLinker);
InitShaderStorageBlockLinker(mState, &resources.shaderStorageBlockLinker);
ProgramPipeline *programPipeline = context->getState().getProgramPipeline();
if (programPipeline && programPipeline->usesShaderProgram(id()))
{
mergedVaryings = context->getState().getProgramPipeline()->getMergedVaryings();
}
else
{
mergedVaryings = getMergedVaryings();
}
mergedVaryings = GetMergedVaryingsFromShaders(*this);
if (!linkMergedVaryings(context, mergedVaryings, &resources.varyingPacking))
{
return angle::Result::Continue;
@ -4560,137 +4552,6 @@ void Program::gatherTransformFeedbackVaryings(const ProgramMergedVaryings &varyi
}
}
ProgramMergedVaryings Program::getMergedVaryings() const
{
ASSERT(mState.mAttachedShaders[ShaderType::Compute] == nullptr);
// Varyings are matched between pairs of consecutive stages, by location if assigned or
// by name otherwise. Note that it's possible for one stage to specify location and the other
// not: https://cvs.khronos.org/bugzilla/show_bug.cgi?id=16261
// Map stages to the previous active stage in the rendering pipeline. When looking at input
// varyings of a stage, this is used to find the stage whose output varyings are being linked
// with them.
ShaderMap<ShaderType> previousActiveStage;
// Note that kAllGraphicsShaderTypes is sorted according to the rendering pipeline.
ShaderType lastActiveStage = ShaderType::InvalidEnum;
for (ShaderType stage : kAllGraphicsShaderTypes)
{
previousActiveStage[stage] = lastActiveStage;
if (mState.mAttachedShaders[stage])
{
lastActiveStage = stage;
}
}
// First, go through output varyings and create two maps (one by name, one by location) for
// faster lookup when matching input varyings.
//
// Note that shader I/O blocks may or may not provide a name, and matching would be done by
// block name instead if either of the shader stages doesn't provide an instance name.
ShaderMap<std::map<std::string, size_t>> outputVaryingNameToIndex;
ShaderMap<std::map<int, size_t>> outputVaryingLocationToIndex;
ProgramMergedVaryings merged;
// Gather output varyings.
for (Shader *shader : mState.mAttachedShaders)
{
if (!shader)
{
continue;
}
ShaderType stage = shader->getType();
for (const sh::ShaderVariable &varying : shader->getOutputVaryings())
{
merged.push_back({});
ProgramVaryingRef *ref = &merged.back();
ref->frontShader = &varying;
ref->frontShaderStage = stage;
ASSERT(!varying.name.empty() || varying.isShaderIOBlock);
// Map by name, or if shader I/O block, block name. Even if location is provided in
// this stage, it may not be in the paired stage.
if (varying.isShaderIOBlock)
{
outputVaryingNameToIndex[stage][varying.structName] = merged.size() - 1;
}
else
{
outputVaryingNameToIndex[stage][varying.name] = merged.size() - 1;
}
// If location is provided, also keep it in a map by location.
if (varying.location != -1)
{
outputVaryingLocationToIndex[stage][varying.location] = merged.size() - 1;
}
}
}
// Gather input varyings, and match them with output varyings of the previous stage.
for (Shader *shader : mState.mAttachedShaders)
{
if (!shader)
{
continue;
}
ShaderType stage = shader->getType();
ShaderType previousStage = previousActiveStage[stage];
for (const sh::ShaderVariable &varying : shader->getInputVaryings())
{
size_t mergedIndex = merged.size();
if (previousStage != ShaderType::InvalidEnum)
{
// If location is provided, see if we can match by location.
if (varying.location != -1)
{
auto byLocationIter =
outputVaryingLocationToIndex[previousStage].find(varying.location);
if (byLocationIter != outputVaryingLocationToIndex[previousStage].end())
{
mergedIndex = byLocationIter->second;
}
}
// If not found, try to match by name.
if (mergedIndex == merged.size())
{
ASSERT(varying.isShaderIOBlock || !varying.name.empty());
const std::string &name =
varying.isShaderIOBlock ? varying.structName : varying.name;
auto byNameIter = outputVaryingNameToIndex[previousStage].find(name);
if (byNameIter != outputVaryingNameToIndex[previousStage].end())
{
mergedIndex = byNameIter->second;
}
}
}
// If no previous stage, or not matched by location or name, create a new entry for it.
if (mergedIndex == merged.size())
{
merged.push_back({});
mergedIndex = merged.size() - 1;
}
ProgramVaryingRef *ref = &merged[mergedIndex];
ref->backShader = &varying;
ref->backShaderStage = stage;
}
}
return merged;
}
bool CompareOutputVariable(const sh::ShaderVariable &a, const sh::ShaderVariable &b)
{
return a.getArraySizeProduct() > b.getArraySizeProduct();
@ -5795,5 +5656,4 @@ void Program::fillProgramStateMap(ShaderMap<const ProgramState *> *programStates
}
}
}
} // namespace gl

Просмотреть файл

@ -470,7 +470,17 @@ struct ProgramVaryingRef
using ProgramMergedVaryings = std::vector<ProgramVaryingRef>;
class Program final : public LabeledObject, public angle::Subject
// TODO: Copy necessary shader state into Program. http://anglebug.com/5506
class HasAttachedShaders
{
public:
virtual Shader *getAttachedShader(ShaderType shaderType) const = 0;
protected:
virtual ~HasAttachedShaders() {}
};
class Program final : public LabeledObject, public angle::Subject, public HasAttachedShaders
{
public:
Program(rx::GLImplFactory *factory, ShaderProgramManager *manager, ShaderProgramID handle);
@ -491,7 +501,7 @@ class Program final : public LabeledObject, public angle::Subject
void detachShader(const Context *context, Shader *shader);
int getAttachedShadersCount() const;
const Shader *getAttachedShader(ShaderType shaderType) const;
Shader *getAttachedShader(ShaderType shaderType) const override;
void bindAttributeLocation(GLuint index, const char *name);
void bindUniformLocation(UniformLocation location, const char *name);
@ -928,7 +938,6 @@ class Program final : public LabeledObject, public angle::Subject
void gatherTransformFeedbackVaryings(const ProgramMergedVaryings &varyings, ShaderType stage);
ProgramMergedVaryings getMergedVaryings() const;
int getOutputLocationForLink(const sh::ShaderVariable &outputVariable) const;
bool isOutputSecondaryForLink(const sh::ShaderVariable &outputVariable) const;
bool linkOutputVariables(const Caps &caps,

Просмотреть файл

@ -409,123 +409,6 @@ void ProgramPipeline::updateExecutable()
updateHasBooleans();
}
ProgramMergedVaryings ProgramPipeline::getMergedVaryings() const
{
ASSERT(!mState.mExecutable->isCompute());
// Varyings are matched between pairs of consecutive stages, by location if assigned or
// by name otherwise. Note that it's possible for one stage to specify location and the other
// not: https://cvs.khronos.org/bugzilla/show_bug.cgi?id=16261
// Map stages to the previous active stage in the rendering pipeline. When looking at input
// varyings of a stage, this is used to find the stage whose output varyings are being linked
// with them.
ShaderMap<ShaderType> previousActiveStage;
// Note that kAllGraphicsShaderTypes is sorted according to the rendering pipeline.
ShaderType lastActiveStage = ShaderType::InvalidEnum;
for (ShaderType shaderType : getExecutable().getLinkedShaderStages())
{
previousActiveStage[shaderType] = lastActiveStage;
const Program *program = getShaderProgram(shaderType);
ASSERT(program);
lastActiveStage = shaderType;
}
// First, go through output varyings and create two maps (one by name, one by location) for
// faster lookup when matching input varyings.
ShaderMap<std::map<std::string, size_t>> outputVaryingNameToIndexShaderMap;
ShaderMap<std::map<int, size_t>> outputVaryingLocationToIndexShaderMap;
ProgramMergedVaryings merged;
// Gather output varyings.
for (ShaderType shaderType : getExecutable().getLinkedShaderStages())
{
const Program *program = getShaderProgram(shaderType);
ASSERT(program);
Shader *shader = program->getState().getAttachedShader(shaderType);
ASSERT(shader);
for (const sh::ShaderVariable &varying : shader->getOutputVaryings())
{
merged.push_back({});
ProgramVaryingRef *ref = &merged.back();
ref->frontShader = &varying;
ref->frontShaderStage = shaderType;
// Always map by name. Even if location is provided in this stage, it may not be in the
// paired stage.
outputVaryingNameToIndexShaderMap[shaderType][varying.name] = merged.size() - 1;
// If location is provided, also keep it in a map by location.
if (varying.location != -1)
{
outputVaryingLocationToIndexShaderMap[shaderType][varying.location] =
merged.size() - 1;
}
}
}
// Gather input varyings, and match them with output varyings of the previous stage.
for (ShaderType shaderType : getExecutable().getLinkedShaderStages())
{
const Program *program = getShaderProgram(shaderType);
ASSERT(program);
Shader *shader = program->getState().getAttachedShader(shaderType);
ASSERT(shader);
ShaderType previousStage = previousActiveStage[shaderType];
for (const sh::ShaderVariable &varying : shader->getInputVaryings())
{
size_t mergedIndex = merged.size();
if (previousStage != ShaderType::InvalidEnum)
{
// If location is provided, see if we can match by location.
if (varying.location != -1)
{
std::map<int, size_t> outputVaryingLocationToIndex =
outputVaryingLocationToIndexShaderMap[previousStage];
auto byLocationIter = outputVaryingLocationToIndex.find(varying.location);
if (byLocationIter != outputVaryingLocationToIndex.end())
{
mergedIndex = byLocationIter->second;
}
}
// If not found, try to match by name.
if (mergedIndex == merged.size())
{
std::map<std::string, size_t> outputVaryingNameToIndex =
outputVaryingNameToIndexShaderMap[previousStage];
auto byNameIter = outputVaryingNameToIndex.find(varying.name);
if (byNameIter != outputVaryingNameToIndex.end())
{
mergedIndex = byNameIter->second;
}
}
}
// If no previous stage, or not matched by location or name, create a new entry for it.
if (mergedIndex == merged.size())
{
merged.push_back({});
mergedIndex = merged.size() - 1;
}
ProgramVaryingRef *ref = &merged[mergedIndex];
ref->backShader = &varying;
ref->backShaderStage = shaderType;
}
}
return merged;
}
// The attached shaders are checked for linking errors by matching up their variables.
// Uniform, input and output variables get collected.
// The code gets compiled into binaries.
@ -556,7 +439,7 @@ angle::Result ProgramPipeline::link(const Context *context)
return angle::Result::Stop;
}
mergedVaryings = getMergedVaryings();
mergedVaryings = GetMergedVaryingsFromShaders(*this);
for (ShaderType shaderType : getExecutable().getLinkedShaderStages())
{
Program *program = mState.mPrograms[shaderType];
@ -707,4 +590,9 @@ void ProgramPipeline::fillProgramStateMap(ShaderMap<const ProgramState *> *progr
}
}
Shader *ProgramPipeline::getAttachedShader(ShaderType shaderType) const
{
const Program *program = mState.mPrograms[shaderType];
return program ? program->getAttachedShader(shaderType) : nullptr;
}
} // namespace gl

Просмотреть файл

@ -91,7 +91,8 @@ class ProgramPipelineState final : angle::NonCopyable
class ProgramPipeline final : public RefCountObject<ProgramPipelineID>,
public LabeledObject,
public angle::ObserverInterface
public angle::ObserverInterface,
public HasAttachedShaders
{
public:
ProgramPipeline(rx::GLImplFactory *factory, ProgramPipelineID handle);
@ -127,7 +128,6 @@ class ProgramPipeline final : public RefCountObject<ProgramPipelineID>,
Program *getShaderProgram(ShaderType shaderType) const { return mState.mPrograms[shaderType]; }
void resetIsLinked() { mState.mIsLinked = false; }
ProgramMergedVaryings getMergedVaryings() const;
angle::Result link(const gl::Context *context);
bool linkVaryings(InfoLog &infoLog) const;
void validate(const gl::Context *context);
@ -145,6 +145,8 @@ class ProgramPipeline final : public RefCountObject<ProgramPipelineID>,
void fillProgramStateMap(gl::ShaderMap<const gl::ProgramState *> *programStatesOut);
Shader *getAttachedShader(ShaderType shaderType) const override;
private:
void updateLinkedShaderStages();
void updateExecutableAttributes();

Просмотреть файл

@ -88,6 +88,27 @@ bool ComparePackedVarying(const PackedVarying &x, const PackedVarying &y)
return gl::CompareShaderVar(*px, *py);
}
bool InterfaceVariablesMatch(const sh::ShaderVariable &front, const sh::ShaderVariable &back)
{
// Matching ruels from 7.4.1 Shader Interface Matching from the GLES 3.2 spec:
// - the two variables match in name, type, and qualification; or
// - the two variables are declared with the same location qualifier and match in type and
// qualification. Note that we use a more permissive check here thanks to front-end validation.
if (back.location != -1 && back.location == front.location)
{
return true;
}
if (front.isShaderIOBlock != back.isShaderIOBlock)
{
return false;
}
// Compare names, or if shader I/O blocks, block names.
const std::string &backName = back.isShaderIOBlock ? back.structName : back.name;
const std::string &frontName = front.isShaderIOBlock ? front.structName : front.name;
return backName == frontName;
}
} // anonymous namespace
// Implementation of VaryingInShaderRef
@ -812,4 +833,77 @@ bool VaryingPacking::packUserVaryings(gl::InfoLog &infoLog,
return true;
}
ProgramMergedVaryings GetMergedVaryingsFromShaders(const HasAttachedShaders &programOrPipeline)
{
Shader *frontShader = nullptr;
ProgramMergedVaryings merged;
for (ShaderType shaderType : kAllGraphicsShaderTypes)
{
Shader *backShader = programOrPipeline.getAttachedShader(shaderType);
if (!backShader)
{
continue;
}
ASSERT(backShader->getType() != ShaderType::Compute);
// Add outputs. These are always unmatched since we walk shader stages sequentially.
for (const sh::ShaderVariable &frontVarying : backShader->getOutputVaryings())
{
ProgramVaryingRef ref;
ref.frontShader = &frontVarying;
ref.frontShaderStage = backShader->getType();
merged.push_back(ref);
}
if (!frontShader)
{
// If this is our first shader stage, and not a VS, we might have unmatched inputs.
for (const sh::ShaderVariable &backVarying : backShader->getInputVaryings())
{
ProgramVaryingRef ref;
ref.backShader = &backVarying;
ref.backShaderStage = backShader->getType();
merged.push_back(ref);
}
}
else
{
// Match inputs with the prior shader stage outputs.
for (const sh::ShaderVariable &backVarying : backShader->getInputVaryings())
{
bool found = false;
for (ProgramVaryingRef &ref : merged)
{
if (ref.frontShader && ref.frontShaderStage == frontShader->getType() &&
InterfaceVariablesMatch(*ref.frontShader, backVarying))
{
ASSERT(ref.backShader == nullptr);
ref.backShader = &backVarying;
ref.backShaderStage = backShader->getType();
found = true;
break;
}
}
// Some outputs are never matched, e.g. some builtin variables.
if (!found)
{
ProgramVaryingRef ref;
ref.backShader = &backVarying;
ref.backShaderStage = backShader->getType();
merged.push_back(ref);
}
}
}
// Save the current back shader to use as the next front shader.
frontShader = backShader;
}
return merged;
}
} // namespace gl

Просмотреть файл

@ -22,6 +22,7 @@
namespace gl
{
class HasAttachedShaders;
class InfoLog;
struct ProgramVaryingRef;
@ -287,6 +288,8 @@ class VaryingPacking final : angle::NonCopyable
ShaderMap<std::vector<std::string>> mActiveOutputBuiltIns;
};
// Takes an abstract handle to a program or pipeline.
ProgramMergedVaryings GetMergedVaryingsFromShaders(const HasAttachedShaders &programOrPipeline);
} // namespace gl
#endif // LIBANGLE_VARYINGPACKING_H_

Просмотреть файл

@ -202,27 +202,7 @@ void ProgramPipelineTest31::drawQuadWithPPO(const std::string &positionAttribNam
const GLfloat positionAttribZ,
const GLfloat positionAttribXYScale)
{
glUseProgram(0);
std::array<Vector3, 6> quadVertices = ANGLETestBase::GetQuadVertices();
for (Vector3 &vertex : quadVertices)
{
vertex.x() *= positionAttribXYScale;
vertex.y() *= positionAttribXYScale;
vertex.z() = positionAttribZ;
}
GLint positionLocation = glGetAttribLocation(mVertProg, positionAttribName.c_str());
glBindBuffer(GL_ARRAY_BUFFER, 0);
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, quadVertices.data());
glEnableVertexAttribArray(positionLocation);
glDrawArrays(GL_TRIANGLES, 0, 6);
glDisableVertexAttribArray(positionLocation);
glVertexAttribPointer(positionLocation, 4, GL_FLOAT, GL_FALSE, 0, nullptr);
return drawQuadPPO(mVertProg, positionAttribName, positionAttribZ, positionAttribXYScale);
}
// Test glUseProgramStages
@ -593,6 +573,83 @@ void main()
ASSERT_GL_ERROR(GL_INVALID_OPERATION);
}
// Tests creating two program pipelines with a common shader and a varying location mismatch.
TEST_P(ProgramPipelineTest31, VaryingLocationMismatch)
{
// Only the Vulkan backend supports PPOs
ANGLE_SKIP_TEST_IF(!IsVulkan());
// http://anglebug.com/5506
ANGLE_SKIP_TEST_IF(IsVulkan());
// Create a fragment shader using the varying location "5".
const char *kFS = R"(#version 310 es
precision mediump float;
layout(location = 5) in vec4 color;
out vec4 colorOut;
void main()
{
colorOut = color;
})";
// Create a pipeline with a vertex shader using varying location "5". Should succeed.
const char *kVSGood = R"(#version 310 es
precision mediump float;
layout(location = 5) out vec4 color;
in vec4 position;
uniform float uniOne;
void main()
{
gl_Position = position;
color = vec4(0, uniOne, 0, 1);
})";
mVertProg = glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &kVSGood);
ASSERT_NE(mVertProg, 0u);
mFragProg = glCreateShaderProgramv(GL_FRAGMENT_SHADER, 1, &kFS);
ASSERT_NE(mFragProg, 0u);
// Generate a program pipeline and attach the programs to their respective stages
glGenProgramPipelines(1, &mPipeline);
glUseProgramStages(mPipeline, GL_VERTEX_SHADER_BIT, mVertProg);
glUseProgramStages(mPipeline, GL_FRAGMENT_SHADER_BIT, mFragProg);
glBindProgramPipeline(mPipeline);
ASSERT_GL_NO_ERROR();
GLint location = glGetUniformLocation(mVertProg, "uniOne");
ASSERT_NE(-1, location);
glActiveShaderProgram(mPipeline, mVertProg);
glUniform1f(location, 1.0);
ASSERT_GL_NO_ERROR();
drawQuadWithPPO("position", 0.5f, 1.0f);
ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
// Create a pipeline with a vertex shader using varying location "3". Should fail.
const char *kVSBad = R"(#version 310 es
precision mediump float;
layout(location = 3) out vec4 color;
in vec4 position;
uniform float uniOne;
void main()
{
gl_Position = position;
color = vec4(0, uniOne, 0, 1);
})";
glDeleteProgram(mVertProg);
mVertProg = glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &kVSBad);
ASSERT_NE(mVertProg, 0u);
glUseProgramStages(mPipeline, GL_VERTEX_SHADER_BIT, mVertProg);
ASSERT_GL_NO_ERROR();
drawQuadWithPPO("position", 0.5f, 1.0f);
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
}
ANGLE_INSTANTIATE_TEST_ES3_AND_ES31(ProgramPipelineTest);
ANGLE_INSTANTIATE_TEST_ES31(ProgramPipelineTest31);

Просмотреть файл

@ -856,6 +856,34 @@ void ANGLETestBase::drawQuad(GLuint program,
}
}
void ANGLETestBase::drawQuadPPO(GLuint vertProgram,
const std::string &positionAttribName,
const GLfloat positionAttribZ,
const GLfloat positionAttribXYScale)
{
glUseProgram(0);
std::array<Vector3, 6> quadVertices = GetQuadVertices();
for (Vector3 &vertex : quadVertices)
{
vertex.x() *= positionAttribXYScale;
vertex.y() *= positionAttribXYScale;
vertex.z() = positionAttribZ;
}
GLint positionLocation = glGetAttribLocation(vertProgram, positionAttribName.c_str());
glBindBuffer(GL_ARRAY_BUFFER, 0);
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, quadVertices.data());
glEnableVertexAttribArray(positionLocation);
glDrawArrays(GL_TRIANGLES, 0, 6);
glDisableVertexAttribArray(positionLocation);
glVertexAttribPointer(positionLocation, 4, GL_FLOAT, GL_FALSE, 0, nullptr);
}
void ANGLETestBase::drawIndexedQuad(GLuint program,
const std::string &positionAttribName,
GLfloat positionAttribZ)

Просмотреть файл

@ -395,6 +395,11 @@ class ANGLETestBase
bool useVertexBuffer,
GLuint numInstances);
void drawQuadPPO(GLuint vertProgram,
const std::string &positionAttribName,
const GLfloat positionAttribZ,
const GLfloat positionAttribXYScale);
static std::array<angle::Vector3, 6> GetQuadVertices();
static std::array<GLushort, 6> GetQuadIndices();
static std::array<angle::Vector3, 4> GetIndexedQuadVertices();