Commit fe3a9ffb by Tim Van Patten Committed by Commit Bot

Support linking PPOs without attached Shaders

The application "Command & Conquer: Rivals" uses the following pattern: // Create and link VS, save binary data glCreateProgram() glAttachShader() glLinkProgram() glGetProgramBinary() // Create and link FS, save binary data glCreateProgram() glAttachShader() glLinkProgram() glGetProgramBinary() ... // Create VS, load binary data glCreateProgram() glProgramBinary() // Create FS, load binary data glCreateProgram() glProgramBinary() ... glUseProgramStages(GL_VERTEX_SHADER_BIT) glUseProgramStages(GL_FRAGMENT_SHADER_BIT) glBindProgramPipeline() Later, when issuing the draw command that uses the PPO, the PPO must be linked with the Programs that were loaded via binary data. Those Programs don't have any Shaders attached, just linked. This CL adds support for linking Programs without attached Shaders. Bug: b/182409935 Test: ProgramPipelineTest31.ProgramBinary Change-Id: Ic5a4776e1374322665f45fbbcbf955838d093d02 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2770685 Commit-Queue: Tim Van Patten <timvp@google.com> Reviewed-by: 's avatarCody Northrop <cnorthrop@google.com> Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org>
parent 86a86a7d
...@@ -1630,7 +1630,7 @@ angle::Result Program::linkImpl(const Context *context) ...@@ -1630,7 +1630,7 @@ angle::Result Program::linkImpl(const Context *context)
InitUniformBlockLinker(mState, &resources.uniformBlockLinker); InitUniformBlockLinker(mState, &resources.uniformBlockLinker);
InitShaderStorageBlockLinker(mState, &resources.shaderStorageBlockLinker); InitShaderStorageBlockLinker(mState, &resources.shaderStorageBlockLinker);
mergedVaryings = GetMergedVaryingsFromShaders(*this); mergedVaryings = GetMergedVaryingsFromShaders(*this, getExecutable());
if (!mState.mExecutable->linkMergedVaryings(context, *this, mergedVaryings, if (!mState.mExecutable->linkMergedVaryings(context, *this, mergedVaryings,
mState.mTransformFeedbackVaryingNames, mState.mTransformFeedbackVaryingNames,
isSeparable(), &resources.varyingPacking)) isSeparable(), &resources.varyingPacking))
......
...@@ -899,17 +899,22 @@ bool ProgramExecutable::linkMergedVaryings( ...@@ -899,17 +899,22 @@ bool ProgramExecutable::linkMergedVaryings(
} }
// Build active shader stage map. // Build active shader stage map.
ShaderBitSet attachedShadersMask; ShaderBitSet activeShadersMask;
for (ShaderType shaderType : kAllGraphicsShaderTypes) for (ShaderType shaderType : kAllGraphicsShaderTypes)
{ {
if (programOrPipeline.getAttachedShader(shaderType)) // - Check for attached shaders to handle the case of a Program linking the currently
// attached shaders.
// - Check for linked shaders to handle the case of a PPO linking separable programs before
// drawing.
if (programOrPipeline.getAttachedShader(shaderType) ||
getLinkedShaderStages().test(shaderType))
{ {
attachedShadersMask[shaderType] = true; activeShadersMask[shaderType] = true;
} }
} }
if (!varyingPacking->collectAndPackUserVaryings(mInfoLog, context->getCaps(), packMode, if (!varyingPacking->collectAndPackUserVaryings(mInfoLog, context->getCaps(), packMode,
attachedShadersMask, mergedVaryings, activeShadersMask, mergedVaryings,
transformFeedbackVaryingNames, isSeparable)) transformFeedbackVaryingNames, isSeparable))
{ {
return false; return false;
......
...@@ -534,7 +534,7 @@ angle::Result ProgramPipeline::link(const Context *context) ...@@ -534,7 +534,7 @@ angle::Result ProgramPipeline::link(const Context *context)
return angle::Result::Stop; return angle::Result::Stop;
} }
mergedVaryings = GetMergedVaryingsFromShaders(*this); mergedVaryings = GetMergedVaryingsFromShaders(*this, getExecutable());
// If separable program objects are in use, the set of attributes captured is taken // If separable program objects are in use, the set of attributes captured is taken
// from the program object active on the last vertex processing stage. // from the program object active on the last vertex processing stage.
Program *tfProgram = mState.mPrograms[ShaderType::Geometry]; Program *tfProgram = mState.mPrograms[ShaderType::Geometry];
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "common/utilities.h" #include "common/utilities.h"
#include "libANGLE/Program.h" #include "libANGLE/Program.h"
#include "libANGLE/ProgramExecutable.h"
#include "libANGLE/Shader.h" #include "libANGLE/Shader.h"
namespace gl namespace gl
...@@ -988,17 +989,18 @@ const VaryingPacking &ProgramVaryingPacking::getOutputPacking(ShaderType frontSh ...@@ -988,17 +989,18 @@ const VaryingPacking &ProgramVaryingPacking::getOutputPacking(ShaderType frontSh
bool ProgramVaryingPacking::collectAndPackUserVaryings(InfoLog &infoLog, bool ProgramVaryingPacking::collectAndPackUserVaryings(InfoLog &infoLog,
const Caps &caps, const Caps &caps,
PackMode packMode, PackMode packMode,
const ShaderBitSet &attachedShadersMask, const ShaderBitSet &activeShadersMask,
const ProgramMergedVaryings &mergedVaryings, const ProgramMergedVaryings &mergedVaryings,
const std::vector<std::string> &tfVaryings, const std::vector<std::string> &tfVaryings,
bool isSeparableProgram) bool isSeparableProgram)
{ {
mBackToFrontStageMap.fill(ShaderType::InvalidEnum); mBackToFrontStageMap.fill(ShaderType::InvalidEnum);
ShaderBitSet attachedShaders = attachedShadersMask; ShaderBitSet activeShaders = activeShadersMask;
ShaderType frontShaderStage = attachedShaders.first(); ASSERT(activeShaders.any());
attachedShaders[frontShaderStage] = false; ShaderType frontShaderStage = activeShaders.first();
activeShaders[frontShaderStage] = false;
// Special case for start-after-vertex. // Special case for start-after-vertex.
if (frontShaderStage != ShaderType::Vertex) if (frontShaderStage != ShaderType::Vertex)
...@@ -1019,7 +1021,7 @@ bool ProgramVaryingPacking::collectAndPackUserVaryings(InfoLog &infoLog, ...@@ -1019,7 +1021,7 @@ bool ProgramVaryingPacking::collectAndPackUserVaryings(InfoLog &infoLog,
} }
// Process input/output shader pairs. // Process input/output shader pairs.
for (ShaderType backShaderStage : attachedShaders) for (ShaderType backShaderStage : activeShaders)
{ {
GLint maxVaryingVectors; GLint maxVaryingVectors;
if (frontShaderStage == ShaderType::Vertex && backShaderStage == ShaderType::Fragment) if (frontShaderStage == ShaderType::Vertex && backShaderStage == ShaderType::Fragment)
...@@ -1066,56 +1068,63 @@ bool ProgramVaryingPacking::collectAndPackUserVaryings(InfoLog &infoLog, ...@@ -1066,56 +1068,63 @@ bool ProgramVaryingPacking::collectAndPackUserVaryings(InfoLog &infoLog,
return true; return true;
} }
ProgramMergedVaryings GetMergedVaryingsFromShaders(const HasAttachedShaders &programOrPipeline) ProgramMergedVaryings GetMergedVaryingsFromShaders(const HasAttachedShaders &programOrPipeline,
const ProgramExecutable &programExecutable)
{ {
Shader *frontShader = nullptr; ShaderType frontShaderType = ShaderType::InvalidEnum;
ProgramMergedVaryings merged; ProgramMergedVaryings merged;
for (ShaderType shaderType : kAllGraphicsShaderTypes) for (ShaderType backShaderType : kAllGraphicsShaderTypes)
{ {
Shader *backShader = programOrPipeline.getAttachedShader(shaderType); Shader *backShader = programOrPipeline.getAttachedShader(backShaderType);
if (!backShader)
if (!backShader && !programExecutable.hasLinkedShaderStage(backShaderType))
{ {
continue; continue;
} }
ASSERT(backShader->getType() != ShaderType::Compute); const std::vector<sh::ShaderVariable> &backShaderOutputVaryings =
backShader ? backShader->getOutputVaryings()
: programExecutable.getLinkedOutputVaryings(backShaderType);
const std::vector<sh::ShaderVariable> &backShaderInputVaryings =
backShader ? backShader->getInputVaryings()
: programExecutable.getLinkedInputVaryings(backShaderType);
// Add outputs. These are always unmatched since we walk shader stages sequentially. // Add outputs. These are always unmatched since we walk shader stages sequentially.
for (const sh::ShaderVariable &frontVarying : backShader->getOutputVaryings()) for (const sh::ShaderVariable &frontVarying : backShaderOutputVaryings)
{ {
ProgramVaryingRef ref; ProgramVaryingRef ref;
ref.frontShader = &frontVarying; ref.frontShader = &frontVarying;
ref.frontShaderStage = backShader->getType(); ref.frontShaderStage = backShaderType;
merged.push_back(ref); merged.push_back(ref);
} }
if (!frontShader) if (frontShaderType == ShaderType::InvalidEnum)
{ {
// If this is our first shader stage, and not a VS, we might have unmatched inputs. // If this is our first shader stage, and not a VS, we might have unmatched inputs.
for (const sh::ShaderVariable &backVarying : backShader->getInputVaryings()) for (const sh::ShaderVariable &backVarying : backShaderInputVaryings)
{ {
ProgramVaryingRef ref; ProgramVaryingRef ref;
ref.backShader = &backVarying; ref.backShader = &backVarying;
ref.backShaderStage = backShader->getType(); ref.backShaderStage = backShaderType;
merged.push_back(ref); merged.push_back(ref);
} }
} }
else else
{ {
// Match inputs with the prior shader stage outputs. // Match inputs with the prior shader stage outputs.
for (const sh::ShaderVariable &backVarying : backShader->getInputVaryings()) for (const sh::ShaderVariable &backVarying : backShaderInputVaryings)
{ {
bool found = false; bool found = false;
for (ProgramVaryingRef &ref : merged) for (ProgramVaryingRef &ref : merged)
{ {
if (ref.frontShader && ref.frontShaderStage == frontShader->getType() && if (ref.frontShader && ref.frontShaderStage == frontShaderType &&
InterfaceVariablesMatch(*ref.frontShader, backVarying)) InterfaceVariablesMatch(*ref.frontShader, backVarying))
{ {
ASSERT(ref.backShader == nullptr); ASSERT(ref.backShader == nullptr);
ref.backShader = &backVarying; ref.backShader = &backVarying;
ref.backShaderStage = backShader->getType(); ref.backShaderStage = backShaderType;
found = true; found = true;
break; break;
} }
...@@ -1126,14 +1135,14 @@ ProgramMergedVaryings GetMergedVaryingsFromShaders(const HasAttachedShaders &pro ...@@ -1126,14 +1135,14 @@ ProgramMergedVaryings GetMergedVaryingsFromShaders(const HasAttachedShaders &pro
{ {
ProgramVaryingRef ref; ProgramVaryingRef ref;
ref.backShader = &backVarying; ref.backShader = &backVarying;
ref.backShaderStage = backShader->getType(); ref.backShaderStage = backShaderType;
merged.push_back(ref); merged.push_back(ref);
} }
} }
} }
// Save the current back shader to use as the next front shader. // Save the current back shader to use as the next front shader.
frontShader = backShader; frontShaderType = backShaderType;
} }
return merged; return merged;
......
...@@ -24,6 +24,7 @@ namespace gl ...@@ -24,6 +24,7 @@ namespace gl
{ {
class HasAttachedShaders; class HasAttachedShaders;
class InfoLog; class InfoLog;
class ProgramExecutable;
struct Caps; struct Caps;
struct ProgramVaryingRef; struct ProgramVaryingRef;
...@@ -308,7 +309,7 @@ class ProgramVaryingPacking final : angle::NonCopyable ...@@ -308,7 +309,7 @@ class ProgramVaryingPacking final : angle::NonCopyable
ANGLE_NO_DISCARD bool collectAndPackUserVaryings(InfoLog &infoLog, ANGLE_NO_DISCARD bool collectAndPackUserVaryings(InfoLog &infoLog,
const Caps &caps, const Caps &caps,
PackMode packMode, PackMode packMode,
const ShaderBitSet &attachedShadersMask, const ShaderBitSet &activeShadersMask,
const ProgramMergedVaryings &mergedVaryings, const ProgramMergedVaryings &mergedVaryings,
const std::vector<std::string> &tfVaryings, const std::vector<std::string> &tfVaryings,
bool isSeparableProgram); bool isSeparableProgram);
...@@ -322,7 +323,8 @@ class ProgramVaryingPacking final : angle::NonCopyable ...@@ -322,7 +323,8 @@ class ProgramVaryingPacking final : angle::NonCopyable
}; };
// Takes an abstract handle to a program or pipeline. // Takes an abstract handle to a program or pipeline.
ProgramMergedVaryings GetMergedVaryingsFromShaders(const HasAttachedShaders &programOrPipeline); ProgramMergedVaryings GetMergedVaryingsFromShaders(const HasAttachedShaders &programOrPipeline,
const ProgramExecutable &programExecutable);
} // namespace gl } // namespace gl
#endif // LIBANGLE_VARYINGPACKING_H_ #endif // LIBANGLE_VARYINGPACKING_H_
...@@ -751,6 +751,98 @@ TEST_P(ProgramPipelineTest31, ModifyAndRelinkShader) ...@@ -751,6 +751,98 @@ TEST_P(ProgramPipelineTest31, ModifyAndRelinkShader)
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red); EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
} }
// Test that a PPO can be used when the attached shader programs are created with glProgramBinary().
// This validates the necessary programs' information is serialized/deserialized so they can be
// linked by the PPO during glDrawArrays.
TEST_P(ProgramPipelineTest31, ProgramBinary)
{
ANGLE_SKIP_TEST_IF(!IsVulkan());
const GLchar *vertString = R"(#version 310 es
precision highp float;
in vec4 a_position;
out vec2 texCoord;
void main()
{
gl_Position = a_position;
texCoord = vec2(a_position.x, a_position.y) * 0.5 + vec2(0.5);
})";
const GLchar *fragString = R"(#version 310 es
precision highp float;
in vec2 texCoord;
uniform sampler2D tex;
out vec4 my_FragColor;
void main()
{
my_FragColor = texture(tex, texCoord);
})";
std::array<GLColor, 4> colors = {
{GLColor::red, GLColor::green, GLColor::blue, GLColor::yellow}};
GLTexture tex;
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, colors.data());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
mVertProg = glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &vertString);
ASSERT_NE(mVertProg, 0u);
mFragProg = glCreateShaderProgramv(GL_FRAGMENT_SHADER, 1, &fragString);
ASSERT_NE(mFragProg, 0u);
// Save the VS program binary out
std::vector<uint8_t> vsBinary(0);
GLint vsProgramLength = 0;
GLint vsWrittenLength = 0;
GLenum vsBinaryFormat = 0;
glGetProgramiv(mVertProg, GL_PROGRAM_BINARY_LENGTH, &vsProgramLength);
ASSERT_GL_NO_ERROR();
vsBinary.resize(vsProgramLength);
glGetProgramBinary(mVertProg, vsProgramLength, &vsWrittenLength, &vsBinaryFormat,
vsBinary.data());
ASSERT_GL_NO_ERROR();
// Save the FS program binary out
std::vector<uint8_t> fsBinary(0);
GLint fsProgramLength = 0;
GLint fsWrittenLength = 0;
GLenum fsBinaryFormat = 0;
glGetProgramiv(mFragProg, GL_PROGRAM_BINARY_LENGTH, &fsProgramLength);
ASSERT_GL_NO_ERROR();
fsBinary.resize(fsProgramLength);
glGetProgramBinary(mFragProg, fsProgramLength, &fsWrittenLength, &fsBinaryFormat,
fsBinary.data());
ASSERT_GL_NO_ERROR();
mVertProg = glCreateProgram();
glProgramBinary(mVertProg, vsBinaryFormat, vsBinary.data(), vsWrittenLength);
mFragProg = glCreateProgram();
glProgramBinary(mFragProg, fsBinaryFormat, fsBinary.data(), fsWrittenLength);
// Generate a program pipeline and attach the programs to their respective stages
glGenProgramPipelines(1, &mPipeline);
EXPECT_GL_NO_ERROR();
glUseProgramStages(mPipeline, GL_VERTEX_SHADER_BIT, mVertProg);
EXPECT_GL_NO_ERROR();
glUseProgramStages(mPipeline, GL_FRAGMENT_SHADER_BIT, mFragProg);
EXPECT_GL_NO_ERROR();
glBindProgramPipeline(mPipeline);
EXPECT_GL_NO_ERROR();
drawQuadWithPPO("a_position", 0.5f, 1.0f);
ASSERT_GL_NO_ERROR();
int w = getWindowWidth() - 2;
int h = getWindowHeight() - 2;
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(w, 0, GLColor::green);
EXPECT_PIXEL_COLOR_EQ(0, h, GLColor::blue);
EXPECT_PIXEL_COLOR_EQ(w, h, GLColor::yellow);
}
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ProgramPipelineTest); GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ProgramPipelineTest);
ANGLE_INSTANTIATE_TEST_ES3_AND_ES31(ProgramPipelineTest); ANGLE_INSTANTIATE_TEST_ES3_AND_ES31(ProgramPipelineTest);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment