From a7d12dc7f57a541e4df90a5d268910d0e0a8d906 Mon Sep 17 00:00:00 2001 From: Jamie Madill Date: Tue, 13 Dec 2016 15:08:19 -0500 Subject: [PATCH] Store uniform block bindings in program binaries. This affects all back-ends - we weren't saving this. Note that bindings can only be set after program linking. The spec is fairly clear in that any programs saved must be loadable and runnable under the same set of state, which would include block bindings. Also add validation for zero binary formats in GetProgramBinary. Also add a workaround for AMD where the block bindings were not applied properly after link, similarly to our original bug. This CL also includes a few fixups for GLProgram (raii). BUG=angleproject:1637 Change-Id: Iae068eb4e1e4c763aa9f9332c033e38708026c8f Reviewed-on: https://chromium-review.googlesource.com/418393 Reviewed-by: Geoff Lang Reviewed-by: Corentin Wallez Commit-Queue: Jamie Madill --- src/libANGLE/Program.cpp | 16 +++- src/libANGLE/Program.h | 3 +- src/libANGLE/renderer/ProgramImpl.h | 6 +- src/libANGLE/renderer/ProgramImpl_mock.h | 2 +- src/libANGLE/renderer/d3d/ProgramD3D.cpp | 6 +- src/libANGLE/renderer/d3d/ProgramD3D.h | 4 +- src/libANGLE/renderer/gl/ContextGL.cpp | 2 +- src/libANGLE/renderer/gl/ContextGL.h | 2 +- src/libANGLE/renderer/gl/ProgramGL.cpp | 16 +++- src/libANGLE/renderer/gl/ProgramGL.h | 4 +- src/libANGLE/renderer/gl/WorkaroundsGL.h | 65 ++++++-------- src/libANGLE/renderer/gl/renderergl_utils.cpp | 4 +- src/libANGLE/renderer/null/ProgramNULL.cpp | 4 +- src/libANGLE/renderer/null/ProgramNULL.h | 4 +- src/libANGLE/renderer/vulkan/ProgramVk.cpp | 4 +- src/libANGLE/renderer/vulkan/ProgramVk.h | 4 +- src/libANGLE/validationES.cpp | 6 ++ src/tests/gl_tests/ProgramBinaryTest.cpp | 88 +++++++++++++++++++ src/tests/test_utils/gl_raii.h | 62 ++++++++----- util/shader_utils.cpp | 17 ++++ util/shader_utils.h | 3 + 21 files changed, 246 insertions(+), 76 deletions(-) diff --git a/src/libANGLE/Program.cpp b/src/libANGLE/Program.cpp index 407ab56ab..191cdfee3 100644 --- a/src/libANGLE/Program.cpp +++ b/src/libANGLE/Program.cpp @@ -818,6 +818,14 @@ Error Program::loadBinary(const Context *context, mState.mUniformBlocks.push_back(uniformBlock); } + for (GLuint bindingIndex = 0; bindingIndex < mState.mUniformBlockBindings.size(); + ++bindingIndex) + { + stream.readInt(&mState.mUniformBlockBindings[bindingIndex]); + mState.mActiveUniformBlockBindings.set(bindingIndex, + mState.mUniformBlockBindings[bindingIndex] != 0); + } + unsigned int transformFeedbackVaryingCount = stream.readInt(); ASSERT(mState.mTransformFeedbackVaryingVars.empty()); for (unsigned int transformFeedbackVaryingIndex = 0; @@ -848,7 +856,7 @@ Error Program::loadBinary(const Context *context, stream.readInt(&mSamplerUniformRange.start); stream.readInt(&mSamplerUniformRange.end); - ANGLE_TRY_RESULT(mProgram->load(mInfoLog, &stream), mLinked); + ANGLE_TRY_RESULT(mProgram->load(context->getImplementation(), mInfoLog, &stream), mLinked); return NoError(); #endif // #if ANGLE_PROGRAM_BINARY_LOAD == ANGLE_ENABLED @@ -936,6 +944,11 @@ Error Program::saveBinary(const Context *context, } } + for (GLuint binding : mState.mUniformBlockBindings) + { + stream.writeInt(binding); + } + stream.writeInt(mState.mTransformFeedbackVaryingVars.size()); for (const sh::Varying &varying : mState.mTransformFeedbackVaryingVars) { @@ -1685,6 +1698,7 @@ const UniformBlock &Program::getUniformBlockByIndex(GLuint index) const void Program::bindUniformBlock(GLuint uniformBlockIndex, GLuint uniformBlockBinding) { mState.mUniformBlockBindings[uniformBlockIndex] = uniformBlockBinding; + mState.mActiveUniformBlockBindings.set(uniformBlockIndex, uniformBlockBinding != 0); mProgram->setUniformBlockBinding(uniformBlockIndex, uniformBlockBinding); } diff --git a/src/libANGLE/Program.h b/src/libANGLE/Program.h index b15a3d504..f281826b8 100644 --- a/src/libANGLE/Program.h +++ b/src/libANGLE/Program.h @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -211,7 +212,7 @@ class ProgramState final : angle::NonCopyable std::vector mTransformFeedbackVaryingVars; GLenum mTransformFeedbackBufferMode; - GLuint mUniformBlockBindings[IMPLEMENTATION_MAX_COMBINED_SHADER_UNIFORM_BUFFERS]; + std::array mUniformBlockBindings; UniformBlockBindingMask mActiveUniformBlockBindings; std::vector mAttributes; diff --git a/src/libANGLE/renderer/ProgramImpl.h b/src/libANGLE/renderer/ProgramImpl.h index 065f6a338..3098bd8a6 100644 --- a/src/libANGLE/renderer/ProgramImpl.h +++ b/src/libANGLE/renderer/ProgramImpl.h @@ -26,13 +26,17 @@ namespace rx { using LinkResult = gl::ErrorOrResult; +class ContextImpl; + class ProgramImpl : angle::NonCopyable { public: ProgramImpl(const gl::ProgramState &state) : mState(state) {} virtual ~ProgramImpl() {} - virtual LinkResult load(gl::InfoLog &infoLog, gl::BinaryInputStream *stream) = 0; + virtual LinkResult load(const ContextImpl *contextImpl, + gl::InfoLog &infoLog, + gl::BinaryInputStream *stream) = 0; virtual gl::Error save(gl::BinaryOutputStream *stream) = 0; virtual void setBinaryRetrievableHint(bool retrievable) = 0; diff --git a/src/libANGLE/renderer/ProgramImpl_mock.h b/src/libANGLE/renderer/ProgramImpl_mock.h index 1bc20bbef..9abd837f7 100644 --- a/src/libANGLE/renderer/ProgramImpl_mock.h +++ b/src/libANGLE/renderer/ProgramImpl_mock.h @@ -23,7 +23,7 @@ class MockProgramImpl : public rx::ProgramImpl MockProgramImpl() : ProgramImpl(gl::ProgramState()) {} virtual ~MockProgramImpl() { destroy(); } - MOCK_METHOD2(load, LinkResult(gl::InfoLog &, gl::BinaryInputStream *)); + MOCK_METHOD3(load, LinkResult(const ContextImpl *, gl::InfoLog &, gl::BinaryInputStream *)); MOCK_METHOD1(save, gl::Error(gl::BinaryOutputStream *)); MOCK_METHOD1(setBinaryRetrievableHint, void(bool)); diff --git a/src/libANGLE/renderer/d3d/ProgramD3D.cpp b/src/libANGLE/renderer/d3d/ProgramD3D.cpp index a1a67dbac..d9c7e368c 100644 --- a/src/libANGLE/renderer/d3d/ProgramD3D.cpp +++ b/src/libANGLE/renderer/d3d/ProgramD3D.cpp @@ -783,8 +783,12 @@ void ProgramD3D::updateSamplerMapping() } } -LinkResult ProgramD3D::load(gl::InfoLog &infoLog, gl::BinaryInputStream *stream) +LinkResult ProgramD3D::load(const ContextImpl *contextImpl, + gl::InfoLog &infoLog, + gl::BinaryInputStream *stream) { + // TODO(jmadill): Use Renderer from contextImpl. + reset(); DeviceIdentifier binaryDeviceIdentifier = {0}; diff --git a/src/libANGLE/renderer/d3d/ProgramD3D.h b/src/libANGLE/renderer/d3d/ProgramD3D.h index a5d5fe587..1dfdc30d1 100644 --- a/src/libANGLE/renderer/d3d/ProgramD3D.h +++ b/src/libANGLE/renderer/d3d/ProgramD3D.h @@ -151,7 +151,9 @@ class ProgramD3D : public ProgramImpl bool usesGeometryShader(GLenum drawMode) const; bool usesInstancedPointSpriteEmulation() const; - LinkResult load(gl::InfoLog &infoLog, gl::BinaryInputStream *stream) override; + LinkResult load(const ContextImpl *contextImpl, + gl::InfoLog &infoLog, + gl::BinaryInputStream *stream) override; gl::Error save(gl::BinaryOutputStream *stream) override; void setBinaryRetrievableHint(bool retrievable) override; diff --git a/src/libANGLE/renderer/gl/ContextGL.cpp b/src/libANGLE/renderer/gl/ContextGL.cpp index b10e6bd74..ae74a0f2f 100644 --- a/src/libANGLE/renderer/gl/ContextGL.cpp +++ b/src/libANGLE/renderer/gl/ContextGL.cpp @@ -369,7 +369,7 @@ StateManagerGL *ContextGL::getStateManager() return mRenderer->getStateManager(); } -const WorkaroundsGL &ContextGL::getWorkaroundsGL() +const WorkaroundsGL &ContextGL::getWorkaroundsGL() const { return mRenderer->getWorkarounds(); } diff --git a/src/libANGLE/renderer/gl/ContextGL.h b/src/libANGLE/renderer/gl/ContextGL.h index b3d5e0ee8..02cd082bb 100644 --- a/src/libANGLE/renderer/gl/ContextGL.h +++ b/src/libANGLE/renderer/gl/ContextGL.h @@ -172,7 +172,7 @@ class ContextGL : public ContextImpl // Handle helpers const FunctionsGL *getFunctions() const; StateManagerGL *getStateManager(); - const WorkaroundsGL &getWorkaroundsGL(); + const WorkaroundsGL &getWorkaroundsGL() const; private: RendererGL *mRenderer; diff --git a/src/libANGLE/renderer/gl/ProgramGL.cpp b/src/libANGLE/renderer/gl/ProgramGL.cpp index 9d74ef7a6..d04c1e76a 100644 --- a/src/libANGLE/renderer/gl/ProgramGL.cpp +++ b/src/libANGLE/renderer/gl/ProgramGL.cpp @@ -8,10 +8,12 @@ #include "libANGLE/renderer/gl/ProgramGL.h" +#include "common/BitSetIterator.h" #include "common/angleutils.h" #include "common/debug.h" #include "common/string_utils.h" #include "common/utilities.h" +#include "libANGLE/renderer/gl/ContextGL.h" #include "libANGLE/renderer/gl/FunctionsGL.h" #include "libANGLE/renderer/gl/ShaderGL.h" #include "libANGLE/renderer/gl/StateManagerGL.h" @@ -46,7 +48,9 @@ ProgramGL::~ProgramGL() mProgramID = 0; } -LinkResult ProgramGL::load(gl::InfoLog &infoLog, gl::BinaryInputStream *stream) +LinkResult ProgramGL::load(const ContextImpl *contextImpl, + gl::InfoLog &infoLog, + gl::BinaryInputStream *stream) { preLink(); @@ -67,6 +71,16 @@ LinkResult ProgramGL::load(gl::InfoLog &infoLog, gl::BinaryInputStream *stream) postLink(); + // Re-apply UBO bindings to work around driver bugs. + const WorkaroundsGL &workaroundsGL = GetAs(contextImpl)->getWorkaroundsGL(); + if (workaroundsGL.reapplyUBOBindingsAfterLoadingBinaryProgram) + { + for (GLuint bindingIndex : angle::IterateBitSet(mState.getActiveUniformBlockBindingsMask())) + { + setUniformBlockBinding(bindingIndex, mState.getUniformBlockBinding(bindingIndex)); + } + } + return true; } diff --git a/src/libANGLE/renderer/gl/ProgramGL.h b/src/libANGLE/renderer/gl/ProgramGL.h index 88e2245fa..9fe31b1c5 100644 --- a/src/libANGLE/renderer/gl/ProgramGL.h +++ b/src/libANGLE/renderer/gl/ProgramGL.h @@ -37,7 +37,9 @@ class ProgramGL : public ProgramImpl bool enablePathRendering); ~ProgramGL() override; - LinkResult load(gl::InfoLog &infoLog, gl::BinaryInputStream *stream) override; + LinkResult load(const ContextImpl *contextImpl, + gl::InfoLog &infoLog, + gl::BinaryInputStream *stream) override; gl::Error save(gl::BinaryOutputStream *stream) override; void setBinaryRetrievableHint(bool retrievable) override; diff --git a/src/libANGLE/renderer/gl/WorkaroundsGL.h b/src/libANGLE/renderer/gl/WorkaroundsGL.h index 0a5c60d84..43b1ab292 100644 --- a/src/libANGLE/renderer/gl/WorkaroundsGL.h +++ b/src/libANGLE/renderer/gl/WorkaroundsGL.h @@ -14,25 +14,6 @@ namespace rx struct WorkaroundsGL { - WorkaroundsGL() - : avoid1BitAlphaTextureFormats(false), - rgba4IsNotSupportedForColorRendering(false), - doesSRGBClearsOnLinearFramebufferAttachments(false), - doWhileGLSLCausesGPUHang(false), - finishDoesNotCauseQueriesToBeAvailable(false), - alwaysCallUseProgramAfterLink(false), - unpackOverlappingRowsSeparatelyUnpackBuffer(false), - emulateAbsIntFunction(false), - addAndTrueToLoopCondition(false), - emulateIsnanFloat(false), - useUnusedBlocksWithStandardOrSharedLayout(false), - dontRemoveInvariantForFragmentInput(false), - removeInvariantAndCentroidForESSL3(false), - rewriteFloatUnaryMinusOperator(false), - emulateAtan2Float(false) - { - } - // When writing a float to a normalized integer framebuffer, desktop OpenGL is allowed to write // one of the two closest normalized integer representations (although round to nearest is // preferred) (see section 2.3.5.2 of the GL 4.5 core specification). OpenGL ES requires that @@ -40,18 +21,18 @@ struct WorkaroundsGL // section 2.1.2 of the OpenGL ES 2.0.25 spec). This issue only shows up on Intel and AMD // drivers on framebuffer formats that have 1-bit alpha, work around this by using higher // precision formats instead. - bool avoid1BitAlphaTextureFormats; + bool avoid1BitAlphaTextureFormats = false; // On some older Intel drivers, GL_RGBA4 is not color renderable, glCheckFramebufferStatus // returns GL_FRAMEBUFFER_UNSUPPORTED. Work around this by using a known color-renderable // format. - bool rgba4IsNotSupportedForColorRendering; + bool rgba4IsNotSupportedForColorRendering = false; // When clearing a framebuffer on Intel or AMD drivers, when GL_FRAMEBUFFER_SRGB is enabled, the // driver clears to the linearized clear color despite the framebuffer not supporting SRGB // blending. It only seems to do this when the framebuffer has only linear attachments, mixed // attachments appear to get the correct clear color. - bool doesSRGBClearsOnLinearFramebufferAttachments; + bool doesSRGBClearsOnLinearFramebufferAttachments = false; // On Mac some GLSL constructs involving do-while loops cause GPU hangs, such as the following: // int i = 1; @@ -60,34 +41,34 @@ struct WorkaroundsGL // continue; // } while (i > 0) // Work around this by rewriting the do-while to use another GLSL construct (block + while) - bool doWhileGLSLCausesGPUHang; + bool doWhileGLSLCausesGPUHang = false; // Calling glFinish doesn't cause all queries to report that the result is available on some // (NVIDIA) drivers. It was found that enabling GL_DEBUG_OUTPUT_SYNCHRONOUS before the finish // causes it to fully finish. - bool finishDoesNotCauseQueriesToBeAvailable; + bool finishDoesNotCauseQueriesToBeAvailable = false; // Always call useProgram after a successful link to avoid a driver bug. // This workaround is meant to reproduce the use_current_program_after_successful_link // workaround in Chromium (http://crbug.com/110263). It has been shown that this workaround is // not necessary for MacOSX 10.9 and higher (http://crrev.com/39eb535b). - bool alwaysCallUseProgramAfterLink; + bool alwaysCallUseProgramAfterLink = false; // In the case of unpacking from a pixel unpack buffer, unpack overlapping rows row by row. - bool unpackOverlappingRowsSeparatelyUnpackBuffer; + bool unpackOverlappingRowsSeparatelyUnpackBuffer = false; // In the case of packing to a pixel pack buffer, pack overlapping rows row by row. - bool packOverlappingRowsSeparatelyPackBuffer; + bool packOverlappingRowsSeparatelyPackBuffer = false; // During initialization, assign the current vertex attributes to the spec-mandated defaults. - bool initializeCurrentVertexAttributes; + bool initializeCurrentVertexAttributes = false; // abs(i) where i is an integer returns unexpected result on Intel Mac. // Emulate abs(i) with i * sign(i). - bool emulateAbsIntFunction; + bool emulateAbsIntFunction = false; // On Intel Mac, calculation of loop conditions in for and while loop has bug. // Add "&& true" to the end of the condition expression to work around the bug. - bool addAndTrueToLoopCondition; + bool addAndTrueToLoopCondition = false; // When uploading textures from an unpack buffer, some drivers count an extra row padding when // checking if the pixel unpack buffer is big enough. Tracking bug: http://anglebug.com/1512 @@ -101,37 +82,43 @@ struct WorkaroundsGL // +-------A--B // The last pixel read will be A, but the driver will think it is B, causing it to generate an // error when the pixel buffer is just big enough. - bool unpackLastRowSeparatelyForPaddingInclusion; + bool unpackLastRowSeparatelyForPaddingInclusion = false; // Equivalent workaround when uploading data from a pixel pack buffer. - bool packLastRowSeparatelyForPaddingInclusion; + bool packLastRowSeparatelyForPaddingInclusion = false; // On some Intel drivers, using isnan() on highp float will get wrong answer. To work around // this bug, we use an expression to emulate function isnan(). // Tracking bug: http://crbug.com/650547 - bool emulateIsnanFloat; + bool emulateIsnanFloat = false; // On Mac with OpenGL version 4.1, unused std140 or shared uniform blocks will be // treated as inactive which is not consistent with WebGL2.0 spec. Reference all members in a // unused std140 or shared uniform block at the beginning of main to work around it. - bool useUnusedBlocksWithStandardOrSharedLayout; + bool useUnusedBlocksWithStandardOrSharedLayout = false; // This flag will keep invariant declaration for input in fragment shader for GLSL >=4.20 // on AMD. - bool dontRemoveInvariantForFragmentInput; + bool dontRemoveInvariantForFragmentInput = false; // This flag is used to fix spec difference between GLSL 4.1 or lower and ESSL3. - bool removeInvariantAndCentroidForESSL3; + bool removeInvariantAndCentroidForESSL3 = false; // On Intel Mac OSX 10.11 driver, using "-float" will get wrong answer. Use "0.0 - float" to // replace "-float". // Tracking bug: http://crbug.com/308366 - bool rewriteFloatUnaryMinusOperator; + bool rewriteFloatUnaryMinusOperator = false; // On NVIDIA drivers, atan(y, x) may return a wrong answer. // Tracking bug: http://crbug.com/672380 - bool emulateAtan2Float; + bool emulateAtan2Float = false; + + // Some drivers seem to forget about UBO bindings when loading program binaries. Work around + // this by re-applying the bindings after the program binary is loaded. + // This only seems to affect AMD OpenGL drivers. + // http://anglebug.com/1660 + bool reapplyUBOBindingsAfterLoadingBinaryProgram = false; }; -} +} // namespace rx #endif // LIBANGLE_RENDERER_GL_WORKAROUNDSGL_H_ diff --git a/src/libANGLE/renderer/gl/renderergl_utils.cpp b/src/libANGLE/renderer/gl/renderergl_utils.cpp index aebae4515..af88f35cf 100644 --- a/src/libANGLE/renderer/gl/renderergl_utils.cpp +++ b/src/libANGLE/renderer/gl/renderergl_utils.cpp @@ -957,9 +957,11 @@ void GenerateWorkarounds(const FunctionsGL *functions, WorkaroundsGL *workaround // TODO(oetuaho): Make this specific to the affected driver versions. Versions that came after // 364 are known to be affected, at least up to 375. workarounds->emulateAtan2Float = IsNvidia(vendor); + + workarounds->reapplyUBOBindingsAfterLoadingBinaryProgram = IsAMD(vendor); } -} +} // namespace nativegl_gl namespace nativegl { diff --git a/src/libANGLE/renderer/null/ProgramNULL.cpp b/src/libANGLE/renderer/null/ProgramNULL.cpp index 5cfd6ddde..3c9af0f79 100644 --- a/src/libANGLE/renderer/null/ProgramNULL.cpp +++ b/src/libANGLE/renderer/null/ProgramNULL.cpp @@ -22,7 +22,9 @@ ProgramNULL::~ProgramNULL() { } -LinkResult ProgramNULL::load(gl::InfoLog &infoLog, gl::BinaryInputStream *stream) +LinkResult ProgramNULL::load(const ContextImpl *contextImpl, + gl::InfoLog &infoLog, + gl::BinaryInputStream *stream) { return true; } diff --git a/src/libANGLE/renderer/null/ProgramNULL.h b/src/libANGLE/renderer/null/ProgramNULL.h index 576a52c18..5681d0afe 100644 --- a/src/libANGLE/renderer/null/ProgramNULL.h +++ b/src/libANGLE/renderer/null/ProgramNULL.h @@ -21,7 +21,9 @@ class ProgramNULL : public ProgramImpl ProgramNULL(const gl::ProgramState &state); ~ProgramNULL() override; - LinkResult load(gl::InfoLog &infoLog, gl::BinaryInputStream *stream) override; + LinkResult load(const ContextImpl *contextImpl, + gl::InfoLog &infoLog, + gl::BinaryInputStream *stream) override; gl::Error save(gl::BinaryOutputStream *stream) override; void setBinaryRetrievableHint(bool retrievable) override; diff --git a/src/libANGLE/renderer/vulkan/ProgramVk.cpp b/src/libANGLE/renderer/vulkan/ProgramVk.cpp index a83f826e7..ef6d6dd63 100644 --- a/src/libANGLE/renderer/vulkan/ProgramVk.cpp +++ b/src/libANGLE/renderer/vulkan/ProgramVk.cpp @@ -22,7 +22,9 @@ ProgramVk::~ProgramVk() { } -LinkResult ProgramVk::load(gl::InfoLog &infoLog, gl::BinaryInputStream *stream) +LinkResult ProgramVk::load(const ContextImpl *contextImpl, + gl::InfoLog &infoLog, + gl::BinaryInputStream *stream) { UNIMPLEMENTED(); return gl::Error(GL_INVALID_OPERATION); diff --git a/src/libANGLE/renderer/vulkan/ProgramVk.h b/src/libANGLE/renderer/vulkan/ProgramVk.h index 8d8a1f7ca..5899cf3d3 100644 --- a/src/libANGLE/renderer/vulkan/ProgramVk.h +++ b/src/libANGLE/renderer/vulkan/ProgramVk.h @@ -21,7 +21,9 @@ class ProgramVk : public ProgramImpl ProgramVk(const gl::ProgramState &state); ~ProgramVk() override; - LinkResult load(gl::InfoLog &infoLog, gl::BinaryInputStream *stream) override; + LinkResult load(const ContextImpl *contextImpl, + gl::InfoLog &infoLog, + gl::BinaryInputStream *stream) override; gl::Error save(gl::BinaryOutputStream *stream) override; void setBinaryRetrievableHint(bool retrievable) override; diff --git a/src/libANGLE/validationES.cpp b/src/libANGLE/validationES.cpp index 1095017cd..d2d3a2bae 100644 --- a/src/libANGLE/validationES.cpp +++ b/src/libANGLE/validationES.cpp @@ -4003,6 +4003,12 @@ bool ValidateGetProgramBinaryBase(Context *context, return false; } + if (context->getCaps().programBinaryFormats.empty()) + { + context->handleError(Error(GL_INVALID_OPERATION, "No program binary formats supported.")); + return false; + } + return true; } diff --git a/src/tests/gl_tests/ProgramBinaryTest.cpp b/src/tests/gl_tests/ProgramBinaryTest.cpp index 7fbb84dde..304dcca36 100644 --- a/src/tests/gl_tests/ProgramBinaryTest.cpp +++ b/src/tests/gl_tests/ProgramBinaryTest.cpp @@ -12,6 +12,7 @@ #include "EGLWindow.h" #include "OSWindow.h" #include "test_utils/angle_test_configs.h" +#include "test_utils/gl_raii.h" using namespace angle; @@ -222,6 +223,93 @@ ANGLE_INSTANTIATE_TEST(ProgramBinaryTest, ES2_OPENGL(), ES3_OPENGL()); +class ProgramBinaryES3Test : public ANGLETest +{ +}; + +// Tests that saving and loading a program perserves uniform block binding info. +TEST_P(ProgramBinaryES3Test, UniformBlockBinding) +{ + // We can't run the test if no program binary formats are supported. + GLint binaryFormatCount = 0; + glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &binaryFormatCount); + if (binaryFormatCount == 0) + { + std::cout << "Test skipped because no program binary formats available." << std::endl; + return; + } + + const std::string &vertexShader = + "#version 300 es\n" + "uniform block {\n" + " float f;\n" + "};\n" + "in vec4 position;\n" + "out vec4 color;\n" + "void main() {\n" + " gl_Position = position;\n" + " color = vec4(f, f, f, 1);\n" + "}"; + const std::string &fragmentShader = + "#version 300 es\n" + "precision mediump float;\n" + "in vec4 color;\n" + "out vec4 colorOut;\n" + "void main() {\n" + " colorOut = color;\n" + "}"; + + // Init and draw with the program. + ANGLE_GL_PROGRAM(program, vertexShader, fragmentShader); + + float fData[4] = {1.0f, 1.0f, 1.0f, 1.0f}; + GLuint bindIndex = 2; + + GLBuffer ubo; + glBindBuffer(GL_UNIFORM_BUFFER, ubo.get()); + glBufferData(GL_UNIFORM_BUFFER, sizeof(fData), &fData, GL_STATIC_DRAW); + glBindBufferRange(GL_UNIFORM_BUFFER, bindIndex, ubo.get(), 0, sizeof(fData)); + + GLint blockIndex = glGetUniformBlockIndex(program.get(), "block"); + ASSERT_NE(-1, blockIndex); + + glUniformBlockBinding(program.get(), blockIndex, bindIndex); + + glClearColor(1.0, 0.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red); + + drawQuad(program.get(), "position", 0.5f); + ASSERT_GL_NO_ERROR(); + EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white); + + // Read back the binary. + GLint programLength = 0; + glGetProgramiv(program.get(), GL_PROGRAM_BINARY_LENGTH_OES, &programLength); + ASSERT_GL_NO_ERROR(); + + GLsizei readLength = 0; + GLenum binaryFormat = GL_NONE; + std::vector binary(programLength); + glGetProgramBinary(program.get(), programLength, &readLength, &binaryFormat, binary.data()); + ASSERT_GL_NO_ERROR(); + + EXPECT_EQ(static_cast(programLength), readLength); + + // Load a new program with the binary and draw. + ANGLE_GL_BINARY_ES3_PROGRAM(binaryProgram, binary, binaryFormat); + + glClearColor(1.0, 0.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red); + + drawQuad(binaryProgram.get(), "position", 0.5f); + ASSERT_GL_NO_ERROR(); + EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white); +} + +ANGLE_INSTANTIATE_TEST(ProgramBinaryES3Test, ES3_D3D11(), ES3_OPENGL(), ES3_OPENGLES()); + class ProgramBinaryTransformFeedbackTest : public ANGLETest { protected: diff --git a/src/tests/test_utils/gl_raii.h b/src/tests/test_utils/gl_raii.h index b42a56396..abb1b059c 100644 --- a/src/tests/test_utils/gl_raii.h +++ b/src/tests/test_utils/gl_raii.h @@ -48,47 +48,65 @@ using GLFramebuffer = GLWrapper; using GLRenderbuffer = GLWrapper; using GLSampler = GLWrapper; +// Don't use GLProgram directly, use ANGLE_GL_PROGRAM. +namespace priv +{ class GLProgram { public: - GLProgram(const std::string &vertexShader, const std::string &fragmentShader) - : mHandle(0), mVertexShader(vertexShader), mFragmentShader(fragmentShader) - { - } - - GLProgram(const std::string &computeShader) : mHandle(0), mComputeShader(computeShader) {} + GLProgram() : mHandle(0) {} ~GLProgram() { glDeleteProgram(mHandle); } + void makeCompute(const std::string &computeShader) + { + mHandle = CompileComputeProgram(computeShader); + } + + void makeRaster(const std::string &vertexShader, const std::string &fragmentShader) + { + mHandle = CompileProgram(vertexShader, fragmentShader); + } + + void makeBinaryOES(const std::vector &binary, GLenum binaryFormat) + { + mHandle = LoadBinaryProgramOES(binary, binaryFormat); + } + + void makeBinaryES3(const std::vector &binary, GLenum binaryFormat) + { + mHandle = LoadBinaryProgramES3(binary, binaryFormat); + } + GLuint get() { - if (mHandle == 0) - { - if (!mComputeShader.empty()) - { - mHandle = CompileComputeProgram(mComputeShader); - } - else - { - mHandle = CompileProgram(mVertexShader, mFragmentShader); - } - } + ASSERT(mHandle != 0); return mHandle; } private: GLuint mHandle; - const std::string mVertexShader; - const std::string mFragmentShader; - const std::string mComputeShader; }; +} // namespace priv #define ANGLE_GL_PROGRAM(name, vertex, fragment) \ - GLProgram name(vertex, fragment); \ + priv::GLProgram name; \ + name.makeRaster(vertex, fragment); \ ASSERT_NE(0u, name.get()); #define ANGLE_GL_COMPUTE_PROGRAM(name, compute) \ - GLProgram name(compute); \ + priv::GLProgram name; \ + name.makeCompute(compute); \ + ASSERT_NE(0u, name.get()); + +#define ANGLE_GL_BINARY_OES_PROGRAM(name, binary, binaryFormat) \ + priv::GLProgram name; \ + name.makeBinaryOES(binary, binaryFormat); \ + ASSERT_NE(0u, name.get()); + +#define ANGLE_GL_BINARY_ES3_PROGRAM(name, binary, binaryFormat) \ + priv::GLProgram name; \ + name.makeBinaryES3(binary, binaryFormat); \ ASSERT_NE(0u, name.get()); } // namespace angle diff --git a/util/shader_utils.cpp b/util/shader_utils.cpp index f92d71570..4970f62c5 100644 --- a/util/shader_utils.cpp +++ b/util/shader_utils.cpp @@ -79,6 +79,9 @@ GLuint CompileShaderFromFile(GLenum type, const std::string &sourcePath) GLuint CheckLinkStatusAndReturnProgram(GLuint program, bool outputErrorMessages) { + if (glGetError() != GL_NO_ERROR) + return 0; + GLint linkStatus; glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); if (linkStatus == 0) @@ -189,3 +192,17 @@ GLuint CompileComputeProgram(const std::string &csSource, bool outputErrorMessag return CheckLinkStatusAndReturnProgram(program, outputErrorMessages); } + +GLuint LoadBinaryProgramOES(const std::vector &binary, GLenum binaryFormat) +{ + GLuint program = glCreateProgram(); + glProgramBinaryOES(program, binaryFormat, binary.data(), static_cast(binary.size())); + return CheckLinkStatusAndReturnProgram(program, true); +} + +GLuint LoadBinaryProgramES3(const std::vector &binary, GLenum binaryFormat) +{ + GLuint program = glCreateProgram(); + glProgramBinary(program, binaryFormat, binary.data(), static_cast(binary.size())); + return CheckLinkStatusAndReturnProgram(program, true); +} diff --git a/util/shader_utils.h b/util/shader_utils.h index b0ed01b96..706b6d7b7 100644 --- a/util/shader_utils.h +++ b/util/shader_utils.h @@ -33,4 +33,7 @@ ANGLE_EXPORT GLuint CompileProgramFromFiles(const std::string &vsPath, const std ANGLE_EXPORT GLuint CompileComputeProgram(const std::string &csSource, bool outputErrorMessages = true); +ANGLE_EXPORT GLuint LoadBinaryProgramOES(const std::vector &binary, GLenum binaryFormat); +ANGLE_EXPORT GLuint LoadBinaryProgramES3(const std::vector &binary, GLenum binaryFormat); + #endif // SAMPLE_UTIL_SHADER_UTILS_H