| // Copyright (C) 2023 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include <log/log.h> |
| |
| #include "GfxstreamEnd2EndTests.h" |
| |
| namespace gfxstream { |
| namespace tests { |
| namespace { |
| |
| using testing::Eq; |
| using testing::Gt; |
| using testing::HasSubstr; |
| using testing::IsEmpty; |
| using testing::IsTrue; |
| using testing::Le; |
| using testing::Not; |
| |
| MATCHER_P(IsOkWith, expected, std::string(" equals ") + expected.ToString()) { |
| const auto& actual = arg; |
| |
| if (actual.ok() && actual.value().r == expected.r && actual.value().g == expected.g && |
| actual.value().b == expected.b && actual.value().a == expected.a) { |
| return true; |
| } |
| |
| if (actual.ok()) { |
| *result_listener << "actual: " << actual.value().ToString(); |
| } else { |
| *result_listener << "actual: {" << " error: " << actual.error() << " };"; |
| } |
| return false; |
| } |
| |
| MATCHER_P4(IsOkWithRGBA, r, g, b, a, |
| std::string(" equals ") + PixelR8G8B8A8(r, g, b, a).ToString()) { |
| const auto& actual = arg; |
| |
| if (actual.ok() && actual.value().r == r && actual.value().g == g && actual.value().b == b && |
| actual.value().a == a) { |
| return true; |
| } |
| |
| if (actual.ok()) { |
| *result_listener << "actual: " << actual.value().ToString(); |
| } else { |
| *result_listener << "actual: {" << " error: " << actual.error() << " };"; |
| } |
| return false; |
| } |
| |
| class SimpleLatch { |
| public: |
| SimpleLatch(std::uint32_t count) : mCount(count) {} |
| |
| SimpleLatch(const SimpleLatch&) = delete; |
| SimpleLatch& operator=(const SimpleLatch&) = delete; |
| |
| SimpleLatch(SimpleLatch&&) = delete; |
| SimpleLatch& operator=(SimpleLatch&&) = delete; |
| |
| void count_down() { |
| { |
| std::unique_lock lock(mMutex); |
| --mCount; |
| } |
| mConditionVariable.notify_all(); |
| } |
| |
| void wait() { |
| std::unique_lock lock(mMutex); |
| mConditionVariable.wait(lock, [this] { return mCount == 0; }); |
| } |
| |
| private: |
| std::mutex mMutex; |
| std::condition_variable mConditionVariable; |
| std::uint32_t mCount; |
| }; |
| |
| class GfxstreamEnd2EndGlTest : public GfxstreamEnd2EndTest { |
| protected: |
| Result<PixelR8G8B8A8> GetPixelAt(GLint x, GLint y) { |
| if (!mGl) { |
| return gfxstream::unexpected("GL not available, running with `with_gl = false`?"); |
| } |
| |
| GLubyte rgba[4] = {0, 0, 0, 0}; |
| mGl->glReadPixels(x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, rgba); |
| |
| if (GLenum error = mGl->glGetError(); error != GL_NO_ERROR) { |
| return gfxstream::unexpected("Failed to glReadPixels() with error " + |
| std::to_string(error)); |
| } |
| |
| return PixelR8G8B8A8(x, y, rgba[0], rgba[1], rgba[2], rgba[3]); |
| } |
| |
| void SetUp() override { |
| GfxstreamEnd2EndTest::SetUp(); |
| |
| SetUpEglContextAndSurface(2, mSurfaceWidth, mSurfaceHeight, &mDisplay, &mContext, |
| &mSurface); |
| } |
| |
| void TearDown() override { |
| TearDownEglContextAndSurface(mDisplay, mContext, mSurface); |
| |
| GfxstreamEnd2EndTest::TearDown(); |
| } |
| |
| int mSurfaceWidth = 32; |
| int mSurfaceHeight = 32; |
| EGLDisplay mDisplay; |
| EGLContext mContext; |
| EGLSurface mSurface; |
| }; |
| |
| TEST_P(GfxstreamEnd2EndGlTest, BasicViewport) { |
| GLint viewport[4] = {}; |
| mGl->glGetIntegerv(GL_VIEWPORT, viewport); |
| |
| EXPECT_THAT(viewport[0], Eq(0)); |
| EXPECT_THAT(viewport[1], Eq(0)); |
| EXPECT_THAT(viewport[2], Eq(mSurfaceWidth)); |
| EXPECT_THAT(viewport[3], Eq(mSurfaceHeight)); |
| } |
| |
| TEST_P(GfxstreamEnd2EndGlTest, CreateWindowSurface) { |
| // clang-format off |
| static const EGLint configAttributes[] = { |
| EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, |
| EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, |
| EGL_NONE, |
| }; |
| // clang-format on |
| |
| int numConfigs = 0; |
| ASSERT_THAT(mGl->eglChooseConfig(mDisplay, configAttributes, nullptr, 1, &numConfigs), |
| IsTrue()); |
| ASSERT_THAT(numConfigs, Gt(0)); |
| |
| EGLConfig config = nullptr; |
| ASSERT_THAT(mGl->eglChooseConfig(mDisplay, configAttributes, &config, 1, &numConfigs), |
| IsTrue()); |
| ASSERT_THAT(config, Not(Eq(nullptr))); |
| |
| // clang-format off |
| static const EGLint contextAttribs[] = { |
| EGL_CONTEXT_CLIENT_VERSION, 3, |
| EGL_NONE, |
| }; |
| // clang-format on |
| |
| EGLContext context = mGl->eglCreateContext(mDisplay, config, EGL_NO_CONTEXT, contextAttribs); |
| ASSERT_THAT(context, Not(Eq(EGL_NO_CONTEXT))); |
| |
| constexpr const int width = 32; |
| constexpr const int height = 32; |
| |
| auto anw = mAnwHelper->createNativeWindowForTesting(mGralloc.get(), width, height); |
| |
| EGLSurface surface = mGl->eglCreateWindowSurface(mDisplay, config, anw, nullptr); |
| ASSERT_THAT(surface, Not(Eq(EGL_NO_SURFACE))); |
| |
| ASSERT_THAT(mGl->eglMakeCurrent(mDisplay, surface, surface, context), IsTrue()); |
| |
| constexpr const int iterations = 120; |
| for (int i = 0; i < iterations; i++) { |
| mGl->glViewport(0, 0, width, height); |
| mGl->glClearColor(1.0f, 0.0f, static_cast<float>(i) / static_cast<float>(iterations), 1.0f); |
| mGl->glClear(GL_COLOR_BUFFER_BIT); |
| mGl->glFinish(); |
| mGl->eglSwapBuffers(mDisplay, surface); |
| } |
| |
| ASSERT_THAT(mGl->eglDestroyContext(mDisplay, context), IsTrue()); |
| ASSERT_THAT(mGl->eglDestroySurface(mDisplay, surface), IsTrue()); |
| |
| mAnwHelper->release(anw); |
| } |
| |
| TEST_P(GfxstreamEnd2EndGlTest, SwitchContext) { |
| ASSERT_THAT(mGl->eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT), |
| IsTrue()); |
| for (int i = 0; i < 100; i++) { |
| ASSERT_THAT(mGl->eglMakeCurrent(mDisplay, mSurface, mSurface, mContext), IsTrue()); |
| ASSERT_THAT(mGl->eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT), |
| IsTrue()); |
| } |
| } |
| |
| TEST_P(GfxstreamEnd2EndGlTest, MappedMemory) { |
| constexpr GLsizei kBufferSize = 64; |
| |
| ScopedGlBuffer buffer(*mGl); |
| mGl->glBindBuffer(GL_ARRAY_BUFFER, buffer); |
| mGl->glBufferData(GL_ARRAY_BUFFER, kBufferSize, 0, GL_DYNAMIC_DRAW); |
| |
| std::vector<uint8_t> bufferData(kBufferSize); |
| for (uint8_t i = 0; i < kBufferSize; ++i) { |
| bufferData[i] = i; |
| } |
| |
| { |
| auto* mappedBufferData = reinterpret_cast<uint8_t*>( |
| mGl->glMapBufferRange(GL_ARRAY_BUFFER, 0, kBufferSize, |
| GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT)); |
| |
| for (uint8_t i = 0; i < kBufferSize; ++i) { |
| mappedBufferData[i] = bufferData[i]; |
| } |
| |
| mGl->glFlushMappedBufferRange(GL_ARRAY_BUFFER, 0, kBufferSize); |
| mGl->glUnmapBuffer(GL_ARRAY_BUFFER); |
| } |
| |
| { |
| auto* mappedBufferData = reinterpret_cast<uint8_t*>( |
| mGl->glMapBufferRange(GL_ARRAY_BUFFER, 0, kBufferSize, GL_MAP_READ_BIT)); |
| |
| for (uint8_t i = 0; i < kBufferSize; ++i) { |
| EXPECT_THAT(mappedBufferData[i], Eq(bufferData[i])); |
| } |
| |
| mGl->glUnmapBuffer(GL_ARRAY_BUFFER); |
| } |
| |
| mGl->glBindBuffer(GL_ARRAY_BUFFER, 0); |
| } |
| |
| TEST_P(GfxstreamEnd2EndGlTest, ContextStrings) { |
| EGLDisplay display = mGl->eglGetDisplay(EGL_DEFAULT_DISPLAY); |
| ASSERT_THAT(display, Not(Eq(EGL_NO_DISPLAY))); |
| |
| int versionMajor = 0; |
| int versionMinor = 0; |
| ASSERT_THAT(mGl->eglInitialize(display, &versionMajor, &versionMinor), IsTrue()); |
| |
| ASSERT_THAT(mGl->eglBindAPI(EGL_OPENGL_ES_API), IsTrue()); |
| |
| // clang-format off |
| static const EGLint configAttributes[] = { |
| EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, |
| EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, |
| EGL_NONE, |
| }; |
| // clang-format on |
| |
| int numConfigs = 0; |
| ASSERT_THAT(mGl->eglChooseConfig(display, configAttributes, nullptr, 1, &numConfigs), IsTrue()); |
| ASSERT_THAT(numConfigs, Gt(0)); |
| |
| EGLConfig config = nullptr; |
| ASSERT_THAT(mGl->eglChooseConfig(display, configAttributes, &config, 1, &numConfigs), IsTrue()); |
| ASSERT_THAT(config, Not(Eq(nullptr))); |
| |
| // clang-format off |
| static const EGLint gles1ContextAttribs[] = { |
| EGL_CONTEXT_CLIENT_VERSION, 1, |
| EGL_NONE, |
| }; |
| // clang-format on |
| |
| EGLContext gles1Context = mGl->eglCreateContext(display, config, EGL_NO_CONTEXT, gles1ContextAttribs); |
| ASSERT_THAT(gles1Context, Not(Eq(EGL_NO_CONTEXT))); |
| |
| // clang-format off |
| static const EGLint gles2ContextAttribs[] = { |
| EGL_CONTEXT_CLIENT_VERSION, 2, |
| EGL_NONE, |
| }; |
| // clang-format on |
| |
| EGLContext gles2Context = mGl->eglCreateContext(display, config, EGL_NO_CONTEXT, gles2ContextAttribs); |
| ASSERT_THAT(gles2Context, Not(Eq(EGL_NO_CONTEXT))); |
| |
| constexpr const int width = 32; |
| constexpr const int height = 32; |
| |
| // clang-format off |
| static const EGLint surfaceAttributes[] = { |
| EGL_WIDTH, width, |
| EGL_HEIGHT, height, |
| EGL_NONE, |
| }; |
| // clang-format on |
| |
| EGLSurface surface = mGl->eglCreatePbufferSurface(display, config, surfaceAttributes); |
| ASSERT_THAT(surface, Not(Eq(EGL_NO_SURFACE))); |
| |
| { |
| ASSERT_THAT(mGl->eglMakeCurrent(display, surface, surface, gles2Context), IsTrue()); |
| const auto versionString = (const char*)mGl->glGetString(GL_VERSION); |
| const auto extensionString = (const char*)mGl->glGetString(GL_EXTENSIONS); |
| EXPECT_THAT(versionString, HasSubstr("ES 3")); |
| EXPECT_THAT(extensionString, Not(HasSubstr("OES_draw_texture"))); |
| } |
| { |
| ASSERT_THAT(mGl->eglMakeCurrent(display, surface, surface, gles1Context), IsTrue()); |
| const auto versionString = (const char*)mGl->glGetString(GL_VERSION); |
| const auto extensionString = (const char*)mGl->glGetString(GL_EXTENSIONS); |
| EXPECT_THAT(versionString, HasSubstr("ES-CM")); |
| EXPECT_THAT(extensionString, HasSubstr("OES_draw_texture")); |
| } |
| { |
| ASSERT_THAT(mGl->eglMakeCurrent(display, surface, surface, gles2Context), IsTrue()); |
| const auto versionString = (const char*)mGl->glGetString(GL_VERSION); |
| const auto extensionString = (const char*)mGl->glGetString(GL_EXTENSIONS); |
| EXPECT_THAT(versionString, HasSubstr("ES 3")); |
| EXPECT_THAT(extensionString, Not(HasSubstr("OES_draw_texture"))); |
| } |
| |
| ASSERT_THAT(mGl->eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT), IsTrue()); |
| ASSERT_THAT(mGl->eglDestroyContext(display, gles1Context), IsTrue()); |
| ASSERT_THAT(mGl->eglDestroyContext(display, gles2Context), IsTrue()); |
| ASSERT_THAT(mGl->eglDestroySurface(display, surface), IsTrue()); |
| } |
| |
| TEST_P(GfxstreamEnd2EndGlTest, FramebufferFetchShader) { |
| const std::string extensionsString = (const char*)mGl->glGetString(GL_EXTENSIONS); |
| ASSERT_THAT(extensionsString, Not(IsEmpty())); |
| |
| const bool supportsFramebufferFetch = |
| extensionsString.find("GL_EXT_shader_framebuffer_fetch") != std::string::npos; |
| |
| const std::string shaderSource = R"(\ |
| #version 300 es |
| #extension GL_EXT_shader_framebuffer_fetch : require |
| precision highp float; |
| in vec3 color_varying; |
| out vec4 fragColor; |
| void main() { |
| fragColor = vec4(color_varying, 1.0); |
| } |
| )"; |
| auto result = SetUpShader(GL_FRAGMENT_SHADER, shaderSource); |
| if (result.ok()) { |
| ASSERT_THAT(supportsFramebufferFetch, Eq(GL_TRUE)); |
| } else { |
| ASSERT_THAT(supportsFramebufferFetch, Eq(GL_FALSE)); |
| } |
| } |
| |
| TEST_P(GfxstreamEnd2EndGlTest, ConstantMatrixShader) { |
| const std::string shaderSource = R"(\ |
| #version 300 es |
| precision mediump float; |
| in highp vec4 dEQP_Position; |
| out vec2 out0; |
| |
| void main() { |
| const mat4x2 matA = mat4x2( 2.0, 4.0, 8.0, 16.0, |
| 32.0, 64.0, 128.0, 256.0); |
| const mat4x2 matB = mat4x2(1.0 / 2.0, 1.0 / 4.0, 1.0 / 8.0, 1.0 / 16.0, |
| 1.0 / 32.0, 1.0 / 64.0, 1.0 / 128.0, 1.0 / 256.0); |
| mat4x2 result = matrixCompMult(matA, matB); |
| |
| out0 = result * vec4(1.0, 1.0, 1.0, 1.0); |
| gl_Position = dEQP_Position; |
| } |
| )"; |
| |
| auto result = SetUpShader(GL_VERTEX_SHADER, shaderSource); |
| ASSERT_THAT(result, IsOk()); |
| } |
| |
| TEST_P(GfxstreamEnd2EndGlTest, Draw) { |
| const std::string vertSource = R"(\ |
| #version 300 es |
| precision highp float; |
| |
| layout (location = 0) in vec2 pos; |
| layout (location = 1) in vec3 color; |
| |
| uniform mat4 transform; |
| |
| out vec3 color_varying; |
| |
| void main() { |
| gl_Position = transform * vec4(pos, 0.0, 1.0); |
| color_varying = (transform * vec4(color, 1.0)).xyz; |
| } |
| )"; |
| |
| const std::string fragSource = R"(\ |
| #version 300 es |
| precision highp float; |
| |
| in vec3 color_varying; |
| |
| out vec4 fragColor; |
| |
| void main() { |
| fragColor = vec4(color_varying, 1.0); |
| } |
| )"; |
| |
| ScopedGlProgram program = GFXSTREAM_ASSERT(SetUpProgram(vertSource, fragSource)); |
| |
| GLint transformUniformLocation = mGl->glGetUniformLocation(program, "transform"); |
| mGl->glEnableVertexAttribArray(0); |
| mGl->glEnableVertexAttribArray(1); |
| |
| struct VertexAttributes { |
| float position[2]; |
| float color[3]; |
| }; |
| const VertexAttributes vertexAttrs[] = { |
| // clang-format off |
| { { -0.5f, -0.5f,}, { 0.2, 0.1, 0.9, }, }, |
| { { 0.5f, -0.5f,}, { 0.8, 0.3, 0.1, }, }, |
| { { 0.0f, 0.5f,}, { 0.1, 0.9, 0.6, }, }, |
| // clang-format on |
| }; |
| |
| ScopedGlBuffer buffer(*mGl); |
| mGl->glBindBuffer(GL_ARRAY_BUFFER, buffer); |
| mGl->glBufferData(GL_ARRAY_BUFFER, sizeof(vertexAttrs), vertexAttrs, GL_STATIC_DRAW); |
| |
| mGl->glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(VertexAttributes), 0); |
| mGl->glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(VertexAttributes), (GLvoid*)offsetof(VertexAttributes, color)); |
| |
| mGl->glUseProgram(program); |
| |
| mGl->glViewport(0, 0, 1, 1); |
| |
| mGl->glClearColor(0.2f, 0.2f, 0.3f, 0.0f); |
| mGl->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
| |
| const float matrix[16] = { |
| // clang-format off |
| 1.0f, 0.0f, 0.0f, 0.0f, |
| 0.0f, 1.0f, 0.0f, 0.0f, |
| 0.0f, 0.0f, 1.0f, 0.0f, |
| 0.0f, 0.0f, 0.0f, 1.0f, |
| // clang-format on |
| }; |
| |
| constexpr uint32_t kDrawIterations = 200; |
| for (uint32_t i = 0; i < kDrawIterations; i++) { |
| mGl->glUniformMatrix4fv(transformUniformLocation, 1, GL_FALSE, matrix); |
| mGl->glBindBuffer(GL_ARRAY_BUFFER, buffer); |
| mGl->glDrawArrays(GL_TRIANGLES, 0, 3); |
| } |
| |
| mGl->glFinish(); |
| mGl->glBindBuffer(GL_ARRAY_BUFFER, 0); |
| mGl->glUseProgram(0); |
| } |
| |
| TEST_P(GfxstreamEnd2EndGlTest, ProgramBinaryWithAHB) { |
| const uint32_t width = 2; |
| const uint32_t height = 2; |
| auto ahb = GFXSTREAM_ASSERT(ScopedAHardwareBuffer::Allocate( |
| *mGralloc, width, height, GFXSTREAM_AHB_FORMAT_R8G8B8A8_UNORM)); |
| |
| GFXSTREAM_ASSERT(FillAhb(ahb, PixelR8G8B8A8(0, 0, 128, 255))); |
| |
| const EGLint ahbImageAttribs[] = { |
| // clang-format off |
| EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, |
| EGL_NONE, |
| // clang-format on |
| }; |
| EGLImageKHR ahbImage = mGl->eglCreateImageKHR(mDisplay, EGL_NO_CONTEXT, |
| EGL_NATIVE_BUFFER_ANDROID, ahb, ahbImageAttribs); |
| ASSERT_THAT(ahbImage, Not(Eq(EGL_NO_IMAGE_KHR))); |
| |
| ScopedGlTexture ahbTexture(*mGl); |
| mGl->glBindTexture(GL_TEXTURE_EXTERNAL_OES, ahbTexture); |
| mGl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| mGl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| mGl->glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, ahbImage); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| GLenum programBinaryFormat = GL_NONE; |
| std::vector<uint8_t> programBinaryData; |
| { |
| const std::string vertSource = R"(\ |
| #version 300 es |
| |
| layout (location = 0) in vec2 pos; |
| layout (location = 1) in vec2 tex; |
| |
| out vec2 vTex; |
| |
| void main() { |
| gl_Position = vec4(pos, 0.0, 1.0); |
| vTex = tex; |
| })"; |
| |
| const std::string fragSource = R"(\ |
| #version 300 es |
| |
| precision highp float; |
| |
| uniform float uMultiplier; |
| uniform sampler2D uTexture; |
| |
| in vec2 vTex; |
| |
| out vec4 oColor; |
| |
| void main() { |
| oColor = texture(uTexture, vTex) * uMultiplier; |
| })"; |
| |
| ScopedGlProgram program = GFXSTREAM_ASSERT(SetUpProgram(vertSource, fragSource)); |
| |
| GLint programBinaryLength = 0; |
| mGl->glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH, &programBinaryLength); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| programBinaryData.resize(programBinaryLength); |
| |
| GLint readProgramBinaryLength = 0; |
| mGl->glGetProgramBinary(program, programBinaryLength, &readProgramBinaryLength, |
| &programBinaryFormat, programBinaryData.data()); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| ASSERT_THAT(programBinaryLength, Eq(readProgramBinaryLength)); |
| } |
| |
| ScopedGlProgram program = |
| GFXSTREAM_ASSERT(SetUpProgram(programBinaryFormat, programBinaryData)); |
| ASSERT_THAT(program, Not(Eq(0))); |
| |
| GLint textureUniformLoc = mGl->glGetUniformLocation(program, "uTexture"); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| ASSERT_THAT(textureUniformLoc, Not(Eq(-1))); |
| |
| GLint multiplierUniformLoc = mGl->glGetUniformLocation(program, "uMultiplier"); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| ASSERT_THAT(multiplierUniformLoc, Not(Eq(-1))); |
| |
| const GLsizei kFramebufferWidth = 4; |
| const GLsizei kFramebufferHeight = 4; |
| ScopedGlFramebuffer framebuffer(*mGl); |
| mGl->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); |
| ScopedGlTexture framebufferTexture(*mGl); |
| mGl->glBindTexture(GL_TEXTURE_2D, framebufferTexture); |
| mGl->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kFramebufferWidth, kFramebufferHeight, 0, GL_RGBA, |
| GL_UNSIGNED_BYTE, nullptr); |
| mGl->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, |
| framebufferTexture, 0); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| ASSERT_THAT(mGl->glCheckFramebufferStatus(GL_FRAMEBUFFER), Eq(GL_FRAMEBUFFER_COMPLETE)); |
| mGl->glBindTexture(GL_TEXTURE_2D, 0); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| struct VertexAttributes { |
| float pos[2]; |
| float tex[2]; |
| }; |
| const VertexAttributes vertexAttrs[] = { |
| // clang-format off |
| { { -1.0f, -1.0f,}, { 0.0f, 0.0f }, }, |
| { { 3.0f, -1.0f,}, { 2.0f, 0.0f }, }, |
| { { -1.0f, 3.0f,}, { 0.0f, 2.0f }, }, |
| // clang-format on |
| }; |
| ScopedGlBuffer buffer(*mGl); |
| mGl->glBindBuffer(GL_ARRAY_BUFFER, buffer); |
| mGl->glBufferData(GL_ARRAY_BUFFER, sizeof(vertexAttrs), vertexAttrs, GL_STATIC_DRAW); |
| |
| mGl->glUseProgram(program); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| mGl->glBindBuffer(GL_ARRAY_BUFFER, buffer); |
| mGl->glEnableVertexAttribArray(0); |
| mGl->glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(VertexAttributes), |
| (GLvoid*)offsetof(VertexAttributes, pos)); |
| mGl->glEnableVertexAttribArray(1); |
| mGl->glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(VertexAttributes), |
| (GLvoid*)offsetof(VertexAttributes, tex)); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| mGl->glActiveTexture(GL_TEXTURE0); |
| mGl->glBindTexture(GL_TEXTURE_2D, ahbTexture); |
| mGl->glUniform1i(textureUniformLoc, 0); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| mGl->glUniform1f(multiplierUniformLoc, 2.0f); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| mGl->glDrawArrays(GL_TRIANGLES, 0, 3); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| for (int x = 0; x < kFramebufferWidth; x++) { |
| for (int y = 0; y < kFramebufferHeight; y++) { |
| EXPECT_THAT(GetPixelAt(x, y), IsOkWithRGBA(0, 0, 255, 255)); |
| } |
| } |
| |
| mGl->glBindFramebuffer(GL_FRAMEBUFFER, 0); |
| } |
| |
| TEST_P(GfxstreamEnd2EndGlTest, ProgramBinaryWithTexture) { |
| const GLsizei kTextureWidth = 2; |
| const GLsizei kTextureHeight = 2; |
| const GLubyte kTextureData[16] = { |
| // clang-format off |
| 0, 0, 128, 255, 0, 0, 128, 255, |
| |
| 0, 0, 128, 255, 0, 0, 128, 255, |
| // clang-format on |
| }; |
| ScopedGlTexture texture(*mGl); |
| mGl->glBindTexture(GL_TEXTURE_2D, texture); |
| mGl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| mGl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| mGl->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kTextureWidth, kTextureHeight, 0, GL_RGBA, |
| GL_UNSIGNED_BYTE, kTextureData); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| GLenum programBinaryFormat = GL_NONE; |
| std::vector<uint8_t> programBinaryData; |
| { |
| const std::string vertSource = R"(\ |
| #version 300 es |
| |
| layout (location = 0) in vec2 pos; |
| layout (location = 1) in vec2 tex; |
| |
| out vec2 vTex; |
| |
| void main() { |
| gl_Position = vec4(pos, 0.0, 1.0); |
| vTex = tex; |
| })"; |
| |
| const std::string fragSource = R"(\ |
| #version 300 es |
| |
| precision highp float; |
| |
| uniform float uMultiplier; |
| uniform sampler2D uTexture; |
| |
| in vec2 vTex; |
| |
| out vec4 oColor; |
| |
| void main() { |
| oColor = texture(uTexture, vTex) * uMultiplier; |
| })"; |
| |
| ScopedGlProgram program = GFXSTREAM_ASSERT(SetUpProgram(vertSource, fragSource)); |
| |
| GLint programBinaryLength = 0; |
| mGl->glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH, &programBinaryLength); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| programBinaryData.resize(programBinaryLength); |
| |
| GLint readProgramBinaryLength = 0; |
| mGl->glGetProgramBinary(program, programBinaryLength, &readProgramBinaryLength, |
| &programBinaryFormat, programBinaryData.data()); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| ASSERT_THAT(programBinaryLength, Eq(readProgramBinaryLength)); |
| } |
| |
| ScopedGlProgram program = |
| GFXSTREAM_ASSERT(SetUpProgram(programBinaryFormat, programBinaryData)); |
| ASSERT_THAT(program, Not(Eq(0))); |
| |
| GLint textureUniformLoc = mGl->glGetUniformLocation(program, "uTexture"); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| ASSERT_THAT(textureUniformLoc, Not(Eq(-1))); |
| |
| GLint multiplierUniformLoc = mGl->glGetUniformLocation(program, "uMultiplier"); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| ASSERT_THAT(multiplierUniformLoc, Not(Eq(-1))); |
| |
| const GLsizei kFramebufferWidth = 4; |
| const GLsizei kFramebufferHeight = 4; |
| ScopedGlFramebuffer framebuffer(*mGl); |
| mGl->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); |
| ScopedGlTexture framebufferTexture(*mGl); |
| mGl->glBindTexture(GL_TEXTURE_2D, framebufferTexture); |
| mGl->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kFramebufferWidth, kFramebufferHeight, 0, GL_RGBA, |
| GL_UNSIGNED_BYTE, nullptr); |
| mGl->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, |
| framebufferTexture, 0); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| ASSERT_THAT(mGl->glCheckFramebufferStatus(GL_FRAMEBUFFER), Eq(GL_FRAMEBUFFER_COMPLETE)); |
| mGl->glBindTexture(GL_TEXTURE_2D, 0); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| struct VertexAttributes { |
| float pos[2]; |
| float tex[2]; |
| }; |
| const VertexAttributes vertexAttrs[] = { |
| // clang-format off |
| { { -1.0f, -1.0f,}, { 0.0f, 0.0f }, }, |
| { { 3.0f, -1.0f,}, { 2.0f, 0.0f }, }, |
| { { -1.0f, 3.0f,}, { 0.0f, 2.0f }, }, |
| // clang-format on |
| }; |
| ScopedGlBuffer buffer(*mGl); |
| mGl->glBindBuffer(GL_ARRAY_BUFFER, buffer); |
| mGl->glBufferData(GL_ARRAY_BUFFER, sizeof(vertexAttrs), vertexAttrs, GL_STATIC_DRAW); |
| |
| mGl->glUseProgram(program); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| mGl->glBindBuffer(GL_ARRAY_BUFFER, buffer); |
| mGl->glEnableVertexAttribArray(0); |
| mGl->glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(VertexAttributes), |
| (GLvoid*)offsetof(VertexAttributes, pos)); |
| mGl->glEnableVertexAttribArray(1); |
| mGl->glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(VertexAttributes), |
| (GLvoid*)offsetof(VertexAttributes, tex)); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| mGl->glActiveTexture(GL_TEXTURE0); |
| mGl->glBindTexture(GL_TEXTURE_2D, texture); |
| mGl->glUniform1i(textureUniformLoc, 0); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| mGl->glUniform1f(multiplierUniformLoc, 2.0f); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| mGl->glDrawArrays(GL_TRIANGLES, 0, 3); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| for (int x = 0; x < kFramebufferWidth; x++) { |
| for (int y = 0; y < kFramebufferHeight; y++) { |
| EXPECT_THAT(GetPixelAt(x, y), IsOkWithRGBA(0, 0, 255, 255)); |
| } |
| } |
| |
| mGl->glBindFramebuffer(GL_FRAMEBUFFER, 0); |
| } |
| |
| TEST_P(GfxstreamEnd2EndGlTest, AhbTextureUploadAndReadback) { |
| const uint32_t width = 2; |
| const uint32_t height = 2; |
| |
| const auto lockPixel = PixelR8G8B8A8(11, 22, 33, 44); |
| |
| const auto uploadPixel = PixelR8G8B8A8(55, 66, 77, 88); |
| const auto uploadPixels = Fill(width, height, uploadPixel); |
| |
| auto ahb = GFXSTREAM_ASSERT(ScopedAHardwareBuffer::Allocate( |
| *mGralloc, width, height, GFXSTREAM_AHB_FORMAT_R8G8B8A8_UNORM)); |
| |
| // Initialize AHB with `lockPixel` |
| GFXSTREAM_ASSERT(FillAhb(ahb, lockPixel)); |
| |
| // Update AHB with `uploadPixel` via texture upload: |
| { |
| const EGLint ahbImageAttribs[] = { |
| // clang-format off |
| EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, |
| EGL_NONE, |
| // clang-format on |
| }; |
| EGLImageKHR ahbImage = mGl->eglCreateImageKHR( |
| mDisplay, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, ahb, ahbImageAttribs); |
| ASSERT_THAT(ahbImage, Not(Eq(EGL_NO_IMAGE_KHR))); |
| |
| ScopedGlTexture ahbTexture(*mGl); |
| mGl->glBindTexture(GL_TEXTURE_2D, ahbTexture); |
| mGl->glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, ahbImage); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| mGl->glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, |
| uploadPixels.data()); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| EGLSync uploadFence = mGl->eglCreateSyncKHR(mDisplay, EGL_SYNC_FENCE_KHR, nullptr); |
| ASSERT_THAT(mGl->eglGetError(), Eq(EGL_SUCCESS)); |
| |
| mGl->glFlush(); |
| |
| ahbTexture.Reset(); |
| |
| mGl->eglClientWaitSyncKHR(mDisplay, uploadFence, /*flags=*/0, |
| /*timeout=2 seconds*/ 2 * 1000 * 1000 * 1000); |
| ASSERT_THAT(mGl->eglGetError(), Eq(EGL_SUCCESS)); |
| |
| mGl->eglDestroySyncKHR(mDisplay, uploadFence); |
| ASSERT_THAT(mGl->eglGetError(), Eq(EGL_SUCCESS)); |
| |
| mGl->eglDestroyImageKHR(mDisplay, ahbImage); |
| ASSERT_THAT(mGl->eglGetError(), Eq(EGL_SUCCESS)); |
| } |
| |
| // Blit from AHB to an additional framebuffer and readback: |
| { |
| const EGLint ahbImageAttribs[] = { |
| // clang-format off |
| EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, |
| EGL_NONE, |
| // clang-format on |
| }; |
| EGLImageKHR ahbImage = mGl->eglCreateImageKHR( |
| mDisplay, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, ahb, ahbImageAttribs); |
| ASSERT_THAT(ahbImage, Not(Eq(EGL_NO_IMAGE_KHR))); |
| |
| ScopedGlTexture ahbTexture(*mGl); |
| mGl->glBindTexture(GL_TEXTURE_2D, ahbTexture); |
| mGl->glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, ahbImage); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| ScopedGlFramebuffer readbackFramebuffer(*mGl); |
| mGl->glBindFramebuffer(GL_FRAMEBUFFER, readbackFramebuffer); |
| mGl->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, ahbTexture, |
| 0); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| ASSERT_THAT(mGl->glCheckFramebufferStatus(GL_FRAMEBUFFER), Eq(GL_FRAMEBUFFER_COMPLETE)); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| for (int x = 0; x < width; x++) { |
| for (int y = 0; y < height; y++) { |
| EXPECT_THAT(GetPixelAt(x, y), IsOkWith(uploadPixel)); |
| } |
| } |
| |
| mGl->glBindFramebuffer(GL_FRAMEBUFFER, 0); |
| } |
| } |
| |
| TEST_P(GfxstreamEnd2EndGlTest, AhbTextureUploadAndBlit) { |
| const uint32_t width = 2; |
| const uint32_t height = 2; |
| |
| ScopedGlTexture blitFramebufferTexture(*mGl); |
| mGl->glBindTexture(GL_TEXTURE_2D, blitFramebufferTexture); |
| mGl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
| mGl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
| mGl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| mGl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| mGl->glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height); |
| mGl->glBindTexture(GL_TEXTURE_2D, 0); |
| |
| ScopedGlFramebuffer blitFramebuffer(*mGl); |
| mGl->glBindFramebuffer(GL_FRAMEBUFFER, blitFramebuffer); |
| mGl->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, |
| blitFramebufferTexture, 0); |
| |
| ASSERT_THAT(mGl->glCheckFramebufferStatus(GL_FRAMEBUFFER), Eq(GL_FRAMEBUFFER_COMPLETE)); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| const auto lockPixel = PixelR8G8B8A8(11, 22, 33, 44); |
| |
| const auto uploadPixel = PixelR8G8B8A8(55, 66, 77, 88); |
| const auto uploadPixels = Fill(width, height, uploadPixel); |
| |
| auto ahb = GFXSTREAM_ASSERT(ScopedAHardwareBuffer::Allocate( |
| *mGralloc, width, height, GFXSTREAM_AHB_FORMAT_R8G8B8A8_UNORM)); |
| |
| // Initialize AHB with `lockPixel` |
| GFXSTREAM_ASSERT(FillAhb(ahb, lockPixel)); |
| |
| // Update AHB with `uploadPixel` via texture upload: |
| { |
| const EGLint ahbImageAttribs[] = { |
| // clang-format off |
| EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, |
| EGL_NONE, |
| // clang-format on |
| }; |
| EGLImageKHR ahbImage = mGl->eglCreateImageKHR( |
| mDisplay, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, ahb, ahbImageAttribs); |
| ASSERT_THAT(ahbImage, Not(Eq(EGL_NO_IMAGE_KHR))); |
| |
| ScopedGlTexture ahbTexture(*mGl); |
| mGl->glBindTexture(GL_TEXTURE_2D, ahbTexture); |
| mGl->glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, ahbImage); |
| mGl->glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, |
| uploadPixels.data()); |
| ASSERT_THAT(mGl->eglGetError(), Eq(EGL_SUCCESS)); |
| |
| EGLSync uploadFence = mGl->eglCreateSyncKHR(mDisplay, EGL_SYNC_FENCE_KHR, nullptr); |
| |
| mGl->glFlush(); |
| |
| ahbTexture.Reset(); |
| |
| mGl->eglClientWaitSyncKHR(mDisplay, uploadFence, /*flags=*/0, |
| /*timeout=2 seconds*/ 2 * 1000 * 1000 * 1000); |
| ASSERT_THAT(mGl->eglGetError(), Eq(EGL_SUCCESS)); |
| |
| mGl->eglDestroySyncKHR(mDisplay, uploadFence); |
| ASSERT_THAT(mGl->eglGetError(), Eq(EGL_SUCCESS)); |
| |
| mGl->eglDestroyImageKHR(mDisplay, ahbImage); |
| ASSERT_THAT(mGl->eglGetError(), Eq(EGL_SUCCESS)); |
| } |
| |
| // Blit from AHB to an additional framebuffer and readback: |
| { |
| const std::string blitTextureVertSource = R"(\ |
| #version 300 es |
| |
| layout (location = 0) in vec2 pos; |
| layout (location = 1) in vec2 tex; |
| |
| out vec2 vTex; |
| |
| void main() { |
| gl_Position = vec4(pos, 0.0, 1.0); |
| vTex = tex; |
| })"; |
| |
| const std::string blitTextureFragSource = R"(\ |
| #version 300 es |
| |
| precision highp float; |
| |
| uniform sampler2D uTexture; |
| |
| in vec2 vTex; |
| |
| out vec4 oColor; |
| |
| void main() { |
| oColor = texture(uTexture, vTex); |
| })"; |
| |
| ScopedGlProgram program = |
| GFXSTREAM_ASSERT(SetUpProgram(blitTextureVertSource, blitTextureFragSource)); |
| |
| GLint textureUniformLoc = mGl->glGetUniformLocation(program, "uTexture"); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| ASSERT_THAT(textureUniformLoc, Not(Eq(-1))); |
| |
| struct VertexAttributes { |
| float pos[2]; |
| float tex[2]; |
| }; |
| const std::vector<VertexAttributes> fullscreenTriVerts = { |
| // clang-format off |
| { .pos = { -1.0f, -1.0f }, .tex = { 0.0f, 0.0f }, }, |
| { .pos = { 3.0f, -1.0f }, .tex = { 2.0f, 0.0f }, }, |
| { .pos = { -1.0f, 3.0f }, .tex = { 0.0f, 2.0f }, }, |
| // clang-format on |
| }; |
| ScopedGlBuffer buffer(*mGl); |
| mGl->glBindBuffer(GL_ARRAY_BUFFER, buffer); |
| mGl->glBufferData(GL_ARRAY_BUFFER, sizeof(VertexAttributes) * fullscreenTriVerts.size(), |
| fullscreenTriVerts.data(), GL_STATIC_DRAW); |
| |
| mGl->glUseProgram(program); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| mGl->glViewport(0, 0, width, height); |
| mGl->glClearColor(0.0f, 0.0f, 0.0f, 0.0f); |
| mGl->glClear(GL_COLOR_BUFFER_BIT); |
| mGl->glBindBuffer(GL_ARRAY_BUFFER, buffer); |
| mGl->glEnableVertexAttribArray(0); |
| mGl->glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(VertexAttributes), |
| (GLvoid*)offsetof(VertexAttributes, pos)); |
| mGl->glEnableVertexAttribArray(1); |
| mGl->glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(VertexAttributes), |
| (GLvoid*)offsetof(VertexAttributes, tex)); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| const EGLint ahbImageAttribs[] = { |
| // clang-format off |
| EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, |
| EGL_NONE, |
| // clang-format on |
| }; |
| EGLImageKHR ahbImage = mGl->eglCreateImageKHR( |
| mDisplay, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, ahb, ahbImageAttribs); |
| ASSERT_THAT(ahbImage, Not(Eq(EGL_NO_IMAGE_KHR))); |
| |
| ScopedGlTexture ahbTexture(*mGl); |
| mGl->glBindTexture(GL_TEXTURE_2D, ahbTexture); |
| mGl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
| mGl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| mGl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| mGl->glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, ahbImage); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| mGl->glActiveTexture(GL_TEXTURE0); |
| mGl->glBindTexture(GL_TEXTURE_2D, ahbTexture); |
| mGl->glUniform1i(textureUniformLoc, 0); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| mGl->glDrawArrays(GL_TRIANGLES, 0, 3); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| mGl->glFinish(); |
| |
| for (int x = 0; x < width; x++) { |
| for (int y = 0; y < height; y++) { |
| EXPECT_THAT(GetPixelAt(x, y), IsOkWith(uploadPixel)); |
| } |
| } |
| |
| mGl->glBindFramebuffer(GL_FRAMEBUFFER, 0); |
| } |
| } |
| |
| TEST_P(GfxstreamEnd2EndGlTest, MultiThreadedAhbTextureUploadAndReadback) { |
| const uint32_t width = 2; |
| const uint32_t height = 2; |
| |
| const auto lockPixel = PixelR8G8B8A8(11, 22, 33, 44); |
| |
| const auto uploadPixel = PixelR8G8B8A8(55, 66, 77, 88); |
| const auto uploadPixels = Fill(width, height, uploadPixel); |
| |
| auto ahb = GFXSTREAM_ASSERT(ScopedAHardwareBuffer::Allocate( |
| *mGralloc, width, height, GFXSTREAM_AHB_FORMAT_R8G8B8A8_UNORM)); |
| |
| const EGLint ahbImageAttribs[] = { |
| // clang-format off |
| EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, |
| EGL_NONE, |
| // clang-format on |
| }; |
| EGLImageKHR ahbImage = mGl->eglCreateImageKHR(mDisplay, EGL_NO_CONTEXT, |
| EGL_NATIVE_BUFFER_ANDROID, ahb, ahbImageAttribs); |
| ASSERT_THAT(ahbImage, Not(Eq(EGL_NO_IMAGE_KHR))); |
| |
| std::vector<PixelR8G8B8A8> readbackPixels; |
| |
| SimpleLatch readbackThreadInitialized{1}; |
| SimpleLatch readbackThreadCanReadback{1}; |
| SimpleLatch readbackThreadDidReadback{1}; |
| SimpleLatch readbackThreadCanCleanup{1}; |
| std::thread readbackThread([&]() { |
| EGLContext readbackThreadContext; |
| EGLSurface readbackThreadSurface; |
| SetUpEglContextAndSurface(2, 32, 32, &mDisplay, &readbackThreadContext, |
| &readbackThreadSurface); |
| { |
| // Create a framebuffer for blitting the AHB into and reading back the blitted results |
| // from: |
| ScopedGlTexture readbackTexture(*mGl); |
| mGl->glBindTexture(GL_TEXTURE_2D, readbackTexture); |
| mGl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
| mGl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
| mGl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| mGl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| mGl->glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height); |
| mGl->glBindTexture(GL_TEXTURE_2D, 0); |
| ScopedGlFramebuffer readbackFramebuffer(*mGl); |
| mGl->glBindFramebuffer(GL_FRAMEBUFFER, readbackFramebuffer); |
| mGl->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, |
| readbackTexture, 0); |
| ASSERT_THAT(mGl->glCheckFramebufferStatus(GL_FRAMEBUFFER), Eq(GL_FRAMEBUFFER_COMPLETE)); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| // Create fullscreen triangle vertex buffer: |
| struct VertexAttributes { |
| float pos[2]; |
| float tex[2]; |
| }; |
| const std::vector<VertexAttributes> fullscreenTriVerts = { |
| // clang-format off |
| { .pos = { -1.0f, -1.0f }, .tex = { 0.0f, 0.0f }, }, |
| { .pos = { 3.0f, -1.0f }, .tex = { 2.0f, 0.0f }, }, |
| { .pos = { -1.0f, 3.0f }, .tex = { 0.0f, 2.0f }, }, |
| // clang-format on |
| }; |
| ScopedGlBuffer buffer(*mGl); |
| mGl->glBindBuffer(GL_ARRAY_BUFFER, buffer); |
| mGl->glBufferData(GL_ARRAY_BUFFER, sizeof(VertexAttributes) * fullscreenTriVerts.size(), |
| fullscreenTriVerts.data(), GL_STATIC_DRAW); |
| |
| const std::string vertSource = R"(\ |
| #version 300 es |
| layout (location = 0) in vec2 pos; |
| layout (location = 1) in vec2 tex; |
| out vec2 vTex; |
| void main() { |
| gl_Position = vec4(pos, 0.0, 1.0); |
| vTex = tex; |
| })"; |
| const std::string fragSource = R"(\ |
| #version 300 es |
| precision highp float; |
| uniform sampler2D uTexture; |
| in vec2 vTex; |
| out vec4 oColor; |
| void main() { |
| oColor = texture(uTexture, vTex); |
| })"; |
| ScopedGlProgram program = GFXSTREAM_ASSERT(SetUpProgram(vertSource, fragSource)); |
| ASSERT_THAT(program, Not(Eq(0))); |
| |
| GLint textureUniformLoc = mGl->glGetUniformLocation(program, "uTexture"); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| ASSERT_THAT(textureUniformLoc, Not(Eq(-1))); |
| |
| readbackThreadInitialized.count_down(); |
| |
| readbackThreadCanReadback.wait(); |
| { |
| EGLImageKHR readbackAhbImage = mGl->eglCreateImageKHR( |
| mDisplay, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, ahb, ahbImageAttribs); |
| ASSERT_THAT(readbackAhbImage, Not(Eq(EGL_NO_IMAGE_KHR))); |
| ScopedGlTexture readbackAhbTexture(*mGl); |
| mGl->glBindTexture(GL_TEXTURE_2D, readbackAhbTexture); |
| mGl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
| mGl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| mGl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| mGl->glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, readbackAhbImage); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| mGl->glUseProgram(program); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| mGl->glViewport(0, 0, width, height); |
| mGl->glClearColor(0.0f, 0.0f, 0.0f, 0.0f); |
| mGl->glClear(GL_COLOR_BUFFER_BIT); |
| mGl->glBindBuffer(GL_ARRAY_BUFFER, buffer); |
| mGl->glEnableVertexAttribArray(0); |
| mGl->glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(VertexAttributes), |
| (GLvoid*)offsetof(VertexAttributes, pos)); |
| mGl->glEnableVertexAttribArray(1); |
| mGl->glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(VertexAttributes), |
| (GLvoid*)offsetof(VertexAttributes, tex)); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| mGl->glActiveTexture(GL_TEXTURE0); |
| mGl->glBindTexture(GL_TEXTURE_2D, readbackAhbTexture); |
| mGl->glUniform1i(textureUniformLoc, 0); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| mGl->glDrawArrays(GL_TRIANGLES, 0, 3); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| mGl->glFinish(); |
| |
| std::vector<uint8_t> readbackBytes(width * height * 4); |
| mGl->glPixelStorei(GL_PACK_ALIGNMENT, 1); |
| mGl->glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, |
| readbackBytes.data()); |
| |
| const uint8_t* readbackBytesPtr = readbackBytes.data(); |
| for (uint32_t y = 0; y < height; y++) { |
| for (uint32_t x = 0; x < width; x++) { |
| PixelR8G8B8A8 readbackPixel; |
| readbackPixel.x = x; |
| readbackPixel.y = y; |
| readbackPixel.r = *readbackBytesPtr; |
| ++readbackBytesPtr; |
| readbackPixel.g = *readbackBytesPtr; |
| ++readbackBytesPtr; |
| readbackPixel.b = *readbackBytesPtr; |
| ++readbackBytesPtr; |
| readbackPixel.a = *readbackBytesPtr; |
| ++readbackBytesPtr; |
| readbackPixels.push_back(readbackPixel); |
| } |
| } |
| } |
| readbackThreadDidReadback.count_down(); |
| } |
| readbackThreadCanCleanup.wait(); |
| TearDownEglContextAndSurface(mDisplay, readbackThreadContext, readbackThreadSurface); |
| }); |
| |
| EGLSync uploadCompleteFence = EGL_NO_SYNC; |
| |
| SimpleLatch uploadThreadInitialized{1}; |
| SimpleLatch uploadThreadStartUpload{1}; |
| SimpleLatch uploadThreadStartedUpload{1}; |
| SimpleLatch uploadThreadCanCleanup{1}; |
| std::thread uploadThread([&]() { |
| EGLContext uploadThreadContext; |
| EGLSurface uploadThreadSurface; |
| SetUpEglContextAndSurface(2, 32, 32, &mDisplay, &uploadThreadContext, &uploadThreadSurface); |
| uploadThreadInitialized.count_down(); |
| |
| { |
| // Update AHB with `uploadPixel` via texture upload: |
| uploadThreadStartUpload.wait(); |
| { |
| ScopedGlTexture uploadTexture(*mGl); |
| mGl->glBindTexture(GL_TEXTURE_2D, uploadTexture); |
| mGl->glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, ahbImage); |
| mGl->glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, |
| GL_UNSIGNED_BYTE, uploadPixels.data()); |
| ASSERT_THAT(mGl->eglGetError(), Eq(EGL_SUCCESS)); |
| |
| uploadCompleteFence = mGl->eglCreateSyncKHR(mDisplay, EGL_SYNC_FENCE_KHR, nullptr); |
| ASSERT_THAT(mGl->eglGetError(), Eq(EGL_SUCCESS)); |
| ASSERT_THAT(uploadCompleteFence, Not(Eq(EGL_NO_SYNC))); |
| |
| mGl->glFlush(); |
| } |
| uploadThreadStartedUpload.count_down(); |
| } |
| |
| uploadThreadCanCleanup.wait(); |
| TearDownEglContextAndSurface(mDisplay, uploadThreadContext, uploadThreadSurface); |
| }); |
| |
| readbackThreadInitialized.wait(); |
| uploadThreadInitialized.wait(); |
| |
| // "MainThread" updates the AHB with `lockPixel` via Gralloc->Lock(): |
| GFXSTREAM_ASSERT(FillAhb(ahb, lockPixel)); |
| |
| // "UploadThread" updates the AHB with `uploadPixel` via GL texture upload: |
| uploadThreadStartUpload.count_down(); |
| |
| // "MainThread" waits on upload fence: |
| { |
| uploadThreadStartedUpload.wait(); |
| |
| ASSERT_THAT(uploadCompleteFence, Not(Eq(EGL_NO_SYNC))); |
| mGl->eglClientWaitSyncKHR(mDisplay, uploadCompleteFence, /*flags=*/0, |
| /*timeout=2 seconds*/ 2 * 1000 * 1000 * 1000); |
| ASSERT_THAT(mGl->eglGetError(), Eq(EGL_SUCCESS)); |
| |
| mGl->eglDestroySyncKHR(mDisplay, uploadCompleteFence); |
| ASSERT_THAT(mGl->eglGetError(), Eq(EGL_SUCCESS)); |
| |
| mGl->eglDestroyImageKHR(mDisplay, ahbImage); |
| } |
| |
| // "ReadbackThread" blits the AHB contents to an internal framebuffer and performs readback: |
| readbackThreadCanReadback.count_down(); |
| |
| // Check readback results: |
| readbackThreadDidReadback.wait(); |
| for (int x = 0; x < width; x++) { |
| for (int y = 0; y < height; y++) { |
| EXPECT_THAT(readbackPixels[(y * width) + x], Eq(uploadPixel)); |
| } |
| } |
| |
| readbackThreadCanCleanup.count_down(); |
| readbackThread.join(); |
| |
| uploadThreadCanCleanup.count_down(); |
| uploadThread.join(); |
| } |
| |
| TEST_P(GfxstreamEnd2EndGlTest, AhbTextureUploadAndExternalOesBlit) { |
| const uint32_t width = 2; |
| const uint32_t height = 2; |
| |
| ScopedGlTexture readbackTexture(*mGl); |
| mGl->glBindTexture(GL_TEXTURE_2D, readbackTexture); |
| mGl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
| mGl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
| mGl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| mGl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| mGl->glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height); |
| mGl->glBindTexture(GL_TEXTURE_2D, 0); |
| ScopedGlFramebuffer readbackFramebuffer(*mGl); |
| mGl->glBindFramebuffer(GL_FRAMEBUFFER, readbackFramebuffer); |
| mGl->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, |
| readbackTexture, 0); |
| ASSERT_THAT(mGl->glCheckFramebufferStatus(GL_FRAMEBUFFER), Eq(GL_FRAMEBUFFER_COMPLETE)); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| const auto lockPixel = PixelR8G8B8A8(11, 22, 33, 44); |
| |
| const auto uploadPixel = PixelR8G8B8A8(55, 66, 77, 88); |
| const auto uploadPixels = Fill(width, height, uploadPixel); |
| |
| auto ahb = GFXSTREAM_ASSERT(ScopedAHardwareBuffer::Allocate( |
| *mGralloc, width, height, GFXSTREAM_AHB_FORMAT_R8G8B8A8_UNORM)); |
| |
| const EGLint ahbImageAttribs[] = { |
| // clang-format off |
| EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, |
| EGL_NONE, |
| // clang-format on |
| }; |
| |
| // Initialize AHB with `lockPixel` |
| GFXSTREAM_ASSERT(FillAhb(ahb, lockPixel)); |
| |
| // Update AHB with `uploadPixel` via texture upload: |
| { |
| EGLImageKHR uploadAhbImage = mGl->eglCreateImageKHR( |
| mDisplay, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, ahb, ahbImageAttribs); |
| ASSERT_THAT(uploadAhbImage, Not(Eq(EGL_NO_IMAGE_KHR))); |
| |
| ScopedGlTexture uploadAhbTexture(*mGl); |
| mGl->glBindTexture(GL_TEXTURE_2D, uploadAhbTexture); |
| mGl->glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, uploadAhbImage); |
| mGl->glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, |
| uploadPixels.data()); |
| ASSERT_THAT(mGl->eglGetError(), Eq(EGL_SUCCESS)); |
| |
| EGLSync uploadFence = mGl->eglCreateSyncKHR(mDisplay, EGL_SYNC_FENCE_KHR, nullptr); |
| |
| mGl->glFlush(); |
| |
| uploadAhbTexture.Reset(); |
| |
| mGl->eglClientWaitSyncKHR(mDisplay, uploadFence, /*flags=*/0, |
| /*timeout=2 seconds*/ 2 * 1000 * 1000 * 1000); |
| ASSERT_THAT(mGl->eglGetError(), Eq(EGL_SUCCESS)); |
| |
| mGl->eglDestroySyncKHR(mDisplay, uploadFence); |
| ASSERT_THAT(mGl->eglGetError(), Eq(EGL_SUCCESS)); |
| |
| mGl->eglDestroyImageKHR(mDisplay, uploadAhbImage); |
| ASSERT_THAT(mGl->eglGetError(), Eq(EGL_SUCCESS)); |
| } |
| |
| // Blit from AHB to an additional framebuffer for readback: |
| { |
| const std::string blitTextureVertSource = R"(\ |
| #version 300 es |
| layout (location = 0) in vec2 pos; |
| layout (location = 1) in vec2 tex; |
| out vec2 vTex; |
| void main() { |
| gl_Position = vec4(pos, 0.0, 1.0); |
| vTex = tex; |
| })"; |
| |
| const std::string blitTextureFragSource = R"(\ |
| #version 300 es |
| #extension GL_OES_EGL_image_external |
| precision highp float; |
| uniform samplerExternalOES uTexture; |
| in vec2 vTex; |
| out vec4 oColor; |
| void main() { |
| oColor = texture(uTexture, vTex); |
| })"; |
| |
| ScopedGlProgram program = |
| GFXSTREAM_ASSERT(SetUpProgram(blitTextureVertSource, blitTextureFragSource)); |
| |
| GLint textureUniformLoc = mGl->glGetUniformLocation(program, "uTexture"); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| ASSERT_THAT(textureUniformLoc, Not(Eq(-1))); |
| |
| struct VertexAttributes { |
| float pos[2]; |
| float tex[2]; |
| }; |
| const std::vector<VertexAttributes> fullscreenTriVerts = { |
| // clang-format off |
| { .pos = { -1.0f, -1.0f }, .tex = { 0.0f, 0.0f }, }, |
| { .pos = { 3.0f, -1.0f }, .tex = { 2.0f, 0.0f }, }, |
| { .pos = { -1.0f, 3.0f }, .tex = { 0.0f, 2.0f }, }, |
| // clang-format on |
| }; |
| ScopedGlBuffer buffer(*mGl); |
| mGl->glBindBuffer(GL_ARRAY_BUFFER, buffer); |
| mGl->glBufferData(GL_ARRAY_BUFFER, sizeof(VertexAttributes) * fullscreenTriVerts.size(), |
| fullscreenTriVerts.data(), GL_STATIC_DRAW); |
| |
| mGl->glUseProgram(program); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| mGl->glViewport(0, 0, width, height); |
| mGl->glClearColor(0.0f, 0.0f, 0.0f, 0.0f); |
| mGl->glClear(GL_COLOR_BUFFER_BIT); |
| mGl->glBindBuffer(GL_ARRAY_BUFFER, buffer); |
| mGl->glEnableVertexAttribArray(0); |
| mGl->glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(VertexAttributes), |
| (GLvoid*)offsetof(VertexAttributes, pos)); |
| mGl->glEnableVertexAttribArray(1); |
| mGl->glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(VertexAttributes), |
| (GLvoid*)offsetof(VertexAttributes, tex)); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| EGLImageKHR blitAhbImage = mGl->eglCreateImageKHR( |
| mDisplay, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, ahb, ahbImageAttribs); |
| ASSERT_THAT(blitAhbImage, Not(Eq(EGL_NO_IMAGE_KHR))); |
| |
| ScopedGlTexture blitAhbTexture(*mGl); |
| mGl->glBindTexture(GL_TEXTURE_EXTERNAL_OES, blitAhbTexture); |
| mGl->glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, blitAhbImage); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| mGl->glActiveTexture(GL_TEXTURE0); |
| mGl->glBindTexture(GL_TEXTURE_EXTERNAL_OES, blitAhbTexture); |
| mGl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
| mGl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| mGl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| |
| mGl->glUniform1i(textureUniformLoc, 0); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| mGl->glDrawArrays(GL_TRIANGLES, 0, 3); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| mGl->glFinish(); |
| } |
| |
| // Readback and compare: |
| { |
| for (int x = 0; x < width; x++) { |
| for (int y = 0; y < height; y++) { |
| EXPECT_THAT(GetPixelAt(x, y), IsOkWith(uploadPixel)); |
| } |
| } |
| |
| mGl->glBindFramebuffer(GL_FRAMEBUFFER, 0); |
| } |
| } |
| |
| TEST_P(GfxstreamEnd2EndGlTest, AhbExternalOesTextureBlit) { |
| const uint32_t width = 2; |
| const uint32_t height = 2; |
| |
| ScopedGlTexture readbackTexture(*mGl); |
| mGl->glBindTexture(GL_TEXTURE_2D, readbackTexture); |
| mGl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
| mGl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
| mGl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| mGl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| mGl->glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height); |
| mGl->glBindTexture(GL_TEXTURE_2D, 0); |
| ScopedGlFramebuffer readbackFramebuffer(*mGl); |
| mGl->glBindFramebuffer(GL_FRAMEBUFFER, readbackFramebuffer); |
| mGl->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, |
| readbackTexture, 0); |
| ASSERT_THAT(mGl->glCheckFramebufferStatus(GL_FRAMEBUFFER), Eq(GL_FRAMEBUFFER_COMPLETE)); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| const auto lockPixel = PixelR8G8B8A8(11, 22, 33, 44); |
| |
| auto ahb = GFXSTREAM_ASSERT(ScopedAHardwareBuffer::Allocate( |
| *mGralloc, width, height, GFXSTREAM_AHB_FORMAT_R8G8B8A8_UNORM)); |
| |
| const EGLint ahbImageAttribs[] = { |
| // clang-format off |
| EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, |
| EGL_NONE, |
| // clang-format on |
| }; |
| |
| // Initialize AHB with `lockPixel` |
| GFXSTREAM_ASSERT(FillAhb(ahb, lockPixel)); |
| |
| // Blit from AHB to an additional framebuffer and readback: |
| { |
| const std::string blitTextureVertSource = R"(\ |
| #version 300 es |
| |
| layout (location = 0) in vec2 pos; |
| layout (location = 1) in vec2 tex; |
| |
| out vec2 vTex; |
| |
| void main() { |
| gl_Position = vec4(pos, 0.0, 1.0); |
| vTex = tex; |
| })"; |
| |
| const std::string blitTextureFragSource = R"(\ |
| #version 300 es |
| #extension GL_OES_EGL_image_external |
| |
| precision highp float; |
| |
| uniform samplerExternalOES uTexture; |
| |
| in vec2 vTex; |
| |
| out vec4 oColor; |
| |
| void main() { |
| oColor = texture(uTexture, vTex); |
| })"; |
| |
| ScopedGlProgram program = |
| GFXSTREAM_ASSERT(SetUpProgram(blitTextureVertSource, blitTextureFragSource)); |
| |
| GLint textureUniformLoc = mGl->glGetUniformLocation(program, "uTexture"); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| ASSERT_THAT(textureUniformLoc, Not(Eq(-1))); |
| |
| struct VertexAttributes { |
| float pos[2]; |
| float tex[2]; |
| }; |
| const std::vector<VertexAttributes> fullscreenTriVerts = { |
| // clang-format off |
| { .pos = { -1.0f, -1.0f }, .tex = { 0.0f, 0.0f }, }, |
| { .pos = { 3.0f, -1.0f }, .tex = { 2.0f, 0.0f }, }, |
| { .pos = { -1.0f, 3.0f }, .tex = { 0.0f, 2.0f }, }, |
| // clang-format on |
| }; |
| ScopedGlBuffer buffer(*mGl); |
| mGl->glBindBuffer(GL_ARRAY_BUFFER, buffer); |
| mGl->glBufferData(GL_ARRAY_BUFFER, sizeof(VertexAttributes) * fullscreenTriVerts.size(), |
| fullscreenTriVerts.data(), GL_STATIC_DRAW); |
| |
| mGl->glUseProgram(program); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| mGl->glViewport(0, 0, width, height); |
| mGl->glClearColor(0.0f, 0.0f, 0.0f, 0.0f); |
| mGl->glClear(GL_COLOR_BUFFER_BIT); |
| mGl->glBindBuffer(GL_ARRAY_BUFFER, buffer); |
| mGl->glEnableVertexAttribArray(0); |
| mGl->glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(VertexAttributes), |
| (GLvoid*)offsetof(VertexAttributes, pos)); |
| mGl->glEnableVertexAttribArray(1); |
| mGl->glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(VertexAttributes), |
| (GLvoid*)offsetof(VertexAttributes, tex)); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| EGLImageKHR blitAhbImage = mGl->eglCreateImageKHR( |
| mDisplay, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, ahb, ahbImageAttribs); |
| ASSERT_THAT(blitAhbImage, Not(Eq(EGL_NO_IMAGE_KHR))); |
| |
| ScopedGlTexture blitAhbTexture(*mGl); |
| mGl->glBindTexture(GL_TEXTURE_EXTERNAL_OES, blitAhbTexture); |
| mGl->glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, blitAhbImage); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| mGl->glActiveTexture(GL_TEXTURE0); |
| mGl->glBindTexture(GL_TEXTURE_EXTERNAL_OES, blitAhbTexture); |
| mGl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
| mGl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| mGl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| |
| mGl->glUniform1i(textureUniformLoc, 0); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| mGl->glDrawArrays(GL_TRIANGLES, 0, 3); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| mGl->glFinish(); |
| |
| for (int x = 0; x < width; x++) { |
| for (int y = 0; y < height; y++) { |
| EXPECT_THAT(GetPixelAt(x, y), IsOkWith(lockPixel)); |
| } |
| } |
| |
| mGl->glBindFramebuffer(GL_FRAMEBUFFER, 0); |
| } |
| } |
| |
| TEST_P(GfxstreamEnd2EndGlTest, AhbExternalOesTextureBlitProgramBinary) { |
| if (GetParam().with_features.count("GlProgramBinaryLinkStatus") == 0) { |
| GTEST_SKIP() << "Skipping test, GlProgramBinaryLinkStatus not enabled."; |
| } |
| |
| const uint32_t width = 2; |
| const uint32_t height = 2; |
| |
| ScopedGlTexture readbackTexture(*mGl); |
| mGl->glBindTexture(GL_TEXTURE_2D, readbackTexture); |
| mGl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
| mGl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
| mGl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| mGl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| mGl->glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height); |
| mGl->glBindTexture(GL_TEXTURE_2D, 0); |
| ScopedGlFramebuffer readbackFramebuffer(*mGl); |
| mGl->glBindFramebuffer(GL_FRAMEBUFFER, readbackFramebuffer); |
| mGl->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, |
| readbackTexture, 0); |
| ASSERT_THAT(mGl->glCheckFramebufferStatus(GL_FRAMEBUFFER), Eq(GL_FRAMEBUFFER_COMPLETE)); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| const auto lockPixel = PixelR8G8B8A8(11, 22, 33, 44); |
| |
| auto ahb = GFXSTREAM_ASSERT(ScopedAHardwareBuffer::Allocate( |
| *mGralloc, width, height, GFXSTREAM_AHB_FORMAT_R8G8B8A8_UNORM)); |
| |
| const EGLint ahbImageAttribs[] = { |
| // clang-format off |
| EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, |
| EGL_NONE, |
| // clang-format on |
| }; |
| |
| // Initialize AHB with `lockPixel` |
| GFXSTREAM_ASSERT(FillAhb(ahb, lockPixel)); |
| |
| // Setup blit program: |
| GLenum programBinaryFormat = GL_NONE; |
| std::vector<uint8_t> programBinaryData; |
| { |
| const std::string vertSource = R"(\ |
| #version 300 es |
| layout (location = 0) in vec2 pos; |
| layout (location = 1) in vec2 tex; |
| out vec2 vTex; |
| void main() { |
| gl_Position = vec4(pos, 0.0, 1.0); |
| vTex = tex; |
| })"; |
| |
| const std::string fragSource = R"(\ |
| #version 300 es |
| #extension GL_OES_EGL_image_external |
| precision highp float; |
| uniform samplerExternalOES uTexture; |
| in vec2 vTex; |
| out vec4 oColor; |
| void main() { |
| oColor = texture(uTexture, vTex); |
| })"; |
| |
| ScopedGlProgram program = GFXSTREAM_ASSERT(SetUpProgram(vertSource, fragSource)); |
| |
| GLint programBinaryLength = 0; |
| mGl->glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH, &programBinaryLength); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| programBinaryData.resize(programBinaryLength); |
| |
| GLint readProgramBinaryLength = 0; |
| mGl->glGetProgramBinary(program, programBinaryLength, &readProgramBinaryLength, |
| &programBinaryFormat, programBinaryData.data()); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| ASSERT_THAT(readProgramBinaryLength, Eq(programBinaryLength)); |
| } |
| |
| // Blit from AHB to an additional framebuffer and readback: |
| { |
| ScopedGlProgram program = |
| GFXSTREAM_ASSERT(SetUpProgram(programBinaryFormat, programBinaryData)); |
| ASSERT_THAT(program, Not(Eq(0))); |
| |
| GLint textureUniformLoc = mGl->glGetUniformLocation(program, "uTexture"); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| ASSERT_THAT(textureUniformLoc, Not(Eq(-1))); |
| |
| struct VertexAttributes { |
| float pos[2]; |
| float tex[2]; |
| }; |
| const std::vector<VertexAttributes> fullscreenTriVerts = { |
| // clang-format off |
| { .pos = { -1.0f, -1.0f }, .tex = { 0.0f, 0.0f }, }, |
| { .pos = { 3.0f, -1.0f }, .tex = { 2.0f, 0.0f }, }, |
| { .pos = { -1.0f, 3.0f }, .tex = { 0.0f, 2.0f }, }, |
| // clang-format on |
| }; |
| ScopedGlBuffer buffer(*mGl); |
| mGl->glBindBuffer(GL_ARRAY_BUFFER, buffer); |
| mGl->glBufferData(GL_ARRAY_BUFFER, sizeof(VertexAttributes) * fullscreenTriVerts.size(), |
| fullscreenTriVerts.data(), GL_STATIC_DRAW); |
| |
| mGl->glUseProgram(program); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| mGl->glViewport(0, 0, width, height); |
| mGl->glClearColor(0.0f, 0.0f, 0.0f, 0.0f); |
| mGl->glClear(GL_COLOR_BUFFER_BIT); |
| mGl->glBindBuffer(GL_ARRAY_BUFFER, buffer); |
| mGl->glEnableVertexAttribArray(0); |
| mGl->glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(VertexAttributes), |
| (GLvoid*)offsetof(VertexAttributes, pos)); |
| mGl->glEnableVertexAttribArray(1); |
| mGl->glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(VertexAttributes), |
| (GLvoid*)offsetof(VertexAttributes, tex)); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| EGLImageKHR blitAhbImage = mGl->eglCreateImageKHR( |
| mDisplay, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, ahb, ahbImageAttribs); |
| ASSERT_THAT(blitAhbImage, Not(Eq(EGL_NO_IMAGE_KHR))); |
| |
| ScopedGlTexture blitAhbTexture(*mGl); |
| mGl->glBindTexture(GL_TEXTURE_EXTERNAL_OES, blitAhbTexture); |
| mGl->glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, blitAhbImage); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| mGl->glActiveTexture(GL_TEXTURE0); |
| mGl->glBindTexture(GL_TEXTURE_EXTERNAL_OES, blitAhbTexture); |
| mGl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
| mGl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| mGl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| |
| mGl->glUniform1i(textureUniformLoc, 0); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| mGl->glDrawArrays(GL_TRIANGLES, 0, 3); |
| ASSERT_THAT(mGl->glGetError(), Eq(GL_NO_ERROR)); |
| |
| mGl->glFinish(); |
| |
| for (int x = 0; x < width; x++) { |
| for (int y = 0; y < height; y++) { |
| EXPECT_THAT(GetPixelAt(x, y), IsOkWith(lockPixel)); |
| } |
| } |
| |
| mGl->glBindFramebuffer(GL_FRAMEBUFFER, 0); |
| } |
| } |
| |
| INSTANTIATE_TEST_CASE_P(GfxstreamEnd2EndTests, GfxstreamEnd2EndGlTest, |
| ::testing::ValuesIn({ |
| TestParams{ |
| .with_gl = true, |
| .with_vk = false, |
| .with_features = {"GlProgramBinaryLinkStatus"}, |
| }, |
| TestParams{ |
| .with_gl = true, |
| .with_vk = true, |
| .with_features = {"GlProgramBinaryLinkStatus"}, |
| }, |
| }), |
| &GetTestName); |
| |
| } // namespace |
| } // namespace tests |
| } // namespace gfxstream |