client egl context and viewport in kotlin UI
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user