/* * 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); } }