client egl context and viewport in kotlin UI

This commit is contained in:
2025-12-28 21:46:18 +01:00
parent 19400fd2b2
commit 6830c61e31
5 changed files with 175 additions and 15 deletions

View File

@@ -1,6 +1,9 @@
cmake_minimum_required(VERSION 3.22.1) cmake_minimum_required(VERSION 3.22.1)
project("mosis") project("mosis")
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(ANDROID_SDK "/Users/omar/Library/Android/sdk") set(ANDROID_SDK "/Users/omar/Library/Android/sdk")
set(BINDER_DIR "${ANDROID_SDK}/platforms/android-36/optional/libbinder_ndk_cpp") set(BINDER_DIR "${ANDROID_SDK}/platforms/android-36/optional/libbinder_ndk_cpp")
@@ -14,7 +17,7 @@ add_library(mosis-service SHARED
target_include_directories(mosis-service PUBLIC target_include_directories(mosis-service PUBLIC
${CMAKE_CURRENT_LIST_DIR} ${BINDER_DIR} glad/include) ${CMAKE_CURRENT_LIST_DIR} ${BINDER_DIR} glad/include)
target_link_libraries(mosis-service target_link_libraries(mosis-service
android log binder_ndk) android log binder_ndk EGL GLESv2)
add_library(mosis-test SHARED add_library(mosis-test SHARED
com/omixlab/mosis/IMosisService.cpp com/omixlab/mosis/IMosisService.cpp
@@ -27,4 +30,4 @@ add_library(mosis-test SHARED
target_include_directories(mosis-test PUBLIC target_include_directories(mosis-test PUBLIC
${CMAKE_CURRENT_LIST_DIR} ${BINDER_DIR} glad/include) ${CMAKE_CURRENT_LIST_DIR} ${BINDER_DIR} glad/include)
target_link_libraries(mosis-test target_link_libraries(mosis-test
android log binder_ndk) android log binder_ndk EGL GLESv2)

View File

@@ -2,8 +2,10 @@
#include "logger.h" #include "logger.h"
#include <glad/gles2.h> #include <glad/gles2.h>
#include <glad/egl.h> #include <glad/egl.h>
#include <android/native_window_jni.h>
#include <vector>
bool egl::Context::create() bool egl::Context::create(ANativeWindow* window)
{ {
int version = gladLoaderLoadEGL(EGL_NO_DISPLAY); int version = gladLoaderLoadEGL(EGL_NO_DISPLAY);
if (version == 0) if (version == 0)
@@ -24,14 +26,22 @@ bool egl::Context::create()
return false; return false;
} }
version = gladLoaderLoadEGL(EGL_DEFAULT_DISPLAY); version = gladLoaderLoadEGL(EGL_DEFAULT_DISPLAY);
const EGLint config_attribs[] = std::vector<EGLint> config_attribs =
{ {
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,
EGL_NONE,
}; };
if (window)
{
config_attribs.append_range(std::to_array({
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_BLUE_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_RED_SIZE, 8,
}));
}
config_attribs.push_back(EGL_NONE);
EGLint num_configs; EGLint num_configs;
if (!eglChooseConfig(m_display, config_attribs, &m_config, 1, &num_configs) || num_configs == 0) if (!eglChooseConfig(m_display, config_attribs.data(), &m_config, 1, &num_configs) || num_configs == 0)
{ {
Logger::Log("eglChooseConfig failed"); Logger::Log("eglChooseConfig failed");
return false; return false;
@@ -48,11 +58,22 @@ bool egl::Context::create()
Logger::Log("eglCreateContext failed"); Logger::Log("eglCreateContext failed");
return false; return false;
} }
if (!eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, m_context)) if (window)
{
m_surface = eglCreateWindowSurface(m_display, m_config, window, nullptr);
m_native_window = window;
}
if (!eglMakeCurrent(m_display, m_surface, m_surface, m_context))
{ {
Logger::Log("eglMakeCurrent failed"); Logger::Log("eglMakeCurrent failed");
return false; return false;
} }
int gl_version = gladLoaderLoadGLES2();
if (gl_version == 0)
{
Logger::Log("glad failed to load GLES2");
return false;
}
return true; return true;
} }
@@ -61,18 +82,36 @@ void egl::Context::destroy()
if (m_display != EGL_NO_DISPLAY) if (m_display != EGL_NO_DISPLAY)
{ {
eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (m_surface != EGL_NO_SURFACE)
{
eglDestroySurface(m_display, m_surface);
}
if (m_context != EGL_NO_CONTEXT) if (m_context != EGL_NO_CONTEXT)
{ {
eglDestroyContext(m_display, m_context); eglDestroyContext(m_display, m_context);
} }
if (m_native_window)
{
ANativeWindow_release(m_native_window);
}
eglTerminate(m_display); eglTerminate(m_display);
} }
m_display = EGL_NO_DISPLAY; m_display = EGL_NO_DISPLAY;
m_context = EGL_NO_CONTEXT; m_context = EGL_NO_CONTEXT;
m_config = EGL_NO_CONFIG_KHR;
m_surface = EGL_NO_SURFACE;
m_native_window = nullptr;
} }
egl::Context::Context() egl::Context::Context() :
: m_display(EGL_NO_DISPLAY), m_config(EGL_NO_CONFIG_KHR), m_context(EGL_NO_CONTEXT) m_display(EGL_NO_DISPLAY), m_config(EGL_NO_CONFIG_KHR),
m_context(EGL_NO_CONTEXT), m_surface(EGL_NO_SURFACE),
m_native_window(nullptr)
{ {
Logger::Log("egl::Context::Context"); Logger::Log("egl::Context::Context");
} }
void egl::Context::swap()
{
eglSwapBuffers(m_display, m_surface);
}

View File

@@ -2,7 +2,9 @@
typedef void* EGLContext; typedef void* EGLContext;
typedef void* EGLDisplay; typedef void* EGLDisplay;
typedef void* EGLSurface;
typedef void* EGLConfig; typedef void* EGLConfig;
struct ANativeWindow;
namespace egl namespace egl
{ {
@@ -10,10 +12,13 @@ class Context
{ {
EGLContext m_context; EGLContext m_context;
EGLDisplay m_display; EGLDisplay m_display;
EGLConfig m_config = nullptr; EGLSurface m_surface;
EGLConfig m_config;
ANativeWindow* m_native_window;
public: public:
Context(); Context();
bool create(); bool create(ANativeWindow* window = nullptr);
void destroy(); void destroy();
void swap();
}; };
} }

View File

@@ -1,11 +1,61 @@
#include <jni.h> #include <jni.h>
#include <android/binder_ibinder_jni.h> #include <android/binder_ibinder_jni.h>
#include <aidl/com/omixlab/mosis/IMosisService.h> #include <aidl/com/omixlab/mosis/IMosisService.h>
#include <android/native_window_jni.h>
#include "logger.h" #include "logger.h"
#include "egl_context.h"
#include <glad/gles2.h>
#include <memory>
#include <thread>
using namespace aidl::com::omixlab::mosis; using namespace aidl::com::omixlab::mosis;
class Renderer
{
std::unique_ptr<egl::Context> m_egl_context;
std::thread m_render_loop;
bool m_active = false;
void render_loop(ANativeWindow* window)
{
m_egl_context = std::make_unique<egl::Context>();
if (m_egl_context->create(window))
{
m_active = true;
while (m_active)
{
render_frame();
}
}
else
{
Logger::Log("Failed to create EGL context");
}
}
void render_frame()
{
glClearColor(0.f, 0.5f, 0.5f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
m_egl_context->swap();
}
public:
bool create(ANativeWindow* window)
{
m_render_loop = std::thread(&Renderer::render_loop, this, window);
return true;
}
void destroy()
{
m_active = false;
if (m_render_loop.joinable())
m_render_loop.join();
m_egl_context->destroy();
m_egl_context.reset();
}
};
std::shared_ptr<IMosisService> g_service; std::shared_ptr<IMosisService> g_service;
std::unique_ptr<Renderer> g_renderer;
extern "C" extern "C"
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_com_omixlab_mosis_MainActivity_serviceConnected(JNIEnv *env, jobject thiz, jobject binder) { Java_com_omixlab_mosis_MainActivity_serviceConnected(JNIEnv *env, jobject thiz, jobject binder) {
@@ -14,3 +64,23 @@ Java_com_omixlab_mosis_MainActivity_serviceConnected(JNIEnv *env, jobject thiz,
g_service = IMosisService::fromBinder(spBinder); g_service = IMosisService::fromBinder(spBinder);
Logger::Log("Service Connected"); Logger::Log("Service Connected");
} }
extern "C"
JNIEXPORT void JNICALL
Java_com_omixlab_mosis_MainActivity_setSurface(JNIEnv *env, jobject thiz, jobject surface) {
Logger::Log("setSurface");
if (!g_renderer)
{
g_renderer = std::make_unique<Renderer>();
g_renderer->create(ANativeWindow_fromSurface(env, surface));
}
}
extern "C"
JNIEXPORT void JNICALL
Java_com_omixlab_mosis_MainActivity_destroySurface(JNIEnv *env, jobject thiz) {
Logger::Log("destroySurface");
if (g_renderer)
{
g_renderer->destroy();
}
}

View File

@@ -6,7 +6,11 @@ import android.content.ServiceConnection
import android.os.Bundle import android.os.Bundle
import android.os.IBinder import android.os.IBinder
import android.util.Log import android.util.Log
import android.view.Surface
import android.view.SurfaceView
import android.view.SurfaceHolder
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.addCallback
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@@ -16,7 +20,9 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.viewinterop.AndroidView
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
init { init {
@@ -25,7 +31,7 @@ class MainActivity : ComponentActivity() {
var remote_service: IMosisService? = null var remote_service: IMosisService? = null
var statusText = mutableStateOf("Status: idle") var statusText = mutableStateOf("Status: idle")
var buttonText = mutableStateOf("Init OS") var buttonText = mutableStateOf("Init OS")
private val connection = object : ServiceConnection { val connection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) { override fun onServiceConnected(className: ComponentName, service: IBinder) {
Log.d("MosisTest", "Service Connected") Log.d("MosisTest", "Service Connected")
statusText.value = "Service Connected" statusText.value = "Service Connected"
@@ -39,6 +45,27 @@ class MainActivity : ComponentActivity() {
statusText.value = "Service Disconnected" statusText.value = "Service Disconnected"
} }
} }
val viewport = object : SurfaceHolder.Callback {
override fun surfaceCreated(holder: SurfaceHolder) {
Log.d("MosisTest", "surfaceCreated")
setSurface(holder.surface)
}
override fun surfaceChanged(
holder: SurfaceHolder,
format: Int,
width: Int,
height: Int
) {
// Optional: Pass resize events to native if needed
// resizeSurface(width, height)
Log.d("MosisTest", "surfaceResized")
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
destroySurface()
Log.d("MosisTest", "surfaceDestroyed")
}
}
fun initOS() fun initOS()
{ {
remote_service?.let { service -> remote_service?.let { service ->
@@ -47,7 +74,7 @@ class MainActivity : ComponentActivity() {
buttonText.value = if (result) "OS Initialized" else "OS Not Initialized" buttonText.value = if (result) "OS Initialized" else "OS Not Initialized"
} }
} }
private fun startRemoteService() { fun startRemoteService() {
val intent = Intent("com.omixlab.mosis.SERVICE") val intent = Intent("com.omixlab.mosis.SERVICE")
intent.setPackage("com.omixlab.mosis") intent.setPackage("com.omixlab.mosis")
try { try {
@@ -73,8 +100,24 @@ class MainActivity : ComponentActivity() {
Text(buttonText.value) Text(buttonText.value)
} }
Text(statusText.value) Text(statusText.value)
NativeViewport(
modifier = Modifier.fillMaxSize(0.8f)
)
} }
} }
} }
@Composable
fun NativeViewport(modifier: Modifier = Modifier) {
AndroidView(
modifier = modifier,
factory = { context ->
SurfaceView(context).apply {
holder.addCallback(viewport)
}
}
)
}
external fun serviceConnected(binder: IBinder) external fun serviceConnected(binder: IBinder)
external fun setSurface(surface: Surface)
external fun destroySurface()
} }