Commit 001c7e8c by Tim Van Patten Committed by Commit Bot

Vulkan: Link PPO during draw validation

From the OpenGL ES 3.1 spec: 11.1.3.11 Validation It is not always possible to determine at link time if a program object can execute successfully, given that LinkProgram can not know the state of the remainder of the pipeline. Therefore validation is done when the first rendering command which triggers shader invocations is issued, to determine if the set of active program objects can be executed. For draws, this CL moves the PPO link operation to ValidateDrawStates() to generate PPO link failures within ANGLE's validation layer, so we fail any rendering commands during command validation. For dispatch, PPOs are linked during Context::prepareForDispatch(), where the PPO is converted from draw to compute, since that conversion requires a re-link. This re-link shouldn't fail due to errors that would have been caught during validation, since the compute shader must have successfully linked before it can be included in the PPO in the first place. We don't re-link when converting back to draw, since it's possible there are validation errors (which we want to catch during validation of the next rendering command). Bug: angleproject:5064 Test: dEQP.GLES31/functional_separate_shader_validation_es31_* Test: ContextNoErrorTest31.DrawWithPPO Test: ProgramPipelineTest31.VerifyPpoLinkErrorSignalledCorrectly Change-Id: Ibb249e893c007a83cc6b813f848a660bfa34ecb0 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2422375 Commit-Queue: Tim Van Patten <timvp@google.com> Reviewed-by: 's avatarShahbaz Youssefi <syoussefi@chromium.org> Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarIan Elliott <ianelliott@google.com>
parent fc4e3cf2
...@@ -256,8 +256,7 @@ enum SubjectIndexes : angle::SubjectIndex ...@@ -256,8 +256,7 @@ enum SubjectIndexes : angle::SubjectIndex
kSamplerMaxSubjectIndex = kSampler0SubjectIndex + IMPLEMENTATION_MAX_ACTIVE_TEXTURES, kSamplerMaxSubjectIndex = kSampler0SubjectIndex + IMPLEMENTATION_MAX_ACTIVE_TEXTURES,
kVertexArraySubjectIndex = kSamplerMaxSubjectIndex, kVertexArraySubjectIndex = kSamplerMaxSubjectIndex,
kReadFramebufferSubjectIndex, kReadFramebufferSubjectIndex,
kDrawFramebufferSubjectIndex, kDrawFramebufferSubjectIndex
kProgramPipelineSubjectIndex
}; };
bool IsClearBufferEnabled(const FramebufferState &mState, GLenum buffer, GLint drawbuffer) bool IsClearBufferEnabled(const FramebufferState &mState, GLenum buffer, GLint drawbuffer)
...@@ -325,7 +324,6 @@ Context::Context(egl::Display *display, ...@@ -325,7 +324,6 @@ Context::Context(egl::Display *display,
mVertexArrayObserverBinding(this, kVertexArraySubjectIndex), mVertexArrayObserverBinding(this, kVertexArraySubjectIndex),
mDrawFramebufferObserverBinding(this, kDrawFramebufferSubjectIndex), mDrawFramebufferObserverBinding(this, kDrawFramebufferSubjectIndex),
mReadFramebufferObserverBinding(this, kReadFramebufferSubjectIndex), mReadFramebufferObserverBinding(this, kReadFramebufferSubjectIndex),
mProgramPipelineObserverBinding(this, kProgramPipelineSubjectIndex),
mThreadPool(nullptr), mThreadPool(nullptr),
mFrameCapture(new angle::FrameCapture), mFrameCapture(new angle::FrameCapture),
mRefCount(0), mRefCount(0),
...@@ -488,7 +486,6 @@ void Context::initialize() ...@@ -488,7 +486,6 @@ void Context::initialize()
mDrawDirtyObjects.set(State::DIRTY_OBJECT_VERTEX_ARRAY); mDrawDirtyObjects.set(State::DIRTY_OBJECT_VERTEX_ARRAY);
mDrawDirtyObjects.set(State::DIRTY_OBJECT_TEXTURES); mDrawDirtyObjects.set(State::DIRTY_OBJECT_TEXTURES);
mDrawDirtyObjects.set(State::DIRTY_OBJECT_PROGRAM); mDrawDirtyObjects.set(State::DIRTY_OBJECT_PROGRAM);
mDrawDirtyObjects.set(State::DIRTY_OBJECT_PROGRAM_PIPELINE);
mDrawDirtyObjects.set(State::DIRTY_OBJECT_SAMPLERS); mDrawDirtyObjects.set(State::DIRTY_OBJECT_SAMPLERS);
mDrawDirtyObjects.set(State::DIRTY_OBJECT_IMAGES); mDrawDirtyObjects.set(State::DIRTY_OBJECT_IMAGES);
...@@ -538,7 +535,6 @@ void Context::initialize() ...@@ -538,7 +535,6 @@ void Context::initialize()
mComputeDirtyObjects.set(State::DIRTY_OBJECT_ACTIVE_TEXTURES); mComputeDirtyObjects.set(State::DIRTY_OBJECT_ACTIVE_TEXTURES);
mComputeDirtyObjects.set(State::DIRTY_OBJECT_TEXTURES); mComputeDirtyObjects.set(State::DIRTY_OBJECT_TEXTURES);
mComputeDirtyObjects.set(State::DIRTY_OBJECT_PROGRAM); mComputeDirtyObjects.set(State::DIRTY_OBJECT_PROGRAM);
mComputeDirtyObjects.set(State::DIRTY_OBJECT_PROGRAM_PIPELINE);
mComputeDirtyObjects.set(State::DIRTY_OBJECT_IMAGES); mComputeDirtyObjects.set(State::DIRTY_OBJECT_IMAGES);
mComputeDirtyObjects.set(State::DIRTY_OBJECT_SAMPLERS); mComputeDirtyObjects.set(State::DIRTY_OBJECT_SAMPLERS);
...@@ -1174,7 +1170,6 @@ void Context::bindProgramPipeline(ProgramPipelineID pipelineHandle) ...@@ -1174,7 +1170,6 @@ void Context::bindProgramPipeline(ProgramPipelineID pipelineHandle)
mImplementation.get(), pipelineHandle); mImplementation.get(), pipelineHandle);
ANGLE_CONTEXT_TRY(mState.setProgramPipelineBinding(this, pipeline)); ANGLE_CONTEXT_TRY(mState.setProgramPipelineBinding(this, pipeline));
mStateCache.onProgramExecutableChange(this); mStateCache.onProgramExecutableChange(this);
mProgramPipelineObserverBinding.bind(pipeline);
} }
void Context::beginQuery(QueryType target, QueryID query) void Context::beginQuery(QueryType target, QueryID query)
...@@ -2741,7 +2736,6 @@ void Context::detachSampler(SamplerID sampler) ...@@ -2741,7 +2736,6 @@ void Context::detachSampler(SamplerID sampler)
void Context::detachProgramPipeline(ProgramPipelineID pipeline) void Context::detachProgramPipeline(ProgramPipelineID pipeline)
{ {
mState.detachProgramPipeline(this, pipeline); mState.detachProgramPipeline(this, pipeline);
mProgramPipelineObserverBinding.bind(nullptr);
} }
void Context::vertexAttribDivisor(GLuint index, GLuint divisor) void Context::vertexAttribDivisor(GLuint index, GLuint divisor)
...@@ -3687,6 +3681,21 @@ ANGLE_INLINE angle::Result Context::prepareForDispatch() ...@@ -3687,6 +3681,21 @@ ANGLE_INLINE angle::Result Context::prepareForDispatch()
// with a PPO, we need to convert it from a "draw"-type to "dispatch"-type. // with a PPO, we need to convert it from a "draw"-type to "dispatch"-type.
convertPpoToComputeOrDraw(true); convertPpoToComputeOrDraw(true);
// Converting a PPO from graphics to compute requires re-linking it.
// The compute shader must have successfully linked before being included in the PPO, so no link
// errors that would have been caught during validation should be possible when re-linking the
// PPO with the compute shader.
Program *program = mState.getProgram();
ProgramPipeline *pipeline = mState.getProgramPipeline();
if (!program && pipeline)
{
bool goodResult = pipeline->link(this) == angle::Result::Continue;
// Linking the PPO can't fail due to a validation error within the compute program,
// since it successfully linked already in order to become part of the PPO in the first
// place.
ANGLE_CHECK(this, goodResult, "Program pipeline link failed", GL_INVALID_OPERATION);
}
ANGLE_TRY(syncDirtyObjects(mComputeDirtyObjects, Command::Dispatch)); ANGLE_TRY(syncDirtyObjects(mComputeDirtyObjects, Command::Dispatch));
return syncDirtyBits(mComputeDirtyBits); return syncDirtyBits(mComputeDirtyBits);
} }
...@@ -5775,6 +5784,13 @@ void Context::dispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGr ...@@ -5775,6 +5784,13 @@ void Context::dispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGr
// We always assume PPOs are used for draws, until they aren't. If we just executed a dispatch // We always assume PPOs are used for draws, until they aren't. If we just executed a dispatch
// with a PPO, we need to convert it back to a "draw"-type. // with a PPO, we need to convert it back to a "draw"-type.
// We don't re-link the PPO again, since it's possible for that link to generate validation
// errors due to bad VS/FS, and we want to catch those errors during validation of the draw
// command: 11.1.3.11 Validation It is not always possible to determine at link time if a
// program object can execute successfully, given that LinkProgram can not know the state of the
// remainder of the pipeline. Therefore validation is done when the first rendering command
// which triggers shader invocations is issued, to determine if the set of active program
// objects can be executed.
convertPpoToComputeOrDraw(false); convertPpoToComputeOrDraw(false);
if (ANGLE_UNLIKELY(IsError(result))) if (ANGLE_UNLIKELY(IsError(result)))
...@@ -5790,12 +5806,11 @@ void Context::convertPpoToComputeOrDraw(bool isCompute) ...@@ -5790,12 +5806,11 @@ void Context::convertPpoToComputeOrDraw(bool isCompute)
if (!program && pipeline) if (!program && pipeline)
{ {
pipeline->getExecutable().setIsCompute(isCompute); pipeline->getExecutable().setIsCompute(isCompute);
pipeline->setDirtyBit(ProgramPipeline::DirtyBitType::DIRTY_BIT_DRAW_DISPATCH_CHANGE); pipeline->resetIsLinked();
mState.mDirtyObjects.set(State::DIRTY_OBJECT_PROGRAM_PIPELINE);
mState.mDirtyBits.set(State::DirtyBitType::DIRTY_BIT_PROGRAM_EXECUTABLE);
// The PPO's isCompute() has changed, so its ProgramExecutable will produce different // The PPO's isCompute() has changed, so its ProgramExecutable will produce different
// results for things like getShaderStorageBlocks() or getImageBindings(). // results for things like getShaderStorageBlocks() or getImageBindings().
mState.mDirtyBits.set(State::DirtyBitType::DIRTY_BIT_PROGRAM_EXECUTABLE);
mStateCache.onProgramExecutableChange(this); mStateCache.onProgramExecutableChange(this);
} }
} }
...@@ -8303,11 +8318,6 @@ void Context::onSubjectStateChange(angle::SubjectIndex index, angle::SubjectMess ...@@ -8303,11 +8318,6 @@ void Context::onSubjectStateChange(angle::SubjectIndex index, angle::SubjectMess
} }
break; break;
case kProgramPipelineSubjectIndex:
ASSERT(message == angle::SubjectMessage::DirtyBitsFlagged);
mState.setProgramPipelineDirty();
break;
default: default:
if (index < kTextureMaxSubjectIndex) if (index < kTextureMaxSubjectIndex)
{ {
......
...@@ -782,7 +782,6 @@ class Context final : public egl::LabeledObject, angle::NonCopyable, public angl ...@@ -782,7 +782,6 @@ class Context final : public egl::LabeledObject, angle::NonCopyable, public angl
angle::ObserverBinding mVertexArrayObserverBinding; angle::ObserverBinding mVertexArrayObserverBinding;
angle::ObserverBinding mDrawFramebufferObserverBinding; angle::ObserverBinding mDrawFramebufferObserverBinding;
angle::ObserverBinding mReadFramebufferObserverBinding; angle::ObserverBinding mReadFramebufferObserverBinding;
angle::ObserverBinding mProgramPipelineObserverBinding;
std::vector<angle::ObserverBinding> mUniformBufferObserverBindings; std::vector<angle::ObserverBinding> mUniformBufferObserverBindings;
std::vector<angle::ObserverBinding> mSamplerObserverBindings; std::vector<angle::ObserverBinding> mSamplerObserverBindings;
std::vector<angle::ObserverBinding> mImageObserverBindings; std::vector<angle::ObserverBinding> mImageObserverBindings;
......
...@@ -498,6 +498,7 @@ MSG kUnrecognizedShaderStageBit = "Unrecognized shader stage bit."; ...@@ -498,6 +498,7 @@ MSG kUnrecognizedShaderStageBit = "Unrecognized shader stage bit.";
MSG kProgramNotSeparable = "Program object was not linked with its PROGRAM_SEPARABLE status set."; MSG kProgramNotSeparable = "Program object was not linked with its PROGRAM_SEPARABLE status set.";
MSG kProgramPipelineDoesNotExist = "Program pipeline does not exist."; MSG kProgramPipelineDoesNotExist = "Program pipeline does not exist.";
MSG kNotAllStagesOfSeparableProgramUsed = "A program object is active for at least one, but not all of the shader stages that were present when the program was linked."; MSG kNotAllStagesOfSeparableProgramUsed = "A program object is active for at least one, but not all of the shader stages that were present when the program was linked.";
MSG kProgramPipelineLinkFailed = "Program pipeline link failed";
// clang-format on // clang-format on
......
...@@ -27,7 +27,11 @@ enum SubjectIndexes : angle::SubjectIndex ...@@ -27,7 +27,11 @@ enum SubjectIndexes : angle::SubjectIndex
}; };
ProgramPipelineState::ProgramPipelineState() ProgramPipelineState::ProgramPipelineState()
: mLabel(), mActiveShaderProgram(nullptr), mValid(false), mExecutable(new ProgramExecutable()) : mLabel(),
mActiveShaderProgram(nullptr),
mValid(false),
mExecutable(new ProgramExecutable()),
mIsLinked(false)
{ {
for (const ShaderType shaderType : gl::AllShaderTypes()) for (const ShaderType shaderType : gl::AllShaderTypes())
{ {
...@@ -213,7 +217,7 @@ void ProgramPipeline::useProgramStages(const Context *context, ...@@ -213,7 +217,7 @@ void ProgramPipeline::useProgramStages(const Context *context,
updateLinkedShaderStages(); updateLinkedShaderStages();
updateExecutable(); updateExecutable();
mDirtyBits.set(DIRTY_BIT_PROGRAM_STAGE); mState.mIsLinked = false;
} }
void ProgramPipeline::updateLinkedShaderStages() void ProgramPipeline::updateLinkedShaderStages()
...@@ -540,6 +544,11 @@ ProgramMergedVaryings ProgramPipeline::getMergedVaryings() const ...@@ -540,6 +544,11 @@ ProgramMergedVaryings ProgramPipeline::getMergedVaryings() const
// The code gets compiled into binaries. // The code gets compiled into binaries.
angle::Result ProgramPipeline::link(const Context *context) angle::Result ProgramPipeline::link(const Context *context)
{ {
if (mState.mIsLinked)
{
return angle::Result::Continue;
}
ProgramMergedVaryings mergedVaryings; ProgramMergedVaryings mergedVaryings;
if (!getExecutable().isCompute()) if (!getExecutable().isCompute())
...@@ -590,6 +599,8 @@ angle::Result ProgramPipeline::link(const Context *context) ...@@ -590,6 +599,8 @@ angle::Result ProgramPipeline::link(const Context *context)
ANGLE_TRY(getImplementation()->link(context, mergedVaryings)); ANGLE_TRY(getImplementation()->link(context, mergedVaryings));
mState.mIsLinked = true;
return angle::Result::Continue; return angle::Result::Continue;
} }
...@@ -697,34 +708,13 @@ bool ProgramPipeline::validateSamplers(InfoLog *infoLog, const Caps &caps) ...@@ -697,34 +708,13 @@ bool ProgramPipeline::validateSamplers(InfoLog *infoLog, const Caps &caps)
return true; return true;
} }
angle::Result ProgramPipeline::syncState(const Context *context)
{
if (mDirtyBits.any())
{
mDirtyBits.reset();
// If there's a Program bound, we still want to link the PPO so we don't
// lose the dirty bit, but, we don't want to signal any errors if it fails
// since the failure would be unrelated to drawing with the Program.
bool goodResult = link(context) == angle::Result::Continue;
if (!context->getState().getProgram())
{
ANGLE_CHECK(const_cast<Context *>(context), goodResult, "Program pipeline link failed",
GL_INVALID_OPERATION);
}
}
return angle::Result::Continue;
}
void ProgramPipeline::onSubjectStateChange(angle::SubjectIndex index, angle::SubjectMessage message) void ProgramPipeline::onSubjectStateChange(angle::SubjectIndex index, angle::SubjectMessage message)
{ {
switch (message) switch (message)
{ {
case angle::SubjectMessage::SubjectChanged: case angle::SubjectMessage::SubjectChanged:
setDirtyBit(ProgramPipeline::DirtyBitType::DIRTY_BIT_PROGRAM_STAGE); mState.mIsLinked = false;
mState.updateExecutableTextures(); mState.updateExecutableTextures();
onStateChange(angle::SubjectMessage::DirtyBitsFlagged);
break; break;
default: default:
UNREACHABLE(); UNREACHABLE();
......
...@@ -83,12 +83,13 @@ class ProgramPipelineState final : angle::NonCopyable ...@@ -83,12 +83,13 @@ class ProgramPipelineState final : angle::NonCopyable
GLboolean mValid; GLboolean mValid;
ProgramExecutable *mExecutable; ProgramExecutable *mExecutable;
bool mIsLinked;
}; };
class ProgramPipeline final : public RefCountObject<ProgramPipelineID>, class ProgramPipeline final : public RefCountObject<ProgramPipelineID>,
public LabeledObject, public LabeledObject,
public angle::ObserverInterface, public angle::ObserverInterface
public angle::Subject
{ {
public: public:
ProgramPipeline(rx::GLImplFactory *factory, ProgramPipelineID handle); ProgramPipeline(rx::GLImplFactory *factory, ProgramPipelineID handle);
...@@ -100,6 +101,7 @@ class ProgramPipeline final : public RefCountObject<ProgramPipelineID>, ...@@ -100,6 +101,7 @@ class ProgramPipeline final : public RefCountObject<ProgramPipelineID>,
const std::string &getLabel() const override; const std::string &getLabel() const override;
const ProgramPipelineState &getState() const { return mState; } const ProgramPipelineState &getState() const { return mState; }
ProgramPipelineState &getState() { return mState; }
const ProgramExecutable &getExecutable() const { return mState.getProgramExecutable(); } const ProgramExecutable &getExecutable() const { return mState.getProgramExecutable(); }
ProgramExecutable &getExecutable() { return mState.getProgramExecutable(); } ProgramExecutable &getExecutable() { return mState.getProgramExecutable(); }
...@@ -122,6 +124,7 @@ class ProgramPipeline final : public RefCountObject<ProgramPipelineID>, ...@@ -122,6 +124,7 @@ class ProgramPipeline final : public RefCountObject<ProgramPipelineID>,
Program *getShaderProgram(ShaderType shaderType) const { return mState.mPrograms[shaderType]; } Program *getShaderProgram(ShaderType shaderType) const { return mState.mPrograms[shaderType]; }
void resetIsLinked() { mState.mIsLinked = false; }
ProgramMergedVaryings getMergedVaryings() const; ProgramMergedVaryings getMergedVaryings() const;
angle::Result link(const gl::Context *context); angle::Result link(const gl::Context *context);
bool linkVaryings(InfoLog &infoLog) const; bool linkVaryings(InfoLog &infoLog) const;
...@@ -133,25 +136,8 @@ class ProgramPipeline final : public RefCountObject<ProgramPipelineID>, ...@@ -133,25 +136,8 @@ class ProgramPipeline final : public RefCountObject<ProgramPipelineID>,
return mState.usesShaderProgram(program); return mState.usesShaderProgram(program);
} }
bool hasAnyDirtyBit() const { return mDirtyBits.any(); }
GLboolean isValid() const { return mState.isValid(); } GLboolean isValid() const { return mState.isValid(); }
// Program pipeline dirty bits.
enum DirtyBitType
{
// One of the program stages in the PPO changed.
DIRTY_BIT_PROGRAM_STAGE,
DIRTY_BIT_DRAW_DISPATCH_CHANGE,
DIRTY_BIT_COUNT = DIRTY_BIT_DRAW_DISPATCH_CHANGE + 1,
};
using DirtyBits = angle::BitSet<DIRTY_BIT_COUNT>;
angle::Result syncState(const Context *context);
void setDirtyBit(DirtyBitType dirtyBitType) { mDirtyBits.set(dirtyBitType); }
// ObserverInterface implementation. // ObserverInterface implementation.
void onSubjectStateChange(angle::SubjectIndex index, angle::SubjectMessage message) override; void onSubjectStateChange(angle::SubjectIndex index, angle::SubjectMessage message) override;
...@@ -170,8 +156,6 @@ class ProgramPipeline final : public RefCountObject<ProgramPipelineID>, ...@@ -170,8 +156,6 @@ class ProgramPipeline final : public RefCountObject<ProgramPipelineID>,
ProgramPipelineState mState; ProgramPipelineState mState;
DirtyBits mDirtyBits;
std::vector<angle::ObserverBinding> mProgramObserverBindings; std::vector<angle::ObserverBinding> mProgramObserverBindings;
angle::ObserverBinding mExecutableObserverBinding; angle::ObserverBinding mExecutableObserverBinding;
}; };
......
...@@ -1880,11 +1880,6 @@ angle::Result State::setProgramPipelineBinding(const Context *context, ProgramPi ...@@ -1880,11 +1880,6 @@ angle::Result State::setProgramPipelineBinding(const Context *context, ProgramPi
if (mProgramPipeline.get()) if (mProgramPipeline.get())
{ {
ANGLE_TRY(onProgramPipelineExecutableChange(context, mProgramPipeline.get())); ANGLE_TRY(onProgramPipelineExecutableChange(context, mProgramPipeline.get()));
if (mProgramPipeline->hasAnyDirtyBit())
{
mDirtyObjects.set(DIRTY_OBJECT_PROGRAM_PIPELINE);
}
} }
return angle::Result::Continue; return angle::Result::Continue;
...@@ -3222,16 +3217,6 @@ angle::Result State::syncProgram(const Context *context, Command command) ...@@ -3222,16 +3217,6 @@ angle::Result State::syncProgram(const Context *context, Command command)
return angle::Result::Continue; return angle::Result::Continue;
} }
angle::Result State::syncProgramPipeline(const Context *context, Command command)
{
// There may not be a program pipeline if the calling application only uses programs.
if (mProgramPipeline.get())
{
return mProgramPipeline->syncState(context);
}
return angle::Result::Continue;
}
angle::Result State::syncDirtyObject(const Context *context, GLenum target) angle::Result State::syncDirtyObject(const Context *context, GLenum target)
{ {
DirtyObjects localSet; DirtyObjects localSet;
...@@ -3352,11 +3337,6 @@ angle::Result State::onProgramPipelineExecutableChange(const Context *context, ...@@ -3352,11 +3337,6 @@ angle::Result State::onProgramPipelineExecutableChange(const Context *context,
{ {
mDirtyBits.set(DIRTY_BIT_PROGRAM_EXECUTABLE); mDirtyBits.set(DIRTY_BIT_PROGRAM_EXECUTABLE);
if (programPipeline->hasAnyDirtyBit())
{
mDirtyObjects.set(DIRTY_OBJECT_PROGRAM_PIPELINE);
}
// Set any bound textures. // Set any bound textures.
const ActiveTextureTypeArray &textureTypes = const ActiveTextureTypeArray &textureTypes =
programPipeline->getExecutable().getActiveSamplerTypes(); programPipeline->getExecutable().getActiveSamplerTypes();
......
...@@ -663,7 +663,6 @@ class State : angle::NonCopyable ...@@ -663,7 +663,6 @@ class State : angle::NonCopyable
DIRTY_OBJECT_IMAGES, // Top-level dirty bit. Also see mDirtyImages. DIRTY_OBJECT_IMAGES, // Top-level dirty bit. Also see mDirtyImages.
DIRTY_OBJECT_SAMPLERS, // Top-level dirty bit. Also see mDirtySamplers. DIRTY_OBJECT_SAMPLERS, // Top-level dirty bit. Also see mDirtySamplers.
DIRTY_OBJECT_PROGRAM, DIRTY_OBJECT_PROGRAM,
DIRTY_OBJECT_PROGRAM_PIPELINE,
DIRTY_OBJECT_UNKNOWN, DIRTY_OBJECT_UNKNOWN,
DIRTY_OBJECT_MAX = DIRTY_OBJECT_UNKNOWN, DIRTY_OBJECT_MAX = DIRTY_OBJECT_UNKNOWN,
}; };
...@@ -701,11 +700,6 @@ class State : angle::NonCopyable ...@@ -701,11 +700,6 @@ class State : angle::NonCopyable
mDirtyObjects.set(DIRTY_OBJECT_DRAW_ATTACHMENTS); mDirtyObjects.set(DIRTY_OBJECT_DRAW_ATTACHMENTS);
} }
ANGLE_INLINE void setProgramPipelineDirty()
{
mDirtyObjects.set(DIRTY_OBJECT_PROGRAM_PIPELINE);
}
// This actually clears the current value dirty bits. // This actually clears the current value dirty bits.
// TODO(jmadill): Pass mutable dirty bits into Impl. // TODO(jmadill): Pass mutable dirty bits into Impl.
AttributesMask getAndResetDirtyCurrentValues() const; AttributesMask getAndResetDirtyCurrentValues() const;
...@@ -888,16 +882,13 @@ class State : angle::NonCopyable ...@@ -888,16 +882,13 @@ class State : angle::NonCopyable
angle::Result syncImages(const Context *context, Command command); angle::Result syncImages(const Context *context, Command command);
angle::Result syncSamplers(const Context *context, Command command); angle::Result syncSamplers(const Context *context, Command command);
angle::Result syncProgram(const Context *context, Command command); angle::Result syncProgram(const Context *context, Command command);
angle::Result syncProgramPipeline(const Context *context, Command command);
using DirtyObjectHandler = angle::Result (State::*)(const Context *context, Command command); using DirtyObjectHandler = angle::Result (State::*)(const Context *context, Command command);
static constexpr DirtyObjectHandler kDirtyObjectHandlers[DIRTY_OBJECT_MAX] = { static constexpr DirtyObjectHandler kDirtyObjectHandlers[DIRTY_OBJECT_MAX] = {
&State::syncActiveTextures, &State::syncTexturesInit, &State::syncImagesInit, &State::syncActiveTextures, &State::syncTexturesInit, &State::syncImagesInit,
&State::syncReadAttachments, &State::syncDrawAttachments, &State::syncReadFramebuffer, &State::syncReadAttachments, &State::syncDrawAttachments, &State::syncReadFramebuffer,
&State::syncDrawFramebuffer, &State::syncVertexArray, &State::syncTextures, &State::syncDrawFramebuffer, &State::syncVertexArray, &State::syncTextures,
&State::syncImages, &State::syncSamplers, &State::syncProgram, &State::syncImages, &State::syncSamplers, &State::syncProgram};
&State::syncProgramPipeline,
};
// Robust init must happen before Framebuffer init for the Vulkan back-end. // Robust init must happen before Framebuffer init for the Vulkan back-end.
static_assert(DIRTY_OBJECT_ACTIVE_TEXTURES < DIRTY_OBJECT_TEXTURES_INIT, "init order"); static_assert(DIRTY_OBJECT_ACTIVE_TEXTURES < DIRTY_OBJECT_TEXTURES_INIT, "init order");
...@@ -918,7 +909,6 @@ class State : angle::NonCopyable ...@@ -918,7 +909,6 @@ class State : angle::NonCopyable
static_assert(DIRTY_OBJECT_IMAGES == 9, "check DIRTY_OBJECT_IMAGES index"); static_assert(DIRTY_OBJECT_IMAGES == 9, "check DIRTY_OBJECT_IMAGES index");
static_assert(DIRTY_OBJECT_SAMPLERS == 10, "check DIRTY_OBJECT_SAMPLERS index"); static_assert(DIRTY_OBJECT_SAMPLERS == 10, "check DIRTY_OBJECT_SAMPLERS index");
static_assert(DIRTY_OBJECT_PROGRAM == 11, "check DIRTY_OBJECT_PROGRAM index"); static_assert(DIRTY_OBJECT_PROGRAM == 11, "check DIRTY_OBJECT_PROGRAM index");
static_assert(DIRTY_OBJECT_PROGRAM_PIPELINE == 12, "check DIRTY_OBJECT_PROGRAM_PIPELINE index");
// Dispatch table for buffer update functions. // Dispatch table for buffer update functions.
static const angle::PackedEnumMap<BufferBinding, BufferBindingSetter> kBufferSetters; static const angle::PackedEnumMap<BufferBinding, BufferBindingSetter> kBufferSetters;
......
...@@ -1680,11 +1680,7 @@ angle::Result FramebufferVk::syncState(const gl::Context *context, ...@@ -1680,11 +1680,7 @@ angle::Result FramebufferVk::syncState(const gl::Context *context,
dirtyBit - gl::Framebuffer::DIRTY_BIT_COLOR_BUFFER_CONTENTS_0); dirtyBit - gl::Framebuffer::DIRTY_BIT_COLOR_BUFFER_CONTENTS_0);
} }
// TODO: uncomment the following ASSERT once deferred clears don't exist if ASSERT(!previousDeferredClears.test(colorIndexGL));
// Context::prepareForDraw() returns an error (and the draw doesn't process the
// deferred clear). http://anglebug.com/5064
//
// ASSERT(!previousDeferredClears.test(colorIndexGL));
ANGLE_TRY(updateColorAttachment(context, deferClears, colorIndexGL)); ANGLE_TRY(updateColorAttachment(context, deferClears, colorIndexGL));
shouldUpdateColorMask = true; shouldUpdateColorMask = true;
......
...@@ -2982,6 +2982,18 @@ const char *ValidateDrawStates(const Context *context) ...@@ -2982,6 +2982,18 @@ const char *ValidateDrawStates(const Context *context)
{ {
return errorMsg; return errorMsg;
} }
bool goodResult = programPipeline->link(context) == angle::Result::Continue;
// If there is no active program for the vertex or fragment shader stages, the results
// of vertex and fragment shader execution will respectively be undefined. However,
// this is not an error, so ANGLE only signals PPO link failures if both VS and FS
// stages are present.
const ProgramExecutable &executable = programPipeline->getExecutable();
if (!goodResult && executable.hasVertexAndFragmentShader())
{
return err::kProgramPipelineLinkFailed;
}
} }
// Do some additional WebGL-specific validation // Do some additional WebGL-specific validation
......
...@@ -43,6 +43,71 @@ class ContextNoErrorTest : public ANGLETest ...@@ -43,6 +43,71 @@ class ContextNoErrorTest : public ANGLETest
GLuint mNaughtyTexture; GLuint mNaughtyTexture;
}; };
class ContextNoErrorTest31 : public ContextNoErrorTest
{
protected:
~ContextNoErrorTest31()
{
glDeleteProgram(mVertProg);
glDeleteProgram(mFragProg);
glDeleteProgramPipelines(1, &mPipeline);
}
void bindProgramPipeline(const GLchar *vertString, const GLchar *fragString);
void drawQuadWithPPO(const std::string &positionAttribName,
const GLfloat positionAttribZ,
const GLfloat positionAttribXYScale);
GLuint mVertProg;
GLuint mFragProg;
GLuint mPipeline;
};
void ContextNoErrorTest31::bindProgramPipeline(const GLchar *vertString, const GLchar *fragString)
{
mVertProg = glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &vertString);
ASSERT_NE(mVertProg, 0u);
mFragProg = glCreateShaderProgramv(GL_FRAGMENT_SHADER, 1, &fragString);
ASSERT_NE(mFragProg, 0u);
// 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();
}
void ContextNoErrorTest31::drawQuadWithPPO(const std::string &positionAttribName,
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);
}
// Tests that error reporting is suppressed when GL_KHR_no_error is enabled // Tests that error reporting is suppressed when GL_KHR_no_error is enabled
TEST_P(ContextNoErrorTest, NoError) TEST_P(ContextNoErrorTest, NoError)
{ {
...@@ -52,4 +117,29 @@ TEST_P(ContextNoErrorTest, NoError) ...@@ -52,4 +117,29 @@ TEST_P(ContextNoErrorTest, NoError)
EXPECT_GL_NO_ERROR(); EXPECT_GL_NO_ERROR();
} }
// Tests that we can draw with a program pipeline when GL_KHR_no_error is enabled.
TEST_P(ContextNoErrorTest31, DrawWithPPO)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_KHR_no_error"));
// Only the Vulkan backend supports PPOs
ANGLE_SKIP_TEST_IF(!IsVulkan());
// TODO(http://anglebug.com/5102): Linking PPOs is currently done during draw call validation,
// so drawing with a PPO fails without validation enabled.
ANGLE_SKIP_TEST_IF(IsGLExtensionEnabled("GL_KHR_no_error"));
// Create two separable program objects from a
// single source string respectively (vertSrc and fragSrc)
const GLchar *vertString = essl31_shaders::vs::Simple();
const GLchar *fragString = essl31_shaders::fs::Red();
bindProgramPipeline(vertString, fragString);
drawQuadWithPPO("a_position", 0.5f, 1.0f);
ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
}
ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(ContextNoErrorTest); ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(ContextNoErrorTest);
ANGLE_INSTANTIATE_TEST_ES31(ContextNoErrorTest31);
...@@ -60,9 +60,9 @@ class ProgramPipelineTest31 : public ProgramPipelineTest ...@@ -60,9 +60,9 @@ class ProgramPipelineTest31 : public ProgramPipelineTest
} }
void bindProgramPipeline(const GLchar *vertString, const GLchar *fragString); void bindProgramPipeline(const GLchar *vertString, const GLchar *fragString);
void drawQuad(const std::string &positionAttribName, void drawQuadWithPPO(const std::string &positionAttribName,
const GLfloat positionAttribZ, const GLfloat positionAttribZ,
const GLfloat positionAttribXYScale); const GLfloat positionAttribXYScale);
GLuint mVertProg; GLuint mVertProg;
GLuint mFragProg; GLuint mFragProg;
...@@ -180,9 +180,9 @@ GLuint createShaderProgram(GLenum type, const GLchar *shaderString) ...@@ -180,9 +180,9 @@ GLuint createShaderProgram(GLenum type, const GLchar *shaderString)
return program; return program;
} }
void ProgramPipelineTest31::drawQuad(const std::string &positionAttribName, void ProgramPipelineTest31::drawQuadWithPPO(const std::string &positionAttribName,
const GLfloat positionAttribZ, const GLfloat positionAttribZ,
const GLfloat positionAttribXYScale) const GLfloat positionAttribXYScale)
{ {
glUseProgram(0); glUseProgram(0);
...@@ -233,7 +233,7 @@ TEST_P(ProgramPipelineTest31, UseProgramStages) ...@@ -233,7 +233,7 @@ TEST_P(ProgramPipelineTest31, UseProgramStages)
glBindProgramPipeline(pipeline); glBindProgramPipeline(pipeline);
EXPECT_GL_NO_ERROR(); EXPECT_GL_NO_ERROR();
ProgramPipelineTest31::drawQuad("a_position", 0.5f, 1.0f); drawQuadWithPPO("a_position", 0.5f, 1.0f);
ASSERT_GL_NO_ERROR(); ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red); EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
} }
...@@ -250,7 +250,7 @@ TEST_P(ProgramPipelineTest31, UseCreateShaderProgramv) ...@@ -250,7 +250,7 @@ TEST_P(ProgramPipelineTest31, UseCreateShaderProgramv)
bindProgramPipeline(vertString, fragString); bindProgramPipeline(vertString, fragString);
ProgramPipelineTest31::drawQuad("a_position", 0.5f, 1.0f); drawQuadWithPPO("a_position", 0.5f, 1.0f);
ASSERT_GL_NO_ERROR(); ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red); EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
} }
...@@ -283,14 +283,14 @@ void main() ...@@ -283,14 +283,14 @@ void main()
glActiveShaderProgram(mPipeline, mFragProg); glActiveShaderProgram(mPipeline, mFragProg);
glUniform1f(location, 1.0); glUniform1f(location, 1.0);
ProgramPipelineTest31::drawQuad("a_position", 0.5f, 1.0f); drawQuadWithPPO("a_position", 0.5f, 1.0f);
ASSERT_GL_NO_ERROR(); ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::yellow); EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::yellow);
glClearColor(0.0, 0.0, 0.0, 0.0); glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT);
ProgramPipelineTest31::drawQuad("a_position", 0.5f, 1.0f); drawQuadWithPPO("a_position", 0.5f, 1.0f);
ASSERT_GL_NO_ERROR(); ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::yellow); EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::yellow);
...@@ -305,7 +305,7 @@ void main() ...@@ -305,7 +305,7 @@ void main()
glActiveShaderProgram(mPipeline, mFragProg); glActiveShaderProgram(mPipeline, mFragProg);
glUniform1f(location, 0.0); glUniform1f(location, 0.0);
ProgramPipelineTest31::drawQuad("a_position", 0.5f, 1.0f); drawQuadWithPPO("a_position", 0.5f, 1.0f);
ASSERT_GL_NO_ERROR(); ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red); EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
...@@ -332,7 +332,7 @@ void main() ...@@ -332,7 +332,7 @@ void main()
bindProgramPipeline(vertString, fragString); bindProgramPipeline(vertString, fragString);
ProgramPipelineTest31::drawQuad("a_position", 0.5f, 1.0f); drawQuadWithPPO("a_position", 0.5f, 1.0f);
ASSERT_GL_NO_ERROR(); ASSERT_GL_NO_ERROR();
int w = getWindowWidth() - 2; int w = getWindowWidth() - 2;
...@@ -380,7 +380,7 @@ void main() ...@@ -380,7 +380,7 @@ void main()
bindProgramPipeline(vertString, fragString); bindProgramPipeline(vertString, fragString);
ProgramPipelineTest31::drawQuad("a_position", 0.5f, 1.0f); drawQuadWithPPO("a_position", 0.5f, 1.0f);
ASSERT_GL_NO_ERROR(); ASSERT_GL_NO_ERROR();
int w = getWindowWidth() - 2; int w = getWindowWidth() - 2;
...@@ -433,7 +433,7 @@ TEST_P(ProgramPipelineTest31, DetachAndModifyShader) ...@@ -433,7 +433,7 @@ TEST_P(ProgramPipelineTest31, DetachAndModifyShader)
EXPECT_GL_NO_ERROR(); EXPECT_GL_NO_ERROR();
// Draw once to ensure this worked fine // Draw once to ensure this worked fine
ProgramPipelineTest31::drawQuad("a_position", 0.5f, 1.0f); drawQuadWithPPO("a_position", 0.5f, 1.0f);
ASSERT_GL_NO_ERROR(); ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green); EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
...@@ -455,7 +455,7 @@ void main() ...@@ -455,7 +455,7 @@ void main()
// Link and draw with the program again, which should be fine since the shader was detached // Link and draw with the program again, which should be fine since the shader was detached
glLinkProgram(mFragProg); glLinkProgram(mFragProg);
ProgramPipelineTest31::drawQuad("a_position", 0.5f, 1.0f); drawQuadWithPPO("a_position", 0.5f, 1.0f);
ASSERT_GL_NO_ERROR(); ASSERT_GL_NO_ERROR();
} }
...@@ -515,7 +515,7 @@ void main() ...@@ -515,7 +515,7 @@ void main()
// Create a pipeline that uses the bad combination. This should fail to link the pipeline. // Create a pipeline that uses the bad combination. This should fail to link the pipeline.
bindProgramPipeline(vertString, fragString); bindProgramPipeline(vertString, fragString);
ProgramPipelineTest31::drawQuad("a_position", 0.5f, 1.0f); drawQuadWithPPO("a_position", 0.5f, 1.0f);
ASSERT_GL_ERROR(GL_INVALID_OPERATION); ASSERT_GL_ERROR(GL_INVALID_OPERATION);
// Update the fragment shader to correctly use 2D texture // Update the fragment shader to correctly use 2D texture
...@@ -532,10 +532,49 @@ void main() ...@@ -532,10 +532,49 @@ void main()
// Bind the pipeline again, which should succeed. // Bind the pipeline again, which should succeed.
bindProgramPipeline(vertString, fragString2); bindProgramPipeline(vertString, fragString2);
ProgramPipelineTest31::drawQuad("a_position", 0.5f, 1.0f); drawQuadWithPPO("a_position", 0.5f, 1.0f);
ASSERT_GL_NO_ERROR(); ASSERT_GL_NO_ERROR();
} }
// Tests that we receive a PPO link validation error when attempting to draw with the bad PPO
TEST_P(ProgramPipelineTest31, VerifyPpoLinkErrorSignalledCorrectly)
{
// Create pipeline that should fail link
// Bind program
// Draw
// Unbind program
// Draw <<--- expect a link validation error here
// Only the Vulkan backend supports PPOs
ANGLE_SKIP_TEST_IF(!IsVulkan());
// Create two separable program objects from a
// single source string respectively (vertSrc and fragSrc)
const GLchar *vertString = essl31_shaders::vs::Simple();
const GLchar *fragString = essl31_shaders::fs::Red();
// Create a fragment shader that takes a color input
// This should cause the PPO link to fail, since the varyings don't match (no output from VS).
const GLchar *fragStringBad = R"(#version 310 es
precision highp float;
layout(location = 0) in vec4 colorIn;
out vec4 my_FragColor;
void main()
{
my_FragColor = colorIn;
})";
bindProgramPipeline(vertString, fragStringBad);
ANGLE_GL_PROGRAM(program, vertString, fragString);
drawQuad(program.get(), essl1_shaders::PositionAttrib(), 0.0f, 1.0f, true);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
// Draw with the PPO, which should generate an error due to the link failure.
glUseProgram(0);
ASSERT_GL_NO_ERROR();
drawQuadWithPPO(essl1_shaders::PositionAttrib(), 0.5f, 1.0f);
ASSERT_GL_ERROR(GL_INVALID_OPERATION);
}
ANGLE_INSTANTIATE_TEST_ES3_AND_ES31(ProgramPipelineTest); ANGLE_INSTANTIATE_TEST_ES3_AND_ES31(ProgramPipelineTest);
ANGLE_INSTANTIATE_TEST_ES31(ProgramPipelineTest31); ANGLE_INSTANTIATE_TEST_ES31(ProgramPipelineTest31);
......
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