Skip to content

Commit

Permalink
fix(renderer.c): preparing renderer to work without X server connected.
Browse files Browse the repository at this point in the history
  • Loading branch information
twaik committed Jan 6, 2025
1 parent d1c059d commit f5f588b
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 50 deletions.
28 changes: 21 additions & 7 deletions app/src/main/cpp/lorie/activity.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <android/looper.h>
#include <wchar.h>
#include "lorie.h"
#include "renderer.h"

#pragma clang diagnostic ignored "-Wunknown-pragmas"
#pragma ide diagnostic ignored "cppcoreguidelines-narrowing-conversions"
Expand Down Expand Up @@ -40,9 +41,6 @@ static struct {
static JNIEnv *guienv = NULL; // Must be used only in GUI thread.
static jobject globalThiz = NULL;

static struct lorie_shared_server_state* state = NULL;
static AHardwareBuffer* sharedBuffer = NULL;

static jclass FindClassOrDie(JNIEnv *env, const char* name) {
jclass clazz = (*env)->FindClass(env, name);
if (!clazz) {
Expand Down Expand Up @@ -132,13 +130,24 @@ static int xcallback(int fd, int events, __unused void* data) {
break;
}
case EVENT_SHARED_SERVER_STATE: {
struct lorie_shared_server_state* state = NULL;
int stateFd = ancil_recv_fd(conn_fd);

if (state)
munmap(state, sizeof(*state));
if (stateFd < 0)
break;

if (!(state = mmap(NULL, sizeof(*state), PROT_READ|PROT_WRITE, MAP_SHARED, stateFd, 0)))
log(ERROR, "Failed to map server state.");
state = mmap(NULL, sizeof(*state), PROT_READ|PROT_WRITE, MAP_SHARED, stateFd, 0);
if (!state || state == MAP_FAILED) {
log(ERROR, "Failed to map server state: %s", strerror(errno));
state = NULL;
}

#if 0
renderer_set_shared_state(state);
#else
// Should pass it to renderer thread here, but currently it is not implemented...
munmap(state, sizeof(*state));
#endif

close(stateFd); // Closing file descriptor does not unmmap shared memory fragment.
break;
Expand Down Expand Up @@ -305,10 +314,15 @@ static void sendTextEvent(JNIEnv *env, __unused jobject thiz, jbyteArray text) {
}
}

static void surfaceChanged(JNIEnv *env, jobject thiz, jobject sfc) {
// Not implemented yet...
}

JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv* env;
static JNINativeMethod methods[] = {
{"nativeInit", "()V", (void *)&nativeInit},
{"surfaceChanged", "(Landroid/view/Surface;)V", (void *)&surfaceChanged},
{"connect", "(I)V", (void *)&connect_},
{"startLogcat", "(I)V", (void *)&startLogcat},
{"setClipboardSyncEnabled", "(ZZ)V", (void *)&setClipboardSyncEnabled},
Expand Down
100 changes: 58 additions & 42 deletions app/src/main/cpp/lorie/renderer.c
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ static EGLDisplay egl_display = EGL_NO_DISPLAY;
static EGLContext ctx = EGL_NO_CONTEXT;
static EGLSurface sfc = EGL_NO_SURFACE;
static EGLConfig cfg = 0;
static EGLNativeWindowType win = 0;
static ANativeWindow* win = 0;
static jobject surface = NULL;
static LorieBuffer *buffer = NULL;
static EGLImageKHR image = NULL;
Expand All @@ -120,12 +120,12 @@ static jmethodID Surface_release = NULL;
static jmethodID Surface_destroy = NULL;

static JNIEnv* renderEnv = NULL;
static volatile bool bufferChanged = false;
static volatile bool surfaceChanged = false;
static volatile bool bufferChanged = false, surfaceChanged = false;
static volatile LorieBuffer* pendingBuffer = NULL;
static volatile jobject pendingSurface = NULL;

static pthread_mutex_t stateLock;
static pthread_cond_t stateCond;
static volatile struct lorie_shared_server_state* state = NULL;
static struct {
GLuint id;
Expand Down Expand Up @@ -176,6 +176,7 @@ int renderer_init(JNIEnv* env) {
(*env)->GetJavaVM(env, &vm);

pthread_mutex_init(&stateLock, NULL);
pthread_cond_init(&stateCond, NULL);

jclass Surface = (*env)->FindClass(env, "android/view/Surface");
Surface_release = (*env)->GetMethodID(env, Surface, "release", "()V");
Expand Down Expand Up @@ -328,7 +329,12 @@ void renderer_test_capabilities(int* legacy_drawing, uint8_t* flip) {
__unused void renderer_set_shared_state(struct lorie_shared_server_state* new_state) {
pthread_mutex_lock(&stateLock);
state = new_state;
pthread_cond_signal(&state->cond);

// We are not sure which conditional variable is used at current moment so let's signal both
if (state)
pthread_cond_signal(&state->cond);
pthread_cond_signal(&stateCond);

pthread_mutex_unlock(&stateLock);
}

Expand All @@ -346,7 +352,11 @@ void renderer_set_buffer(JNIEnv* env, LorieBuffer* buf) {
if (pendingBuffer)
LorieBuffer_acquire(pendingBuffer);

pthread_cond_signal(&state->cond);
// We are not sure which conditional variable is used at current moment so let's signal both
if (state)
pthread_cond_signal(&state->cond);
pthread_cond_signal(&stateCond);

end: pthread_mutex_unlock(&state->lock);
}

Expand All @@ -368,7 +378,12 @@ void renderer_set_window(JNIEnv* env, jobject new_surface) {

pendingSurface = new_surface;
surfaceChanged = TRUE;
pthread_cond_signal(&state->cond);

// We are not sure which conditional variable is used at current moment so let's signal both
if (state)
pthread_cond_signal(&state->cond);
pthread_cond_signal(&stateCond);

pthread_mutex_unlock(&state->lock);
}

Expand Down Expand Up @@ -396,36 +411,37 @@ static inline __always_inline void release_win_and_surface(JNIEnv *env, jobject*

void renderer_refresh_context(JNIEnv* env) {
uint32_t emptyData = {0};
ANativeWindow* window = pendingSurface ? ANativeWindow_fromSurface(env, pendingSurface) : NULL;
if ((pendingSurface && surface && pendingSurface != surface && (*env)->IsSameObject(env, pendingSurface, surface)) || (window && win == window)) {
ANativeWindow* pendingWin = pendingSurface ? ANativeWindow_fromSurface(env, pendingSurface) : NULL;
if ((pendingSurface && surface && pendingSurface != surface && (*env)->IsSameObject(env, pendingSurface, surface)) || (pendingWin && win == pendingWin)) {
(*env)->DeleteGlobalRef(env, pendingSurface);
pendingSurface = NULL;
surfaceChanged = FALSE;
return;
}

int width = window ? ANativeWindow_getWidth(window) : 0;
int height = window ? ANativeWindow_getHeight(window) : 0;
log("renderer_set_window %p %d %d", window, width, height);
int width = pendingWin ? ANativeWindow_getWidth(pendingWin) : 0;
int height = pendingWin ? ANativeWindow_getHeight(pendingWin) : 0;
log("renderer_set_window %p %d %d", pendingWin, width, height);

if (window)
ANativeWindow_acquire(window);
if (pendingWin)
ANativeWindow_acquire(pendingWin);

release_win_and_surface(env, &surface, &win, &sfc);

if (window && (width <= 0 || height <= 0)) {
if (pendingWin && (width <= 0 || height <= 0)) {
log("Xlorie: We've got invalid surface. Probably it became invalid before we started working with it.\n");
release_win_and_surface(env, &pendingSurface, &window, NULL);
release_win_and_surface(env, &pendingSurface, &pendingWin, NULL);
}

win = window;
win = pendingWin;
surface = pendingSurface;
pendingSurface = NULL;
surfaceChanged = FALSE;

if (!win) {
eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
state->contextAvailable = false;
if (state)
state->contextAvailable = false;
return;
}

Expand All @@ -434,11 +450,14 @@ void renderer_refresh_context(JNIEnv* env) {
return vprintEglError("eglCreateWindowSurface failed", __LINE__);

if (eglMakeCurrent(egl_display, sfc, sfc, ctx) != EGL_TRUE) {
state->contextAvailable = false;
if (state)
state->contextAvailable = false;
return vprintEglError("eglMakeCurrent failed", __LINE__);
}

state->contextAvailable = true;
if (state)
// We should redraw image at least once right after surface change
state->contextAvailable = state->drawRequested = state->cursor.updated = true;

if (!g_texture_program) {
g_texture_program = create_program(vertex_shader, fragment_shader);
Expand Down Expand Up @@ -475,7 +494,8 @@ void renderer_refresh_context(JNIEnv* env) {

if (display.desc.data && display.desc.width > 0 && display.desc.height > 0) {
int format = display.desc.format == AHARDWAREBUFFER_FORMAT_B8G8R8A8_UNORM ? GL_BGRA_EXT : GL_RGBA;
glTexImage2D(GL_TEXTURE_2D, 0, format, display.desc.width, display.desc.height, 0, format, GL_UNSIGNED_BYTE, display.desc.data);
// The image will be updated in redraw call because of `drawRequested` flag, so we are not uploading pixels
glTexImage2D(GL_TEXTURE_2D, 0, format, display.desc.width, display.desc.height, 0, format, GL_UNSIGNED_BYTE, NULL);
} else
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &emptyData);
}
Expand All @@ -486,7 +506,6 @@ static void draw_cursor(void);

static void renderer_renew_image(void) {
const EGLint imageAttributes[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE};
AHardwareBuffer_Desc desc = {0};
uint32_t emptyData = {0};

if (image)
Expand All @@ -504,30 +523,31 @@ static void renderer_renew_image(void) {
if (display.desc.buffer)
image = eglCreateImageKHR(egl_display, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, eglGetNativeClientBufferANDROID(display.desc.buffer), imageAttributes);

if (state->contextAvailable) {
if (eglGetCurrentContext() != EGL_NO_CONTEXT) {
if (state)
// We should redraw image at least once right after buffer change
state->contextAvailable = state->drawRequested = state->cursor.updated = true;

bindLinearTexture(display.id);
if (image)
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
else if (display.desc.data && display.desc.width > 0 && display.desc.height > 0) {
int format = display.desc.format == AHARDWAREBUFFER_FORMAT_B8G8R8A8_UNORM ? GL_BGRA_EXT : GL_RGBA;
glTexImage2D(GL_TEXTURE_2D, 0, format, display.desc.width, display.desc.height, 0, format, GL_UNSIGNED_BYTE, display.desc.data);
// The image will be updated in redraw call because of `drawRequested` flag, so we are not uploading pixels
glTexImage2D(GL_TEXTURE_2D, 0, format, display.desc.width, display.desc.height, 0, format, GL_UNSIGNED_BYTE, NULL);
} else {
loge("There is no %s, nothing to be bound.", !buffer ? "AHardwareBuffer" : "EGLImage");
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &emptyData);
}
}

log("renderer: buffer changed %p %d %d", buffer, desc.width, desc.height);
}

int renderer_redraw(void) {
return state->contextAvailable;
log("renderer: buffer changed %p %d %d", buffer, display.desc.width, display.desc.height);
}

int renderer_redraw_locked(JNIEnv* env) {
int err = EGL_SUCCESS;

// We should not read root window content while server is writing it.
// We should signal X server to not use root window or change cursor while we actively use them
pthread_mutex_lock(&state->lock);
// Non-null display.desc.data means we have root window created in legacy drawing mode so we should update it on each frame.
if (display.desc.data && state->drawRequested) {
Expand Down Expand Up @@ -567,16 +587,16 @@ int renderer_redraw_locked(JNIEnv* env) {
}

static inline __always_inline bool renderer_should_wait(void) {
if (!state && !surfaceChanged && !bufferChanged)
// No need to draw in the case if there is no shared server state, pending surface of buffer.
return true;
if (surfaceChanged || bufferChanged)
// If there are pending changes we should process them immediately.
return false;

if (!state->drawRequested && !state->cursor.updated && !state->cursor.moved)
// Even if there is state or pending surface/buffer,
// no need to update anything if server did not report any changes.
return true;
if (state && (state->drawRequested || state->cursor.moved || state->cursor.updated))
// X server reported drawing or cursor changes, no need to wait.
return false;

return false;
// Probably spurious wake, no changes we can work with.
return true;
}

__noreturn static void* renderer_thread(void* closure) {
Expand All @@ -589,18 +609,14 @@ __noreturn static void* renderer_thread(void* closure) {
while (renderer_should_wait())
pthread_cond_wait(&state->cond, &stateLock);

// we should signal X server to not use root window or change cursor while we actively use them
pthread_mutex_lock(&state->lock);
if (surfaceChanged)
renderer_refresh_context(env);

if (bufferChanged)
renderer_renew_image();
pthread_mutex_unlock(&state->lock);

if (state->contextAvailable)
if (state && state->contextAvailable && (state->drawRequested || state->cursor.moved || state->cursor.updated))
renderer_redraw_locked(env);

}
pthread_mutex_unlock(&stateLock);
}
Expand Down
9 changes: 8 additions & 1 deletion app/src/main/java/com/termux/x11/LorieView.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ interface PixelFormat {
private final SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() {
@Override public void surfaceCreated(@NonNull SurfaceHolder holder) {
holder.setFormat(PixelFormat.BGRA_8888);
LorieView.this.surfaceChanged(holder.getSurface());
}

@Override public void surfaceChanged(@NonNull SurfaceHolder holder, int f, int width, int height) {
Expand All @@ -72,12 +73,15 @@ interface PixelFormat {
return;

getDimensionsFromSettings();
mCallback.changed(holder.getSurface(), width, height, p.x, p.y);
if (mCallback != null)
mCallback.changed(holder.getSurface(), width, height, p.x, p.y);
LorieView.this.surfaceChanged(holder.getSurface());
}

@Override public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
if (mCallback != null)
mCallback.changed(holder.getSurface(), 0, 0, 0, 0);
LorieView.this.surfaceChanged(holder.getSurface());
}
};

Expand Down Expand Up @@ -183,6 +187,8 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (prefs.displayStretch.get()
|| "native".equals(prefs.displayResolutionMode.get())
|| "scaled".equals(prefs.displayResolutionMode.get())) {
getHolder().setSizeFromLayout();
return;
}

getDimensionsFromSettings();
Expand Down Expand Up @@ -356,6 +362,7 @@ public boolean commitText(CharSequence text, int newCursorPosition) {
}

@FastNative private native void nativeInit();
@FastNative private native void surfaceChanged(Surface surface);
@FastNative static native void connect(int fd);
@FastNative static native void startLogcat(int fd);
@FastNative static native void setClipboardSyncEnabled(boolean enabled, boolean ignored);
Expand Down

0 comments on commit f5f588b

Please sign in to comment.