Commit 4b654982 by James Darpinian Committed by Commit Bot

GL backend: Transform feedback driver bug workaround

In some drivers, if transform feedback is paused and a new program is bound, calling endTransformFeedback does not correctly unpause first, creating an invalid paused but inactive state that causes errors later. Before calling endTransformFeedback we first ensure that the current program is the one associated with this transform feedback object when beginTransformFeedback was called. Bug: 832238 Change-Id: I2373b0c123fd20a7ee3ada76ed878d4968184476 Reviewed-on: https://chromium-review.googlesource.com/c/1448661Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org> Commit-Queue: James Darpinian <jdarpinian@chromium.org>
parent 4154c1c1
...@@ -19,6 +19,7 @@ namespace rx ...@@ -19,6 +19,7 @@ namespace rx
{ {
class GLImplFactory; class GLImplFactory;
class TransformFeedbackImpl; class TransformFeedbackImpl;
class TransformFeedbackGL;
} // namespace rx } // namespace rx
namespace gl namespace gl
...@@ -36,6 +37,7 @@ class TransformFeedbackState final : angle::NonCopyable ...@@ -36,6 +37,7 @@ class TransformFeedbackState final : angle::NonCopyable
const OffsetBindingPointer<Buffer> &getIndexedBuffer(size_t idx) const; const OffsetBindingPointer<Buffer> &getIndexedBuffer(size_t idx) const;
const std::vector<OffsetBindingPointer<Buffer>> &getIndexedBuffers() const; const std::vector<OffsetBindingPointer<Buffer>> &getIndexedBuffers() const;
const Program *getBoundProgram() const { return mProgram; }
private: private:
friend class TransformFeedback; friend class TransformFeedback;
......
...@@ -2245,7 +2245,7 @@ void StateManagerGL::syncTransformFeedbackState(const gl::Context *context) ...@@ -2245,7 +2245,7 @@ void StateManagerGL::syncTransformFeedbackState(const gl::Context *context)
TransformFeedbackGL *transformFeedbackGL = TransformFeedbackGL *transformFeedbackGL =
GetImplAs<TransformFeedbackGL>(transformFeedback); GetImplAs<TransformFeedbackGL>(transformFeedback);
bindTransformFeedback(GL_TRANSFORM_FEEDBACK, transformFeedbackGL->getTransformFeedbackID()); bindTransformFeedback(GL_TRANSFORM_FEEDBACK, transformFeedbackGL->getTransformFeedbackID());
transformFeedbackGL->syncActiveState(transformFeedback->isActive(), transformFeedbackGL->syncActiveState(context, transformFeedback->isActive(),
transformFeedback->getPrimitiveMode()); transformFeedback->getPrimitiveMode());
transformFeedbackGL->syncPausedState(transformFeedback->isPaused()); transformFeedbackGL->syncPausedState(transformFeedback->isPaused());
mCurrentTransformFeedback = transformFeedbackGL; mCurrentTransformFeedback = transformFeedbackGL;
......
...@@ -9,10 +9,14 @@ ...@@ -9,10 +9,14 @@
#include "libANGLE/renderer/gl/TransformFeedbackGL.h" #include "libANGLE/renderer/gl/TransformFeedbackGL.h"
#include "common/debug.h" #include "common/debug.h"
#include "libANGLE/Context.h"
#include "libANGLE/State.h" #include "libANGLE/State.h"
#include "libANGLE/renderer/gl/BufferGL.h" #include "libANGLE/renderer/gl/BufferGL.h"
#include "libANGLE/renderer/gl/FunctionsGL.h" #include "libANGLE/renderer/gl/FunctionsGL.h"
#include "libANGLE/renderer/gl/ProgramGL.h"
#include "libANGLE/renderer/gl/StateManagerGL.h" #include "libANGLE/renderer/gl/StateManagerGL.h"
#include "libANGLE/renderer/gl/WorkaroundsGL.h"
#include "libANGLE/renderer/gl/renderergl_utils.h"
namespace rx namespace rx
{ {
...@@ -25,7 +29,8 @@ TransformFeedbackGL::TransformFeedbackGL(const gl::TransformFeedbackState &state ...@@ -25,7 +29,8 @@ TransformFeedbackGL::TransformFeedbackGL(const gl::TransformFeedbackState &state
mStateManager(stateManager), mStateManager(stateManager),
mTransformFeedbackID(0), mTransformFeedbackID(0),
mIsActive(false), mIsActive(false),
mIsPaused(false) mIsPaused(false),
mActiveProgram(0)
{ {
mFunctions->genTransformFeedbacks(1, &mTransformFeedbackID); mFunctions->genTransformFeedbacks(1, &mTransformFeedbackID);
} }
...@@ -48,7 +53,7 @@ angle::Result TransformFeedbackGL::end(const gl::Context *context) ...@@ -48,7 +53,7 @@ angle::Result TransformFeedbackGL::end(const gl::Context *context)
mStateManager->onTransformFeedbackStateChange(); mStateManager->onTransformFeedbackStateChange();
// Immediately end the transform feedback so that the results are visible. // Immediately end the transform feedback so that the results are visible.
syncActiveState(false, gl::PrimitiveMode::InvalidEnum); syncActiveState(context, false, gl::PrimitiveMode::InvalidEnum);
return angle::Result::Continue; return angle::Result::Continue;
} }
...@@ -107,7 +112,9 @@ GLuint TransformFeedbackGL::getTransformFeedbackID() const ...@@ -107,7 +112,9 @@ GLuint TransformFeedbackGL::getTransformFeedbackID() const
return mTransformFeedbackID; return mTransformFeedbackID;
} }
void TransformFeedbackGL::syncActiveState(bool active, gl::PrimitiveMode primitiveMode) const void TransformFeedbackGL::syncActiveState(const gl::Context *context,
bool active,
gl::PrimitiveMode primitiveMode) const
{ {
if (mIsActive != active) if (mIsActive != active)
{ {
...@@ -118,10 +125,13 @@ void TransformFeedbackGL::syncActiveState(bool active, gl::PrimitiveMode primiti ...@@ -118,10 +125,13 @@ void TransformFeedbackGL::syncActiveState(bool active, gl::PrimitiveMode primiti
if (mIsActive) if (mIsActive)
{ {
ASSERT(primitiveMode != gl::PrimitiveMode::InvalidEnum); ASSERT(primitiveMode != gl::PrimitiveMode::InvalidEnum);
mActiveProgram = GetImplAs<ProgramGL>(mState.getBoundProgram())->getProgramID();
mStateManager->useProgram(mActiveProgram);
mFunctions->beginTransformFeedback(gl::ToGLenum(primitiveMode)); mFunctions->beginTransformFeedback(gl::ToGLenum(primitiveMode));
} }
else else
{ {
mStateManager->useProgram(mActiveProgram);
mFunctions->endTransformFeedback(); mFunctions->endTransformFeedback();
} }
} }
......
...@@ -38,7 +38,9 @@ class TransformFeedbackGL : public TransformFeedbackImpl ...@@ -38,7 +38,9 @@ class TransformFeedbackGL : public TransformFeedbackImpl
GLuint getTransformFeedbackID() const; GLuint getTransformFeedbackID() const;
void syncActiveState(bool active, gl::PrimitiveMode primitiveMode) const; void syncActiveState(const gl::Context *context,
bool active,
gl::PrimitiveMode primitiveMode) const;
void syncPausedState(bool paused) const; void syncPausedState(bool paused) const;
private: private:
...@@ -49,6 +51,7 @@ class TransformFeedbackGL : public TransformFeedbackImpl ...@@ -49,6 +51,7 @@ class TransformFeedbackGL : public TransformFeedbackImpl
mutable bool mIsActive; mutable bool mIsActive;
mutable bool mIsPaused; mutable bool mIsPaused;
mutable GLuint mActiveProgram;
}; };
} // namespace rx } // namespace rx
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include "test_utils/ANGLETest.h" #include "test_utils/ANGLETest.h"
#include "test_utils/gl_raii.h" #include "test_utils/gl_raii.h"
#include "util/EGLWindow.h" #include "util/EGLWindow.h"
#include "util/gles_loader_autogen.h"
#include "util/random_utils.h" #include "util/random_utils.h"
using namespace angle; using namespace angle;
...@@ -1491,6 +1492,162 @@ TEST_P(TransformFeedbackTest, NoTransformFeedbackVaryingsInUse) ...@@ -1491,6 +1492,162 @@ TEST_P(TransformFeedbackTest, NoTransformFeedbackVaryingsInUse)
EXPECT_GL_ERROR(GL_INVALID_OPERATION); EXPECT_GL_ERROR(GL_INVALID_OPERATION);
} }
// Test that you can pause transform feedback without drawing first.
TEST_P(TransformFeedbackTest, SwitchProgramBeforeDraw)
{
std::vector<std::string> tfVaryings;
tfVaryings.push_back("gl_Position");
compileDefaultProgram(tfVaryings, GL_INTERLEAVED_ATTRIBS);
ANGLE_GL_PROGRAM(nonTFProgram, essl3_shaders::vs::Simple(), essl3_shaders::fs::Red());
// Set up transform feedback, but pause it.
glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, mTransformFeedback);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, mTransformFeedbackBuffer);
glUseProgram(mProgram);
glBeginTransformFeedback(GL_TRIANGLES);
glPauseTransformFeedback();
// Switch programs and draw while transform feedback is paused.
glUseProgram(nonTFProgram);
GLint positionLocation = glGetAttribLocation(nonTFProgram, essl1_shaders::PositionAttrib());
glDisableVertexAttribArray(positionLocation);
glVertexAttrib4f(positionLocation, 0.0f, 0.0f, 0.0f, 1.0f);
glDrawArrays(GL_TRIANGLES, 0, 3);
glEndTransformFeedback();
ASSERT_GL_NO_ERROR();
}
// Test that ending transform feedback with a different program bound does not cause internal
// errors.
TEST_P(TransformFeedbackTest, EndWithDifferentProgram)
{
// AMD drivers fail because they perform transform feedback when it should be paused.
ANGLE_SKIP_TEST_IF(IsAMD() && IsOpenGL());
std::vector<std::string> tfVaryings;
tfVaryings.push_back("gl_Position");
compileDefaultProgram(tfVaryings, GL_INTERLEAVED_ATTRIBS);
ANGLE_GL_PROGRAM(nonTFProgram, essl3_shaders::vs::Simple(), essl3_shaders::fs::Red());
// Set up transform feedback, but pause it.
glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, mTransformFeedback);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, mTransformFeedbackBuffer);
// Make sure the buffer has zero'd data
std::vector<float> data(mTransformFeedbackBufferSize / sizeof(float), 0.0f);
glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, mTransformFeedbackBufferSize, data.data(),
GL_STATIC_DRAW);
glUseProgram(mProgram);
glBeginTransformFeedback(GL_TRIANGLES);
glPauseTransformFeedback();
// Transform feedback should not happen
drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
// Draw using a different program.
glUseProgram(nonTFProgram);
GLint positionLocation = glGetAttribLocation(nonTFProgram, essl1_shaders::PositionAttrib());
glDisableVertexAttribArray(positionLocation);
glVertexAttrib4f(positionLocation, 0.0f, 0.0f, 0.0f, 1.0f);
glDrawArrays(GL_TRIANGLES, 0, 3);
// End transform feedback without unpausing and with a different program bound. This triggers
// the bug.
glEndTransformFeedback();
glUseProgram(mProgram);
glBeginTransformFeedback(GL_TRIANGLES);
// On a buggy driver without the workaround this will cause a GL error because the driver
// thinks transform feedback is still paused, but rendering will still write to the transform
// feedback buffers.
glPauseTransformFeedback();
drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
glEndTransformFeedback();
// Make sure that transform feedback did not happen. We always paused transform feedback before
// rendering, but a buggy driver will fail to pause.
const void *mapPointer =
glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(Vector4) * 4, GL_MAP_READ_BIT);
ASSERT_NE(nullptr, mapPointer);
const Vector4 *vecPointer = static_cast<const Vector4 *>(mapPointer);
ASSERT_EQ(vecPointer[0], Vector4(0, 0, 0, 0));
glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER);
ASSERT_GL_NO_ERROR();
}
// Test that switching contexts with paused transform feedback does not cause internal errors.
TEST_P(TransformFeedbackTest, EndWithDifferentProgramContextSwitch)
{
// AMD drivers fail because they perform transform feedback when it should be paused.
ANGLE_SKIP_TEST_IF(IsAMD() && IsOpenGL());
std::vector<std::string> tfVaryings;
tfVaryings.push_back("gl_Position");
compileDefaultProgram(tfVaryings, GL_INTERLEAVED_ATTRIBS);
EGLWindow *window = getEGLWindow();
EGLDisplay display = window->getDisplay();
EGLConfig config = window->getConfig();
EGLSurface surface = window->getSurface();
EGLint contextAttributes[] = {
EGL_CONTEXT_MAJOR_VERSION_KHR,
GetParam().majorVersion,
EGL_CONTEXT_MINOR_VERSION_KHR,
GetParam().minorVersion,
EGL_NONE,
};
auto context1 = eglGetCurrentContext();
auto context2 = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttributes);
ASSERT_NE(context2, EGL_NO_CONTEXT);
// Compile a program on the second context.
eglMakeCurrent(display, surface, surface, context2);
ANGLE_GL_PROGRAM(nonTFProgram, essl3_shaders::vs::Simple(), essl3_shaders::fs::Red());
eglMakeCurrent(display, surface, surface, context1);
// Set up transform feedback, but pause it.
glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, mTransformFeedback);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, mTransformFeedbackBuffer);
// Make sure the buffer has zero'd data
std::vector<float> data(mTransformFeedbackBufferSize / sizeof(float), 0.0f);
glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, mTransformFeedbackBufferSize, data.data(),
GL_STATIC_DRAW);
glUseProgram(mProgram);
glBeginTransformFeedback(GL_TRIANGLES);
glPauseTransformFeedback();
drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
// Leave transform feedback active but paused while we switch to a second context and render
// something.
eglMakeCurrent(display, surface, surface, context2);
glUseProgram(nonTFProgram);
GLint positionLocation = glGetAttribLocation(nonTFProgram, essl1_shaders::PositionAttrib());
glDisableVertexAttribArray(positionLocation);
glVertexAttrib4f(positionLocation, 0.0f, 0.0f, 0.0f, 1.0f);
glDrawArrays(GL_TRIANGLES, 0, 3);
// Switch back to the first context and end transform feedback. On a buggy driver, this will
// cause the transform feedback object to enter an invalid "inactive, but paused" state unless
// the workaround is applied.
eglMakeCurrent(display, surface, surface, context1);
glEndTransformFeedback();
glBeginTransformFeedback(GL_TRIANGLES);
// On a buggy driver without the workaround this will cause a GL error because the driver
// thinks transform feedback is still paused, but rendering will still write to the transform
// feedback buffers.
glPauseTransformFeedback();
drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
glEndTransformFeedback();
// Make sure that transform feedback did not happen. We always paused transform feedback before
// rendering, but a buggy driver will fail to pause.
const void *mapPointer =
glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(Vector4) * 4, GL_MAP_READ_BIT);
ASSERT_NE(nullptr, mapPointer);
const Vector4 *vecPointer = static_cast<const Vector4 *>(mapPointer);
ASSERT_EQ(vecPointer[0], Vector4(0, 0, 0, 0));
glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER);
eglDestroyContext(display, context2);
ASSERT_GL_NO_ERROR();
}
// Use this to select which configurations (e.g. which renderer, which GLES major version) these // Use this to select which configurations (e.g. which renderer, which GLES major version) these
// tests should be run against. // tests should be run against.
ANGLE_INSTANTIATE_TEST(TransformFeedbackTest, ES3_D3D11(), ES3_OPENGL(), ES3_OPENGLES()); ANGLE_INSTANTIATE_TEST(TransformFeedbackTest, ES3_D3D11(), ES3_OPENGL(), ES3_OPENGLES());
......
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