[UI] Android surface [skip appveyor]

This commit is contained in:
Triang3l
2022-02-01 22:18:04 +03:00
parent c6fc8f706a
commit 413d7ded49
14 changed files with 369 additions and 30 deletions

View File

@@ -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 "

View File

@@ -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<IDXGIOutput> GetDXGIOutputForMonitor(
IDXGIFactory1* factory, HMONITOR monitor);

View File

@@ -29,17 +29,42 @@ std::unique_ptr<Window> Window::Create(WindowedAppContext& app_context,
AndroidWindow::~AndroidWindow() {
EnterDestructor();
AndroidWindowedAppContext& android_app_context =
auto& android_app_context =
static_cast<AndroidWindowedAppContext&>(app_context());
if (android_app_context.GetActivityWindow() == this) {
android_app_context.SetActivityWindow(nullptr);
}
}
void AndroidWindow::OnActivitySurfaceLayoutChange() {
auto& android_app_context =
static_cast<const AndroidWindowedAppContext&>(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<const AndroidWindowedAppContext&>(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<AndroidWindowedAppContext&>(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<AndroidWindowedAppContext&>(app_context());
if (android_app_context.GetActivityWindow() == this) {
android_app_context.SetActivityWindow(nullptr);
@@ -78,13 +107,24 @@ void AndroidWindow::RequestCloseImpl() {
std::unique_ptr<Surface> AndroidWindow::CreateSurfaceImpl(
Surface::TypeFlags allowed_types) {
if (allowed_types & Surface::kTypeFlag_AndroidNativeWindow) {
// TODO(Triang3l): AndroidNativeWindowSurface for the ANativeWindow.
auto& android_app_context =
static_cast<const AndroidWindowedAppContext&>(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<AndroidNativeWindowSurface>(
activity_window_surface);
}
}
return nullptr;
}
void AndroidWindow::RequestPaintImpl() {
// TODO(Triang3l): postInvalidate.
auto& android_app_context =
static_cast<AndroidWindowedAppContext&>(app_context());
assert_true(android_app_context.GetActivityWindow() == this);
android_app_context.PostInvalidateWindowSurface();
}
std::unique_ptr<ui::MenuItem> MenuItem::Create(Type type,

View File

@@ -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;

View File

@@ -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 <android/configuration.h>
#include <android/log.h>
#include <android/looper.h>
#include <android/native_window.h>
#include <android/native_window_jni.h>
#include <fcntl.h>
#include <jni.h>
#include <unistd.h>
@@ -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<WindowedApp> (
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<jlong>(
@@ -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<xe::ui::AndroidWindowedAppContext*>(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<xe::ui::AndroidWindowedAppContext*>(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<xe::ui::AndroidWindowedAppContext*>(app_context_ptr)
->JniActivityPaintWindow(bool(force_paint));
}
} // extern "C"

View File

@@ -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 <android/asset_manager.h>
#include <android/configuration.h>
#include <android/looper.h>
#include <android/native_window.h>
#include <jni.h>
#include <array>
#include <memory>
@@ -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<int, 2> 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<WindowedApp> app_;