| /* |
| * Copyright (C) 2017 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 <EGL/egl.h> |
| #include <EGL/eglext.h> |
| #include <GLES2/gl2.h> |
| #include <GLES2/gl2ext.h> |
| #include <jni.h> |
| #include <stdlib.h> |
| #include <android/hardware_buffer.h> |
| #include <android/log.h> |
| #include <cmath> |
| #include <string> |
| #include <sstream> |
| |
| #define LOG_TAG "VrExtensionsJni" |
| #define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE,LOG_TAG,__VA_ARGS__) |
| |
| using PFNEGLGETNATIVECLIENTBUFFERANDROID = |
| EGLClientBuffer(EGLAPIENTRYP)(const AHardwareBuffer* buffer); |
| |
| using PFNGLEGLIMAGETARGETTEXTURE2DOESPROC = void(GL_APIENTRYP)(GLenum target, |
| void* image); |
| |
| using PFNGLBUFFERSTORAGEEXTERNALEXTPROC = |
| void(GL_APIENTRYP)(GLenum target, GLintptr offset, GLsizeiptr size, |
| void* clientBuffer, GLbitfield flags); |
| |
| using PFNGLMAPBUFFERRANGEPROC = void*(GL_APIENTRYP)(GLenum target, |
| GLintptr offset, |
| GLsizeiptr length, |
| GLbitfield access); |
| |
| using PFNGLUNMAPBUFFERPROC = void*(GL_APIENTRYP)(GLenum target); |
| |
| PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES; |
| PFNEGLGETNATIVECLIENTBUFFERANDROID eglGetNativeClientBufferANDROID; |
| PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR; |
| PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC glFramebufferTextureMultiviewOVR; |
| PFNGLFRAMEBUFFERTEXTUREMULTISAMPLEMULTIVIEWOVRPROC |
| glFramebufferTextureMultisampleMultiviewOVR; |
| PFNGLBUFFERSTORAGEEXTERNALEXTPROC glBufferStorageExternalEXT; |
| PFNGLMAPBUFFERRANGEPROC glMapBufferRange; |
| PFNGLUNMAPBUFFERPROC glUnmapBuffer; |
| |
| #define NO_ERROR 0 |
| #define GL_UNIFORM_BUFFER 0x8A11 |
| |
| // Declare flags that are added to MapBufferRange via EXT_buffer_storage. |
| // https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_buffer_storage.txt |
| #define GL_MAP_PERSISTENT_BIT_EXT 0x0040 |
| #define GL_MAP_COHERENT_BIT_EXT 0x0080 |
| |
| // Declare tokens added as a part of EGL_EXT_image_gl_colorspace. |
| #define EGL_GL_COLORSPACE_DEFAULT_EXT 0x314D |
| |
| #define LOAD_PROC(NAME, TYPE) \ |
| NAME = reinterpret_cast<TYPE>(eglGetProcAddress(# NAME)) |
| |
| #define ASSERT(condition, format, args...) \ |
| if (!(condition)) { \ |
| fail(env, format, ## args); \ |
| return; \ |
| } |
| |
| #define ASSERT_TRUE(a) \ |
| ASSERT((a), "assert failed on (" #a ") at " __FILE__ ":%d", __LINE__) |
| #define ASSERT_FALSE(a) \ |
| ASSERT(!(a), "assert failed on (!" #a ") at " __FILE__ ":%d", __LINE__) |
| #define ASSERT_EQ(a, b) \ |
| ASSERT((a) == (b), "assert failed on (" #a ") at " __FILE__ ":%d", __LINE__) |
| #define ASSERT_NE(a, b) \ |
| ASSERT((a) != (b), "assert failed on (" #a ") at " __FILE__ ":%d", __LINE__) |
| #define ASSERT_GT(a, b) \ |
| ASSERT((a) > (b), "assert failed on (" #a ") at " __FILE__ ":%d", __LINE__) |
| #define ASSERT_NEAR(a, b, delta) \ |
| ASSERT((a - delta) <= (b) && (b) <= (a + delta), \ |
| "assert failed on (" #a ") at " __FILE__ ":%d", __LINE__) |
| |
| void fail(JNIEnv* env, const char* format, ...) { |
| va_list args; |
| va_start(args, format); |
| char* msg; |
| vasprintf(&msg, format, args); |
| va_end(args); |
| jclass exClass; |
| const char* className = "java/lang/AssertionError"; |
| exClass = env->FindClass(className); |
| env->ThrowNew(exClass, msg); |
| free(msg); |
| } |
| |
| static void testEglImageArray(JNIEnv* env, AHardwareBuffer_Desc desc, |
| int nsamples) { |
| ASSERT_GT(desc.layers, 1); |
| AHardwareBuffer* hwbuffer = nullptr; |
| int error = AHardwareBuffer_allocate(&desc, &hwbuffer); |
| ASSERT_FALSE(error); |
| // Create EGLClientBuffer from the AHardwareBuffer. |
| EGLClientBuffer native_buffer = eglGetNativeClientBufferANDROID(hwbuffer); |
| ASSERT_TRUE(native_buffer); |
| // Create EGLImage from EGLClientBuffer. |
| EGLint attrs[] = {EGL_NONE}; |
| EGLImageKHR image = |
| eglCreateImageKHR(eglGetCurrentDisplay(), EGL_NO_CONTEXT, |
| EGL_NATIVE_BUFFER_ANDROID, native_buffer, attrs); |
| ASSERT_TRUE(image); |
| // Create OpenGL texture from the EGLImage. |
| GLuint texid; |
| glGenTextures(1, &texid); |
| glBindTexture(GL_TEXTURE_2D_ARRAY, texid); |
| glEGLImageTargetTexture2DOES(GL_TEXTURE_2D_ARRAY, image); |
| ASSERT_EQ(glGetError(), GL_NO_ERROR); |
| // Create FBO and add multiview attachment. |
| GLuint fboid; |
| glGenFramebuffers(1, &fboid); |
| glBindFramebuffer(GL_FRAMEBUFFER, fboid); |
| const GLint miplevel = 0; |
| const GLint base_view = 0; |
| const GLint num_views = desc.layers; |
| if (nsamples == 1) { |
| glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, |
| texid, miplevel, base_view, num_views); |
| } else { |
| glFramebufferTextureMultisampleMultiviewOVR( |
| GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texid, miplevel, nsamples, |
| base_view, num_views); |
| } |
| ASSERT_EQ(glGetError(), GL_NO_ERROR); |
| ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), |
| GL_FRAMEBUFFER_COMPLETE); |
| // Release memory. |
| glDeleteTextures(1, &texid); |
| glDeleteFramebuffers(1, &fboid); |
| AHardwareBuffer_release(hwbuffer); |
| } |
| |
| extern "C" JNIEXPORT void JNICALL |
| Java_android_vr_cts_VrExtensionBehaviorTest_nativeTestEglImageArray( |
| JNIEnv* env, jclass /* unused */) { |
| // First, load entry points provided by extensions. |
| LOAD_PROC(glEGLImageTargetTexture2DOES, |
| PFNGLEGLIMAGETARGETTEXTURE2DOESPROC); |
| ASSERT_NE(glEGLImageTargetTexture2DOES, nullptr); |
| LOAD_PROC(eglGetNativeClientBufferANDROID, |
| PFNEGLGETNATIVECLIENTBUFFERANDROID); |
| ASSERT_NE(eglGetNativeClientBufferANDROID, nullptr); |
| LOAD_PROC(eglCreateImageKHR, PFNEGLCREATEIMAGEKHRPROC); |
| ASSERT_NE(eglCreateImageKHR, nullptr); |
| LOAD_PROC(glFramebufferTextureMultiviewOVR, |
| PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC); |
| ASSERT_NE(glFramebufferTextureMultiviewOVR, nullptr); |
| LOAD_PROC(glFramebufferTextureMultisampleMultiviewOVR, |
| PFNGLFRAMEBUFFERTEXTUREMULTISAMPLEMULTIVIEWOVRPROC); |
| ASSERT_NE(glFramebufferTextureMultisampleMultiviewOVR, nullptr); |
| // Try creating a 32x32 AHardwareBuffer and attaching it to a multiview |
| // framebuffer, with various formats and depths. |
| AHardwareBuffer_Desc desc = {}; |
| desc.width = 32; |
| desc.height = 32; |
| desc.usage = AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE | |
| AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT; |
| const int layers[] = {2, 4}; |
| const int formats[] = { |
| AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM, |
| AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, |
| AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM, |
| AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT, |
| // Do not test AHARDWAREBUFFER_FORMAT_BLOB, it isn't color-renderable. |
| }; |
| const int samples[] = {1, 2, 4}; |
| for (int nsamples : samples) { |
| for (auto nlayers : layers) { |
| for (auto format : formats) { |
| desc.layers = nlayers; |
| desc.format = format; |
| testEglImageArray(env, desc, nsamples); |
| } |
| } |
| } |
| } |
| |
| static void testExternalBuffer(JNIEnv* env, uint64_t usage, bool write_hwbuffer, |
| const std::string& test_string) { |
| // Create a blob AHardwareBuffer suitable for holding the string. |
| AHardwareBuffer_Desc desc = {}; |
| desc.width = test_string.size(); |
| desc.height = 1; |
| desc.layers = 1; |
| desc.format = AHARDWAREBUFFER_FORMAT_BLOB; |
| desc.usage = usage; |
| AHardwareBuffer* hwbuffer = nullptr; |
| int error = AHardwareBuffer_allocate(&desc, &hwbuffer); |
| ASSERT_EQ(error, NO_ERROR); |
| // Create EGLClientBuffer from the AHardwareBuffer. |
| EGLClientBuffer native_buffer = eglGetNativeClientBufferANDROID(hwbuffer); |
| ASSERT_TRUE(native_buffer); |
| // Create uniform buffer from EGLClientBuffer. |
| const GLbitfield flags = GL_MAP_READ_BIT | GL_MAP_WRITE_BIT | |
| GL_MAP_COHERENT_BIT_EXT | GL_MAP_PERSISTENT_BIT_EXT; |
| GLuint buf = 0; |
| glGenBuffers(1, &buf); |
| glBindBuffer(GL_UNIFORM_BUFFER, buf); |
| ASSERT_EQ(glGetError(), GL_NO_ERROR); |
| const GLsizeiptr bufsize = desc.width * desc.height; |
| glBufferStorageExternalEXT(GL_UNIFORM_BUFFER, 0, |
| bufsize, native_buffer, flags); |
| ASSERT_EQ(glGetError(), GL_NO_ERROR); |
| // Obtain a writeable pointer using either OpenGL or the Android API, |
| // then copy the test string into it. |
| if (write_hwbuffer) { |
| void* data = nullptr; |
| error = AHardwareBuffer_lock(hwbuffer, |
| AHARDWAREBUFFER_USAGE_CPU_READ_RARELY, -1, |
| NULL, &data); |
| ASSERT_EQ(error, NO_ERROR); |
| ASSERT_TRUE(data); |
| memcpy(data, test_string.c_str(), test_string.size()); |
| error = AHardwareBuffer_unlock(hwbuffer, nullptr); |
| ASSERT_EQ(error, NO_ERROR); |
| } else { |
| void* data = |
| glMapBufferRange(GL_UNIFORM_BUFFER, 0, bufsize, |
| GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT_EXT); |
| ASSERT_EQ(glGetError(), GL_NO_ERROR); |
| ASSERT_TRUE(data); |
| memcpy(data, test_string.c_str(), test_string.size()); |
| glUnmapBuffer(GL_UNIFORM_BUFFER); |
| ASSERT_EQ(glGetError(), GL_NO_ERROR); |
| } |
| // Obtain a readable pointer and verify the data. |
| void* data = glMapBufferRange(GL_UNIFORM_BUFFER, 0, bufsize, GL_MAP_READ_BIT); |
| ASSERT_TRUE(data); |
| ASSERT_EQ(strncmp(static_cast<char*>(data), test_string.c_str(), |
| test_string.size()), 0); |
| glUnmapBuffer(GL_UNIFORM_BUFFER); |
| ASSERT_EQ(glGetError(), GL_NO_ERROR); |
| AHardwareBuffer_release(hwbuffer); |
| } |
| |
| extern "C" JNIEXPORT void JNICALL |
| Java_android_vr_cts_VrExtensionBehaviorTest_nativeTestExternalBuffer( |
| JNIEnv* env, jclass /* unused */) { |
| // First, check for EXT_external_buffer in the extension string. |
| auto exts = reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS)); |
| ASSERT_TRUE(exts && strstr(exts, "GL_EXT_external_buffer")); |
| // Next, load entry points provided by extensions. |
| LOAD_PROC(eglGetNativeClientBufferANDROID, PFNEGLGETNATIVECLIENTBUFFERANDROID); |
| ASSERT_NE(eglGetNativeClientBufferANDROID, nullptr); |
| LOAD_PROC(glBufferStorageExternalEXT, PFNGLBUFFERSTORAGEEXTERNALEXTPROC); |
| ASSERT_NE(glBufferStorageExternalEXT, nullptr); |
| LOAD_PROC(glMapBufferRange, PFNGLMAPBUFFERRANGEPROC); |
| ASSERT_NE(glMapBufferRange, nullptr); |
| LOAD_PROC(glUnmapBuffer, PFNGLUNMAPBUFFERPROC); |
| ASSERT_NE(glUnmapBuffer, nullptr); |
| const uint64_t usage = AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN | |
| AHARDWAREBUFFER_USAGE_CPU_READ_RARELY | |
| AHARDWAREBUFFER_USAGE_GPU_DATA_BUFFER | |
| AHARDWAREBUFFER_USAGE_SENSOR_DIRECT_DATA; |
| const std::string test_string = "Hello, world."; |
| // First try writing to the buffer using OpenGL, then try writing to it via |
| // the AHardwareBuffer API. |
| testExternalBuffer(env, usage, false, test_string); |
| testExternalBuffer(env, usage, true, test_string); |
| } |
| |
| const GLchar* const kSrgbVertexCode = R"( |
| // vertex position in clip space (-1..1) |
| attribute vec4 position; |
| varying mediump vec2 uv; |
| void main() { |
| gl_Position = position; |
| uv = vec2(0.5 * (position.x + 1.0), 0.5); |
| })"; |
| |
| const GLchar* const kSrgbFragmentCode = R"( |
| varying mediump vec2 uv; |
| uniform sampler2D tex; |
| void main() { |
| gl_FragColor = texture2D(tex, uv); |
| })"; |
| |
| static inline float SrgbChannelToLinear(float cs) { |
| if (cs <= 0.04045) |
| return cs / 12.92f; |
| else |
| return std::pow((cs + 0.055f) / 1.055f, 2.4f); |
| } |
| |
| static inline float LinearChannelToSrgb(float cs) { |
| if (cs <= 0.0f) |
| return 0.0f; |
| else if (cs < 0.0031308f) |
| return 12.92f * cs; |
| else if (cs < 1.0f) |
| return 1.055f * std::pow(cs, 0.41666f) - 0.055f; |
| else |
| return 1.0f; |
| } |
| |
| static uint32_t SrgbColorToLinear(uint32_t color) { |
| float r = SrgbChannelToLinear((color & 0xff) / 255.0f); |
| float g = SrgbChannelToLinear(((color >> 8) & 0xff) / 255.0f); |
| float b = SrgbChannelToLinear(((color >> 16) & 0xff) / 255.0f); |
| uint32_t r8 = r * 255.0f; |
| uint32_t g8 = g * 255.0f; |
| uint32_t b8 = b * 255.0f; |
| uint32_t a8 = color >> 24; |
| return (a8 << 24) | (b8 << 16) | (g8 << 8) | r8; |
| } |
| |
| static uint32_t LinearColorToSrgb(uint32_t color) { |
| float r = LinearChannelToSrgb((color & 0xff) / 255.0f); |
| float g = LinearChannelToSrgb(((color >> 8) & 0xff) / 255.0f); |
| float b = LinearChannelToSrgb(((color >> 16) & 0xff) / 255.0f); |
| uint32_t r8 = r * 255.0f; |
| uint32_t g8 = g * 255.0f; |
| uint32_t b8 = b * 255.0f; |
| uint32_t a8 = color >> 24; |
| return (a8 << 24) | (b8 << 16) | (g8 << 8) | r8; |
| } |
| |
| static uint32_t LerpColor(uint32_t color0, uint32_t color1, float t) { |
| float r0 = (color0 & 0xff) / 255.0f; |
| float g0 = ((color0 >> 8) & 0xff) / 255.0f; |
| float b0 = ((color0 >> 16) & 0xff) / 255.0f; |
| float a0 = ((color0 >> 24) & 0xff) / 255.0f; |
| float r1 = (color1 & 0xff) / 255.0f; |
| float g1 = ((color1 >> 8) & 0xff) / 255.0f; |
| float b1 = ((color1 >> 16) & 0xff) / 255.0f; |
| float a1 = ((color1 >> 24) & 0xff) / 255.0f; |
| uint32_t r8 = (r0 * (1.0f - t) + r1 * t) * 255.0f; |
| uint32_t g8 = (g0 * (1.0f - t) + g1 * t) * 255.0f; |
| uint32_t b8 = (b0 * (1.0f - t) + b1 * t) * 255.0f; |
| uint32_t a8 = (a0 * (1.0f - t) + a1 * t) * 255.0f; |
| return (a8 << 24) | (b8 << 16) | (g8 << 8) | r8; |
| } |
| |
| // Choose an odd-numbered framebuffer width so that we can |
| // extract the middle pixel of a gradient. |
| constexpr uint32_t kFramebufferWidth = 31; |
| |
| // Declare the pixel data for the 2x1 texture. |
| // Color components are ordered like this: AABBGGRR |
| constexpr uint32_t kTextureData[] = { |
| 0xff800000, // Half-Blue |
| 0xff000080, // Half-Red |
| }; |
| constexpr uint32_t kTextureWidth = sizeof(kTextureData) / sizeof(kTextureData[0]); |
| |
| // Declare expected values for the middle pixel for various sampling behaviors. |
| const uint32_t kExpectedMiddlePixel_NoSrgb = LerpColor(kTextureData[0], kTextureData[1], 0.5f); |
| const uint32_t kExpectedMiddlePixel_LinearizeAfterFiltering = |
| SrgbColorToLinear(kExpectedMiddlePixel_NoSrgb); |
| const uint32_t kExpectedMiddlePixel_LinearizeBeforeFiltering = |
| LerpColor(SrgbColorToLinear(kTextureData[0]), SrgbColorToLinear(kTextureData[1]), 0.5f); |
| |
| // Declare expected values for the final pixel color for various blending behaviors. |
| constexpr uint32_t kBlendDestColor = 0xff000080; |
| constexpr uint32_t kBlendSourceColor = 0x80800000; |
| const uint32_t kExpectedBlendedPixel_NoSrgb = LerpColor(kBlendSourceColor, kBlendDestColor, 0.5f); |
| const uint32_t kExpectedBlendedPixel_Srgb = |
| LinearColorToSrgb(LerpColor(kBlendSourceColor, SrgbColorToLinear(kBlendDestColor), 0.5f)); |
| |
| // Define a set of test flags. Not using an enum to avoid lots of casts. |
| namespace SrgbFlag { |
| constexpr uint32_t kHardwareBuffer = 1 << 0; |
| constexpr uint32_t kSrgbFormat = 1 << 1; |
| constexpr uint32_t kEglColorspaceDefault = 1 << 2; |
| constexpr uint32_t kEglColorspaceLinear = 1 << 3; |
| constexpr uint32_t kEglColorspaceSrgb = 1 << 4; |
| } // namespace SrgbFlag |
| |
| static void configureEglColorspace(EGLint attrs[4], uint32_t srgb_flags) { |
| if (srgb_flags & SrgbFlag::kEglColorspaceDefault) { |
| attrs[0] = EGL_GL_COLORSPACE_KHR; |
| attrs[1] = EGL_GL_COLORSPACE_DEFAULT_EXT; |
| } else if (srgb_flags & SrgbFlag::kEglColorspaceLinear) { |
| attrs[0] = EGL_GL_COLORSPACE_KHR; |
| attrs[1] = EGL_GL_COLORSPACE_LINEAR_KHR; |
| } else if (srgb_flags & SrgbFlag::kEglColorspaceSrgb) { |
| attrs[0] = EGL_GL_COLORSPACE_KHR; |
| attrs[1] = EGL_GL_COLORSPACE_SRGB_KHR; |
| } else { |
| attrs[0] = EGL_NONE; |
| attrs[1] = EGL_NONE; |
| } |
| attrs[2] = EGL_NONE; |
| attrs[3] = EGL_NONE; |
| } |
| |
| static void printSrgbFlags(std::ostream& out, uint32_t srgb_flags) { |
| if (srgb_flags & SrgbFlag::kHardwareBuffer) { |
| out << " AHardwareBuffer"; |
| } |
| if (srgb_flags & SrgbFlag::kSrgbFormat) { |
| out << " GL_SRGB_ALPHA"; |
| } |
| if (srgb_flags & SrgbFlag::kEglColorspaceDefault) { |
| out << " EGL_GL_COLORSPACE_DEFAULT_KHR"; |
| } |
| if (srgb_flags & SrgbFlag::kEglColorspaceLinear) { |
| out << " EGL_GL_COLORSPACE_LINEAR_KHR"; |
| } |
| if (srgb_flags & SrgbFlag::kEglColorspaceSrgb) { |
| out << " EGL_GL_COLORSPACE_SRGB_KHR"; |
| } |
| } |
| |
| // Draws a gradient and extracts the middle pixel. Returns void to allow ASSERT to work. |
| static void testLinearMagnification(JNIEnv* env, uint32_t flags, uint32_t* middle_pixel) { |
| const bool use_hwbuffer = flags & SrgbFlag::kHardwareBuffer; |
| const bool use_srgb_format = flags & SrgbFlag::kSrgbFormat; |
| GLuint srgbtex; |
| glGenTextures(1, &srgbtex); |
| glBindTexture(GL_TEXTURE_2D, srgbtex); |
| if (use_hwbuffer) { |
| // Create a one-dimensional AHardwareBuffer. |
| AHardwareBuffer_Desc desc = {}; |
| desc.width = kTextureWidth; |
| desc.height = 1; |
| desc.layers = 1; |
| desc.format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM; |
| desc.usage = |
| AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE | AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT; |
| AHardwareBuffer* hwbuffer = nullptr; |
| int error = AHardwareBuffer_allocate(&desc, &hwbuffer); |
| ASSERT_EQ(error, NO_ERROR); |
| // Populate the pixels. |
| uint32_t* pixels = nullptr; |
| error = AHardwareBuffer_lock(hwbuffer, AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, -1, nullptr, |
| reinterpret_cast<void**>(&pixels)); |
| ASSERT_EQ(error, NO_ERROR); |
| ASSERT_TRUE(pixels); |
| memcpy(pixels, kTextureData, sizeof(kTextureData)); |
| error = AHardwareBuffer_unlock(hwbuffer, nullptr); |
| ASSERT_EQ(error, NO_ERROR); |
| // Create EGLClientBuffer from the AHardwareBuffer. |
| EGLClientBuffer native_buffer = eglGetNativeClientBufferANDROID(hwbuffer); |
| ASSERT_TRUE(native_buffer); |
| // Create EGLImage from EGLClientBuffer. |
| EGLint attrs[4]; |
| configureEglColorspace(attrs, flags); |
| EGLImageKHR image = eglCreateImageKHR(eglGetCurrentDisplay(), EGL_NO_CONTEXT, |
| EGL_NATIVE_BUFFER_ANDROID, native_buffer, attrs); |
| ASSERT_TRUE(image); |
| // Allocate the OpenGL texture using the EGLImage. |
| glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); |
| } else { |
| GLenum internal_format = use_srgb_format ? GL_SRGB8_ALPHA8_EXT : GL_RGBA8_OES; |
| GLenum format = use_srgb_format ? GL_SRGB_ALPHA_EXT : GL_RGBA; |
| glTexImage2D(GL_TEXTURE_2D, 0, internal_format, kTextureWidth, 1, 0, format, |
| GL_UNSIGNED_BYTE, kTextureData); |
| } |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| ASSERT_EQ(glGetError(), GL_NO_ERROR); |
| // Clear to an interesting constant color to make it easier to spot bugs. |
| glClearColor(1.0, 0.0, 0.5, 0.25); |
| glClear(GL_COLOR_BUFFER_BIT); |
| // Draw the texture. |
| const float kTriangleCoords[] = {-1, -1, -1, 1, 1, -1, 1, 1}; |
| glBindTexture(GL_TEXTURE_2D, srgbtex); |
| const int kPositionSlot = 0; |
| glVertexAttribPointer(kPositionSlot, 2, GL_FLOAT, false, 0, kTriangleCoords); |
| glEnableVertexAttribArray(kPositionSlot); |
| glViewport(0, 0, kFramebufferWidth, 1); |
| glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); |
| // Read back the framebuffer. |
| glReadPixels(kFramebufferWidth / 2, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, middle_pixel); |
| std::ostringstream flag_string; |
| printSrgbFlags(flag_string, flags); |
| LOGV("Filtered Result: %8.8X Flags =%s", *middle_pixel, flag_string.str().c_str()); |
| ASSERT_EQ(glGetError(), GL_NO_ERROR); |
| } |
| |
| // Blends a color into an (optionally) sRGB-encoded framebuffer and extracts the final color. |
| // Returns void to allow ASSERT to work. |
| static void testFramebufferBlending(JNIEnv* env, uint32_t flags, uint32_t* final_color) { |
| const bool use_hwbuffer = flags & SrgbFlag::kHardwareBuffer; |
| const bool use_srgb_format = flags & SrgbFlag::kSrgbFormat; |
| const bool override_egl_colorspace = use_hwbuffer && (flags & SrgbFlag::kEglColorspaceSrgb); |
| GLuint tex; |
| glGenTextures(1, &tex); |
| glBindTexture(GL_TEXTURE_2D, tex); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| // Create a 1x1 half-blue, half-opaque texture. |
| const uint32_t kTextureData[] = { |
| kBlendSourceColor, |
| }; |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, |
| GL_UNSIGNED_BYTE, kTextureData); |
| // Create 1x1 framebuffer object. |
| GLuint fbo; |
| glGenFramebuffers(1, &fbo); |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo); |
| GLuint fbotex; |
| glGenTextures(1, &fbotex); |
| glBindTexture(GL_TEXTURE_2D, fbotex); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| if (use_hwbuffer) { |
| AHardwareBuffer_Desc desc = {}; |
| desc.width = 1; |
| desc.height = 1; |
| desc.layers = 1; |
| desc.format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM; |
| desc.usage = |
| AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE | AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT; |
| AHardwareBuffer* hwbuffer = nullptr; |
| int error = AHardwareBuffer_allocate(&desc, &hwbuffer); |
| ASSERT_EQ(error, NO_ERROR); |
| // Create EGLClientBuffer from the AHardwareBuffer. |
| EGLClientBuffer native_buffer = eglGetNativeClientBufferANDROID(hwbuffer); |
| ASSERT_TRUE(native_buffer); |
| // Create EGLImage from EGLClientBuffer. |
| EGLint attrs[4]; |
| configureEglColorspace(attrs, flags); |
| EGLImageKHR image = eglCreateImageKHR(eglGetCurrentDisplay(), EGL_NO_CONTEXT, |
| EGL_NATIVE_BUFFER_ANDROID, native_buffer, attrs); |
| ASSERT_TRUE(image); |
| // Allocate the OpenGL texture using the EGLImage. |
| glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); |
| } else { |
| GLenum internal_format = use_srgb_format ? GL_SRGB8_ALPHA8_EXT : GL_RGBA8_OES; |
| GLenum format = use_srgb_format ? GL_SRGB_ALPHA_EXT : GL_RGBA; |
| glTexImage2D(GL_TEXTURE_2D, 0, internal_format, 1, 1, 0, format, |
| GL_UNSIGNED_BYTE, nullptr); |
| } |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, |
| GL_TEXTURE_2D, fbotex, 0); |
| ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GL_FRAMEBUFFER_COMPLETE); |
| ASSERT_EQ(glGetError(), GL_NO_ERROR); |
| // Clear to half-red. |
| if (use_srgb_format || override_egl_colorspace) { |
| glClearColor(SrgbChannelToLinear(0.5), 0.0, 0.0, 1.0); |
| } else { |
| glClearColor(0.5, 0.0, 0.0, 1.0); |
| } |
| glClear(GL_COLOR_BUFFER_BIT); |
| // Sanity check the cleared color. |
| uint32_t cleared_color = 0; |
| glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &cleared_color); |
| LOGV(" Cleared Color: %8.8X", cleared_color); |
| ASSERT_EQ(cleared_color, kBlendDestColor); |
| // Draw the texture. |
| glEnable(GL_BLEND); |
| glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); |
| const float kTriangleCoords[] = {-1, -1, -1, 1, 1, -1, 1, 1}; |
| glBindTexture(GL_TEXTURE_2D, tex); |
| const int kPositionSlot = 0; |
| glVertexAttribPointer(kPositionSlot, 2, GL_FLOAT, false, 0, kTriangleCoords); |
| glEnableVertexAttribArray(kPositionSlot); |
| glViewport(0, 0, 1, 1); |
| glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); |
| // Read back the framebuffer. |
| glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, final_color); |
| std::ostringstream flag_string; |
| printSrgbFlags(flag_string, flags); |
| LOGV("Blending Result: %8.8X Flags =%s", *final_color, flag_string.str().c_str()); |
| ASSERT_EQ(glGetError(), GL_NO_ERROR); |
| } |
| |
| extern "C" JNIEXPORT void JNICALL |
| Java_android_vr_cts_VrExtensionBehaviorTest_nativeTestSrgbBuffer( |
| JNIEnv* env, jclass /* unused */) { |
| // First, check the published extension strings against expectations. |
| const char *egl_exts = |
| eglQueryString(eglGetCurrentDisplay(), EGL_EXTENSIONS); |
| LOGV("EGL Extensions: %s", egl_exts); |
| ASSERT_TRUE(egl_exts); |
| bool egl_colorspace_supported = strstr(egl_exts, "EGL_EXT_image_gl_colorspace"); |
| auto gl_exts = reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS)); |
| LOGV("OpenGL Extensions: %s", gl_exts); |
| ASSERT_TRUE(gl_exts); |
| // Load ancillary entry points provided by extensions. |
| LOAD_PROC(eglGetNativeClientBufferANDROID, |
| PFNEGLGETNATIVECLIENTBUFFERANDROID); |
| ASSERT_NE(eglGetNativeClientBufferANDROID, nullptr); |
| LOAD_PROC(eglCreateImageKHR, PFNEGLCREATEIMAGEKHRPROC); |
| ASSERT_NE(eglCreateImageKHR, nullptr); |
| LOAD_PROC(glEGLImageTargetTexture2DOES, |
| PFNGLEGLIMAGETARGETTEXTURE2DOESPROC); |
| ASSERT_NE(glEGLImageTargetTexture2DOES, nullptr); |
| // Create a plain old one-dimensional FBO to render to. |
| GLuint fbo; |
| glGenFramebuffers(1, &fbo); |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo); |
| GLuint fbotex; |
| glGenTextures(1, &fbotex); |
| glBindTexture(GL_TEXTURE_2D, fbotex); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kFramebufferWidth, 1, 0, GL_RGBA, |
| GL_UNSIGNED_BYTE, nullptr); |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, |
| GL_TEXTURE_2D, fbotex, 0); |
| ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GL_FRAMEBUFFER_COMPLETE); |
| ASSERT_EQ(glGetError(), GL_NO_ERROR); |
| // Compile and link shaders. |
| int program = glCreateProgram(); |
| int vshader = glCreateShader(GL_VERTEX_SHADER); |
| glShaderSource(vshader, 1, &kSrgbVertexCode, nullptr); |
| glCompileShader(vshader); |
| glAttachShader(program, vshader); |
| int fshader = glCreateShader(GL_FRAGMENT_SHADER); |
| glShaderSource(fshader, 1, &kSrgbFragmentCode, nullptr); |
| glCompileShader(fshader); |
| glAttachShader(program, fshader); |
| glLinkProgram(program); |
| int status; |
| glGetProgramiv(program, GL_LINK_STATUS, &status); |
| ASSERT_EQ(status, GL_TRUE); |
| glUseProgram(program); |
| ASSERT_EQ(glGetError(), GL_NO_ERROR); |
| |
| // Filtering test. |
| LOGV("Expected value for NoSrgb = %8.8X", kExpectedMiddlePixel_NoSrgb); |
| LOGV("Expected value for Srgb = %8.8X", kExpectedMiddlePixel_LinearizeBeforeFiltering); |
| uint32_t middle_pixel; |
| // First do a sanity check with plain old pre-linearized textures. |
| testLinearMagnification(env, 0, &middle_pixel); |
| ASSERT_NEAR(middle_pixel, kExpectedMiddlePixel_NoSrgb, 1); |
| testLinearMagnification(env, SrgbFlag::kHardwareBuffer, &middle_pixel); |
| ASSERT_NEAR(middle_pixel, kExpectedMiddlePixel_NoSrgb, 1); |
| // Try a "normally allocated" OpenGL texture with an sRGB source format. |
| testLinearMagnification(env, SrgbFlag::kSrgbFormat, &middle_pixel); |
| ASSERT_NEAR(middle_pixel, kExpectedMiddlePixel_LinearizeBeforeFiltering, 1); |
| // Try EGL_EXT_image_gl_colorspace. |
| if (egl_colorspace_supported) { |
| testLinearMagnification(env, SrgbFlag::kHardwareBuffer | SrgbFlag::kEglColorspaceDefault, &middle_pixel); |
| ASSERT_NEAR(middle_pixel, kExpectedMiddlePixel_NoSrgb, 1); |
| testLinearMagnification(env, SrgbFlag::kHardwareBuffer | SrgbFlag::kEglColorspaceLinear, &middle_pixel); |
| ASSERT_NEAR(middle_pixel, kExpectedMiddlePixel_NoSrgb, 1); |
| testLinearMagnification(env, SrgbFlag::kHardwareBuffer | SrgbFlag::kEglColorspaceSrgb, &middle_pixel); |
| ASSERT_NEAR(middle_pixel, kExpectedMiddlePixel_LinearizeBeforeFiltering, 1); |
| } |
| |
| // Blending test. |
| LOGV("Expected value for NoSrgb = %8.8X", kExpectedBlendedPixel_NoSrgb); |
| LOGV("Expected value for Srgb = %8.8X", kExpectedBlendedPixel_Srgb); |
| uint32_t final_color; |
| // First do a sanity check with plain old pre-linearized textures. |
| testFramebufferBlending(env, 0, &final_color); |
| ASSERT_NEAR(final_color, kExpectedBlendedPixel_NoSrgb, 1); |
| testFramebufferBlending(env, SrgbFlag::kHardwareBuffer, &final_color); |
| ASSERT_NEAR(final_color, kExpectedBlendedPixel_NoSrgb, 1); |
| // Try a "normally allocated" OpenGL texture with an sRGB source format. |
| testFramebufferBlending(env, SrgbFlag::kSrgbFormat, &final_color); |
| ASSERT_NEAR(final_color, kExpectedBlendedPixel_Srgb, 1); |
| // Try EGL_EXT_image_gl_colorspace. |
| if (egl_colorspace_supported) { |
| testFramebufferBlending(env, SrgbFlag::kHardwareBuffer | SrgbFlag::kEglColorspaceDefault, &final_color); |
| ASSERT_NEAR(final_color, kExpectedBlendedPixel_NoSrgb, 1); |
| testFramebufferBlending(env, SrgbFlag::kHardwareBuffer | SrgbFlag::kEglColorspaceLinear, &final_color); |
| ASSERT_NEAR(final_color, kExpectedBlendedPixel_NoSrgb, 1); |
| testFramebufferBlending(env, SrgbFlag::kHardwareBuffer | SrgbFlag::kEglColorspaceSrgb, &final_color); |
| ASSERT_NEAR(final_color, kExpectedBlendedPixel_Srgb, 1); |
| } |
| } |