[UI] Android surface [skip appveyor]
This commit is contained in:
@@ -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 "
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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_;
|
||||
|
||||
Reference in New Issue
Block a user