From c4667b1cf05b0242b9a856ce6f3a459f229364a8 Mon Sep 17 00:00:00 2001 From: David Griswold Date: Sat, 1 Mar 2025 13:01:42 -0300 Subject: [PATCH 1/9] Enable the SecondScreenPresentation class --- .../citra_emu/activities/EmulationActivity.kt | 28 +++++++++++- .../display/SecondScreenPresentation.kt | 44 +++++++++++++++++++ .../citra_emu/fragments/EmulationFragment.kt | 10 ++++- 3 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 src/android/app/src/main/java/org/citra/citra_emu/display/SecondScreenPresentation.kt diff --git a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt index 8d9bf24e3..69c7f248a 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt @@ -4,14 +4,18 @@ package org.citra.citra_emu.activities +import SecondScreenPresentation import android.Manifest.permission import android.annotation.SuppressLint -import android.app.Activity +import android.app.Presentation +import android.content.Context import android.content.Intent import android.content.SharedPreferences import android.content.pm.PackageManager +import android.hardware.display.DisplayManager import android.net.Uri import android.os.Bundle +import android.view.Display import android.view.InputDevice import android.view.KeyEvent import android.view.MotionEvent @@ -56,6 +60,24 @@ class EmulationActivity : AppCompatActivity() { private lateinit var binding: ActivityEmulationBinding private lateinit var screenAdjustmentUtil: ScreenAdjustmentUtil private lateinit var hotkeyUtility: HotkeyUtility + private var secondScreenPresentation: Presentation? = null + private lateinit var displayManager: DisplayManager + + private fun updatePresentation(display: Display?) { + if (secondScreenPresentation == null || secondScreenPresentation?.display != display) { + secondScreenPresentation?.dismiss() + // this should only happen if the second screen is enabled in a setting + // eventually. Otherwise it should continue to mirror. + if (display != null) + secondScreenPresentation = SecondScreenPresentation(this, display) + secondScreenPresentation?.show(); + } + + } + + private fun getCustomerDisplay(): Display? { + return displayManager?.displays?.firstOrNull { it.isValid && it.displayId != Display.DEFAULT_DISPLAY } + } private val emulationFragment: EmulationFragment get() { @@ -69,6 +91,10 @@ class EmulationActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { ThemeUtil.setTheme(this) + /* Find a second display if it's connected and create a basic presentationon it */ + displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager + val display = getCustomerDisplay(); + updatePresentation(display); settingsViewModel.settings.loadSettings() super.onCreate(savedInstanceState) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/display/SecondScreenPresentation.kt b/src/android/app/src/main/java/org/citra/citra_emu/display/SecondScreenPresentation.kt new file mode 100644 index 000000000..d66642a8a --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/display/SecondScreenPresentation.kt @@ -0,0 +1,44 @@ +import android.app.Presentation +import android.content.Context +import android.graphics.Color +import android.graphics.Paint +import android.os.Bundle +import android.view.Display +import android.view.SurfaceHolder +import android.view.SurfaceView + +class SecondScreenPresentation( + context: Context, + display: Display, +) : Presentation(context, display) { + private lateinit var surfaceView: SurfaceView + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // Initialize SurfaceView + surfaceView = SurfaceView(context) + surfaceView.holder.addCallback(object : SurfaceHolder.Callback { + override fun surfaceCreated(holder: SurfaceHolder) { + + } + + override fun surfaceChanged( + holder: SurfaceHolder, + format: Int, + width: Int, + height: Int + ) { + } + + override fun surfaceDestroyed(holder: SurfaceHolder) {} + }) + + setContentView(surfaceView) // Set SurfaceView as content + } + + // Publicly accessible method to get the SurfaceHolder + fun getSurfaceHolder(): SurfaceHolder { + return surfaceView.holder + } +} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt index e91876584..26a5d453b 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt @@ -5,9 +5,12 @@ package org.citra.citra_emu.fragments import android.annotation.SuppressLint +import android.app.Presentation import android.content.Context import android.content.DialogInterface import android.content.SharedPreferences +import android.hardware.display.DisplayManager +import android.media.MediaRouter import android.net.Uri import android.os.Bundle import android.os.Handler @@ -16,6 +19,7 @@ import android.os.SystemClock import android.text.Editable import android.text.TextWatcher import android.view.Choreographer +import android.view.Display import android.view.LayoutInflater import android.view.MotionEvent import android.view.Surface @@ -26,6 +30,7 @@ import android.widget.PopupMenu import android.widget.TextView import android.widget.Toast import androidx.activity.OnBackPressedCallback +import androidx.core.content.ContextCompat.getSystemService import androidx.core.content.res.ResourcesCompat import androidx.core.graphics.Insets import androidx.core.view.ViewCompat @@ -79,6 +84,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram private lateinit var emulationState: EmulationState private var perfStatsUpdater: Runnable? = null + private lateinit var emulationActivity: EmulationActivity private var _binding: FragmentEmulationBinding? = null @@ -146,6 +152,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram retainInstance = true emulationState = EmulationState(game.path) emulationActivity = requireActivity() as EmulationActivity + screenAdjustmentUtil = ScreenAdjustmentUtil(requireContext(), requireActivity().windowManager, settingsViewModel.settings) EmulationLifecycleUtil.addShutdownHook(hook = { emulationState.stop() }) EmulationLifecycleUtil.addPauseResumeHook(hook = { togglePause() }) @@ -1214,6 +1221,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram private class EmulationState(private val gamePath: String) { private var state: State private var surface: Surface? = null + private var surface2: Surface? = null init { // Starting state is stopped. @@ -1321,7 +1329,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram } private fun runWithValidSurface() { - NativeLibrary.surfaceChanged(surface!!) + NativeLibrary.surfaceChanged(surface!!, surface2!!) when (state) { State.STOPPED -> { Thread({ From 12afea90157c7d042fd2537913e7816751f9bc5b Mon Sep 17 00:00:00 2001 From: David Griswold Date: Sun, 2 Mar 2025 17:15:18 -0300 Subject: [PATCH 2/9] Update everything to enable second screen on android under GL and Vulkan. Still some issues! --- .../java/org/citra/citra_emu/NativeLibrary.kt | 3 ++ .../display/SecondScreenPresentation.kt | 10 ++-- .../citra_emu/fragments/EmulationFragment.kt | 2 +- .../src/main/jni/emu_window/emu_window.cpp | 7 +-- .../app/src/main/jni/emu_window/emu_window.h | 8 +++- .../src/main/jni/emu_window/emu_window_gl.cpp | 26 ++++++---- .../src/main/jni/emu_window/emu_window_gl.h | 4 +- .../src/main/jni/emu_window/emu_window_vk.cpp | 4 +- .../src/main/jni/emu_window/emu_window_vk.h | 2 +- src/android/app/src/main/jni/native.cpp | 48 +++++++++++++++++-- src/core/frontend/emu_window.cpp | 7 +++ src/video_core/renderer_base.cpp | 2 +- .../renderer_opengl/renderer_opengl.cpp | 13 ++++- .../renderer_vulkan/renderer_vulkan.cpp | 10 ++++ 14 files changed, 113 insertions(+), 33 deletions(-) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt index 40dbc94c9..11ee2b187 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt @@ -125,6 +125,9 @@ object NativeLibrary { external fun surfaceDestroyed() external fun doFrame() + //Second window + external fun enableSecondWindow(secondary_surface: Surface) + external fun disableSecondWindow() /** * Unpauses emulation from a paused state. */ diff --git a/src/android/app/src/main/java/org/citra/citra_emu/display/SecondScreenPresentation.kt b/src/android/app/src/main/java/org/citra/citra_emu/display/SecondScreenPresentation.kt index d66642a8a..b4c63424e 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/display/SecondScreenPresentation.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/display/SecondScreenPresentation.kt @@ -1,11 +1,10 @@ import android.app.Presentation import android.content.Context -import android.graphics.Color -import android.graphics.Paint import android.os.Bundle import android.view.Display import android.view.SurfaceHolder import android.view.SurfaceView +import org.citra.citra_emu.NativeLibrary class SecondScreenPresentation( context: Context, @@ -20,7 +19,7 @@ class SecondScreenPresentation( surfaceView = SurfaceView(context) surfaceView.holder.addCallback(object : SurfaceHolder.Callback { override fun surfaceCreated(holder: SurfaceHolder) { - + NativeLibrary.enableSecondWindow(holder.surface) } override fun surfaceChanged( @@ -29,9 +28,12 @@ class SecondScreenPresentation( width: Int, height: Int ) { + NativeLibrary.enableSecondWindow(holder.surface) } - override fun surfaceDestroyed(holder: SurfaceHolder) {} + override fun surfaceDestroyed(holder: SurfaceHolder) { + NativeLibrary.disableSecondWindow(); + } }) setContentView(surfaceView) // Set SurfaceView as content diff --git a/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt index 26a5d453b..f5e52b265 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt @@ -1329,7 +1329,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram } private fun runWithValidSurface() { - NativeLibrary.surfaceChanged(surface!!, surface2!!) + NativeLibrary.surfaceChanged(surface!!) when (state) { State.STOPPED -> { Thread({ diff --git a/src/android/app/src/main/jni/emu_window/emu_window.cpp b/src/android/app/src/main/jni/emu_window/emu_window.cpp index b33c734f5..1d7220782 100644 --- a/src/android/app/src/main/jni/emu_window/emu_window.cpp +++ b/src/android/app/src/main/jni/emu_window/emu_window.cpp @@ -49,16 +49,17 @@ void EmuWindow_Android::OnFramebufferSizeChanged() { const int bigger{window_width > window_height ? window_width : window_height}; const int smaller{window_width < window_height ? window_width : window_height}; - if (is_portrait_mode) { + if (is_portrait_mode && !is_secondary) { UpdateCurrentFramebufferLayout(smaller, bigger, is_portrait_mode); } else { UpdateCurrentFramebufferLayout(bigger, smaller, is_portrait_mode); } } -EmuWindow_Android::EmuWindow_Android(ANativeWindow* surface) : host_window{surface} { +EmuWindow_Android::EmuWindow_Android(ANativeWindow *surface, bool is_secondary) : EmuWindow{ + is_secondary}, host_window(surface) { LOG_DEBUG(Frontend, "Initializing EmuWindow_Android"); - + if (is_secondary) LOG_DEBUG(Frontend, "Initializing secondary window Android"); if (!surface) { LOG_CRITICAL(Frontend, "surface is nullptr"); return; diff --git a/src/android/app/src/main/jni/emu_window/emu_window.h b/src/android/app/src/main/jni/emu_window/emu_window.h index 4266fd1bb..41f7633e6 100644 --- a/src/android/app/src/main/jni/emu_window/emu_window.h +++ b/src/android/app/src/main/jni/emu_window/emu_window.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include "core/frontend/emu_window.h" namespace Core { @@ -13,7 +14,7 @@ class System; class EmuWindow_Android : public Frontend::EmuWindow { public: - EmuWindow_Android(ANativeWindow* surface); + EmuWindow_Android(ANativeWindow* surface, bool is_secondary = false); ~EmuWindow_Android(); /// Called by the onSurfaceChanges() method to change the surface @@ -30,7 +31,10 @@ public: void DoneCurrent() override; virtual void TryPresenting() {} - + // EGL Context must be shared + // could probably use the existing + // SharedContext for this instead, this is maybe temporary + virtual EGLContext* GetEGLContext() {return NULL;} virtual void StopPresenting() {} protected: diff --git a/src/android/app/src/main/jni/emu_window/emu_window_gl.cpp b/src/android/app/src/main/jni/emu_window/emu_window_gl.cpp index 25db55bbe..4037fff54 100644 --- a/src/android/app/src/main/jni/emu_window/emu_window_gl.cpp +++ b/src/android/app/src/main/jni/emu_window/emu_window_gl.cpp @@ -72,8 +72,8 @@ private: EGLContext egl_context{}; }; -EmuWindow_Android_OpenGL::EmuWindow_Android_OpenGL(Core::System& system_, ANativeWindow* surface) - : EmuWindow_Android{surface}, system{system_} { +EmuWindow_Android_OpenGL::EmuWindow_Android_OpenGL(Core::System& system_, ANativeWindow* surface, bool is_secondary, EGLContext* sharedContext) + : EmuWindow_Android{surface,is_secondary}, system{system_} { if (egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); egl_display == EGL_NO_DISPLAY) { LOG_CRITICAL(Frontend, "eglGetDisplay() failed"); return; @@ -96,8 +96,9 @@ EmuWindow_Android_OpenGL::EmuWindow_Android_OpenGL(Core::System& system_, ANativ if (eglQuerySurface(egl_display, egl_surface, EGL_HEIGHT, &window_height) != EGL_TRUE) { return; } - - if (egl_context = eglCreateContext(egl_display, egl_config, 0, egl_context_attribs.data()); + if (sharedContext) { + egl_context = *sharedContext; + }else if (egl_context = eglCreateContext(egl_display, egl_config, 0, egl_context_attribs.data()); egl_context == EGL_NO_CONTEXT) { LOG_CRITICAL(Frontend, "eglCreateContext() failed"); return; @@ -127,6 +128,10 @@ EmuWindow_Android_OpenGL::EmuWindow_Android_OpenGL(Core::System& system_, ANativ OnFramebufferSizeChanged(); } +EGLContext* EmuWindow_Android_OpenGL::GetEGLContext() { + return &egl_context; +} + bool EmuWindow_Android_OpenGL::CreateWindowSurface() { if (!host_window) { return true; @@ -204,14 +209,15 @@ void EmuWindow_Android_OpenGL::TryPresenting() { return; } if (presenting_state == PresentingState::Initial) [[unlikely]] { - eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); presenting_state = PresentingState::Running; } - if (presenting_state != PresentingState::Running) [[unlikely]] { - return; - } +// if (presenting_state != PresentingState::Running) [[unlikely]] { +// return; +// } + eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); eglSwapInterval(egl_display, Settings::values.use_vsync_new ? 1 : 0); - system.GPU().Renderer().TryPresent(0); + system.GPU().Renderer().TryPresent(100,is_secondary); eglSwapBuffers(egl_display, egl_surface); + } diff --git a/src/android/app/src/main/jni/emu_window/emu_window_gl.h b/src/android/app/src/main/jni/emu_window/emu_window_gl.h index a705174ac..c3f668483 100644 --- a/src/android/app/src/main/jni/emu_window/emu_window_gl.h +++ b/src/android/app/src/main/jni/emu_window/emu_window_gl.h @@ -19,13 +19,13 @@ struct ANativeWindow; class EmuWindow_Android_OpenGL : public EmuWindow_Android { public: - EmuWindow_Android_OpenGL(Core::System& system, ANativeWindow* surface); + EmuWindow_Android_OpenGL(Core::System& system, ANativeWindow* surface, bool is_secondary, EGLContext* sharedContext = NULL); ~EmuWindow_Android_OpenGL() override = default; void TryPresenting() override; void StopPresenting() override; void PollEvents() override; - + EGLContext* GetEGLContext() override; std::unique_ptr CreateSharedContext() const override; private: diff --git a/src/android/app/src/main/jni/emu_window/emu_window_vk.cpp b/src/android/app/src/main/jni/emu_window/emu_window_vk.cpp index 238e1ae1a..f34957660 100644 --- a/src/android/app/src/main/jni/emu_window/emu_window_vk.cpp +++ b/src/android/app/src/main/jni/emu_window/emu_window_vk.cpp @@ -24,8 +24,8 @@ private: }; EmuWindow_Android_Vulkan::EmuWindow_Android_Vulkan( - ANativeWindow* surface, std::shared_ptr driver_library_) - : EmuWindow_Android{surface}, driver_library{driver_library_} { + ANativeWindow* surface, std::shared_ptr driver_library_, bool is_secondary) + : EmuWindow_Android{surface,is_secondary}, driver_library{driver_library_} { CreateWindowSurface(); if (core_context = CreateSharedContext(); !core_context) { diff --git a/src/android/app/src/main/jni/emu_window/emu_window_vk.h b/src/android/app/src/main/jni/emu_window/emu_window_vk.h index fe54f9a36..60e2435a7 100644 --- a/src/android/app/src/main/jni/emu_window/emu_window_vk.h +++ b/src/android/app/src/main/jni/emu_window/emu_window_vk.h @@ -11,7 +11,7 @@ struct ANativeWindow; class EmuWindow_Android_Vulkan : public EmuWindow_Android { public: EmuWindow_Android_Vulkan(ANativeWindow* surface, - std::shared_ptr driver_library); + std::shared_ptr driver_library, bool is_secondary); ~EmuWindow_Android_Vulkan() override = default; void PollEvents() override {} diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index d4263c18a..aa8fb0838 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -65,9 +65,12 @@ namespace { ANativeWindow* s_surf; +ANativeWindow* s_secondary_surface; +bool secondary_enabled = false; std::shared_ptr vulkan_library{}; std::unique_ptr window; +std::unique_ptr second_window; std::atomic stop_run{true}; std::atomic pause_emulation{false}; @@ -118,8 +121,10 @@ static void TryShutdown() { } window->DoneCurrent(); + if (second_window) second_window->DoneCurrent(); Core::System::GetInstance().Shutdown(); window.reset(); + if (second_window) second_window.reset(); InputManager::Shutdown(); MicroProfileShutdown(); } @@ -148,12 +153,17 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) { switch (graphics_api) { #ifdef ENABLE_OPENGL case Settings::GraphicsAPI::OpenGL: - window = std::make_unique(system, s_surf); + window = std::make_unique(system, s_surf,false); + if (secondary_enabled) { + EGLContext* c = window->GetEGLContext(); + second_window = std::make_unique(system,s_secondary_surface, true, c); + } break; #endif #ifdef ENABLE_VULKAN case Settings::GraphicsAPI::Vulkan: - window = std::make_unique(s_surf, vulkan_library); + window = std::make_unique(s_surf, vulkan_library,false); + if (secondary_enabled) second_window = std::make_unique(s_secondary_surface, vulkan_library, true); break; #endif default: @@ -161,9 +171,14 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) { "Unknown or unsupported graphics API {}, falling back to available default", graphics_api); #ifdef ENABLE_OPENGL - window = std::make_unique(system, s_surf); + window = std::make_unique(system, s_surf, false); + if (secondary_enabled) { + EGLContext *c = window->GetEGLContext(); + second_window = std::make_unique(system, s_secondary_surface,true, c); + } #elif ENABLE_VULKAN window = std::make_unique(s_surf, vulkan_library); + if (secondary_enabled) second_window = std::make_unique(s_secondary_surface, vulkan_library, true); #else // TODO: Add a null renderer backend for this, perhaps. #error "At least one renderer must be enabled." @@ -202,7 +217,8 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) { InputManager::Init(); window->MakeCurrent(); - const Core::System::ResultStatus load_result{system.Load(*window, filepath)}; + //if (second_window) second_window->MakeCurrent(); + const Core::System::ResultStatus load_result{system.Load(*window, filepath,second_window.get())}; if (load_result != Core::System::ResultStatus::Success) { return load_result; } @@ -249,6 +265,7 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) { std::unique_lock pause_lock{paused_mutex}; running_cv.wait(pause_lock, [] { return !pause_emulation || stop_run; }); window->PollEvents(); + //if (second_window) second_window->PollEvents(); } } @@ -306,6 +323,23 @@ void Java_org_citra_citra_1emu_NativeLibrary_surfaceChanged(JNIEnv* env, LOG_INFO(Frontend, "Surface changed"); } +void Java_org_citra_citra_1emu_NativeLibrary_enableSecondWindow(JNIEnv* env, + [[maybe_unused]] jobject obj,jobject surf) { + s_secondary_surface = ANativeWindow_fromSurface(env, surf); + secondary_enabled = true; + + LOG_INFO(Frontend, "Secondary Surface Enabled"); +} + +void Java_org_citra_citra_1emu_NativeLibrary_disableSecondWindow(JNIEnv* env, + [[maybe_unused]] jobject obj) { + + secondary_enabled = false; + // how do I delete the window? TODO + + LOG_INFO(Frontend, "Secondary Surface Disabled"); +} + void Java_org_citra_citra_1emu_NativeLibrary_surfaceDestroyed([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) { if (s_surf != nullptr) { @@ -325,6 +359,9 @@ void Java_org_citra_citra_1emu_NativeLibrary_doFrame([[maybe_unused]] JNIEnv* en if (window) { window->TryPresenting(); } + if (second_window) { + second_window->TryPresenting(); + } } void JNICALL Java_org_citra_citra_1emu_NativeLibrary_initializeGpuDriver( @@ -481,6 +518,7 @@ void Java_org_citra_citra_1emu_NativeLibrary_stopEmulation([[maybe_unused]] JNIE stop_run = true; pause_emulation = false; window->StopPresenting(); + if (second_window) second_window->StopPresenting(); running_cv.notify_all(); } @@ -745,4 +783,4 @@ void Java_org_citra_citra_1emu_NativeLibrary_logDeviceInfo([[maybe_unused]] JNIE LOG_INFO(Frontend, "Host OS: Android API level {}", android_get_device_api_level()); } -} // extern "C" +} \ No newline at end of file diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp index 6c2811cf0..9b65d5e99 100644 --- a/src/core/frontend/emu_window.cpp +++ b/src/core/frontend/emu_window.cpp @@ -251,6 +251,13 @@ void EmuWindow::UpdateCurrentFramebufferLayout(u32 width, u32 height, bool is_po break; } } +#ifdef ANDROID + // if is_secondary is set on android, MUST be a second window + if (is_secondary) { + layout = Layout::SeparateWindowsLayout(width, height, is_secondary, + Settings::values.upright_screen.GetValue()); + } +#endif UpdateMinimumWindowSize(min_size); if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::CardboardVR) { diff --git a/src/video_core/renderer_base.cpp b/src/video_core/renderer_base.cpp index 16b59107a..fbebee2b8 100644 --- a/src/video_core/renderer_base.cpp +++ b/src/video_core/renderer_base.cpp @@ -35,7 +35,7 @@ void RendererBase::UpdateCurrentFramebufferLayout(bool is_portrait_mode) { window.UpdateCurrentFramebufferLayout(layout.width, layout.height, is_portrait_mode); }; update_layout(render_window); - if (secondary_window) { + if (secondary_window != nullptr) { update_layout(*secondary_window); } } diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 4f5dcb131..8139723ec 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -96,7 +96,7 @@ void RendererOpenGL::SwapBuffers() { PrepareRendertarget(); RenderScreenshot(); - const auto& main_layout = render_window.GetFramebufferLayout(); + const auto &main_layout = render_window.GetFramebufferLayout(); RenderToMailbox(main_layout, render_window.mailbox, false); #ifndef ANDROID @@ -106,6 +106,15 @@ void RendererOpenGL::SwapBuffers() { RenderToMailbox(secondary_layout, secondary_window->mailbox, false); secondary_window->PollEvents(); } +#endif +#ifdef ANDROID + // on android, if secondary_window is defined at all + // it means we have a second display + if (secondary_window) { + const auto &secondary_layout = secondary_window->GetFramebufferLayout(); + RenderToMailbox(secondary_layout, secondary_window->mailbox, false); + secondary_window->PollEvents(); + } #endif if (frame_dumper.IsDumping()) { try { @@ -553,7 +562,7 @@ void RendererOpenGL::DrawSingleScreen(const ScreenInfo& screen_info, float x, fl state.texture_units[0].texture_2d = 0; state.texture_units[0].sampler = 0; state.Apply(); -} + } /** * Draws a single texture to the emulator window, rotating the texture to correct for the 3DS's LCD diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index 6c95a22b7..4e3664aaa 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -829,6 +829,16 @@ void RendererVulkan::SwapBuffers() { RenderToWindow(*second_window, secondary_layout, false); secondary_window->PollEvents(); } +#endif +#ifdef ANDROID + if (secondary_window) { + const auto &secondary_layout = secondary_window->GetFramebufferLayout(); + if (!second_window) { + second_window = std::make_unique(*secondary_window, instance, scheduler); + } + RenderToWindow(*second_window, secondary_layout, false); + secondary_window->PollEvents(); + } #endif rasterizer.TickFrame(); EndFrame(); From dc885ca674db3054b572f2aa7cf09915c52cb337 Mon Sep 17 00:00:00 2001 From: David Griswold Date: Tue, 4 Mar 2025 19:11:34 -0300 Subject: [PATCH 3/9] Some attempts to enable surface changes --- src/android/app/src/main/jni/native.cpp | 12 +++++++++++- src/video_core/renderer_vulkan/renderer_vulkan.h | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index aa8fb0838..f8b915918 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -327,10 +327,20 @@ void Java_org_citra_citra_1emu_NativeLibrary_enableSecondWindow(JNIEnv* env, [[maybe_unused]] jobject obj,jobject surf) { s_secondary_surface = ANativeWindow_fromSurface(env, surf); secondary_enabled = true; + bool notify = false; + if (second_window) { + notify = second_window->OnSurfaceChanged(s_secondary_surface); + } + auto& system = Core::System::GetInstance(); + if (notify && system.IsPoweredOn()) { + system.GPU().Renderer().NotifySurfaceChanged(); + } - LOG_INFO(Frontend, "Secondary Surface Enabled"); + LOG_INFO(Frontend, "Secondary Surface changed"); } + + void Java_org_citra_citra_1emu_NativeLibrary_disableSecondWindow(JNIEnv* env, [[maybe_unused]] jobject obj) { diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h index b52142e88..cad1cf204 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.h +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h @@ -76,6 +76,7 @@ public: void NotifySurfaceChanged() override { main_window.NotifySurfaceChanged(); + if (second_window) second_window->NotifySurfaceChanged(); } void SwapBuffers() override; From 37f73070a9fc08d0ee3cc322f0b18a90a9d6c89e Mon Sep 17 00:00:00 2001 From: David Griswold Date: Wed, 5 Mar 2025 08:10:50 -0300 Subject: [PATCH 4/9] OpenGL is working on surface change, vulkan still no --- src/android/app/src/main/jni/native.cpp | 30 +++++++++++-------- src/video_core/renderer_base.h | 3 +- .../renderer_vulkan/renderer_vulkan.cpp | 6 ++++ .../renderer_vulkan/renderer_vulkan.h | 5 +--- 4 files changed, 27 insertions(+), 17 deletions(-) diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index f8b915918..5a65877ff 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -317,7 +317,7 @@ void Java_org_citra_citra_1emu_NativeLibrary_surfaceChanged(JNIEnv* env, auto& system = Core::System::GetInstance(); if (notify && system.IsPoweredOn()) { - system.GPU().Renderer().NotifySurfaceChanged(); + system.GPU().Renderer().NotifySurfaceChanged(false); } LOG_INFO(Frontend, "Surface changed"); @@ -325,16 +325,16 @@ void Java_org_citra_citra_1emu_NativeLibrary_surfaceChanged(JNIEnv* env, void Java_org_citra_citra_1emu_NativeLibrary_enableSecondWindow(JNIEnv* env, [[maybe_unused]] jobject obj,jobject surf) { - s_secondary_surface = ANativeWindow_fromSurface(env, surf); - secondary_enabled = true; - bool notify = false; - if (second_window) { - notify = second_window->OnSurfaceChanged(s_secondary_surface); - } - auto& system = Core::System::GetInstance(); - if (notify && system.IsPoweredOn()) { - system.GPU().Renderer().NotifySurfaceChanged(); - } + s_secondary_surface = ANativeWindow_fromSurface(env, surf); + secondary_enabled = true; + bool notify = false; + if (second_window) { + notify = second_window->OnSurfaceChanged(s_secondary_surface); + } + auto& system = Core::System::GetInstance(); + if (notify && system.IsPoweredOn()) { + system.GPU().Renderer().NotifySurfaceChanged(true); + } LOG_INFO(Frontend, "Secondary Surface changed"); } @@ -345,7 +345,13 @@ void Java_org_citra_citra_1emu_NativeLibrary_disableSecondWindow(JNIEnv* env, [[maybe_unused]] jobject obj) { secondary_enabled = false; - // how do I delete the window? TODO + if (s_secondary_surface != nullptr) { + ANativeWindow_release(s_secondary_surface); + s_secondary_surface = nullptr; + if (second_window) { + second_window->OnSurfaceChanged(s_secondary_surface); + } + } LOG_INFO(Frontend, "Secondary Surface Disabled"); } diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h index 55cb90d0b..526ce51d7 100644 --- a/src/video_core/renderer_base.h +++ b/src/video_core/renderer_base.h @@ -64,7 +64,8 @@ public: virtual void Sync() {} /// This is called to notify the rendering backend of a surface change - virtual void NotifySurfaceChanged() {} + // if second == true then it is the second screen + virtual void NotifySurfaceChanged(bool second) {} /// Returns the resolution scale factor relative to the native 3DS screen resolution u32 GetResolutionScaleFactor(); diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index 4e3664aaa..616898d38 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -1129,4 +1129,10 @@ bool RendererVulkan::TryRenderScreenshotWithHostMemory() { return true; } + void RendererVulkan::NotifySurfaceChanged(bool second) { + if (second && second_window) second_window->NotifySurfaceChanged(); + if (!second) main_window.NotifySurfaceChanged(); + + } + } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h index cad1cf204..a35fb477b 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.h +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h @@ -74,10 +74,7 @@ public: return &rasterizer; } - void NotifySurfaceChanged() override { - main_window.NotifySurfaceChanged(); - if (second_window) second_window->NotifySurfaceChanged(); - } + void NotifySurfaceChanged(bool second) override; void SwapBuffers() override; void TryPresent(int timeout_ms, bool is_secondary) override {} From da9b06bdac31a15abf199cf280aa9db89365ba2f Mon Sep 17 00:00:00 2001 From: David Griswold Date: Wed, 5 Mar 2025 08:26:42 -0300 Subject: [PATCH 5/9] release surfaces (also fixed vulkan?) --- .../java/org/citra/citra_emu/activities/EmulationActivity.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt index 69c7f248a..0b3666d77 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt @@ -167,6 +167,8 @@ class EmulationActivity : AppCompatActivity() { EmulationLifecycleUtil.clear() isEmulationRunning = false instance = null + secondScreenPresentation?.dismiss(); + secondScreenPresentation = null; super.onDestroy() } From 5eb4e2adffd36a2d4a013b1ff9a81d6db26172ca Mon Sep 17 00:00:00 2001 From: David Griswold Date: Thu, 6 Mar 2025 09:46:30 -0300 Subject: [PATCH 6/9] added and enabled layout setting --- .../citra/citra_emu/display/ScreenLayout.kt | 14 ++++++++++++++ .../features/settings/model/IntSetting.kt | 1 + .../settings/ui/SettingsFragmentPresenter.kt | 11 +++++++++++ .../features/settings/utils/SettingsFile.kt | 3 +++ src/android/app/src/main/jni/config.cpp | 4 ++++ src/android/app/src/main/jni/default_ini.h | 9 +++++++++ src/android/app/src/main/res/values/arrays.xml | 14 ++++++++++++++ .../app/src/main/res/values/strings.xml | 2 ++ src/common/settings.cpp | 2 ++ src/common/settings.h | 7 +++++++ src/core/frontend/emu_window.cpp | 6 +----- src/core/frontend/framebuffer_layout.cpp | 18 ++++++++++++++++-- src/core/frontend/framebuffer_layout.h | 10 ++++++++++ 13 files changed, 94 insertions(+), 7 deletions(-) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/display/ScreenLayout.kt b/src/android/app/src/main/java/org/citra/citra_emu/display/ScreenLayout.kt index 2ec4716b7..17ea3c0be 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/display/ScreenLayout.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/display/ScreenLayout.kt @@ -48,4 +48,18 @@ enum class PortraitScreenLayout(val int: Int) { return entries.firstOrNull { it.int == int } ?: TOP_FULL_WIDTH } } +} + +enum class SecondaryScreenLayout(val int: Int) { + // These must match what is defined in src/common/settings.h + NONE(0), + TOP_SCREEN(1), + BOTTOM_SCREEN(2), + SIDE_BY_SIDE(3); + + companion object { + fun from(int: Int): SecondaryScreenLayout { + return entries.firstOrNull { it.int == int } ?: NONE + } + } } \ No newline at end of file diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt index 0f1ca8d43..22d6eb934 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt @@ -34,6 +34,7 @@ enum class IntSetting( LANDSCAPE_BOTTOM_WIDTH("custom_bottom_width",Settings.SECTION_LAYOUT,640), LANDSCAPE_BOTTOM_HEIGHT("custom_bottom_height",Settings.SECTION_LAYOUT,480), PORTRAIT_SCREEN_LAYOUT("portrait_layout_option",Settings.SECTION_LAYOUT,0), + SECONDARY_SCREEN_LAYOUT("secondary_screen_layout",Settings.SECTION_LAYOUT,0), PORTRAIT_TOP_X("custom_portrait_top_x",Settings.SECTION_LAYOUT,0), PORTRAIT_TOP_Y("custom_portrait_top_y",Settings.SECTION_LAYOUT,0), PORTRAIT_TOP_WIDTH("custom_portrait_top_width",Settings.SECTION_LAYOUT,800), diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt index 31401567f..809ee5692 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -962,6 +962,17 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) IntSetting.PORTRAIT_SCREEN_LAYOUT.defaultValue ) ) + add( + SingleChoiceSetting( + IntSetting.SECONDARY_SCREEN_LAYOUT, + R.string.emulation_switch_secondary_layout, + 0, + R.array.secondaryLayouts, + R.array.secondaryLayoutValues, + IntSetting.SECONDARY_SCREEN_LAYOUT.key, + IntSetting.SECONDARY_SCREEN_LAYOUT.defaultValue + ) + ) add( SingleChoiceSetting( IntSetting.SMALL_SCREEN_POSITION, diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/utils/SettingsFile.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/utils/SettingsFile.kt index dec3e4e0a..c0d4907ce 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/utils/SettingsFile.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/utils/SettingsFile.kt @@ -272,6 +272,9 @@ object SettingsFile { val settings = section.settings val keySet: Set = settings.keys for (key in keySet) { + if (key.equals("secondary_screen_layout")) { + println("hi"); + } val setting = settings[key] parser.put(header, setting!!.key, setting.valueAsString) } diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp index 19e422324..30e8fbb23 100644 --- a/src/android/app/src/main/jni/config.cpp +++ b/src/android/app/src/main/jni/config.cpp @@ -204,6 +204,10 @@ void Config::ReadValues() { static_cast(sdl2_config->GetInteger( "Layout", "portrait_layout_option", static_cast(Settings::PortraitLayoutOption::PortraitTopFullWidth))); + Settings::values.secondary_screen_layout = + static_cast(sdl2_config->GetInteger( + "Layout", "secondary_screen_layout", + static_cast(Settings::SecondaryScreenLayout::None))); ReadSetting("Layout", Settings::values.custom_portrait_top_x); ReadSetting("Layout", Settings::values.custom_portrait_top_y); ReadSetting("Layout", Settings::values.custom_portrait_top_width); diff --git a/src/android/app/src/main/jni/default_ini.h b/src/android/app/src/main/jni/default_ini.h index 5c802856e..cf9925746 100644 --- a/src/android/app/src/main/jni/default_ini.h +++ b/src/android/app/src/main/jni/default_ini.h @@ -251,6 +251,15 @@ custom_portrait_bottom_height = # 0 (default): Top Screen is prominent, 1: Bottom Screen is prominent swap_screen = +# Secondary Screen Layout +# What the game should do if a secondary screen is connected physically or using +# Miracast / Chromecast screen mirroring +# 0 (default) - Use System Default (mirror) +# 1 - Show Top Screen Only +# 2 - Show Bottom Screen Only +# 3 - Show both screens side by side +secondary_screen_layout = + # Screen placement settings when using Cardboard VR (render3d = 4) # 30 - 100: Screen size as a percentage of the viewport. 85 (default) cardboard_screen_size = diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index 4159d9e4e..ceae4ea58 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -34,11 +34,25 @@ @string/emulation_screen_layout_custom + + @string/emulation_secondary_screen_default + @string/emulation_top_screen + @string/emulation_bottom_screen + @string/emulation_screen_layout_sidebyside + + 0 1 + + 0 + 1 + 2 + 3 + + @string/small_screen_position_top_right @string/small_screen_position_middle_right diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 5495a2a23..dad181c64 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -373,6 +373,7 @@ Open Cheats Landscape Screen Layout Portrait Screen Layout + Secondary Screen Layout Large Screen Portrait Single Screen @@ -380,6 +381,7 @@ Hybrid Screens Original Default + System Default (mirror) Custom Layout Small Screen Position Where should the small screen appear relative to the large one in Large Screen Layout? diff --git a/src/common/settings.cpp b/src/common/settings.cpp index d5e57fbc2..207d3e690 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -111,6 +111,7 @@ void LogSettings() { } log_setting("Layout_LayoutOption", values.layout_option.GetValue()); log_setting("Layout_PortraitLayoutOption", values.portrait_layout_option.GetValue()); + log_setting("Layout_SecondScreenLayout",values.secondary_screen_layout.GetValue()); log_setting("Layout_SwapScreen", values.swap_screen.GetValue()); log_setting("Layout_UprightScreen", values.upright_screen.GetValue()); log_setting("Layout_LargeScreenProportion", values.large_screen_proportion.GetValue()); @@ -203,6 +204,7 @@ void RestoreGlobalState(bool is_powered_on) { values.delay_game_render_thread_us.SetGlobal(true); values.layout_option.SetGlobal(true); values.portrait_layout_option.SetGlobal(true); + values.secondary_screen_layout.SetGlobal(true); values.swap_screen.SetGlobal(true); values.upright_screen.SetGlobal(true); values.large_screen_proportion.SetGlobal(true); diff --git a/src/common/settings.h b/src/common/settings.h index f8e9e3e2e..930499722 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -53,6 +53,12 @@ enum class PortraitLayoutOption : u32 { PortraitCustomLayout, }; +enum class SecondaryScreenLayout : u32 { + None, + TopScreenOnly, + BottomScreenOnly, + SideBySide +}; /** Defines where the small screen will appear relative to the large screen * when in Large Screen mode */ @@ -503,6 +509,7 @@ struct Values { SwitchableSetting layout_option{LayoutOption::Default, "layout_option"}; SwitchableSetting swap_screen{false, "swap_screen"}; SwitchableSetting upright_screen{false, "upright_screen"}; + SwitchableSetting secondary_screen_layout{SecondaryScreenLayout::None, "secondary_screen_layout"}; SwitchableSetting large_screen_proportion{4.f, 1.f, 16.f, "large_screen_proportion"}; SwitchableSetting small_screen_position{SmallScreenPosition::BottomRight, diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp index 9b65d5e99..ffcdcbe43 100644 --- a/src/core/frontend/emu_window.cpp +++ b/src/core/frontend/emu_window.cpp @@ -252,11 +252,7 @@ void EmuWindow::UpdateCurrentFramebufferLayout(u32 width, u32 height, bool is_po } } #ifdef ANDROID - // if is_secondary is set on android, MUST be a second window - if (is_secondary) { - layout = Layout::SeparateWindowsLayout(width, height, is_secondary, - Settings::values.upright_screen.GetValue()); - } + if (is_secondary) layout = Layout::AndroidSecondaryLayout(width,height); #endif UpdateMinimumWindowSize(min_size); diff --git a/src/core/frontend/framebuffer_layout.cpp b/src/core/frontend/framebuffer_layout.cpp index 7cf52a29c..133655a11 100644 --- a/src/core/frontend/framebuffer_layout.cpp +++ b/src/core/frontend/framebuffer_layout.cpp @@ -71,7 +71,7 @@ FramebufferLayout SingleFrameLayout(u32 width, u32 height, bool swapped, bool up const bool stretched = (Settings::values.screen_top_stretch.GetValue() && !swapped) || (Settings::values.screen_bottom_stretch.GetValue() && swapped); - const float window_aspect_ratio = static_cast(height) / width; + const float window_aspect_ratio = static_cast(height) / static_cast(width); if (stretched) { top_screen = {Settings::values.screen_top_leftright_padding.GetValue(), @@ -113,7 +113,7 @@ FramebufferLayout LargeFrameLayout(u32 width, u32 height, bool swapped, bool upr FramebufferLayout res{width, height, true, true, {}, {}, !upright}; // Split the window into two parts. Give proportional width to the smaller screen // To do that, find the total emulation box and maximize that based on window size - const float window_aspect_ratio = static_cast(height) / width; + const float window_aspect_ratio = static_cast(height) / static_cast(width); float emulation_aspect_ratio; float large_height = @@ -297,6 +297,20 @@ FramebufferLayout SeparateWindowsLayout(u32 width, u32 height, bool is_secondary return SingleFrameLayout(width, height, is_secondary, upright); } +FramebufferLayout AndroidSecondaryLayout(u32 width, u32 height) { + const Settings::SecondaryScreenLayout layout = Settings::values.secondary_screen_layout.GetValue(); + switch (layout) { + case Settings::SecondaryScreenLayout::None: + // this should never happen, but if it does, somehow, send the top screen + case Settings::SecondaryScreenLayout::TopScreenOnly: + return SingleFrameLayout(width, height, false, Settings::values.upright_screen.GetValue()); + case Settings::SecondaryScreenLayout::BottomScreenOnly: + return SingleFrameLayout(width, height, true, Settings::values.upright_screen.GetValue()); + case Settings::SecondaryScreenLayout::SideBySide: + return LargeFrameLayout(width,height,false,Settings::values.upright_screen.GetValue(),1.0f,Settings::SmallScreenPosition::MiddleRight); + } +} + FramebufferLayout CustomFrameLayout(u32 width, u32 height, bool is_swapped, bool is_portrait_mode) { ASSERT(width > 0); ASSERT(height > 0); diff --git a/src/core/frontend/framebuffer_layout.h b/src/core/frontend/framebuffer_layout.h index adcf26630..6088ca0d2 100644 --- a/src/core/frontend/framebuffer_layout.h +++ b/src/core/frontend/framebuffer_layout.h @@ -119,6 +119,16 @@ FramebufferLayout HybridScreenLayout(u32 width, u32 height, bool swapped, bool u */ FramebufferLayout SeparateWindowsLayout(u32 width, u32 height, bool is_secondary, bool upright); + +/** + * Method for constructing the secondary layout for Android, based on + * the appropriate setting. + * @param width Window framebuffer width in pixels + * @param height Window framebuffer height in pixels + */ +FramebufferLayout AndroidSecondaryLayout(u32 width, u32 height); + + /** * Factory method for constructing a custom FramebufferLayout * @param width Window framebuffer width in pixels From 73b7ca7ace84e296c5f85563b284732f7b99a943 Mon Sep 17 00:00:00 2001 From: David Griswold Date: Sat, 8 Mar 2025 13:47:02 -0300 Subject: [PATCH 7/9] resolve merge conflict --- .../citra_emu/activities/EmulationActivity.kt | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt index 0b3666d77..b1e7dae62 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt @@ -36,6 +36,7 @@ import org.citra.citra_emu.camera.StillImageCameraHelper.OnFilePickerResult import org.citra.citra_emu.contracts.OpenFileResultContract import org.citra.citra_emu.databinding.ActivityEmulationBinding import org.citra.citra_emu.display.ScreenAdjustmentUtil +import org.citra.citra_emu.display.SecondaryScreenLayout import org.citra.citra_emu.features.hotkeys.HotkeyUtility import org.citra.citra_emu.features.settings.model.BooleanSetting import org.citra.citra_emu.features.settings.model.IntSetting @@ -63,16 +64,20 @@ class EmulationActivity : AppCompatActivity() { private var secondScreenPresentation: Presentation? = null private lateinit var displayManager: DisplayManager - private fun updatePresentation(display: Display?) { + private fun updatePresentation() { + displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager + val display = getCustomerDisplay(); if (secondScreenPresentation == null || secondScreenPresentation?.display != display) { secondScreenPresentation?.dismiss() - // this should only happen if the second screen is enabled in a setting - // eventually. Otherwise it should continue to mirror. - if (display != null) + if (display != null && IntSetting.SECONDARY_SCREEN_LAYOUT.int != SecondaryScreenLayout.NONE.int) { secondScreenPresentation = SecondScreenPresentation(this, display) - secondScreenPresentation?.show(); + secondScreenPresentation?.show(); + } } - + } + private fun releasePresentation() { + secondScreenPresentation?.dismiss(); + secondScreenPresentation = null; } private fun getCustomerDisplay(): Display? { @@ -90,14 +95,9 @@ class EmulationActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { ThemeUtil.setTheme(this) - - /* Find a second display if it's connected and create a basic presentationon it */ - displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager - val display = getCustomerDisplay(); - updatePresentation(display); settingsViewModel.settings.loadSettings() - super.onCreate(savedInstanceState) + updatePresentation(); binding = ActivityEmulationBinding.inflate(layoutInflater) screenAdjustmentUtil = ScreenAdjustmentUtil(this, windowManager, settingsViewModel.settings) @@ -143,6 +143,11 @@ class EmulationActivity : AppCompatActivity() { applyOrientationSettings() // Check for orientation settings changes on runtime } + override fun onStop() { + releasePresentation() + super.onStop() + } + override fun onWindowFocusChanged(hasFocus: Boolean) { super.onWindowFocusChanged(hasFocus) enableFullscreenImmersive() @@ -150,6 +155,7 @@ class EmulationActivity : AppCompatActivity() { public override fun onRestart() { super.onRestart() + updatePresentation() NativeLibrary.reloadCameraDevices() } @@ -167,8 +173,7 @@ class EmulationActivity : AppCompatActivity() { EmulationLifecycleUtil.clear() isEmulationRunning = false instance = null - secondScreenPresentation?.dismiss(); - secondScreenPresentation = null; + releasePresentation() super.onDestroy() } From c22ac3367e86aba94914ce9fc2590c1e817c3009 Mon Sep 17 00:00:00 2001 From: David Griswold Date: Sat, 8 Mar 2025 14:47:39 -0300 Subject: [PATCH 8/9] rearrange switch cases to satisfy linux compiler --- src/core/frontend/framebuffer_layout.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/core/frontend/framebuffer_layout.cpp b/src/core/frontend/framebuffer_layout.cpp index 133655a11..df172b587 100644 --- a/src/core/frontend/framebuffer_layout.cpp +++ b/src/core/frontend/framebuffer_layout.cpp @@ -300,14 +300,16 @@ FramebufferLayout SeparateWindowsLayout(u32 width, u32 height, bool is_secondary FramebufferLayout AndroidSecondaryLayout(u32 width, u32 height) { const Settings::SecondaryScreenLayout layout = Settings::values.secondary_screen_layout.GetValue(); switch (layout) { - case Settings::SecondaryScreenLayout::None: - // this should never happen, but if it does, somehow, send the top screen - case Settings::SecondaryScreenLayout::TopScreenOnly: - return SingleFrameLayout(width, height, false, Settings::values.upright_screen.GetValue()); + case Settings::SecondaryScreenLayout::BottomScreenOnly: return SingleFrameLayout(width, height, true, Settings::values.upright_screen.GetValue()); case Settings::SecondaryScreenLayout::SideBySide: return LargeFrameLayout(width,height,false,Settings::values.upright_screen.GetValue(),1.0f,Settings::SmallScreenPosition::MiddleRight); + case Settings::SecondaryScreenLayout::None: + // this should never happen, but if it does, somehow, send the top screen + case Settings::SecondaryScreenLayout::TopScreenOnly: + default: + return SingleFrameLayout(width, height, false, Settings::values.upright_screen.GetValue()); } } From 3929d448156e75c74c145e9984d8d4a0642bbe9d Mon Sep 17 00:00:00 2001 From: David Griswold Date: Tue, 11 Mar 2025 16:24:32 -0300 Subject: [PATCH 9/9] openGL is working! --- .../java/org/citra/citra_emu/NativeLibrary.kt | 5 +- .../citra_emu/activities/EmulationActivity.kt | 22 +- .../display/SecondScreenPresentation.kt | 8 +- src/android/app/src/main/jni/native.cpp | 369 ++++++++++-------- src/video_core/renderer_base.cpp | 9 + src/video_core/renderer_base.h | 9 +- .../renderer_opengl/renderer_opengl.cpp | 9 + .../renderer_opengl/renderer_opengl.h | 1 + 8 files changed, 264 insertions(+), 168 deletions(-) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt index 11ee2b187..728dd1f41 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt @@ -126,8 +126,9 @@ object NativeLibrary { external fun doFrame() //Second window - external fun enableSecondWindow(secondary_surface: Surface) - external fun disableSecondWindow() + external fun secondarySurfaceChanged(secondary_surface: Surface) + external fun secondarySurfaceDestroyed() + external fun disableSecondaryScreen() /** * Unpauses emulation from a paused state. */ diff --git a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt index b1e7dae62..789ed2acb 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt @@ -67,17 +67,23 @@ class EmulationActivity : AppCompatActivity() { private fun updatePresentation() { displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager val display = getCustomerDisplay(); - if (secondScreenPresentation == null || secondScreenPresentation?.display != display) { - secondScreenPresentation?.dismiss() - if (display != null && IntSetting.SECONDARY_SCREEN_LAYOUT.int != SecondaryScreenLayout.NONE.int) { - secondScreenPresentation = SecondScreenPresentation(this, display) - secondScreenPresentation?.show(); - } + if (secondScreenPresentation != null && (IntSetting.SECONDARY_SCREEN_LAYOUT.int == SecondaryScreenLayout.NONE.int || display == null)) { + releasePresentation(); + } + if (secondScreenPresentation == null || secondScreenPresentation?.display != display) { + secondScreenPresentation?.dismiss() + if (display != null && IntSetting.SECONDARY_SCREEN_LAYOUT.int != SecondaryScreenLayout.NONE.int) { + secondScreenPresentation = SecondScreenPresentation(this, display) + secondScreenPresentation?.show(); } + } } private fun releasePresentation() { - secondScreenPresentation?.dismiss(); - secondScreenPresentation = null; + if (secondScreenPresentation != null) { + NativeLibrary.disableSecondaryScreen() + secondScreenPresentation?.dismiss(); + secondScreenPresentation = null; + } } private fun getCustomerDisplay(): Display? { diff --git a/src/android/app/src/main/java/org/citra/citra_emu/display/SecondScreenPresentation.kt b/src/android/app/src/main/java/org/citra/citra_emu/display/SecondScreenPresentation.kt index b4c63424e..ba353787f 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/display/SecondScreenPresentation.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/display/SecondScreenPresentation.kt @@ -6,6 +6,8 @@ import android.view.SurfaceHolder import android.view.SurfaceView import org.citra.citra_emu.NativeLibrary + + class SecondScreenPresentation( context: Context, display: Display, @@ -19,7 +21,7 @@ class SecondScreenPresentation( surfaceView = SurfaceView(context) surfaceView.holder.addCallback(object : SurfaceHolder.Callback { override fun surfaceCreated(holder: SurfaceHolder) { - NativeLibrary.enableSecondWindow(holder.surface) + } override fun surfaceChanged( @@ -28,11 +30,11 @@ class SecondScreenPresentation( width: Int, height: Int ) { - NativeLibrary.enableSecondWindow(holder.surface) + NativeLibrary.secondarySurfaceChanged(holder.surface) } override fun surfaceDestroyed(holder: SurfaceHolder) { - NativeLibrary.disableSecondWindow(); + NativeLibrary.secondarySurfaceDestroyed() } }) diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 5a65877ff..99397938c 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -16,11 +16,15 @@ #include #include "audio_core/dsp_interface.h" #include "common/arch.h" + #if CITRA_ARCH(arm64) + #include "common/aarch64/cpu_detect.h" + #elif CITRA_ARCH(x86_64) #include "common/x64/cpu_detect.h" #endif + #include "common/common_paths.h" #include "common/dynamic_library/dynamic_library.h" #include "common/file_util.h" @@ -44,12 +48,18 @@ #include "jni/camera/ndk_camera.h" #include "jni/camera/still_image_camera.h" #include "jni/config.h" + #ifdef ENABLE_OPENGL + #include "jni/emu_window/emu_window_gl.h" + #endif #ifdef ENABLE_VULKAN + #include "jni/emu_window/emu_window_vk.h" + #endif + #include "jni/id_cache.h" #include "jni/input_manager.h" #include "jni/ndk_motion.h" @@ -59,61 +69,63 @@ #include "video_core/renderer_base.h" #if defined(ENABLE_VULKAN) && CITRA_ARCH(arm64) + #include + #endif namespace { -ANativeWindow* s_surf; -ANativeWindow* s_secondary_surface; -bool secondary_enabled = false; + ANativeWindow *s_surf; + ANativeWindow *s_secondary_surface; + bool secondary_enabled = false; -std::shared_ptr vulkan_library{}; -std::unique_ptr window; -std::unique_ptr second_window; + std::shared_ptr vulkan_library{}; + std::unique_ptr window; + std::unique_ptr second_window; -std::atomic stop_run{true}; -std::atomic pause_emulation{false}; + std::atomic stop_run{true}; + std::atomic pause_emulation{false}; -std::mutex paused_mutex; -std::mutex running_mutex; -std::condition_variable running_cv; + std::mutex paused_mutex; + std::mutex running_mutex; + std::condition_variable running_cv; } // Anonymous namespace static jobject ToJavaCoreError(Core::System::ResultStatus result) { - static const std::map CoreErrorNameMap{ - {Core::System::ResultStatus::ErrorSystemFiles, "ErrorSystemFiles"}, - {Core::System::ResultStatus::ErrorSavestate, "ErrorSavestate"}, - {Core::System::ResultStatus::ErrorArticDisconnected, "ErrorArticDisconnected"}, - {Core::System::ResultStatus::ErrorUnknown, "ErrorUnknown"}, + static const std::map CoreErrorNameMap{ + {Core::System::ResultStatus::ErrorSystemFiles, "ErrorSystemFiles"}, + {Core::System::ResultStatus::ErrorSavestate, "ErrorSavestate"}, + {Core::System::ResultStatus::ErrorArticDisconnected, "ErrorArticDisconnected"}, + {Core::System::ResultStatus::ErrorUnknown, "ErrorUnknown"}, }; const auto name = CoreErrorNameMap.count(result) ? CoreErrorNameMap.at(result) : "ErrorUnknown"; - JNIEnv* env = IDCache::GetEnvForThread(); + JNIEnv *env = IDCache::GetEnvForThread(); const jclass core_error_class = IDCache::GetCoreErrorClass(); return env->GetStaticObjectField( - core_error_class, env->GetStaticFieldID(core_error_class, name, - "Lorg/citra/citra_emu/NativeLibrary$CoreError;")); + core_error_class, env->GetStaticFieldID(core_error_class, name, + "Lorg/citra/citra_emu/NativeLibrary$CoreError;")); } -static bool HandleCoreError(Core::System::ResultStatus result, const std::string& details) { - JNIEnv* env = IDCache::GetEnvForThread(); +static bool HandleCoreError(Core::System::ResultStatus result, const std::string &details) { + JNIEnv *env = IDCache::GetEnvForThread(); return env->CallStaticBooleanMethod(IDCache::GetNativeLibraryClass(), IDCache::GetOnCoreError(), ToJavaCoreError(result), env->NewStringUTF(details.c_str())) != JNI_FALSE; } static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max) { - JNIEnv* env = IDCache::GetEnvForThread(); + JNIEnv *env = IDCache::GetEnvForThread(); env->CallStaticVoidMethod(IDCache::GetDiskCacheProgressClass(), IDCache::GetDiskCacheLoadProgress(), IDCache::GetJavaLoadCallbackStage(stage), static_cast(progress), static_cast(max)); } -static Camera::NDK::Factory* g_ndk_factory{}; +static Camera::NDK::Factory *g_ndk_factory{}; static void TryShutdown() { if (!window) { @@ -134,7 +146,7 @@ static bool CheckMicPermission() { IDCache::GetRequestMicPermission()); } -static Core::System::ResultStatus RunCitra(const std::string& filepath) { +static Core::System::ResultStatus RunCitra(const std::string &filepath) { // Citra core only supports a single running instance std::scoped_lock lock(running_mutex); @@ -147,43 +159,49 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) { return Core::System::ResultStatus::ErrorLoader; } - Core::System& system{Core::System::GetInstance()}; + Core::System &system{Core::System::GetInstance()}; const auto graphics_api = Settings::values.graphics_api.GetValue(); switch (graphics_api) { #ifdef ENABLE_OPENGL - case Settings::GraphicsAPI::OpenGL: - window = std::make_unique(system, s_surf,false); - if (secondary_enabled) { - EGLContext* c = window->GetEGLContext(); - second_window = std::make_unique(system,s_secondary_surface, true, c); - } - break; + case Settings::GraphicsAPI::OpenGL: + window = std::make_unique(system, s_surf, false); + if (secondary_enabled) { + EGLContext *c = window->GetEGLContext(); + second_window = std::make_unique(system, + s_secondary_surface, + true, c); + } + break; #endif #ifdef ENABLE_VULKAN - case Settings::GraphicsAPI::Vulkan: - window = std::make_unique(s_surf, vulkan_library,false); - if (secondary_enabled) second_window = std::make_unique(s_secondary_surface, vulkan_library, true); - break; + case Settings::GraphicsAPI::Vulkan: + window = std::make_unique(s_surf, vulkan_library, false); + if (secondary_enabled) + second_window = std::make_unique(s_secondary_surface, + vulkan_library, true); + break; #endif - default: - LOG_CRITICAL(Frontend, - "Unknown or unsupported graphics API {}, falling back to available default", - graphics_api); + default: + LOG_CRITICAL(Frontend, + "Unknown or unsupported graphics API {}, falling back to available default", + graphics_api); #ifdef ENABLE_OPENGL - window = std::make_unique(system, s_surf, false); - if (secondary_enabled) { - EGLContext *c = window->GetEGLContext(); - second_window = std::make_unique(system, s_secondary_surface,true, c); - } + window = std::make_unique(system, s_surf, false); + if (secondary_enabled) { + EGLContext *c = window->GetEGLContext(); + second_window = std::make_unique(system, + s_secondary_surface, + true, c); + } #elif ENABLE_VULKAN - window = std::make_unique(s_surf, vulkan_library); - if (secondary_enabled) second_window = std::make_unique(s_secondary_surface, vulkan_library, true); + window = std::make_unique(s_surf, vulkan_library); + if (secondary_enabled) second_window = std::make_unique(s_secondary_surface, vulkan_library, true); #else -// TODO: Add a null renderer backend for this, perhaps. + // TODO: Add a null renderer backend for this, perhaps. #error "At least one renderer must be enabled." #endif - break; + break; } // Forces a config reload on game boot, if the user changed settings in the UI @@ -217,8 +235,8 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) { InputManager::Init(); window->MakeCurrent(); - //if (second_window) second_window->MakeCurrent(); - const Core::System::ResultStatus load_result{system.Load(*window, filepath,second_window.get())}; + const Core::System::ResultStatus load_result{ + system.Load(*window, filepath, second_window.get())}; if (load_result != Core::System::ResultStatus::Success) { return load_result; } @@ -272,12 +290,12 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) { return Core::System::ResultStatus::Success; } -void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir, - const std::string& custom_driver_name, - const std::string& file_redirect_dir) { +void InitializeGpuDriver(const std::string &hook_lib_dir, const std::string &custom_driver_dir, + const std::string &custom_driver_name, + const std::string &file_redirect_dir) { #if defined(ENABLE_VULKAN) && CITRA_ARCH(arm64) - void* handle{}; - const char* file_redirect_dir_{}; + void *handle{}; + const char *file_redirect_dir_{}; int featureFlags{}; // Enable driver file redirection when renderer debugging is enabled. @@ -289,8 +307,8 @@ void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& cus // Try to load a custom driver. if (custom_driver_name.size()) { handle = adrenotools_open_libvulkan( - RTLD_NOW, featureFlags | ADRENOTOOLS_DRIVER_CUSTOM, nullptr, hook_lib_dir.c_str(), - custom_driver_dir.c_str(), custom_driver_name.c_str(), file_redirect_dir_, nullptr); + RTLD_NOW, featureFlags | ADRENOTOOLS_DRIVER_CUSTOM, nullptr, hook_lib_dir.c_str(), + custom_driver_dir.c_str(), custom_driver_name.c_str(), file_redirect_dir_, nullptr); } // Try to load the system driver. @@ -305,7 +323,7 @@ void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& cus extern "C" { -void Java_org_citra_citra_1emu_NativeLibrary_surfaceChanged(JNIEnv* env, +void Java_org_citra_citra_1emu_NativeLibrary_surfaceChanged(JNIEnv *env, [[maybe_unused]] jobject obj, jobject surf) { s_surf = ANativeWindow_fromSurface(env, surf); @@ -315,7 +333,7 @@ void Java_org_citra_citra_1emu_NativeLibrary_surfaceChanged(JNIEnv* env, notify = window->OnSurfaceChanged(s_surf); } - auto& system = Core::System::GetInstance(); + auto &system = Core::System::GetInstance(); if (notify && system.IsPoweredOn()) { system.GPU().Renderer().NotifySurfaceChanged(false); } @@ -323,15 +341,39 @@ void Java_org_citra_citra_1emu_NativeLibrary_surfaceChanged(JNIEnv* env, LOG_INFO(Frontend, "Surface changed"); } -void Java_org_citra_citra_1emu_NativeLibrary_enableSecondWindow(JNIEnv* env, - [[maybe_unused]] jobject obj,jobject surf) { +void Java_org_citra_citra_1emu_NativeLibrary_secondarySurfaceChanged(JNIEnv *env, + [[maybe_unused]] jobject obj, + jobject surf) { + auto &system = Core::System::GetInstance(); s_secondary_surface = ANativeWindow_fromSurface(env, surf); secondary_enabled = true; bool notify = false; - if (second_window) { - notify = second_window->OnSurfaceChanged(s_secondary_surface); + if (!s_secondary_surface) { + // did not create the surface, so disable second screen + secondary_enabled = false; + if (system.IsPoweredOn()) { + system.GPU().Renderer().setSecondaryWindow(nullptr); + } + return; } - auto& system = Core::System::GetInstance(); + if (second_window) { + //second window already created, so update it + notify = second_window->OnSurfaceChanged(s_secondary_surface); + } else if (system.IsPoweredOn() && window) { + // emulation running, window is new + // create a new window and set it + const auto graphics_api = Settings::values.graphics_api.GetValue(); + if (graphics_api == Settings::GraphicsAPI::OpenGL) { + EGLContext *c = window->GetEGLContext(); + second_window = std::make_unique(system, + s_secondary_surface,true, c); + }else{ + second_window = std::make_unique(s_secondary_surface, + vulkan_library, true); + } + system.GPU().Renderer().setSecondaryWindow(second_window.get()); + } + if (notify && system.IsPoweredOn()) { system.GPU().Renderer().NotifySurfaceChanged(true); } @@ -340,23 +382,37 @@ void Java_org_citra_citra_1emu_NativeLibrary_enableSecondWindow(JNIEnv* env, } - -void Java_org_citra_citra_1emu_NativeLibrary_disableSecondWindow(JNIEnv* env, - [[maybe_unused]] jobject obj) { - +void Java_org_citra_citra_1emu_NativeLibrary_secondarySurfaceDestroyed(JNIEnv *env, + [[maybe_unused]] jobject obj) { + //auto &system = Core::System::GetInstance(); secondary_enabled = false; if (s_secondary_surface != nullptr) { ANativeWindow_release(s_secondary_surface); s_secondary_surface = nullptr; - if (second_window) { - second_window->OnSurfaceChanged(s_secondary_surface); - } } - LOG_INFO(Frontend, "Secondary Surface Disabled"); + LOG_INFO(Frontend, "Secondary Surface Destroyed"); } -void Java_org_citra_citra_1emu_NativeLibrary_surfaceDestroyed([[maybe_unused]] JNIEnv* env, +void Java_org_citra_citra_1emu_NativeLibrary_disableSecondaryScreen(JNIEnv *env, + [[maybe_unused]] jobject obj) { + auto &system = Core::System::GetInstance(); + secondary_enabled = false; + if (s_secondary_surface != nullptr) { + ANativeWindow_release(s_secondary_surface); + s_secondary_surface = nullptr; + } + if (system.IsPoweredOn()) { + system.GPU().Renderer().setSecondaryWindow(nullptr); + } + if (second_window) { + second_window.release(); + second_window = nullptr; + } + LOG_INFO(Frontend, "Secondary Window Disabled"); + +} +void Java_org_citra_citra_1emu_NativeLibrary_surfaceDestroyed([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jobject obj) { if (s_surf != nullptr) { ANativeWindow_release(s_surf); @@ -367,7 +423,7 @@ void Java_org_citra_citra_1emu_NativeLibrary_surfaceDestroyed([[maybe_unused]] J } } -void Java_org_citra_citra_1emu_NativeLibrary_doFrame([[maybe_unused]] JNIEnv* env, +void Java_org_citra_citra_1emu_NativeLibrary_doFrame([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jobject obj) { if (stop_run || pause_emulation) { return; @@ -381,33 +437,33 @@ void Java_org_citra_citra_1emu_NativeLibrary_doFrame([[maybe_unused]] JNIEnv* en } void JNICALL Java_org_citra_citra_1emu_NativeLibrary_initializeGpuDriver( - JNIEnv* env, jobject obj, jstring hook_lib_dir, jstring custom_driver_dir, - jstring custom_driver_name, jstring file_redirect_dir) { + JNIEnv *env, jobject obj, jstring hook_lib_dir, jstring custom_driver_dir, + jstring custom_driver_name, jstring file_redirect_dir) { InitializeGpuDriver(GetJString(env, hook_lib_dir), GetJString(env, custom_driver_dir), GetJString(env, custom_driver_name), GetJString(env, file_redirect_dir)); } -void Java_org_citra_citra_1emu_NativeLibrary_notifyOrientationChange([[maybe_unused]] JNIEnv* env, +void Java_org_citra_citra_1emu_NativeLibrary_notifyOrientationChange([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jobject obj, jint layout_option, jint rotation, jboolean portrait) { Settings::values.layout_option = static_cast(layout_option); } -void Java_org_citra_citra_1emu_NativeLibrary_updateFramebuffer([[maybe_unused]] JNIEnv* env, +void Java_org_citra_citra_1emu_NativeLibrary_updateFramebuffer([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jobject obj, jboolean is_portrait_mode) { - auto& system = Core::System::GetInstance(); + auto &system = Core::System::GetInstance(); if (system.IsPoweredOn()) { system.GPU().Renderer().UpdateCurrentFramebufferLayout(is_portrait_mode); } } -void Java_org_citra_citra_1emu_NativeLibrary_swapScreens([[maybe_unused]] JNIEnv* env, +void Java_org_citra_citra_1emu_NativeLibrary_swapScreens([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jobject obj, jboolean swap_screens, jint rotation) { Settings::values.swap_screen = swap_screens; - auto& system = Core::System::GetInstance(); + auto &system = Core::System::GetInstance(); if (system.IsPoweredOn()) { system.GPU().Renderer().UpdateCurrentFramebufferLayout(IsPortraitMode()); } @@ -415,14 +471,14 @@ void Java_org_citra_citra_1emu_NativeLibrary_swapScreens([[maybe_unused]] JNIEnv Camera::NDK::g_rotation = rotation; } -jboolean Java_org_citra_citra_1emu_NativeLibrary_areKeysAvailable([[maybe_unused]] JNIEnv* env, +jboolean Java_org_citra_citra_1emu_NativeLibrary_areKeysAvailable([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jobject obj) { HW::AES::InitKeys(); return HW::AES::IsKeyXAvailable(HW::AES::KeySlotID::NCCHSecure1) && HW::AES::IsKeyXAvailable(HW::AES::KeySlotID::NCCHSecure2); } -jstring Java_org_citra_citra_1emu_NativeLibrary_getHomeMenuPath(JNIEnv* env, +jstring Java_org_citra_citra_1emu_NativeLibrary_getHomeMenuPath(JNIEnv *env, [[maybe_unused]] jobject obj, jint region) { const std::string path = Core::GetHomeMenuNcchPath(region); @@ -432,43 +488,44 @@ jstring Java_org_citra_citra_1emu_NativeLibrary_getHomeMenuPath(JNIEnv* env, return ToJString(env, ""); } -void Java_org_citra_citra_1emu_NativeLibrary_setUserDirectory(JNIEnv* env, +void Java_org_citra_citra_1emu_NativeLibrary_setUserDirectory(JNIEnv *env, [[maybe_unused]] jobject obj, jstring j_directory) { FileUtil::SetCurrentDir(GetJString(env, j_directory)); } jobjectArray Java_org_citra_citra_1emu_NativeLibrary_getInstalledGamePaths( - JNIEnv* env, [[maybe_unused]] jclass clazz) { + JNIEnv *env, [[maybe_unused]] jclass clazz) { std::vector games; const FileUtil::DirectoryEntryCallable ScanDir = - [&games, &ScanDir](u64*, const std::string& directory, const std::string& virtual_name) { - std::string path = directory + virtual_name; - if (FileUtil::IsDirectory(path)) { - path += '/'; - FileUtil::ForeachDirectoryEntry(nullptr, path, ScanDir); - } else { - if (!FileUtil::Exists(path)) - return false; - auto loader = Loader::GetLoader(path); - if (loader) { - bool executable{}; - const Loader::ResultStatus result = loader->IsExecutable(executable); - if (Loader::ResultStatus::Success == result && executable) { - games.emplace_back(path); + [&games, &ScanDir](u64 *, const std::string &directory, + const std::string &virtual_name) { + std::string path = directory + virtual_name; + if (FileUtil::IsDirectory(path)) { + path += '/'; + FileUtil::ForeachDirectoryEntry(nullptr, path, ScanDir); + } else { + if (!FileUtil::Exists(path)) + return false; + auto loader = Loader::GetLoader(path); + if (loader) { + bool executable{}; + const Loader::ResultStatus result = loader->IsExecutable(executable); + if (Loader::ResultStatus::Success == result && executable) { + games.emplace_back(path); + } } } - } - return true; - }; + return true; + }; ScanDir(nullptr, "", FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + - "Nintendo " - "3DS/00000000000000000000000000000000/" - "00000000000000000000000000000000/title/00040000"); + "Nintendo " + "3DS/00000000000000000000000000000000/" + "00000000000000000000000000000000/title/00040000"); ScanDir(nullptr, "", FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + - "00000000000000000000000000000000/title/00040010"); + "00000000000000000000000000000000/title/00040010"); jobjectArray jgames = env->NewObjectArray(static_cast(games.size()), env->FindClass("java/lang/String"), nullptr); for (jsize i = 0; i < games.size(); ++i) @@ -476,7 +533,7 @@ jobjectArray Java_org_citra_citra_1emu_NativeLibrary_getInstalledGamePaths( return jgames; } -jlongArray Java_org_citra_citra_1emu_NativeLibrary_getSystemTitleIds(JNIEnv* env, +jlongArray Java_org_citra_citra_1emu_NativeLibrary_getSystemTitleIds(JNIEnv *env, [[maybe_unused]] jobject obj, jint system_type, jint region) { @@ -484,11 +541,11 @@ jlongArray Java_org_citra_citra_1emu_NativeLibrary_getSystemTitleIds(JNIEnv* env const std::vector titles = Core::GetSystemTitleIds(mode, region); jlongArray jTitles = env->NewLongArray(titles.size()); env->SetLongArrayRegion(jTitles, 0, titles.size(), - reinterpret_cast(titles.data())); + reinterpret_cast(titles.data())); return jTitles; } -jobject Java_org_citra_citra_1emu_NativeLibrary_downloadTitleFromNus([[maybe_unused]] JNIEnv* env, +jobject Java_org_citra_citra_1emu_NativeLibrary_downloadTitleFromNus([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jobject obj, jlong title) { [[maybe_unused]] const auto title_id = static_cast(title); @@ -506,7 +563,7 @@ jobject Java_org_citra_citra_1emu_NativeLibrary_downloadTitleFromNus([[maybe_unu } jboolean JNICALL Java_org_citra_citra_1emu_utils_GpuDriverHelper_supportsCustomDriverLoading( - JNIEnv* env, jobject instance) { + JNIEnv *env, jobject instance) { #ifdef CITRA_ARCH_arm64 // If the KGSL device exists custom drivers can be loaded using adrenotools return SupportsCustomDriver(); @@ -516,20 +573,20 @@ jboolean JNICALL Java_org_citra_citra_1emu_utils_GpuDriverHelper_supportsCustomD } // TODO(xperia64): ensure these cannot be called in an invalid state (e.g. after StopEmulation) -void Java_org_citra_citra_1emu_NativeLibrary_unPauseEmulation([[maybe_unused]] JNIEnv* env, +void Java_org_citra_citra_1emu_NativeLibrary_unPauseEmulation([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jobject obj) { pause_emulation = false; running_cv.notify_all(); InputManager::NDKMotionHandler()->EnableSensors(); } -void Java_org_citra_citra_1emu_NativeLibrary_pauseEmulation([[maybe_unused]] JNIEnv* env, +void Java_org_citra_citra_1emu_NativeLibrary_pauseEmulation([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jobject obj) { pause_emulation = true; InputManager::NDKMotionHandler()->DisableSensors(); } -void Java_org_citra_citra_1emu_NativeLibrary_stopEmulation([[maybe_unused]] JNIEnv* env, +void Java_org_citra_citra_1emu_NativeLibrary_stopEmulation([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jobject obj) { stop_run = true; pause_emulation = false; @@ -538,19 +595,19 @@ void Java_org_citra_citra_1emu_NativeLibrary_stopEmulation([[maybe_unused]] JNIE running_cv.notify_all(); } -jboolean Java_org_citra_citra_1emu_NativeLibrary_isRunning([[maybe_unused]] JNIEnv* env, +jboolean Java_org_citra_citra_1emu_NativeLibrary_isRunning([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jobject obj) { return static_cast(!stop_run); } -jlong Java_org_citra_citra_1emu_NativeLibrary_getRunningTitleId([[maybe_unused]] JNIEnv* env, +jlong Java_org_citra_citra_1emu_NativeLibrary_getRunningTitleId([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jobject obj) { u64 title_id{}; Core::System::GetInstance().GetAppLoader().ReadProgramId(title_id); return static_cast(title_id); } -jboolean Java_org_citra_citra_1emu_NativeLibrary_onGamePadEvent([[maybe_unused]] JNIEnv* env, +jboolean Java_org_citra_citra_1emu_NativeLibrary_onGamePadEvent([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jobject obj, [[maybe_unused]] jstring j_device, jint j_button, jint action) { @@ -565,8 +622,9 @@ jboolean Java_org_citra_citra_1emu_NativeLibrary_onGamePadEvent([[maybe_unused]] } jboolean Java_org_citra_citra_1emu_NativeLibrary_onGamePadMoveEvent( - [[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj, [[maybe_unused]] jstring j_device, - jint axis, jfloat x, jfloat y) { + [[maybe_unused]] JNIEnv *env, [[maybe_unused]] jobject obj, + [[maybe_unused]] jstring j_device, + jint axis, jfloat x, jfloat y) { // Clamp joystick movement to supported minimum and maximum // Citra uses an inverted y axis sent by the frontend x = std::clamp(x, -1.f, 1.f); @@ -584,27 +642,28 @@ jboolean Java_org_citra_citra_1emu_NativeLibrary_onGamePadMoveEvent( } jboolean Java_org_citra_citra_1emu_NativeLibrary_onGamePadAxisEvent( - [[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj, [[maybe_unused]] jstring j_device, - jint axis_id, jfloat axis_val) { + [[maybe_unused]] JNIEnv *env, [[maybe_unused]] jobject obj, + [[maybe_unused]] jstring j_device, + jint axis_id, jfloat axis_val) { return static_cast( - InputManager::ButtonHandler()->AnalogButtonEvent(axis_id, axis_val)); + InputManager::ButtonHandler()->AnalogButtonEvent(axis_id, axis_val)); } -jboolean Java_org_citra_citra_1emu_NativeLibrary_onTouchEvent([[maybe_unused]] JNIEnv* env, +jboolean Java_org_citra_citra_1emu_NativeLibrary_onTouchEvent([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jobject obj, jfloat x, jfloat y, jboolean pressed) { return static_cast( - window->OnTouchEvent(static_cast(x + 0.5), static_cast(y + 0.5), pressed)); + window->OnTouchEvent(static_cast(x + 0.5), static_cast(y + 0.5), pressed)); } -void Java_org_citra_citra_1emu_NativeLibrary_onTouchMoved([[maybe_unused]] JNIEnv* env, +void Java_org_citra_citra_1emu_NativeLibrary_onTouchMoved([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jobject obj, jfloat x, jfloat y) { - window->OnTouchMoved((int)x, (int)y); + window->OnTouchMoved((int) x, (int) y); } -jlong Java_org_citra_citra_1emu_NativeLibrary_getTitleId(JNIEnv* env, [[maybe_unused]] jobject obj, +jlong Java_org_citra_citra_1emu_NativeLibrary_getTitleId(JNIEnv *env, [[maybe_unused]] jobject obj, jstring j_filename) { std::string filepath = GetJString(env, j_filename); const auto loader = Loader::GetLoader(filepath); @@ -616,7 +675,7 @@ jlong Java_org_citra_citra_1emu_NativeLibrary_getTitleId(JNIEnv* env, [[maybe_un return static_cast(title_id); } -jboolean Java_org_citra_citra_1emu_NativeLibrary_getIsSystemTitle(JNIEnv* env, +jboolean Java_org_citra_citra_1emu_NativeLibrary_getIsSystemTitle(JNIEnv *env, [[maybe_unused]] jobject obj, jstring path) { const std::string filepath = GetJString(env, path); @@ -632,19 +691,19 @@ jboolean Java_org_citra_citra_1emu_NativeLibrary_getIsSystemTitle(JNIEnv* env, return ((program_id >> 32) & 0xFFFFFFFF) == 0x00040010; } -void Java_org_citra_citra_1emu_NativeLibrary_createConfigFile([[maybe_unused]] JNIEnv* env, +void Java_org_citra_citra_1emu_NativeLibrary_createConfigFile([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jobject obj) { Config{}; } -void Java_org_citra_citra_1emu_NativeLibrary_createLogFile([[maybe_unused]] JNIEnv* env, +void Java_org_citra_citra_1emu_NativeLibrary_createLogFile([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jobject obj) { Common::Log::Initialize(); Common::Log::Start(); LOG_INFO(Frontend, "Logging backend initialised"); } -void Java_org_citra_citra_1emu_NativeLibrary_logUserDirectory(JNIEnv* env, +void Java_org_citra_citra_1emu_NativeLibrary_logUserDirectory(JNIEnv *env, [[maybe_unused]] jobject obj, jstring j_path) { std::string_view path = env->GetStringUTFChars(j_path, 0); @@ -652,10 +711,10 @@ void Java_org_citra_citra_1emu_NativeLibrary_logUserDirectory(JNIEnv* env, env->ReleaseStringUTFChars(j_path, path.data()); } -void Java_org_citra_citra_1emu_NativeLibrary_reloadSettings([[maybe_unused]] JNIEnv* env, +void Java_org_citra_citra_1emu_NativeLibrary_reloadSettings([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jobject obj) { Config{}; - Core::System& system{Core::System::GetInstance()}; + Core::System &system{Core::System::GetInstance()}; // Replace with game-specific settings if (system.IsPoweredOn()) { @@ -666,9 +725,9 @@ void Java_org_citra_citra_1emu_NativeLibrary_reloadSettings([[maybe_unused]] JNI system.ApplySettings(); } -jdoubleArray Java_org_citra_citra_1emu_NativeLibrary_getPerfStats(JNIEnv* env, +jdoubleArray Java_org_citra_citra_1emu_NativeLibrary_getPerfStats(JNIEnv *env, [[maybe_unused]] jobject obj) { - auto& core = Core::System::GetInstance(); + auto &core = Core::System::GetInstance(); jdoubleArray j_stats = env->NewDoubleArray(4); if (core.IsPoweredOn()) { @@ -684,7 +743,7 @@ jdoubleArray Java_org_citra_citra_1emu_NativeLibrary_getPerfStats(JNIEnv* env, return j_stats; } -void Java_org_citra_citra_1emu_NativeLibrary_run__Ljava_lang_String_2(JNIEnv* env, +void Java_org_citra_citra_1emu_NativeLibrary_run__Ljava_lang_String_2(JNIEnv *env, [[maybe_unused]] jobject obj, jstring j_path) { const std::string path = GetJString(env, j_path); @@ -701,19 +760,19 @@ void Java_org_citra_citra_1emu_NativeLibrary_run__Ljava_lang_String_2(JNIEnv* en } } -void Java_org_citra_citra_1emu_NativeLibrary_reloadCameraDevices([[maybe_unused]] JNIEnv* env, +void Java_org_citra_citra_1emu_NativeLibrary_reloadCameraDevices([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jobject obj) { if (g_ndk_factory) { g_ndk_factory->ReloadCameraDevices(); } } -jboolean Java_org_citra_citra_1emu_NativeLibrary_loadAmiibo(JNIEnv* env, +jboolean Java_org_citra_citra_1emu_NativeLibrary_loadAmiibo(JNIEnv *env, [[maybe_unused]] jobject obj, jstring j_file) { std::string filepath = GetJString(env, j_file); - Core::System& system{Core::System::GetInstance()}; - Service::SM::ServiceManager& sm = system.ServiceManager(); + Core::System &system{Core::System::GetInstance()}; + Service::SM::ServiceManager &sm = system.ServiceManager(); auto nfc = sm.GetService("nfc:u"); if (nfc == nullptr) { return static_cast(false); @@ -722,10 +781,10 @@ jboolean Java_org_citra_citra_1emu_NativeLibrary_loadAmiibo(JNIEnv* env, return static_cast(nfc->LoadAmiibo(filepath)); } -void Java_org_citra_citra_1emu_NativeLibrary_removeAmiibo([[maybe_unused]] JNIEnv* env, +void Java_org_citra_citra_1emu_NativeLibrary_removeAmiibo([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jobject obj) { - Core::System& system{Core::System::GetInstance()}; - Service::SM::ServiceManager& sm = system.ServiceManager(); + Core::System &system{Core::System::GetInstance()}; + Service::SM::ServiceManager &sm = system.ServiceManager(); auto nfc = sm.GetService("nfc:u"); if (nfc == nullptr) { return; @@ -735,19 +794,20 @@ void Java_org_citra_citra_1emu_NativeLibrary_removeAmiibo([[maybe_unused]] JNIEn } JNIEXPORT jobject JNICALL Java_org_citra_citra_1emu_utils_CiaInstallWorker_installCIA( - JNIEnv* env, jobject jobj, jstring jpath) { + JNIEnv *env, jobject jobj, jstring jpath) { std::string path = GetJString(env, jpath); Service::AM::InstallStatus res = Service::AM::InstallCIA( - path, [env, jobj](std::size_t total_bytes_read, std::size_t file_size) { - env->CallVoidMethod(jobj, IDCache::GetCiaInstallHelperSetProgress(), - static_cast(file_size), static_cast(total_bytes_read)); - }); + path, [env, jobj](std::size_t total_bytes_read, std::size_t file_size) { + env->CallVoidMethod(jobj, IDCache::GetCiaInstallHelperSetProgress(), + static_cast(file_size), + static_cast(total_bytes_read)); + }); return IDCache::GetJavaCiaInstallStatus(res); } jobjectArray Java_org_citra_citra_1emu_NativeLibrary_getSavestateInfo( - JNIEnv* env, [[maybe_unused]] jobject obj) { + JNIEnv *env, [[maybe_unused]] jobject obj) { const jclass date_class = env->FindClass("java/util/Date"); const auto date_constructor = env->GetMethodID(date_class, "", "(J)V"); @@ -755,7 +815,7 @@ jobjectArray Java_org_citra_citra_1emu_NativeLibrary_getSavestateInfo( const auto slot_field = env->GetFieldID(savestate_info_class, "slot", "I"); const auto date_field = env->GetFieldID(savestate_info_class, "time", "Ljava/util/Date;"); - const Core::System& system{Core::System::GetInstance()}; + const Core::System &system{Core::System::GetInstance()}; if (!system.IsPoweredOn()) { return nullptr; } @@ -767,7 +827,8 @@ jobjectArray Java_org_citra_citra_1emu_NativeLibrary_getSavestateInfo( const auto savestates = Core::ListSaveStates(title_id, system.Movie().GetCurrentMovieID()); const jobjectArray array = - env->NewObjectArray(static_cast(savestates.size()), savestate_info_class, nullptr); + env->NewObjectArray(static_cast(savestates.size()), savestate_info_class, + nullptr); for (std::size_t i = 0; i < savestates.size(); ++i) { const jobject object = env->AllocObject(savestate_info_class); env->SetIntField(object, slot_field, static_cast(savestates[i].slot)); @@ -780,17 +841,17 @@ jobjectArray Java_org_citra_citra_1emu_NativeLibrary_getSavestateInfo( return array; } -void Java_org_citra_citra_1emu_NativeLibrary_saveState([[maybe_unused]] JNIEnv* env, +void Java_org_citra_citra_1emu_NativeLibrary_saveState([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jobject obj, jint slot) { Core::System::GetInstance().SendSignal(Core::System::Signal::Save, slot); } -void Java_org_citra_citra_1emu_NativeLibrary_loadState([[maybe_unused]] JNIEnv* env, +void Java_org_citra_citra_1emu_NativeLibrary_loadState([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jobject obj, jint slot) { Core::System::GetInstance().SendSignal(Core::System::Signal::Load, slot); } -void Java_org_citra_citra_1emu_NativeLibrary_logDeviceInfo([[maybe_unused]] JNIEnv* env, +void Java_org_citra_citra_1emu_NativeLibrary_logDeviceInfo([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jobject obj) { LOG_INFO(Frontend, "Azahar Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc); diff --git a/src/video_core/renderer_base.cpp b/src/video_core/renderer_base.cpp index fbebee2b8..a3ee517b5 100644 --- a/src/video_core/renderer_base.cpp +++ b/src/video_core/renderer_base.cpp @@ -67,4 +67,13 @@ void RendererBase::RequestScreenshot(void* data, std::function callb settings.screenshot_requested = true; } + Frontend::EmuWindow *RendererBase::getSecondaryWindow() const { + return secondary_window; + } + + void RendererBase::setSecondaryWindow(Frontend::EmuWindow *secondaryWindow) { + secondary_window = secondaryWindow; + if (secondary_window) secondary_window->PollEvents(); + } + } // namespace VideoCore diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h index 526ce51d7..cbd5fd244 100644 --- a/src/video_core/renderer_base.h +++ b/src/video_core/renderer_base.h @@ -111,7 +111,14 @@ protected: Core::System& system; RendererSettings settings; Frontend::EmuWindow& render_window; ///< Reference to the render window handle. - Frontend::EmuWindow* secondary_window; ///< Reference to the secondary render window handle. + Frontend::EmuWindow* secondary_window; +public: + Frontend::EmuWindow *getSecondaryWindow() const; + + virtual void setSecondaryWindow(Frontend::EmuWindow *secondaryWindow); + +protected: + ///< Reference to the secondary render window handle. f32 current_fps = 0.0f; ///< Current framerate, should be set by the renderer s32 current_frame = 0; ///< Current frame, should be set by the renderer }; diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 8139723ec..951b82005 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -87,6 +87,15 @@ RendererOpenGL::RendererOpenGL(Core::System& system, Pica::PicaCore& pica_, } RendererOpenGL::~RendererOpenGL() = default; +void RendererOpenGL::setSecondaryWindow(Frontend::EmuWindow *secondaryWindow) { + if (secondaryWindow) { + secondary_window = secondaryWindow; + secondary_window->mailbox = std::make_unique(driver.HasDebugTool()); + }else { + secondary_window = nullptr; + // should I release something here? The mailbox?? + } +} void RendererOpenGL::SwapBuffers() { // Maintain the rasterizer's state as a priority diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index 7885a2f5b..c5b10242c 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -54,6 +54,7 @@ public: void PrepareVideoDumping() override; void CleanupVideoDumping() override; void Sync() override; + void setSecondaryWindow(Frontend::EmuWindow *secondaryWindow) override; private: void InitOpenGLObjects();