#include "egl_context.h" #include #include #include #define TAG "LckEglContext" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__) #define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__) #ifndef EGL_NATIVE_BUFFER_ANDROID #define EGL_NATIVE_BUFFER_ANDROID 0x3140 #endif #ifndef EGL_SYNC_NATIVE_FENCE_ANDROID #define EGL_SYNC_NATIVE_FENCE_ANDROID 0x3144 #endif #ifndef EGL_SYNC_NATIVE_FENCE_FD_ANDROID #define EGL_SYNC_NATIVE_FENCE_FD_ANDROID 0x3145 #endif #ifndef EGL_RECORDABLE_ANDROID #define EGL_RECORDABLE_ANDROID 0x3142 #endif EglContext::EglContext() {} EglContext::~EglContext() { Release(); } bool EglContext::LoadExtensions() { eglCreateSyncKHR = (PFNEGLCREATESYNCKHRPROC)eglGetProcAddress("eglCreateSyncKHR"); eglWaitSyncKHR = (PFNEGLWAITSYNCKHRPROC)eglGetProcAddress("eglWaitSyncKHR"); eglDestroySyncKHR = (PFNEGLDESTROYSYNCKHRPROC)eglGetProcAddress("eglDestroySyncKHR"); eglGetNativeClientBufferANDROID = (PFNEGLGETNATIVECLIENTBUFFERANDROIDPROC)eglGetProcAddress("eglGetNativeClientBufferANDROID"); eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress("eglCreateImageKHR"); eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR"); glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES"); eglPresentationTimeANDROID = (PFNEGLPRESENTATIONTIMEANDROIDPROC)eglGetProcAddress("eglPresentationTimeANDROID"); if (!eglGetNativeClientBufferANDROID || !eglCreateImageKHR || !eglDestroyImageKHR || !glEGLImageTargetTexture2DOES) { LOGE("Missing required EGL extensions for HardwareBuffer import"); return false; } return true; } bool EglContext::Init() { display = eglGetDisplay(EGL_DEFAULT_DISPLAY); if (display == EGL_NO_DISPLAY) { LOGE("eglGetDisplay failed"); return false; } EGLint major, minor; if (!eglInitialize(display, &major, &minor)) { LOGE("eglInitialize failed"); return false; } LOGI("EGL initialized: %d.%d", major, minor); // EGL config: RGBA8, ES3, recordable for MediaCodec EGLint configAttribs[] = { EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RECORDABLE_ANDROID, EGL_TRUE, EGL_NONE }; EGLint numConfigs; if (!eglChooseConfig(display, configAttribs, &config, 1, &numConfigs) || numConfigs == 0) { LOGE("eglChooseConfig failed"); return false; } EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE }; context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs); if (context == EGL_NO_CONTEXT) { LOGE("eglCreateContext failed"); return false; } if (!LoadExtensions()) { return false; } LOGI("EGL context created successfully"); return true; } bool EglContext::CreateWindowSurface(ANativeWindow* window) { if (surface != EGL_NO_SURFACE) { eglDestroySurface(display, surface); } surface = eglCreateWindowSurface(display, config, window, nullptr); if (surface == EGL_NO_SURFACE) { LOGE("eglCreateWindowSurface failed: 0x%x", eglGetError()); return false; } eglQuerySurface(display, surface, EGL_WIDTH, &surfaceWidth); eglQuerySurface(display, surface, EGL_HEIGHT, &surfaceHeight); LOGI("EGL window surface created: %dx%d", surfaceWidth, surfaceHeight); return true; } GLuint EglContext::ImportHardwareBuffer(AHardwareBuffer* buffer) { if (!eglGetNativeClientBufferANDROID || !eglCreateImageKHR || !glEGLImageTargetTexture2DOES) { LOGE("Missing EGL extensions for HardwareBuffer import"); return 0; } EGLClientBuffer clientBuffer = eglGetNativeClientBufferANDROID(buffer); if (!clientBuffer) { LOGE("eglGetNativeClientBufferANDROID failed"); return 0; } EGLint imageAttribs[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE }; EGLImageKHR image = eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, clientBuffer, imageAttribs); if (image == EGL_NO_IMAGE_KHR) { LOGE("eglCreateImageKHR failed: 0x%x", eglGetError()); return 0; } GLuint textureId; glGenTextures(1, &textureId); glBindTexture(GL_TEXTURE_EXTERNAL_OES, textureId); glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, image); // We need to keep the image alive — store it associated with the texture // The caller must call ReleaseImportedTexture to clean up // For now, we destroy the image immediately since the texture retains the content eglDestroyImageKHR(display, image); return textureId; } void EglContext::ReleaseImportedTexture(GLuint textureId, EGLImageKHR image) { if (textureId) { glDeleteTextures(1, &textureId); } if (image != EGL_NO_IMAGE_KHR && eglDestroyImageKHR) { eglDestroyImageKHR(display, image); } } void EglContext::WaitFence(int fenceFd) { if (fenceFd < 0) return; if (eglCreateSyncKHR && eglWaitSyncKHR && eglDestroySyncKHR) { EGLint attribs[] = { EGL_SYNC_NATIVE_FENCE_FD_ANDROID, fenceFd, EGL_NONE }; EGLSyncKHR sync = eglCreateSyncKHR(display, EGL_SYNC_NATIVE_FENCE_ANDROID, attribs); if (sync != EGL_NO_SYNC_KHR) { // GPU-side wait — doesn't block CPU eglWaitSyncKHR(display, sync, 0); eglDestroySyncKHR(display, sync); // eglCreateSyncKHR takes ownership of fenceFd return; } } // Fallback: CPU-side wait close(fenceFd); } void EglContext::SetPresentationTime(int64_t timestampNs) { if (eglPresentationTimeANDROID && surface != EGL_NO_SURFACE) { eglPresentationTimeANDROID(display, surface, timestampNs); } } bool EglContext::MakeCurrent() { return eglMakeCurrent(display, surface, surface, context) == EGL_TRUE; } bool EglContext::SwapBuffers() { return eglSwapBuffers(display, surface) == EGL_TRUE; } bool EglContext::CreatePreviewSurface(ANativeWindow* window) { if (!window || display == EGL_NO_DISPLAY) return false; DestroyPreviewSurface(); previewSurface = eglCreateWindowSurface(display, config, window, nullptr); if (previewSurface == EGL_NO_SURFACE) { LOGE("eglCreateWindowSurface (preview) failed: 0x%x", eglGetError()); return false; } previewWindow = window; eglQuerySurface(display, previewSurface, EGL_WIDTH, &previewWidth); eglQuerySurface(display, previewSurface, EGL_HEIGHT, &previewHeight); LOGI("Preview surface created: %dx%d", previewWidth, previewHeight); return true; } void EglContext::DestroyPreviewSurface() { if (previewSurface != EGL_NO_SURFACE && display != EGL_NO_DISPLAY) { // Make sure preview isn't current before destroying eglMakeCurrent(display, surface, surface, context); eglDestroySurface(display, previewSurface); previewSurface = EGL_NO_SURFACE; LOGI("Preview surface destroyed"); } if (previewWindow) { ANativeWindow_release(previewWindow); previewWindow = nullptr; } previewWidth = 0; previewHeight = 0; } bool EglContext::MakePreviewCurrent() { if (previewSurface == EGL_NO_SURFACE) return false; return eglMakeCurrent(display, previewSurface, previewSurface, context) == EGL_TRUE; } bool EglContext::MakeEncoderCurrent() { return eglMakeCurrent(display, surface, surface, context) == EGL_TRUE; } bool EglContext::SwapPreviewBuffers() { if (previewSurface == EGL_NO_SURFACE) return false; return eglSwapBuffers(display, previewSurface) == EGL_TRUE; } void EglContext::Release() { if (display != EGL_NO_DISPLAY) { eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); DestroyPreviewSurface(); if (surface != EGL_NO_SURFACE) { eglDestroySurface(display, surface); surface = EGL_NO_SURFACE; } if (context != EGL_NO_CONTEXT) { eglDestroyContext(display, context); context = EGL_NO_CONTEXT; } eglTerminate(display); display = EGL_NO_DISPLAY; } LOGI("EGL resources released"); }