From 413d7ded490e1244311993d8e8546a753473c044 Mon Sep 17 00:00:00 2001 From: Triang3l Date: Tue, 1 Feb 2022 22:18:04 +0300 Subject: [PATCH] [UI] Android surface [skip appveyor] --- .../android_studio_project/app/build.gradle | 4 + .../app/src/main/AndroidManifest.xml | 4 +- .../java/jp/xenia/XeniaRuntimeException.java | 6 +- .../jp/xenia/emulator/WindowDemoActivity.java | 10 ++ .../jp/xenia/emulator/WindowSurfaceView.java | 42 ++++++ .../xenia/emulator/WindowedAppActivity.java | 123 +++++++++++++++++- .../main/res/layout/activity_window_demo.xml | 7 +- .../app/src/main/res/values/strings.xml | 1 + src/xenia/ui/presenter.cc | 14 +- src/xenia/ui/presenter.h | 4 +- src/xenia/ui/window_android.cc | 52 +++++++- src/xenia/ui/window_android.h | 6 + src/xenia/ui/windowed_app_context_android.cc | 90 ++++++++++++- src/xenia/ui/windowed_app_context_android.h | 36 ++++- 14 files changed, 369 insertions(+), 30 deletions(-) create mode 100644 android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowSurfaceView.java diff --git a/android/android_studio_project/app/build.gradle b/android/android_studio_project/app/build.gradle index 4888e7d60..ae753e1b2 100644 --- a/android/android_studio_project/app/build.gradle +++ b/android/android_studio_project/app/build.gradle @@ -81,4 +81,8 @@ android { path file('../../../build/xenia.wks.Android.mk') } } +} + +dependencies { + implementation 'org.jetbrains:annotations:15.0' } \ No newline at end of file diff --git a/android/android_studio_project/app/src/main/AndroidManifest.xml b/android/android_studio_project/app/src/main/AndroidManifest.xml index 8f6d53cb0..25bf5a95f 100644 --- a/android/android_studio_project/app/src/main/AndroidManifest.xml +++ b/android/android_studio_project/app/src/main/AndroidManifest.xml @@ -29,7 +29,9 @@ android:supportsRtl="true" android:theme="@android:style/Theme.Material.Light"> - + diff --git a/android/android_studio_project/app/src/main/java/jp/xenia/XeniaRuntimeException.java b/android/android_studio_project/app/src/main/java/jp/xenia/XeniaRuntimeException.java index 93a046dfb..e5d324e78 100644 --- a/android/android_studio_project/app/src/main/java/jp/xenia/XeniaRuntimeException.java +++ b/android/android_studio_project/app/src/main/java/jp/xenia/XeniaRuntimeException.java @@ -7,15 +7,15 @@ public class XeniaRuntimeException extends RuntimeException { public XeniaRuntimeException() { } - public XeniaRuntimeException(String name) { + public XeniaRuntimeException(final String name) { super(name); } - public XeniaRuntimeException(String name, Throwable cause) { + public XeniaRuntimeException(final String name, final Throwable cause) { super(name, cause); } - public XeniaRuntimeException(Exception cause) { + public XeniaRuntimeException(final Exception cause) { super(cause); } } diff --git a/android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowDemoActivity.java b/android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowDemoActivity.java index a0dd36f0e..d03a07788 100644 --- a/android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowDemoActivity.java +++ b/android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowDemoActivity.java @@ -1,8 +1,18 @@ package jp.xenia.emulator; +import android.os.Bundle; + public class WindowDemoActivity extends WindowedAppActivity { @Override protected String getWindowedAppIdentifier() { return "xenia_ui_window_vulkan_demo"; } + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_window_demo); + setWindowSurfaceView(findViewById(R.id.window_demo_surface_view)); + } } diff --git a/android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowSurfaceView.java b/android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowSurfaceView.java new file mode 100644 index 000000000..23b014632 --- /dev/null +++ b/android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowSurfaceView.java @@ -0,0 +1,42 @@ +package jp.xenia.emulator; + +import android.content.Context; +import android.graphics.Canvas; +import android.util.AttributeSet; +import android.view.SurfaceView; + +public class WindowSurfaceView extends SurfaceView { + public WindowSurfaceView(final Context context) { + super(context); + // Native drawing is invoked from onDraw. + setWillNotDraw(false); + } + + public WindowSurfaceView(final Context context, final AttributeSet attrs) { + super(context, attrs); + setWillNotDraw(false); + } + + public WindowSurfaceView( + final Context context, final AttributeSet attrs, final int defStyleAttr) { + super(context, attrs, defStyleAttr); + setWillNotDraw(false); + } + + public WindowSurfaceView( + final Context context, final AttributeSet attrs, final int defStyleAttr, + final int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + setWillNotDraw(false); + } + + @Override + protected void onDraw(final Canvas canvas) { + final Context context = getContext(); + if (!(context instanceof WindowedAppActivity)) { + return; + } + final WindowedAppActivity activity = (WindowedAppActivity) context; + activity.onWindowSurfaceDraw(false); + } +} diff --git a/android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowedAppActivity.java b/android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowedAppActivity.java index 9deb60358..073283529 100644 --- a/android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowedAppActivity.java +++ b/android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowedAppActivity.java @@ -3,6 +3,11 @@ package jp.xenia.emulator; import android.app.Activity; import android.content.res.AssetManager; import android.os.Bundle; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.View; + +import org.jetbrains.annotations.Nullable; import jp.xenia.XeniaRuntimeException; @@ -12,21 +17,87 @@ public abstract class WindowedAppActivity extends Activity { System.loadLibrary("xenia-ui-window-vulkan-demo"); } - private long mAppContext; + private final WindowSurfaceOnLayoutChangeListener mWindowSurfaceOnLayoutChangeListener = + new WindowSurfaceOnLayoutChangeListener(); + private final WindowSurfaceHolderCallback mWindowSurfaceHolderCallback = + new WindowSurfaceHolderCallback(); - private native long initializeWindowedAppOnCreateNative( + // May be 0 while destroying (mainly while the superclass is). + private long mAppContext = 0; + + @Nullable + private WindowSurfaceView mWindowSurfaceView = null; + + private native long initializeWindowedAppOnCreate( String windowedAppIdentifier, AssetManager assetManager); private native void onDestroyNative(long appContext); + private native void onWindowSurfaceLayoutChange( + long appContext, int left, int top, int right, int bottom); + + private native void onWindowSurfaceChanged(long appContext, Surface windowSurface); + + private native void paintWindow(long appContext, boolean forcePaint); + protected abstract String getWindowedAppIdentifier(); + protected void setWindowSurfaceView(@Nullable final WindowSurfaceView windowSurfaceView) { + if (mWindowSurfaceView == windowSurfaceView) { + return; + } + + // Detach from the old surface. + if (mWindowSurfaceView != null) { + mWindowSurfaceView.getHolder().removeCallback(mWindowSurfaceHolderCallback); + mWindowSurfaceView.removeOnLayoutChangeListener(mWindowSurfaceOnLayoutChangeListener); + mWindowSurfaceView = null; + if (mAppContext != 0) { + onWindowSurfaceChanged(mAppContext, null); + } + } + + if (windowSurfaceView == null) { + return; + } + + mWindowSurfaceView = windowSurfaceView; + // The native window code assumes that, when the surface exists, it covers the entire + // window. + // FIXME(Triang3l): This doesn't work if the layout has already been performed. + mWindowSurfaceView.addOnLayoutChangeListener(mWindowSurfaceOnLayoutChangeListener); + final SurfaceHolder windowSurfaceHolder = mWindowSurfaceView.getHolder(); + windowSurfaceHolder.addCallback(mWindowSurfaceHolderCallback); + // If setting after the creation of the surface. + if (mAppContext != 0) { + final Surface windowSurface = windowSurfaceHolder.getSurface(); + if (windowSurface != null) { + onWindowSurfaceChanged(mAppContext, windowSurface); + } + } + } + + public void onWindowSurfaceDraw(final boolean forcePaint) { + if (mAppContext == 0) { + return; + } + paintWindow(mAppContext, forcePaint); + } + + // Used from the native WindowedAppContext. May be called from non-UI threads. + protected void postInvalidateWindowSurface() { + if (mWindowSurfaceView == null) { + return; + } + mWindowSurfaceView.postInvalidate(); + } + @Override - protected void onCreate(Bundle savedInstanceState) { + protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); final String windowedAppIdentifier = getWindowedAppIdentifier(); - mAppContext = initializeWindowedAppOnCreateNative(windowedAppIdentifier, getAssets()); + mAppContext = initializeWindowedAppOnCreate(windowedAppIdentifier, getAssets()); if (mAppContext == 0) { finish(); throw new XeniaRuntimeException( @@ -36,10 +107,54 @@ public abstract class WindowedAppActivity extends Activity { @Override protected void onDestroy() { + setWindowSurfaceView(null); if (mAppContext != 0) { onDestroyNative(mAppContext); } mAppContext = 0; super.onDestroy(); } + + private class WindowSurfaceOnLayoutChangeListener implements View.OnLayoutChangeListener { + @Override + public void onLayoutChange( + final View v, final int left, final int top, final int right, final int bottom, + final int oldLeft, final int oldTop, final int oldRight, final int oldBottom) { + if (mAppContext != 0) { + onWindowSurfaceLayoutChange(mAppContext, left, top, right, bottom); + } + } + } + + private class WindowSurfaceHolderCallback implements SurfaceHolder.Callback2 { + @Override + public void surfaceCreated(final SurfaceHolder holder) { + if (mAppContext == 0) { + return; + } + onWindowSurfaceChanged(mAppContext, holder.getSurface()); + } + + @Override + public void surfaceChanged( + final SurfaceHolder holder, final int format, final int width, final int height) { + if (mAppContext == 0) { + return; + } + onWindowSurfaceChanged(mAppContext, holder.getSurface()); + } + + @Override + public void surfaceDestroyed(final SurfaceHolder holder) { + if (mAppContext == 0) { + return; + } + onWindowSurfaceChanged(mAppContext, null); + } + + @Override + public void surfaceRedrawNeeded(final SurfaceHolder holder) { + onWindowSurfaceDraw(true); + } + } } diff --git a/android/android_studio_project/app/src/main/res/layout/activity_window_demo.xml b/android/android_studio_project/app/src/main/res/layout/activity_window_demo.xml index 79f49f81a..3bbd1211b 100644 --- a/android/android_studio_project/app/src/main/res/layout/activity_window_demo.xml +++ b/android/android_studio_project/app/src/main/res/layout/activity_window_demo.xml @@ -1,8 +1,7 @@ - - - \ No newline at end of file + tools:context="jp.xenia.emulator.WindowDemoActivity" /> diff --git a/android/android_studio_project/app/src/main/res/values/strings.xml b/android/android_studio_project/app/src/main/res/values/strings.xml index 6379b7bcb..ed3f21a79 100644 --- a/android/android_studio_project/app/src/main/res/values/strings.xml +++ b/android/android_studio_project/app/src/main/res/values/strings.xml @@ -1,3 +1,4 @@ Xenia + Xenia Window Demo \ No newline at end of file diff --git a/src/xenia/ui/presenter.cc b/src/xenia/ui/presenter.cc index aef8f7309..661a37a8a 100644 --- a/src/xenia/ui/presenter.cc +++ b/src/xenia/ui/presenter.cc @@ -26,13 +26,13 @@ // presenting from the thread refreshing the guest output is not absolutely // necessary, but still may be nice for bypassing the scheduling and the // message queue. -// On GTK, the frame rate of draw signals is limited to the display refresh rate -// internally, so for the lowest latency especially in case the refresh rates -// differ significantly on the guest and the host (like 30/60 Hz presented to -// 144 Hz), drawing from the guest output refreshing thread is highly desirable. -// Presenting directly from the GPU emulation thread also makes debugging GPU -// emulation easier with external tools, as presenting in most cases happens -// exactly between emulation frames. +// On Android and GTK, the frame rate of draw events is limited to the display +// refresh rate internally, so for the lowest latency especially in case the +// refresh rates differ significantly on the guest and the host (like 30/60 Hz +// presented to 144 Hz), drawing from the guest output refreshing thread is +// highly desirable. Presenting directly from the GPU emulation thread also +// makes debugging GPU emulation easier with external tools, as presenting in +// most cases happens exactly between emulation frames. DEFINE_bool( host_present_from_non_ui_thread, true, "Allow the GPU emulation thread to present the guest output to the host " diff --git a/src/xenia/ui/presenter.h b/src/xenia/ui/presenter.h index 9e89c8573..5ead8cb90 100644 --- a/src/xenia/ui/presenter.h +++ b/src/xenia/ui/presenter.h @@ -974,8 +974,8 @@ class Presenter { // frame rates wasting the CPU and the GPU resources and starving everything // else. The waits performed here must be interruptible by guest output // presentation requests to prevent adding arbitrary amounts of latency to it. - // On GTK, this is not needed, the frame rate of draw signals is limited to - // the display refresh rate internally. + // On Android and GTK, this is not needed, the frame rate of draw events is + // limited to the display refresh rate internally. #if XE_PLATFORM_WIN32 static Microsoft::WRL::ComPtr GetDXGIOutputForMonitor( IDXGIFactory1* factory, HMONITOR monitor); diff --git a/src/xenia/ui/window_android.cc b/src/xenia/ui/window_android.cc index c653df217..817f439c7 100644 --- a/src/xenia/ui/window_android.cc +++ b/src/xenia/ui/window_android.cc @@ -29,17 +29,42 @@ std::unique_ptr Window::Create(WindowedAppContext& app_context, AndroidWindow::~AndroidWindow() { EnterDestructor(); - AndroidWindowedAppContext& android_app_context = + auto& android_app_context = static_cast(app_context()); if (android_app_context.GetActivityWindow() == this) { android_app_context.SetActivityWindow(nullptr); } } +void AndroidWindow::OnActivitySurfaceLayoutChange() { + auto& android_app_context = + static_cast(app_context()); + assert_true(android_app_context.GetActivityWindow() == this); + uint32_t physical_width = + uint32_t(android_app_context.window_surface_layout_right() - + android_app_context.window_surface_layout_left()); + uint32_t physical_height = + uint32_t(android_app_context.window_surface_layout_bottom() - + android_app_context.window_surface_layout_top()); + OnDesiredLogicalSizeUpdate(SizeToLogical(physical_width), + SizeToLogical(physical_height)); + WindowDestructionReceiver destruction_receiver(this); + OnActualSizeUpdate(physical_width, physical_height, destruction_receiver); + if (destruction_receiver.IsWindowDestroyedOrClosed()) { + return; + } +} + +uint32_t AndroidWindow::GetLatestDpiImpl() const { + auto& android_app_context = + static_cast(app_context()); + return android_app_context.GetPixelDensity(); +} + bool AndroidWindow::OpenImpl() { // The window is a proxy between the main activity and Xenia, so there can be - // only one for an activity. - AndroidWindowedAppContext& android_app_context = + // only one open window for an activity. + auto& android_app_context = static_cast(app_context()); AndroidWindow* previous_activity_window = android_app_context.GetActivityWindow(); @@ -50,6 +75,10 @@ bool AndroidWindow::OpenImpl() { return false; } android_app_context.SetActivityWindow(this); + + // Report the initial layout. + OnActivitySurfaceLayoutChange(); + return true; } @@ -68,7 +97,7 @@ void AndroidWindow::RequestCloseImpl() { } OnAfterClose(); - AndroidWindowedAppContext& android_app_context = + auto& android_app_context = static_cast(app_context()); if (android_app_context.GetActivityWindow() == this) { android_app_context.SetActivityWindow(nullptr); @@ -78,13 +107,24 @@ void AndroidWindow::RequestCloseImpl() { std::unique_ptr AndroidWindow::CreateSurfaceImpl( Surface::TypeFlags allowed_types) { if (allowed_types & Surface::kTypeFlag_AndroidNativeWindow) { - // TODO(Triang3l): AndroidNativeWindowSurface for the ANativeWindow. + auto& android_app_context = + static_cast(app_context()); + assert_true(android_app_context.GetActivityWindow() == this); + ANativeWindow* activity_window_surface = + android_app_context.GetWindowSurface(); + if (activity_window_surface) { + return std::make_unique( + activity_window_surface); + } } return nullptr; } void AndroidWindow::RequestPaintImpl() { - // TODO(Triang3l): postInvalidate. + auto& android_app_context = + static_cast(app_context()); + assert_true(android_app_context.GetActivityWindow() == this); + android_app_context.PostInvalidateWindowSurface(); } std::unique_ptr MenuItem::Create(Type type, diff --git a/src/xenia/ui/window_android.h b/src/xenia/ui/window_android.h index 74832576d..52fac19ec 100644 --- a/src/xenia/ui/window_android.h +++ b/src/xenia/ui/window_android.h @@ -29,7 +29,13 @@ class AndroidWindow : public Window { uint32_t GetMediumDpi() const override { return 160; } + void OnActivitySurfaceLayoutChange(); + void OnActivitySurfaceChanged() { OnSurfaceChanged(true); } + void PaintActivitySurface(bool force_paint) { OnPaint(force_paint); } + protected: + uint32_t GetLatestDpiImpl() const override; + bool OpenImpl() override; void RequestCloseImpl() override; diff --git a/src/xenia/ui/windowed_app_context_android.cc b/src/xenia/ui/windowed_app_context_android.cc index 66d1d42ba..2deb4e95e 100644 --- a/src/xenia/ui/windowed_app_context_android.cc +++ b/src/xenia/ui/windowed_app_context_android.cc @@ -2,7 +2,7 @@ ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** - * Copyright 2021 Ben Vanik. All rights reserved. * + * Copyright 2022 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include #include #include @@ -22,6 +24,7 @@ #include "xenia/base/assert.h" #include "xenia/base/logging.h" #include "xenia/base/main_android.h" +#include "xenia/ui/window_android.h" #include "xenia/ui/windowed_app.h" namespace xe { @@ -52,6 +55,16 @@ void AndroidWindowedAppContext::PlatformQuitFromUIThread() { } } +void AndroidWindowedAppContext::PostInvalidateWindowSurface() { + // May be called from non-UI threads. + JNIEnv* jni_env = GetAndroidThreadJniEnv(); + if (!jni_env) { + return; + } + jni_env->CallVoidMethod(activity_, + activity_method_post_invalidate_window_surface_); +} + AndroidWindowedAppContext* AndroidWindowedAppContext::JniActivityInitializeWindowedAppOnCreate( JNIEnv* jni_env, jobject activity, jstring windowed_app_identifier, @@ -100,9 +113,54 @@ void AndroidWindowedAppContext::JniActivityOnDestroy() { app_->InvokeOnDestroy(); app_.reset(); } + // Expecting that the destruction of the app will destroy the window as well, + // no need to notify it explicitly. + assert_null(activity_window_); RequestDestruction(); } +void AndroidWindowedAppContext::JniActivityOnWindowSurfaceLayoutChange( + jint left, jint top, jint right, jint bottom) { + window_surface_layout_left_ = left; + window_surface_layout_top_ = top; + window_surface_layout_right_ = right; + window_surface_layout_bottom_ = bottom; + if (activity_window_) { + activity_window_->OnActivitySurfaceLayoutChange(); + } +} + +void AndroidWindowedAppContext::JniActivityOnWindowSurfaceChanged( + jobject window_surface_object) { + // Detach from the old surface. + if (window_surface_) { + ANativeWindow* old_window_surface = window_surface_; + window_surface_ = nullptr; + if (activity_window_) { + activity_window_->OnActivitySurfaceChanged(); + } + ANativeWindow_release(old_window_surface); + } + if (!window_surface_object) { + return; + } + window_surface_ = + ANativeWindow_fromSurface(ui_thread_jni_env_, window_surface_object); + if (!window_surface_) { + return; + } + if (activity_window_) { + activity_window_->OnActivitySurfaceChanged(); + } +} + +void AndroidWindowedAppContext::JniActivityPaintWindow(bool force_paint) { + if (!activity_window_) { + return; + } + activity_window_->PaintActivitySurface(force_paint); +} + AndroidWindowedAppContext::~AndroidWindowedAppContext() { Shutdown(); } bool AndroidWindowedAppContext::Initialize(JNIEnv* ui_thread_jni_env, @@ -205,6 +263,11 @@ bool AndroidWindowedAppContext::Initialize(JNIEnv* ui_thread_jni_env, activity_ids_obtained &= (activity_method_finish_ = ui_thread_jni_env_->GetMethodID( activity_class_, "finish", "()V")) != nullptr; + activity_ids_obtained &= + (activity_method_post_invalidate_window_surface_ = + ui_thread_jni_env_->GetMethodID( + activity_class_, "postInvalidateWindowSurface", "()V")) != + nullptr; if (!activity_ids_obtained) { XELOGE("AndroidWindowedAppContext: Failed to get the activity class IDs"); Shutdown(); @@ -407,7 +470,7 @@ bool AndroidWindowedAppContext::InitializeApp(std::unique_ptr ( extern "C" { JNIEXPORT jlong JNICALL -Java_jp_xenia_emulator_WindowedAppActivity_initializeWindowedAppOnCreateNative( +Java_jp_xenia_emulator_WindowedAppActivity_initializeWindowedAppOnCreate( JNIEnv* jni_env, jobject activity, jstring windowed_app_identifier, jobject asset_manager) { return reinterpret_cast( @@ -423,4 +486,27 @@ Java_jp_xenia_emulator_WindowedAppActivity_onDestroyNative( ->JniActivityOnDestroy(); } +JNIEXPORT void JNICALL +Java_jp_xenia_emulator_WindowedAppActivity_onWindowSurfaceLayoutChange( + JNIEnv* jni_env, jobject activity, jlong app_context_ptr, jint left, + jint top, jint right, jint bottom) { + reinterpret_cast(app_context_ptr) + ->JniActivityOnWindowSurfaceLayoutChange(left, top, right, bottom); +} + +JNIEXPORT void JNICALL +Java_jp_xenia_emulator_WindowedAppActivity_onWindowSurfaceChanged( + JNIEnv* jni_env, jobject activity, jlong app_context_ptr, + jobject window_surface_object) { + reinterpret_cast(app_context_ptr) + ->JniActivityOnWindowSurfaceChanged(window_surface_object); +} + +JNIEXPORT void JNICALL Java_jp_xenia_emulator_WindowedAppActivity_paintWindow( + JNIEnv* jni_env, jobject activity, jlong app_context_ptr, + jboolean force_paint) { + reinterpret_cast(app_context_ptr) + ->JniActivityPaintWindow(bool(force_paint)); +} + } // extern "C" diff --git a/src/xenia/ui/windowed_app_context_android.h b/src/xenia/ui/windowed_app_context_android.h index d12d25766..022826fc1 100644 --- a/src/xenia/ui/windowed_app_context_android.h +++ b/src/xenia/ui/windowed_app_context_android.h @@ -2,7 +2,7 @@ ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** - * Copyright 2021 Ben Vanik. All rights reserved. * + * Copyright 2022 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -33,6 +34,27 @@ class AndroidWindowedAppContext final : public WindowedAppContext { void PlatformQuitFromUIThread() override; + uint32_t GetPixelDensity() const { + return configuration_ ? uint32_t(AConfiguration_getDensity(configuration_)) + : 160; + } + + int32_t window_surface_layout_left() const { + return window_surface_layout_left_; + } + int32_t window_surface_layout_top() const { + return window_surface_layout_top_; + } + int32_t window_surface_layout_right() const { + return window_surface_layout_right_; + } + int32_t window_surface_layout_bottom() const { + return window_surface_layout_bottom_; + } + + ANativeWindow* GetWindowSurface() const { return window_surface_; } + void PostInvalidateWindowSurface(); + // The single Window instance that will be receiving window callbacks. // Multiple windows cannot be created as one activity or fragment can have // only one layout. This window acts purely as a proxy between the activity @@ -46,6 +68,10 @@ class AndroidWindowedAppContext final : public WindowedAppContext { JNIEnv* jni_env, jobject activity, jstring windowed_app_identifier, jobject asset_manager); void JniActivityOnDestroy(); + void JniActivityOnWindowSurfaceLayoutChange(jint left, jint top, jint right, + jint bottom); + void JniActivityOnWindowSurfaceChanged(jobject window_surface_object); + void JniActivityPaintWindow(bool force_paint); private: enum class UIThreadLooperCallbackCommand : uint8_t { @@ -93,6 +119,7 @@ class AndroidWindowedAppContext final : public WindowedAppContext { jobject activity_ = nullptr; jmethodID activity_method_finish_ = nullptr; + jmethodID activity_method_post_invalidate_window_surface_ = nullptr; // May be read by non-UI threads in NotifyUILoopOfPendingFunctions. ALooper* ui_thread_looper_ = nullptr; @@ -101,6 +128,13 @@ class AndroidWindowedAppContext final : public WindowedAppContext { std::array ui_thread_looper_callback_pipe_{-1, -1}; bool ui_thread_looper_callback_registered_ = false; + int32_t window_surface_layout_left_ = 0; + int32_t window_surface_layout_top_ = 0; + int32_t window_surface_layout_right_ = 0; + int32_t window_surface_layout_bottom_ = 0; + + ANativeWindow* window_surface_ = nullptr; + AndroidWindow* activity_window_ = nullptr; std::unique_ptr app_;