1/* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#define LOG_TAG "AssetAtlasService" 18 19#include "jni.h" 20#include "JNIHelp.h" 21#include "android/graphics/GraphicsJNI.h" 22 23#include <android_view_GraphicBuffer.h> 24#include <cutils/log.h> 25 26#include <GLES2/gl2.h> 27#include <GLES2/gl2ext.h> 28 29#include <EGL/egl.h> 30#include <EGL/eglext.h> 31 32// Disable warnings for Skia. 33#pragma GCC diagnostic push 34#pragma GCC diagnostic ignored "-Wunused-parameter" 35#include <SkCanvas.h> 36#include <SkBitmap.h> 37#pragma GCC diagnostic pop 38 39namespace android { 40 41// ---------------------------------------------------------------------------- 42// Defines 43// ---------------------------------------------------------------------------- 44 45// Defines how long to wait for the GPU when uploading the atlas 46// This timeout is defined in nanoseconds (see EGL_KHR_fence_sync extension) 47#define FENCE_TIMEOUT 2000000000 48 49// ---------------------------------------------------------------------------- 50// Canvas management 51// ---------------------------------------------------------------------------- 52 53#define CLEANUP_GL_AND_RETURN(result) \ 54 if (fence != EGL_NO_SYNC_KHR) eglDestroySyncKHR(display, fence); \ 55 if (image) eglDestroyImageKHR(display, image); \ 56 if (texture) glDeleteTextures(1, &texture); \ 57 if (surface != EGL_NO_SURFACE) eglDestroySurface(display, surface); \ 58 if (context != EGL_NO_CONTEXT) eglDestroyContext(display, context); \ 59 eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); \ 60 eglReleaseThread(); \ 61 eglTerminate(display); \ 62 return result; 63 64static jboolean com_android_server_AssetAtlasService_upload(JNIEnv* env, jobject, 65 jobject graphicBuffer, jobject bitmapHandle) { 66 67 SkBitmap bitmap; 68 GraphicsJNI::getSkBitmap(env, bitmapHandle, &bitmap); 69 SkAutoLockPixels alp(bitmap); 70 71 // The goal of this method is to copy the bitmap into the GraphicBuffer 72 // using the GPU to swizzle the texture content 73 sp<GraphicBuffer> buffer(graphicBufferForJavaObject(env, graphicBuffer)); 74 75 if (buffer != NULL) { 76 EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); 77 if (display == EGL_NO_DISPLAY) return JNI_FALSE; 78 79 EGLint major; 80 EGLint minor; 81 if (!eglInitialize(display, &major, &minor)) { 82 ALOGW("Could not initialize EGL"); 83 return JNI_FALSE; 84 } 85 86 // We're going to use a 1x1 pbuffer surface later on 87 // The configuration doesn't really matter for what we're trying to do 88 EGLint configAttrs[] = { 89 EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, 90 EGL_RED_SIZE, 8, 91 EGL_GREEN_SIZE, 8, 92 EGL_BLUE_SIZE, 8, 93 EGL_ALPHA_SIZE, 0, 94 EGL_DEPTH_SIZE, 0, 95 EGL_STENCIL_SIZE, 0, 96 EGL_NONE 97 }; 98 EGLConfig configs[1]; 99 EGLint configCount; 100 if (!eglChooseConfig(display, configAttrs, configs, 1, &configCount)) { 101 ALOGW("Could not select EGL configuration"); 102 eglReleaseThread(); 103 eglTerminate(display); 104 return JNI_FALSE; 105 } 106 if (configCount <= 0) { 107 ALOGW("Could not find EGL configuration"); 108 eglReleaseThread(); 109 eglTerminate(display); 110 return JNI_FALSE; 111 } 112 113 // These objects are initialized below but the default "null" 114 // values are used to cleanup properly at any point in the 115 // initialization sequence 116 GLuint texture = 0; 117 EGLImageKHR image = EGL_NO_IMAGE_KHR; 118 EGLSurface surface = EGL_NO_SURFACE; 119 EGLSyncKHR fence = EGL_NO_SYNC_KHR; 120 121 EGLint attrs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; 122 EGLContext context = eglCreateContext(display, configs[0], EGL_NO_CONTEXT, attrs); 123 if (context == EGL_NO_CONTEXT) { 124 ALOGW("Could not create EGL context"); 125 CLEANUP_GL_AND_RETURN(JNI_FALSE); 126 } 127 128 // Create the 1x1 pbuffer 129 EGLint surfaceAttrs[] = { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE }; 130 surface = eglCreatePbufferSurface(display, configs[0], surfaceAttrs); 131 if (surface == EGL_NO_SURFACE) { 132 ALOGW("Could not create EGL surface"); 133 CLEANUP_GL_AND_RETURN(JNI_FALSE); 134 } 135 136 if (!eglMakeCurrent(display, surface, surface, context)) { 137 ALOGW("Could not change current EGL context"); 138 CLEANUP_GL_AND_RETURN(JNI_FALSE); 139 } 140 141 // We use an EGLImage to access the content of the GraphicBuffer 142 // The EGL image is later bound to a 2D texture 143 EGLClientBuffer clientBuffer = (EGLClientBuffer) buffer->getNativeBuffer(); 144 EGLint imageAttrs[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE }; 145 image = eglCreateImageKHR(display, EGL_NO_CONTEXT, 146 EGL_NATIVE_BUFFER_ANDROID, clientBuffer, imageAttrs); 147 if (image == EGL_NO_IMAGE_KHR) { 148 ALOGW("Could not create EGL image"); 149 CLEANUP_GL_AND_RETURN(JNI_FALSE); 150 } 151 152 glGenTextures(1, &texture); 153 glBindTexture(GL_TEXTURE_2D, texture); 154 glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); 155 if (glGetError() != GL_NO_ERROR) { 156 ALOGW("Could not create/bind texture"); 157 CLEANUP_GL_AND_RETURN(JNI_FALSE); 158 } 159 160 // Upload the content of the bitmap in the GraphicBuffer 161 glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap.bytesPerPixel()); 162 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bitmap.width(), bitmap.height(), 163 GL_RGBA, GL_UNSIGNED_BYTE, bitmap.getPixels()); 164 if (glGetError() != GL_NO_ERROR) { 165 ALOGW("Could not upload to texture"); 166 CLEANUP_GL_AND_RETURN(JNI_FALSE); 167 } 168 169 // The fence is used to wait for the texture upload to finish 170 // properly. We cannot rely on glFlush() and glFinish() as 171 // some drivers completely ignore these API calls 172 fence = eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, NULL); 173 if (fence == EGL_NO_SYNC_KHR) { 174 ALOGW("Could not create sync fence %#x", eglGetError()); 175 CLEANUP_GL_AND_RETURN(JNI_FALSE); 176 } 177 178 // The flag EGL_SYNC_FLUSH_COMMANDS_BIT_KHR will trigger a 179 // pipeline flush (similar to what a glFlush() would do.) 180 EGLint waitStatus = eglClientWaitSyncKHR(display, fence, 181 EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, FENCE_TIMEOUT); 182 if (waitStatus != EGL_CONDITION_SATISFIED_KHR) { 183 ALOGW("Failed to wait for the fence %#x", eglGetError()); 184 CLEANUP_GL_AND_RETURN(JNI_FALSE); 185 } 186 187 CLEANUP_GL_AND_RETURN(JNI_TRUE); 188 } 189 190 return JNI_FALSE; 191} 192 193// ---------------------------------------------------------------------------- 194// JNI Glue 195// ---------------------------------------------------------------------------- 196 197#define FIND_CLASS(var, className) \ 198 var = env->FindClass(className); \ 199 LOG_FATAL_IF(! var, "Unable to find class " className); 200 201#define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \ 202 var = env->GetMethodID(clazz, methodName, methodDescriptor); \ 203 LOG_FATAL_IF(!var, "Unable to find method " methodName); 204 205const char* const kClassPathName = "com/android/server/AssetAtlasService"; 206 207static const JNINativeMethod gMethods[] = { 208 { "nUploadAtlas", "(Landroid/view/GraphicBuffer;Landroid/graphics/Bitmap;)Z", 209 (void*) com_android_server_AssetAtlasService_upload }, 210}; 211 212int register_android_server_AssetAtlasService(JNIEnv* env) { 213 return jniRegisterNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods)); 214} 215 216}; 217