| // |
| // Copyright 2015 The ANGLE Project Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| // |
| |
| #include "test_utils/ANGLETest.h" |
| |
| #include <stdint.h> |
| #include <memory> |
| |
| #include "common/string_utils.h" |
| #include "test_utils/angle_test_configs.h" |
| #include "test_utils/gl_raii.h" |
| #include "util/EGLWindow.h" |
| #include "util/OSWindow.h" |
| |
| using namespace angle; |
| |
| class ProgramBinaryTest : public ANGLETest |
| { |
| protected: |
| ProgramBinaryTest() |
| { |
| setWindowWidth(128); |
| setWindowHeight(128); |
| setConfigRedBits(8); |
| setConfigGreenBits(8); |
| setConfigBlueBits(8); |
| setConfigAlphaBits(8); |
| |
| // Test flakiness was noticed when reusing displays. |
| forceNewDisplay(); |
| } |
| |
| void testSetUp() override |
| { |
| mProgram = CompileProgram(essl1_shaders::vs::Simple(), essl1_shaders::fs::Red()); |
| if (mProgram == 0) |
| { |
| FAIL() << "shader compilation failed."; |
| } |
| |
| glGenBuffers(1, &mBuffer); |
| glBindBuffer(GL_ARRAY_BUFFER, mBuffer); |
| glBufferData(GL_ARRAY_BUFFER, 128, nullptr, GL_STATIC_DRAW); |
| glBindBuffer(GL_ARRAY_BUFFER, 0); |
| |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| void testTearDown() override |
| { |
| glDeleteProgram(mProgram); |
| glDeleteBuffers(1, &mBuffer); |
| } |
| |
| GLint getAvailableProgramBinaryFormatCount() const |
| { |
| GLint formatCount; |
| glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS_OES, &formatCount); |
| return formatCount; |
| } |
| |
| bool supported() const |
| { |
| if (!IsGLExtensionEnabled("GL_OES_get_program_binary")) |
| { |
| std::cout << "Test skipped because GL_OES_get_program_binary is not available." |
| << std::endl; |
| return false; |
| } |
| |
| if (getAvailableProgramBinaryFormatCount() == 0) |
| { |
| std::cout << "Test skipped because no program binary formats are available." |
| << std::endl; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void saveAndLoadProgram(GLuint programToSave, GLuint loadedProgram) |
| { |
| GLint programLength = 0; |
| GLint writtenLength = 0; |
| GLenum binaryFormat = 0; |
| |
| glGetProgramiv(programToSave, GL_PROGRAM_BINARY_LENGTH_OES, &programLength); |
| EXPECT_GL_NO_ERROR(); |
| |
| std::vector<uint8_t> binary(programLength); |
| glGetProgramBinaryOES(programToSave, programLength, &writtenLength, &binaryFormat, |
| binary.data()); |
| EXPECT_GL_NO_ERROR(); |
| |
| // The lengths reported by glGetProgramiv and glGetProgramBinaryOES should match |
| EXPECT_EQ(programLength, writtenLength); |
| |
| if (writtenLength) |
| { |
| glProgramBinaryOES(loadedProgram, binaryFormat, binary.data(), writtenLength); |
| |
| EXPECT_GL_NO_ERROR(); |
| |
| GLint linkStatus; |
| glGetProgramiv(loadedProgram, GL_LINK_STATUS, &linkStatus); |
| if (linkStatus == 0) |
| { |
| GLint infoLogLength; |
| glGetProgramiv(loadedProgram, GL_INFO_LOG_LENGTH, &infoLogLength); |
| |
| if (infoLogLength > 0) |
| { |
| std::vector<GLchar> infoLog(infoLogLength); |
| glGetProgramInfoLog(loadedProgram, static_cast<GLsizei>(infoLog.size()), |
| nullptr, &infoLog[0]); |
| FAIL() << "program link failed: " << &infoLog[0]; |
| } |
| else |
| { |
| FAIL() << "program link failed."; |
| } |
| } |
| else |
| { |
| glUseProgram(loadedProgram); |
| glBindBuffer(GL_ARRAY_BUFFER, mBuffer); |
| |
| glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 8, nullptr); |
| glEnableVertexAttribArray(0); |
| glDrawArrays(GL_POINTS, 0, 1); |
| |
| EXPECT_GL_NO_ERROR(); |
| } |
| } |
| } |
| |
| GLuint mProgram; |
| GLuint mBuffer; |
| }; |
| |
| // This tests the assumption that float attribs of different size |
| // should not internally cause a vertex shader recompile (for conversion). |
| TEST_P(ProgramBinaryTest, FloatDynamicShaderSize) |
| { |
| if (!supported()) |
| { |
| return; |
| } |
| |
| glUseProgram(mProgram); |
| glBindBuffer(GL_ARRAY_BUFFER, mBuffer); |
| |
| glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 8, nullptr); |
| glEnableVertexAttribArray(0); |
| glDrawArrays(GL_POINTS, 0, 1); |
| |
| GLint programLength; |
| glGetProgramiv(mProgram, GL_PROGRAM_BINARY_LENGTH_OES, &programLength); |
| |
| EXPECT_GL_NO_ERROR(); |
| |
| for (GLsizei size = 1; size <= 3; size++) |
| { |
| glVertexAttribPointer(0, size, GL_FLOAT, GL_FALSE, 8, nullptr); |
| glEnableVertexAttribArray(0); |
| glDrawArrays(GL_POINTS, 0, 1); |
| |
| GLint newProgramLength; |
| glGetProgramiv(mProgram, GL_PROGRAM_BINARY_LENGTH_OES, &newProgramLength); |
| EXPECT_GL_NO_ERROR(); |
| EXPECT_EQ(programLength, newProgramLength); |
| } |
| } |
| |
| // Tests that switching between signed and unsigned un-normalized data doesn't trigger a bug |
| // in the D3D11 back-end. |
| TEST_P(ProgramBinaryTest, DynamicShadersSignatureBug) |
| { |
| glUseProgram(mProgram); |
| glBindBuffer(GL_ARRAY_BUFFER, mBuffer); |
| |
| GLint attribLocation = glGetAttribLocation(mProgram, essl1_shaders::PositionAttrib()); |
| ASSERT_NE(-1, attribLocation); |
| glEnableVertexAttribArray(attribLocation); |
| |
| glVertexAttribPointer(attribLocation, 2, GL_BYTE, GL_FALSE, 0, nullptr); |
| glDrawArrays(GL_POINTS, 0, 1); |
| |
| glVertexAttribPointer(attribLocation, 2, GL_UNSIGNED_BYTE, GL_FALSE, 0, nullptr); |
| glDrawArrays(GL_POINTS, 0, 1); |
| } |
| |
| // This tests the ability to successfully save and load a program binary. |
| TEST_P(ProgramBinaryTest, SaveAndLoadBinary) |
| { |
| if (!supported()) |
| { |
| return; |
| } |
| |
| GLuint programToLoad = glCreateProgram(); |
| |
| saveAndLoadProgram(mProgram, programToLoad); |
| |
| glDeleteProgram(programToLoad); |
| EXPECT_GL_NO_ERROR(); |
| } |
| |
| // This tests the ability to successfully save and load a program binary and then |
| // save and load from the same program that was loaded. |
| TEST_P(ProgramBinaryTest, SaveAndLoadBinaryTwice) |
| { |
| if (!supported()) |
| { |
| return; |
| } |
| |
| GLuint programToLoad = glCreateProgram(); |
| GLuint programToLoad2 = glCreateProgram(); |
| |
| saveAndLoadProgram(mProgram, programToLoad); |
| saveAndLoadProgram(programToLoad, programToLoad2); |
| |
| glDeleteProgram(programToLoad); |
| glDeleteProgram(programToLoad2); |
| EXPECT_GL_NO_ERROR(); |
| } |
| |
| // Ensures that we init the compiler before calling ProgramBinary. |
| TEST_P(ProgramBinaryTest, CallProgramBinaryBeforeLink) |
| { |
| if (!supported()) |
| { |
| return; |
| } |
| |
| // Initialize a simple program. |
| glUseProgram(mProgram); |
| |
| GLsizei length = 0; |
| glGetProgramiv(mProgram, GL_PROGRAM_BINARY_LENGTH, &length); |
| ASSERT_GL_NO_ERROR(); |
| ASSERT_GT(length, 0); |
| |
| GLsizei readLength = 0; |
| GLenum binaryFormat = GL_NONE; |
| std::vector<uint8_t> binaryBlob(length); |
| glGetProgramBinaryOES(mProgram, length, &readLength, &binaryFormat, binaryBlob.data()); |
| ASSERT_GL_NO_ERROR(); |
| |
| // Shutdown and restart GL entirely. |
| recreateTestFixture(); |
| |
| ANGLE_GL_BINARY_OES_PROGRAM(binaryProgram, binaryBlob, binaryFormat); |
| ASSERT_GL_NO_ERROR(); |
| |
| drawQuad(binaryProgram, essl1_shaders::PositionAttrib(), 0.5f); |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| // Test that unlinked programs have a binary size of 0 |
| TEST_P(ProgramBinaryTest, ZeroSizedUnlinkedBinary) |
| { |
| ANGLE_SKIP_TEST_IF(!supported()); |
| |
| ANGLE_GL_EMPTY_PROGRAM(program); |
| GLsizei length = 0; |
| glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH, &length); |
| ASSERT_EQ(0, length); |
| } |
| |
| // Use this to select which configurations (e.g. which renderer, which GLES major version) these |
| // tests should be run against. |
| ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(ProgramBinaryTest); |
| |
| class ProgramBinaryES3Test : public ANGLETest |
| { |
| protected: |
| ProgramBinaryES3Test() |
| { |
| // Test flakiness was noticed when reusing displays. |
| forceNewDisplay(); |
| } |
| |
| void testBinaryAndUBOBlockIndexes(bool drawWithProgramFirst); |
| }; |
| |
| void ProgramBinaryES3Test::testBinaryAndUBOBlockIndexes(bool drawWithProgramFirst) |
| { |
| // We can't run the test if no program binary formats are supported. |
| GLint binaryFormatCount = 0; |
| glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &binaryFormatCount); |
| ANGLE_SKIP_TEST_IF(!binaryFormatCount); |
| |
| constexpr char kVS[] = |
| "#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" |
| "}"; |
| |
| constexpr char kFS[] = |
| "#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, kVS, kFS); |
| |
| 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); |
| |
| if (drawWithProgramFirst) |
| { |
| 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<uint8_t> binary(programLength); |
| glGetProgramBinary(program.get(), programLength, &readLength, &binaryFormat, binary.data()); |
| ASSERT_GL_NO_ERROR(); |
| |
| EXPECT_EQ(static_cast<GLsizei>(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); |
| } |
| |
| // Tests that saving and loading a program perserves uniform block binding info. |
| TEST_P(ProgramBinaryES3Test, UniformBlockBindingWithDraw) |
| { |
| testBinaryAndUBOBlockIndexes(true); |
| } |
| |
| // Same as above, but does not do an initial draw with the program. Covers an ANGLE crash. |
| // http://anglebug.com/1637 |
| TEST_P(ProgramBinaryES3Test, UniformBlockBindingNoDraw) |
| { |
| testBinaryAndUBOBlockIndexes(false); |
| } |
| |
| // Tests the difference between uniform static and active use |
| TEST_P(ProgramBinaryES3Test, ActiveUniformShader) |
| { |
| // We can't run the test if no program binary formats are supported. |
| GLint binaryFormatCount = 0; |
| glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &binaryFormatCount); |
| ANGLE_SKIP_TEST_IF(!binaryFormatCount); |
| |
| constexpr char kVS[] = |
| "#version 300 es\n" |
| "in vec4 position;\n" |
| "void main() {\n" |
| " gl_Position = position;\n" |
| "}"; |
| |
| constexpr char kFS[] = |
| "#version 300 es\n" |
| "precision mediump float;\n" |
| "uniform float values[2];\n" |
| "out vec4 color;\n" |
| "bool isZero(float value) {\n" |
| " return value == 0.0f;\n" |
| "}\n" |
| "void main() {\n" |
| " color = isZero(values[1]) ? vec4(1.0f,0.0f,0.0f,1.0f) : vec4(0.0f,1.0f,0.0f,1.0f);\n" |
| "}"; |
| |
| // Init and draw with the program. |
| ANGLE_GL_PROGRAM(program, kVS, kFS); |
| |
| GLint valuesLoc = glGetUniformLocation(program.get(), "values"); |
| ASSERT_NE(-1, valuesLoc); |
| |
| glUseProgram(program.get()); |
| GLfloat values[2] = {0.5f, 1.0f}; |
| glUniform1fv(valuesLoc, 2, values); |
| ASSERT_GL_NO_ERROR(); |
| |
| 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::green); |
| |
| // 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<uint8_t> binary(programLength); |
| glGetProgramBinary(program.get(), programLength, &readLength, &binaryFormat, binary.data()); |
| ASSERT_GL_NO_ERROR(); |
| |
| EXPECT_EQ(static_cast<GLsizei>(programLength), readLength); |
| |
| // Load a new program with the binary and draw. |
| ANGLE_GL_BINARY_ES3_PROGRAM(binaryProgram, binary, binaryFormat); |
| |
| valuesLoc = glGetUniformLocation(program.get(), "values"); |
| ASSERT_NE(-1, valuesLoc); |
| |
| glUseProgram(binaryProgram.get()); |
| GLfloat values2[2] = {0.1f, 1.0f}; |
| glUniform1fv(valuesLoc, 2, values2); |
| ASSERT_GL_NO_ERROR(); |
| |
| 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::green); |
| } |
| |
| // Test that uses many uniforms in the shaders |
| TEST_P(ProgramBinaryES3Test, BinaryWithLargeUniformCount) |
| { |
| // Suspecting AMD driver bug - failure seen on bots running on ATI GPU on Windows. |
| // http://anglebug.com/3721 |
| ANGLE_SKIP_TEST_IF(IsAMD() && IsOpenGL() && IsWindows()); |
| |
| // We can't run the test if no program binary formats are supported. |
| GLint binaryFormatCount = 0; |
| glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &binaryFormatCount); |
| ANGLE_SKIP_TEST_IF(!binaryFormatCount); |
| |
| constexpr char kVS[] = |
| "#version 300 es\n" |
| "uniform float redVS; \n" |
| "uniform block0 {\n" |
| " float val0;\n" |
| "};\n" |
| "uniform float greenVS; \n" |
| "uniform float blueVS; \n" |
| "in vec4 position;\n" |
| "out vec4 color;\n" |
| "void main() {\n" |
| " gl_Position = position;\n" |
| " color = vec4(redVS + val0, greenVS, blueVS, 1.0f);\n" |
| "}"; |
| |
| constexpr char kFS[] = |
| "#version 300 es\n" |
| "precision mediump float;\n" |
| "uniform float redFS; \n" |
| "uniform float greenFS; \n" |
| "uniform block1 {\n" |
| " float val1;\n" |
| " float val2;\n" |
| "};\n" |
| "uniform float blueFS; \n" |
| "in vec4 color;\n" |
| "out vec4 colorOut;\n" |
| "void main() {\n" |
| " colorOut = vec4(color.r + redFS,\n" |
| " color.g + greenFS + val1,\n" |
| " color.b + blueFS + val2, \n" |
| " color.a);\n" |
| "}"; |
| |
| // Init and draw with the program. |
| ANGLE_GL_PROGRAM(program, kVS, kFS); |
| |
| float block0Data[4] = {-0.7f, 1.0f, 1.0f, 1.0f}; |
| float block1Data[4] = {0.4f, -0.8f, 1.0f, 1.0f}; |
| GLuint bindIndex0 = 5; |
| GLuint bindIndex1 = 2; |
| |
| GLBuffer ubo0; |
| glBindBuffer(GL_UNIFORM_BUFFER, ubo0.get()); |
| glBufferData(GL_UNIFORM_BUFFER, sizeof(block0Data), &block0Data, GL_STATIC_DRAW); |
| glBindBufferRange(GL_UNIFORM_BUFFER, bindIndex0, ubo0.get(), 0, sizeof(block0Data)); |
| ASSERT_GL_NO_ERROR(); |
| |
| GLBuffer ubo1; |
| glBindBuffer(GL_UNIFORM_BUFFER, ubo1.get()); |
| glBufferData(GL_UNIFORM_BUFFER, sizeof(block1Data), &block1Data, GL_STATIC_DRAW); |
| glBindBufferRange(GL_UNIFORM_BUFFER, bindIndex1, ubo1.get(), 0, sizeof(block1Data)); |
| ASSERT_GL_NO_ERROR(); |
| |
| GLint block0Index = glGetUniformBlockIndex(program.get(), "block0"); |
| ASSERT_NE(-1, block0Index); |
| |
| GLint block1Index = glGetUniformBlockIndex(program.get(), "block1"); |
| ASSERT_NE(-1, block1Index); |
| |
| glUniformBlockBinding(program.get(), block0Index, bindIndex0); |
| glUniformBlockBinding(program.get(), block1Index, bindIndex1); |
| ASSERT_GL_NO_ERROR(); |
| |
| GLint redVSLoc = glGetUniformLocation(program.get(), "redVS"); |
| ASSERT_NE(-1, redVSLoc); |
| GLint greenVSLoc = glGetUniformLocation(program.get(), "greenVS"); |
| ASSERT_NE(-1, greenVSLoc); |
| GLint blueVSLoc = glGetUniformLocation(program.get(), "blueVS"); |
| ASSERT_NE(-1, blueVSLoc); |
| GLint redFSLoc = glGetUniformLocation(program.get(), "redFS"); |
| ASSERT_NE(-1, redFSLoc); |
| GLint greenFSLoc = glGetUniformLocation(program.get(), "greenFS"); |
| ASSERT_NE(-1, greenFSLoc); |
| GLint blueFSLoc = glGetUniformLocation(program.get(), "blueFS"); |
| ASSERT_NE(-1, blueFSLoc); |
| |
| glUseProgram(program.get()); |
| glUniform1f(redVSLoc, 0.6f); |
| glUniform1f(greenVSLoc, 0.2f); |
| glUniform1f(blueVSLoc, 1.1f); |
| glUniform1f(redFSLoc, 0.1f); |
| glUniform1f(greenFSLoc, 0.4f); |
| glUniform1f(blueFSLoc, 0.7f); |
| ASSERT_GL_NO_ERROR(); |
| |
| 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::cyan); |
| |
| // 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<uint8_t> binary(programLength); |
| glGetProgramBinary(program.get(), programLength, &readLength, &binaryFormat, binary.data()); |
| ASSERT_GL_NO_ERROR(); |
| |
| EXPECT_EQ(static_cast<GLsizei>(programLength), readLength); |
| |
| // Load a new program with the binary and draw. |
| ANGLE_GL_BINARY_ES3_PROGRAM(binaryProgram, binary, binaryFormat); |
| |
| redVSLoc = glGetUniformLocation(program.get(), "redVS"); |
| ASSERT_NE(-1, redVSLoc); |
| greenVSLoc = glGetUniformLocation(program.get(), "greenVS"); |
| ASSERT_NE(-1, greenVSLoc); |
| blueVSLoc = glGetUniformLocation(program.get(), "blueVS"); |
| ASSERT_NE(-1, blueVSLoc); |
| redFSLoc = glGetUniformLocation(program.get(), "redFS"); |
| ASSERT_NE(-1, redFSLoc); |
| greenFSLoc = glGetUniformLocation(program.get(), "greenFS"); |
| ASSERT_NE(-1, greenFSLoc); |
| blueFSLoc = glGetUniformLocation(program.get(), "blueFS"); |
| ASSERT_NE(-1, blueFSLoc); |
| |
| glUseProgram(binaryProgram.get()); |
| glUniform1f(redVSLoc, 0.2f); |
| glUniform1f(greenVSLoc, -0.6f); |
| glUniform1f(blueVSLoc, 1.0f); |
| glUniform1f(redFSLoc, 1.5f); |
| glUniform1f(greenFSLoc, 0.2f); |
| glUniform1f(blueFSLoc, 0.8f); |
| ASSERT_GL_NO_ERROR(); |
| |
| 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::magenta); |
| } |
| |
| ANGLE_INSTANTIATE_TEST_ES3(ProgramBinaryES3Test); |
| |
| class ProgramBinaryES31Test : public ANGLETest |
| { |
| protected: |
| ProgramBinaryES31Test() |
| { |
| setWindowWidth(128); |
| setWindowHeight(128); |
| setConfigRedBits(8); |
| setConfigGreenBits(8); |
| setConfigBlueBits(8); |
| setConfigAlphaBits(8); |
| |
| // Test flakiness was noticed when reusing displays. |
| forceNewDisplay(); |
| } |
| }; |
| |
| // Tests that saving and loading a program attached with computer shader. |
| TEST_P(ProgramBinaryES31Test, ProgramBinaryWithComputeShader) |
| { |
| // We can't run the test if no program binary formats are supported. |
| GLint binaryFormatCount = 0; |
| glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &binaryFormatCount); |
| ANGLE_SKIP_TEST_IF(!binaryFormatCount); |
| // http://anglebug.com/4092 |
| ANGLE_SKIP_TEST_IF(IsVulkan()); |
| |
| constexpr char kCS[] = |
| "#version 310 es\n" |
| "layout(local_size_x=4, local_size_y=3, local_size_z=1) in;\n" |
| "uniform block {\n" |
| " vec2 f;\n" |
| "};\n" |
| "uniform vec2 g;\n" |
| "uniform highp sampler2D tex;\n" |
| "void main() {\n" |
| " vec4 color = texture(tex, f + g);\n" |
| "}"; |
| |
| ANGLE_GL_COMPUTE_PROGRAM(program, kCS); |
| |
| // Read back the binary. |
| GLint programLength = 0; |
| glGetProgramiv(program.get(), GL_PROGRAM_BINARY_LENGTH, &programLength); |
| ASSERT_GL_NO_ERROR(); |
| |
| GLsizei readLength = 0; |
| GLenum binaryFormat = GL_NONE; |
| std::vector<uint8_t> binary(programLength); |
| glGetProgramBinary(program.get(), programLength, &readLength, &binaryFormat, binary.data()); |
| ASSERT_GL_NO_ERROR(); |
| |
| EXPECT_EQ(static_cast<GLsizei>(programLength), readLength); |
| |
| // Load a new program with the binary. |
| ANGLE_GL_BINARY_ES3_PROGRAM(binaryProgram, binary, binaryFormat); |
| ASSERT_GL_NO_ERROR(); |
| |
| // Dispatch compute with the loaded binary program |
| glUseProgram(binaryProgram.get()); |
| glDispatchCompute(8, 4, 2); |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| // Tests that saving and loading a program attached with computer shader. |
| TEST_P(ProgramBinaryES31Test, ProgramBinaryWithAtomicCounterComputeShader) |
| { |
| // http://anglebug.com/4092 |
| ANGLE_SKIP_TEST_IF(IsAndroid() && IsVulkan()); |
| // We can't run the test if no program binary formats are supported. |
| GLint binaryFormatCount = 0; |
| glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &binaryFormatCount); |
| ANGLE_SKIP_TEST_IF(binaryFormatCount == 0); |
| |
| constexpr char kComputeShader[] = R"(#version 310 es |
| layout(local_size_x=1, local_size_y=1, local_size_z=1) in; |
| layout(binding = 0, offset = 4) uniform atomic_uint ac[2]; |
| void main() { |
| atomicCounterIncrement(ac[0]); |
| atomicCounterDecrement(ac[1]); |
| })"; |
| |
| ANGLE_GL_COMPUTE_PROGRAM(program, kComputeShader); |
| |
| // Read back the binary. |
| GLint programLength = 0; |
| glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH, &programLength); |
| ASSERT_GL_NO_ERROR(); |
| |
| GLsizei readLength = 0; |
| GLenum binaryFormat = GL_NONE; |
| std::vector<uint8_t> binary(programLength); |
| glGetProgramBinary(program, programLength, &readLength, &binaryFormat, binary.data()); |
| ASSERT_GL_NO_ERROR(); |
| |
| EXPECT_EQ(static_cast<GLsizei>(programLength), readLength); |
| |
| // Load a new program with the binary. |
| ANGLE_GL_BINARY_ES3_PROGRAM(binaryProgram, binary, binaryFormat); |
| ASSERT_GL_NO_ERROR(); |
| |
| // Dispatch compute with the loaded binary program |
| glUseProgram(binaryProgram); |
| |
| // The initial value of 'ac[0]' is 3u, 'ac[1]' is 1u. |
| unsigned int bufferData[3] = {11u, 3u, 1u}; |
| GLBuffer atomicCounterBuffer; |
| glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, atomicCounterBuffer); |
| glBufferData(GL_ATOMIC_COUNTER_BUFFER, sizeof(bufferData), bufferData, GL_STATIC_DRAW); |
| |
| glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, atomicCounterBuffer); |
| |
| glDispatchCompute(1, 1, 1); |
| EXPECT_GL_NO_ERROR(); |
| |
| glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT); |
| |
| glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, atomicCounterBuffer); |
| void *mappedBuffer = |
| glMapBufferRange(GL_ATOMIC_COUNTER_BUFFER, 0, sizeof(GLuint) * 3, GL_MAP_READ_BIT); |
| memcpy(bufferData, mappedBuffer, sizeof(bufferData)); |
| glUnmapBuffer(GL_ATOMIC_COUNTER_BUFFER); |
| |
| EXPECT_EQ(11u, bufferData[0]); |
| EXPECT_EQ(4u, bufferData[1]); |
| EXPECT_EQ(0u, bufferData[2]); |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| // Tests that image texture works correctly when loading a program from binary. |
| TEST_P(ProgramBinaryES31Test, ImageTextureBinding) |
| { |
| // We can't run the test if no program binary formats are supported. |
| GLint binaryFormatCount = 0; |
| glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &binaryFormatCount); |
| ANGLE_SKIP_TEST_IF(!binaryFormatCount); |
| |
| const char kComputeShader[] = |
| R"(#version 310 es |
| layout(local_size_x=1, local_size_y=1, local_size_z=1) in; |
| layout(r32ui, binding = 1) writeonly uniform highp uimage2D writeImage; |
| void main() |
| { |
| imageStore(writeImage, ivec2(gl_LocalInvocationID.xy), uvec4(200u)); |
| })"; |
| |
| ANGLE_GL_COMPUTE_PROGRAM(program, kComputeShader); |
| |
| // Read back the binary. |
| GLint programLength = 0; |
| glGetProgramiv(program.get(), GL_PROGRAM_BINARY_LENGTH, &programLength); |
| ASSERT_GL_NO_ERROR(); |
| |
| GLsizei readLength = 0; |
| GLenum binaryFormat = GL_NONE; |
| std::vector<uint8_t> binary(programLength); |
| glGetProgramBinary(program.get(), programLength, &readLength, &binaryFormat, binary.data()); |
| ASSERT_GL_NO_ERROR(); |
| |
| EXPECT_EQ(static_cast<GLsizei>(programLength), readLength); |
| |
| // Load a new program with the binary. |
| ANGLE_GL_BINARY_ES3_PROGRAM(binaryProgram, binary, binaryFormat); |
| ASSERT_GL_NO_ERROR(); |
| |
| // Dispatch compute with the loaded binary program |
| glUseProgram(binaryProgram.get()); |
| GLTexture texture; |
| glBindTexture(GL_TEXTURE_2D, texture); |
| glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, 1, 1); |
| constexpr GLuint kInputValue = 100u; |
| glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RED_INTEGER, GL_UNSIGNED_INT, &kInputValue); |
| EXPECT_GL_NO_ERROR(); |
| |
| glBindImageTexture(1, texture, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R32UI); |
| |
| glDispatchCompute(1, 1, 1); |
| EXPECT_GL_NO_ERROR(); |
| |
| GLFramebuffer framebuffer; |
| glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer); |
| glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); |
| |
| GLuint outputValue; |
| glReadPixels(0, 0, 1, 1, GL_RED_INTEGER, GL_UNSIGNED_INT, &outputValue); |
| EXPECT_EQ(200u, outputValue); |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| ANGLE_INSTANTIATE_TEST_ES31(ProgramBinaryES31Test); |
| |
| class ProgramBinaryTransformFeedbackTest : public ANGLETest |
| { |
| protected: |
| ProgramBinaryTransformFeedbackTest() |
| { |
| setWindowWidth(128); |
| setWindowHeight(128); |
| setConfigRedBits(8); |
| setConfigGreenBits(8); |
| setConfigBlueBits(8); |
| setConfigAlphaBits(8); |
| } |
| |
| void testSetUp() override |
| { |
| constexpr char kVS[] = R"(#version 300 es |
| in vec4 inputAttribute; |
| out vec4 outputVarying; |
| void main() |
| { |
| outputVarying = inputAttribute; |
| })"; |
| |
| constexpr char kFS[] = R"(#version 300 es |
| precision highp float; |
| out vec4 outputColor; |
| void main() |
| { |
| outputColor = vec4(1,0,0,1); |
| })"; |
| |
| std::vector<std::string> transformFeedbackVaryings; |
| transformFeedbackVaryings.push_back("outputVarying"); |
| |
| mProgram = CompileProgramWithTransformFeedback(kVS, kFS, transformFeedbackVaryings, |
| GL_SEPARATE_ATTRIBS); |
| if (mProgram == 0) |
| { |
| FAIL() << "shader compilation failed."; |
| } |
| |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| void testTearDown() override { glDeleteProgram(mProgram); } |
| |
| GLint getAvailableProgramBinaryFormatCount() const |
| { |
| GLint formatCount; |
| glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS_OES, &formatCount); |
| return formatCount; |
| } |
| |
| GLuint mProgram; |
| }; |
| |
| // This tests the assumption that float attribs of different size |
| // should not internally cause a vertex shader recompile (for conversion). |
| TEST_P(ProgramBinaryTransformFeedbackTest, GetTransformFeedbackVarying) |
| { |
| ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_get_program_binary")); |
| |
| ANGLE_SKIP_TEST_IF(!getAvailableProgramBinaryFormatCount()); |
| |
| // http://anglebug.com/3690 |
| ANGLE_SKIP_TEST_IF(IsAndroid() && (IsPixel2() || IsPixel2XL()) && IsVulkan()); |
| // http://anglebug.com/4092 |
| ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGLES()); |
| |
| std::vector<uint8_t> binary(0); |
| GLint programLength = 0; |
| GLint writtenLength = 0; |
| GLenum binaryFormat = 0; |
| |
| // Save the program binary out |
| glGetProgramiv(mProgram, GL_PROGRAM_BINARY_LENGTH_OES, &programLength); |
| ASSERT_GL_NO_ERROR(); |
| binary.resize(programLength); |
| glGetProgramBinaryOES(mProgram, programLength, &writtenLength, &binaryFormat, binary.data()); |
| ASSERT_GL_NO_ERROR(); |
| |
| glDeleteProgram(mProgram); |
| |
| // Load program binary |
| mProgram = glCreateProgram(); |
| glProgramBinaryOES(mProgram, binaryFormat, binary.data(), writtenLength); |
| |
| // Ensure the loaded binary is linked |
| GLint linkStatus; |
| glGetProgramiv(mProgram, GL_LINK_STATUS, &linkStatus); |
| EXPECT_TRUE(linkStatus != 0); |
| |
| // Query information about the transform feedback varying |
| char varyingName[64]; |
| GLsizei varyingSize = 0; |
| GLenum varyingType = GL_NONE; |
| |
| glGetTransformFeedbackVarying(mProgram, 0, 64, &writtenLength, &varyingSize, &varyingType, |
| varyingName); |
| EXPECT_GL_NO_ERROR(); |
| |
| EXPECT_EQ(13, writtenLength); |
| EXPECT_STREQ("outputVarying", varyingName); |
| EXPECT_EQ(1, varyingSize); |
| EXPECT_GLENUM_EQ(GL_FLOAT_VEC4, varyingType); |
| |
| EXPECT_GL_NO_ERROR(); |
| } |
| |
| // Use this to select which configurations (e.g. which renderer, which GLES major version) these |
| // tests should be run against. |
| ANGLE_INSTANTIATE_TEST_ES3(ProgramBinaryTransformFeedbackTest); |
| |
| // For the ProgramBinariesAcrossPlatforms tests, we need two sets of params: |
| // - a set to save the program binary |
| // - a set to load the program binary |
| // We combine these into one struct extending PlatformParameters so we can reuse existing ANGLE test |
| // macros |
| struct PlatformsWithLinkResult : PlatformParameters |
| { |
| PlatformsWithLinkResult(PlatformParameters saveParams, |
| PlatformParameters loadParamsIn, |
| bool expectedLinkResultIn) |
| { |
| majorVersion = saveParams.majorVersion; |
| minorVersion = saveParams.minorVersion; |
| eglParameters = saveParams.eglParameters; |
| loadParams = loadParamsIn; |
| expectedLinkResult = expectedLinkResultIn; |
| } |
| |
| PlatformParameters loadParams; |
| bool expectedLinkResult; |
| }; |
| |
| // Provide a custom gtest parameter name function for PlatformsWithLinkResult |
| // to avoid returning the same parameter name twice. Such a conflict would happen |
| // between ES2_D3D11_to_ES2D3D11 and ES2_D3D11_to_ES3D3D11 as they were both |
| // named ES2_D3D11 |
| std::ostream &operator<<(std::ostream &stream, const PlatformsWithLinkResult &platform) |
| { |
| const PlatformParameters &platform1 = platform; |
| const PlatformParameters &platform2 = platform.loadParams; |
| stream << platform1 << "_to_" << platform2; |
| return stream; |
| } |
| |
| class ProgramBinariesAcrossPlatforms : public testing::TestWithParam<PlatformsWithLinkResult> |
| { |
| public: |
| void SetUp() override |
| { |
| mOSWindow = OSWindow::New(); |
| bool result = mOSWindow->initialize("ProgramBinariesAcrossRenderersTests", 100, 100); |
| |
| if (result == false) |
| { |
| FAIL() << "Failed to create OS window"; |
| } |
| |
| mEntryPointsLib.reset( |
| angle::OpenSharedLibrary(ANGLE_EGL_LIBRARY_NAME, angle::SearchType::ApplicationDir)); |
| } |
| |
| EGLWindow *createAndInitEGLWindow(angle::PlatformParameters ¶m) |
| { |
| EGLWindow *eglWindow = EGLWindow::New(param.majorVersion, param.minorVersion); |
| ConfigParameters configParams; |
| bool result = eglWindow->initializeGL(mOSWindow, mEntryPointsLib.get(), param.eglParameters, |
| configParams); |
| if (!result) |
| { |
| EGLWindow::Delete(&eglWindow); |
| } |
| |
| angle::LoadGLES(eglGetProcAddress); |
| |
| return eglWindow; |
| } |
| |
| void destroyEGLWindow(EGLWindow **eglWindow) |
| { |
| ASSERT_NE(nullptr, *eglWindow); |
| (*eglWindow)->destroyGL(); |
| EGLWindow::Delete(eglWindow); |
| } |
| |
| GLuint createES2ProgramFromSource() |
| { |
| return CompileProgram(essl1_shaders::vs::Simple(), essl1_shaders::fs::Red()); |
| } |
| |
| GLuint createES3ProgramFromSource() |
| { |
| return CompileProgram(essl3_shaders::vs::Simple(), essl3_shaders::fs::Red()); |
| } |
| |
| void drawWithProgram(GLuint program) |
| { |
| glClearColor(0, 0, 0, 1); |
| glClear(GL_COLOR_BUFFER_BIT); |
| |
| GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib()); |
| |
| glUseProgram(program); |
| |
| const GLfloat vertices[] = { |
| -1.0f, 1.0f, 0.5f, -1.0f, -1.0f, 0.5f, 1.0f, -1.0f, 0.5f, |
| |
| -1.0f, 1.0f, 0.5f, 1.0f, -1.0f, 0.5f, 1.0f, 1.0f, 0.5f, |
| }; |
| |
| glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, vertices); |
| glEnableVertexAttribArray(positionLocation); |
| |
| glDrawArrays(GL_TRIANGLES, 0, 6); |
| |
| glDisableVertexAttribArray(positionLocation); |
| glVertexAttribPointer(positionLocation, 4, GL_FLOAT, GL_FALSE, 0, nullptr); |
| |
| EXPECT_PIXEL_EQ(mOSWindow->getWidth() / 2, mOSWindow->getHeight() / 2, 255, 0, 0, 255); |
| } |
| |
| void TearDown() override |
| { |
| mOSWindow->destroy(); |
| OSWindow::Delete(&mOSWindow); |
| } |
| |
| OSWindow *mOSWindow = nullptr; |
| std::unique_ptr<angle::Library> mEntryPointsLib; |
| }; |
| |
| // Tries to create a program binary using one set of platform params, then load it using a different |
| // sent of params |
| TEST_P(ProgramBinariesAcrossPlatforms, CreateAndReloadBinary) |
| { |
| angle::PlatformParameters firstRenderer = GetParam(); |
| angle::PlatformParameters secondRenderer = GetParam().loadParams; |
| bool expectedLinkResult = GetParam().expectedLinkResult; |
| |
| // First renderer not supported, skipping test. |
| ANGLE_SKIP_TEST_IF(!(IsPlatformAvailable(firstRenderer))); |
| |
| // Second renderer not supported, skipping test. |
| ANGLE_SKIP_TEST_IF(!(IsPlatformAvailable(secondRenderer))); |
| |
| EGLWindow *eglWindow = nullptr; |
| std::vector<uint8_t> binary(0); |
| GLuint program = 0; |
| |
| GLint programLength = 0; |
| GLint writtenLength = 0; |
| GLenum binaryFormat = 0; |
| |
| // Create a EGL window with the first renderer |
| eglWindow = createAndInitEGLWindow(firstRenderer); |
| if (eglWindow == nullptr) |
| { |
| FAIL() << "Failed to create EGL window"; |
| return; |
| } |
| |
| // If the test is trying to use both the default GPU and WARP, but the default GPU *IS* WARP, |
| // then our expectations for the test results will be invalid. |
| if (firstRenderer.eglParameters.deviceType != EGL_PLATFORM_ANGLE_DEVICE_TYPE_D3D_WARP_ANGLE && |
| secondRenderer.eglParameters.deviceType == EGL_PLATFORM_ANGLE_DEVICE_TYPE_D3D_WARP_ANGLE) |
| { |
| std::string rendererString = |
| std::string(reinterpret_cast<const char *>(glGetString(GL_RENDERER))); |
| angle::ToLower(&rendererString); |
| |
| auto basicRenderPos = rendererString.find(std::string("microsoft basic render")); |
| auto softwareAdapterPos = rendererString.find(std::string("software adapter")); |
| |
| // The first renderer is using WARP, even though we didn't explictly request it |
| // We should skip this test |
| ANGLE_SKIP_TEST_IF(basicRenderPos != std::string::npos || |
| softwareAdapterPos != std::string::npos); |
| } |
| |
| // Create a program |
| if (firstRenderer.majorVersion == 3) |
| { |
| program = createES3ProgramFromSource(); |
| } |
| else |
| { |
| program = createES2ProgramFromSource(); |
| } |
| |
| if (program == 0) |
| { |
| destroyEGLWindow(&eglWindow); |
| FAIL() << "Failed to create program from source"; |
| } |
| |
| // Draw using the program to ensure it works as expected |
| drawWithProgram(program); |
| EXPECT_GL_NO_ERROR(); |
| |
| // Save the program binary out from this renderer |
| glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH_OES, &programLength); |
| EXPECT_GL_NO_ERROR(); |
| binary.resize(programLength); |
| glGetProgramBinaryOES(program, programLength, &writtenLength, &binaryFormat, binary.data()); |
| EXPECT_GL_NO_ERROR(); |
| |
| // Destroy the first renderer |
| glDeleteProgram(program); |
| destroyEGLWindow(&eglWindow); |
| |
| // Create an EGL window with the second renderer |
| eglWindow = createAndInitEGLWindow(secondRenderer); |
| if (eglWindow == nullptr) |
| { |
| FAIL() << "Failed to create EGL window"; |
| return; |
| } |
| |
| program = glCreateProgram(); |
| glProgramBinaryOES(program, binaryFormat, binary.data(), writtenLength); |
| |
| GLint linkStatus; |
| glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); |
| EXPECT_EQ(expectedLinkResult, (linkStatus != 0)); |
| |
| if (linkStatus != 0) |
| { |
| // If the link was successful, then we should try to draw using the program to ensure it |
| // works as expected |
| drawWithProgram(program); |
| EXPECT_GL_NO_ERROR(); |
| } |
| |
| // Destroy the second renderer |
| glDeleteProgram(program); |
| destroyEGLWindow(&eglWindow); |
| } |
| |
| // clang-format off |
| ANGLE_INSTANTIATE_TEST(ProgramBinariesAcrossPlatforms, |
| // | Save the program | Load the program | Expected |
| // | using these params | using these params | link result |
| PlatformsWithLinkResult(ES2_D3D11(), ES2_D3D11(), true ), // Loading + reloading binary should work |
| PlatformsWithLinkResult(ES3_D3D11(), ES3_D3D11(), true ), // Loading + reloading binary should work |
| PlatformsWithLinkResult(ES2_D3D11(), ES2_D3D9(), false ), // Switching from D3D11 to D3D9 shouldn't work |
| PlatformsWithLinkResult(ES2_D3D9(), ES2_D3D11(), false ), // Switching from D3D9 to D3D11 shouldn't work |
| PlatformsWithLinkResult(ES2_D3D11(), ES3_D3D11(), false ), // Switching to newer client version shouldn't work |
| ); |
| // clang-format on |