blob: 6f304b8882b34c1c59eb666b49c847ba5b97d9f2 [file] [log] [blame]
/*
* 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 <string>
#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
#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__)
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,
// 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);
}