From aca8b45664b04e24dffdc1087e99983e3ee8e87c Mon Sep 17 00:00:00 2001 From: David Griswold Date: Fri, 8 Aug 2025 23:41:52 +0300 Subject: [PATCH] android: Implement secondary display support (#617) * Enable the SecondScreenPresentation class * Update everything to enable second screen on android under GL and Vulkan. Still some issues! * Some attempts to enable surface changes * OpenGL is working on surface change, vulkan still no * release surfaces (also fixed vulkan?) * added and enabled layout setting * resolve merge conflict * rearrange switch cases to satisfy linux compiler * openGL is working! * several vk changes to try to fix crashes * maybe vulkan is working? * removing unnecessary code attempts * Simplified secondscreen for better performance * vk_platform.cpp: Fixed build failure caused by bad rebase * vk_present_window.h: Removed stray newline * Applied clang-format * bug fix for odin 2 * Applied clang-format * Updated license headers * Moved SecondScreen class to org.citra.citra_emu.display * Various formatting and readability improvements * Added brackets where previously absent for readability * Additional readability improvement * RendererVulkan::NotifySurfaceChanged: Simplified condition checking * change all references to "secondary screen" to "secondary display" to limit confusion with top screen / bottom screen * rename main_window to main_present_window and second_window to secondary_present_window * Reverted accidentally downgraded compatibility list submodule * Removed unnecessary log message * Applied clang-format * Added a description to the Secondary Display Screen Layout setting * Added `_ptr` suffix to `secondary_present_window` This distinguishes it as a pointer, as `main_present_window` isn't a pointer, so there could be confusion on whether to use `.` or `->` --------- Co-authored-by: OpenSauce04 --- .../java/org/citra/citra_emu/NativeLibrary.kt | 4 + .../citra_emu/activities/EmulationActivity.kt | 15 ++- .../citra/citra_emu/display/ScreenLayout.kt | 14 +++ .../citra_emu/display/SecondaryDisplay.kt | 113 ++++++++++++++++++ .../features/settings/model/IntSetting.kt | 1 + .../settings/ui/SettingsFragmentPresenter.kt | 14 ++- src/android/app/src/main/jni/config.cpp | 3 + src/android/app/src/main/jni/default_ini.h | 9 ++ .../src/main/jni/emu_window/emu_window.cpp | 8 +- .../app/src/main/jni/emu_window/emu_window.h | 12 +- .../src/main/jni/emu_window/emu_window_gl.cpp | 25 ++-- .../src/main/jni/emu_window/emu_window_gl.h | 7 +- .../src/main/jni/emu_window/emu_window_vk.cpp | 7 +- .../src/main/jni/emu_window/emu_window_vk.h | 5 +- src/android/app/src/main/jni/native.cpp | 111 ++++++++++++++--- .../app/src/main/res/values/arrays.xml | 14 +++ .../app/src/main/res/values/strings.xml | 3 + src/common/settings.cpp | 2 + src/common/settings.h | 3 + src/core/frontend/emu_window.cpp | 5 + src/core/frontend/emu_window.h | 5 +- src/core/frontend/framebuffer_layout.cpp | 24 +++- src/core/frontend/framebuffer_layout.h | 8 ++ src/video_core/renderer_base.cpp | 5 +- src/video_core/renderer_base.h | 13 +- .../renderer_opengl/renderer_opengl.cpp | 11 +- .../renderer_vulkan/renderer_vulkan.cpp | 64 +++++++--- .../renderer_vulkan/renderer_vulkan.h | 8 +- 28 files changed, 431 insertions(+), 82 deletions(-) create mode 100644 src/android/app/src/main/java/org/citra/citra_emu/display/SecondaryDisplay.kt 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 6d93725a4..eec388463 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 @@ -124,6 +124,10 @@ object NativeLibrary { external fun surfaceDestroyed() external fun doFrame() + // Second window + external fun secondarySurfaceChanged(secondary_surface: Surface) + external fun secondarySurfaceDestroyed() + /** * 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 9f59ea2c4..3ff594ce9 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 @@ -33,6 +33,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.SecondaryDisplay 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 @@ -59,6 +60,7 @@ class EmulationActivity : AppCompatActivity() { private lateinit var binding: ActivityEmulationBinding private lateinit var screenAdjustmentUtil: ScreenAdjustmentUtil private lateinit var hotkeyUtility: HotkeyUtility + private lateinit var secondaryDisplay: SecondaryDisplay; private val emulationFragment: EmulationFragment get() { @@ -73,10 +75,10 @@ class EmulationActivity : AppCompatActivity() { requestWindowFeature(Window.FEATURE_NO_TITLE) ThemeUtil.setTheme(this) - settingsViewModel.settings.loadSettings() - super.onCreate(savedInstanceState) + secondaryDisplay = SecondaryDisplay(this); + secondaryDisplay.updateDisplay(); binding = ActivityEmulationBinding.inflate(layoutInflater) screenAdjustmentUtil = ScreenAdjustmentUtil(this, windowManager, settingsViewModel.settings) @@ -136,6 +138,11 @@ class EmulationActivity : AppCompatActivity() { applyOrientationSettings() // Check for orientation settings changes on runtime } + override fun onStop() { + secondaryDisplay.releasePresentation() + super.onStop() + } + override fun onWindowFocusChanged(hasFocus: Boolean) { super.onWindowFocusChanged(hasFocus) enableFullscreenImmersive() @@ -143,6 +150,7 @@ class EmulationActivity : AppCompatActivity() { public override fun onRestart() { super.onRestart() + secondaryDisplay.updateDisplay() NativeLibrary.reloadCameraDevices() } @@ -161,6 +169,9 @@ class EmulationActivity : AppCompatActivity() { NativeLibrary.playTimeManagerStop() isEmulationRunning = false instance = null + secondaryDisplay.releasePresentation() + secondaryDisplay.releaseVD(); + super.onDestroy() } 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 ea82fc07e..c3877560c 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 @@ -49,4 +49,18 @@ enum class PortraitScreenLayout(val int: Int) { return entries.firstOrNull { it.int == int } ?: TOP_FULL_WIDTH } } +} + +enum class SecondaryDisplayLayout(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): SecondaryDisplayLayout { + 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/display/SecondaryDisplay.kt b/src/android/app/src/main/java/org/citra/citra_emu/display/SecondaryDisplay.kt new file mode 100644 index 000000000..4af42792c --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/display/SecondaryDisplay.kt @@ -0,0 +1,113 @@ +// Copyright Citra Emulator Project / Azahar Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +package org.citra.citra_emu.display + +import android.app.Presentation +import android.content.Context +import android.graphics.SurfaceTexture +import android.hardware.display.DisplayManager +import android.hardware.display.VirtualDisplay +import android.os.Bundle +import android.view.Display +import android.view.Surface +import android.view.SurfaceHolder +import android.view.SurfaceView +import org.citra.citra_emu.NativeLibrary +import org.citra.citra_emu.features.settings.model.IntSetting + +class SecondaryDisplay(val context: Context) { + private var pres: SecondaryDisplayPresentation? = null + private val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager + private val vd: VirtualDisplay + + init { + val st = SurfaceTexture(0) + st.setDefaultBufferSize(1920, 1080) + val vdSurface = Surface(st) + vd = displayManager.createVirtualDisplay( + "HiddenDisplay", + 1920, + 1080, + 320, + vdSurface, + DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION + ) + } + + fun updateSurface() { + NativeLibrary.secondarySurfaceChanged(pres!!.getSurfaceHolder().surface) + } + + fun destroySurface() { + NativeLibrary.secondarySurfaceDestroyed() + } + + fun updateDisplay() { + // decide if we are going to the external display or the internal one + var display = getCustomerDisplay() + if (display == null || + IntSetting.SECONDARY_DISPLAY_LAYOUT.int == SecondaryDisplayLayout.NONE.int) { + display = vd.display + } + + // if our presentation is already on the right display, ignore + if (pres?.display == display) return; + + // otherwise, make a new presentation + releasePresentation() + pres = SecondaryDisplayPresentation(context, display!!, this) + pres?.show() + } + + private fun getCustomerDisplay(): Display? { + val displays = displayManager.displays + // code taken from MelonDS dual screen - should fix odin 2 detection bug + return displayManager.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION) + .firstOrNull { it.displayId != Display.DEFAULT_DISPLAY && it.name != "Built-in Screen" && it.name != "HiddenDisplay"} + } + + fun releasePresentation() { + pres?.dismiss() + pres = null + } + + fun releaseVD() { + vd.release() + } +} +class SecondaryDisplayPresentation( + context: Context, display: Display, val parent: SecondaryDisplay +) : 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 + ) { + parent.updateSurface() + } + + override fun surfaceDestroyed(holder: SurfaceHolder) { + parent.destroySurface() + } + }) + + 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/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt index bc8ede6ee..5b2016ac9 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 @@ -35,6 +35,7 @@ enum class IntSetting( LANDSCAPE_BOTTOM_HEIGHT("custom_bottom_height",Settings.SECTION_LAYOUT,480), SCREEN_GAP("screen_gap",Settings.SECTION_LAYOUT,0), PORTRAIT_SCREEN_LAYOUT("portrait_layout_option",Settings.SECTION_LAYOUT,0), + SECONDARY_DISPLAY_LAYOUT("secondary_display_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 378a2cc66..28fb82e84 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 @@ -14,11 +14,8 @@ import android.os.Build import android.text.TextUtils import androidx.preference.PreferenceManager import com.google.android.material.dialog.MaterialAlertDialogBuilder -import kotlin.math.min import org.citra.citra_emu.CitraApplication import org.citra.citra_emu.R -import org.citra.citra_emu.display.PortraitScreenLayout -import org.citra.citra_emu.display.ScreenLayout import org.citra.citra_emu.features.settings.model.AbstractBooleanSetting import org.citra.citra_emu.features.settings.model.AbstractIntSetting import org.citra.citra_emu.features.settings.model.AbstractSetting @@ -1111,6 +1108,17 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) IntSetting.PORTRAIT_SCREEN_LAYOUT.defaultValue ) ) + add( + SingleChoiceSetting( + IntSetting.SECONDARY_DISPLAY_LAYOUT, + R.string.emulation_switch_secondary_layout, + R.string.emulation_switch_secondary_layout_description, + R.array.secondaryLayouts, + R.array.secondaryLayoutValues, + IntSetting.SECONDARY_DISPLAY_LAYOUT.key, + IntSetting.SECONDARY_DISPLAY_LAYOUT.defaultValue + ) + ) add( SingleChoiceSetting( IntSetting.ASPECT_RATIO, diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp index f4c25d1f0..1f2e8708b 100644 --- a/src/android/app/src/main/jni/config.cpp +++ b/src/android/app/src/main/jni/config.cpp @@ -209,6 +209,9 @@ void Config::ReadValues() { static_cast(sdl2_config->GetInteger( "Layout", "portrait_layout_option", static_cast(Settings::PortraitLayoutOption::PortraitTopFullWidth))); + Settings::values.secondary_display_layout = static_cast( + sdl2_config->GetInteger("Layout", "secondary_display_layout", + static_cast(Settings::SecondaryDisplayLayout::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 fbf4ce030..46083be50 100644 --- a/src/android/app/src/main/jni/default_ini.h +++ b/src/android/app/src/main/jni/default_ini.h @@ -288,6 +288,15 @@ swap_screen = # 0 (default): Off, 1: On expand_to_cutout_area = +# Secondary Display Layout +# What the game should do if a secondary display 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_display_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/jni/emu_window/emu_window.cpp b/src/android/app/src/main/jni/emu_window/emu_window.cpp index b33c734f5..8458a4f2b 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 @@ -1,4 +1,4 @@ -// Copyright 2019 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -49,16 +49,16 @@ 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 (!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..23c50f9a9 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 @@ -1,10 +1,11 @@ -// Copyright 2019 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #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,12 @@ 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 nullptr; + } 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..87533533c 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 @@ -1,4 +1,4 @@ -// Copyright 2019 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -72,8 +72,9 @@ 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,9 +97,11 @@ 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()); - egl_context == EGL_NO_CONTEXT) { + 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 +130,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 +211,14 @@ 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; } + 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..fcbfa1147 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 @@ -1,4 +1,4 @@ -// Copyright 2019 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -19,13 +19,14 @@ 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..3cc24bc34 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 @@ -1,4 +1,4 @@ -// Copyright 2019 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -24,8 +24,9 @@ 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..365a1916a 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 @@ -1,4 +1,4 @@ -// Copyright 2022 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -11,7 +11,8 @@ 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 9fddc5c32..328bd3256 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -16,11 +16,13 @@ #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" @@ -47,12 +49,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" +#if CITRA_ARCH(arm64) +#include #endif +#endif + #include "jni/id_cache.h" #include "jni/input_manager.h" #include "jni/ndk_motion.h" @@ -61,16 +69,14 @@ #include "video_core/gpu.h" #include "video_core/renderer_base.h" -#if defined(ENABLE_VULKAN) && CITRA_ARCH(arm64) -#include -#endif - namespace { -ANativeWindow* s_surf; +ANativeWindow* s_surface; +ANativeWindow* s_secondary_surface; std::shared_ptr vulkan_library{}; std::unique_ptr window; +std::unique_ptr secondary_window; std::unique_ptr play_time_manager; jlong ptm_current_title_id = std::numeric_limits::max(); // Arbitrary default value @@ -124,8 +130,17 @@ static void TryShutdown() { } window->DoneCurrent(); + if (secondary_window) { + secondary_window->DoneCurrent(); + } + Core::System::GetInstance().Shutdown(); + window.reset(); + if (secondary_window) { + secondary_window.reset(); + } + InputManager::Shutdown(); MicroProfileShutdown(); } @@ -151,15 +166,21 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) { Core::System& system{Core::System::GetInstance()}; const auto graphics_api = Settings::values.graphics_api.GetValue(); + EGLContext* shared_context; switch (graphics_api) { #ifdef ENABLE_OPENGL case Settings::GraphicsAPI::OpenGL: - window = std::make_unique(system, s_surf); + window = std::make_unique(system, s_surface, false); + shared_context = window->GetEGLContext(); + secondary_window = std::make_unique(system, s_secondary_surface, + true, shared_context); break; #endif #ifdef ENABLE_VULKAN case Settings::GraphicsAPI::Vulkan: - window = std::make_unique(s_surf, vulkan_library); + window = std::make_unique(s_surface, vulkan_library, false); + secondary_window = + std::make_unique(s_secondary_surface, vulkan_library, true); break; #endif default: @@ -167,11 +188,17 @@ 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_surface, false); + shared_context = window->GetEGLContext(); + secondary_window = std::make_unique(system, s_secondary_surface, + true, shared_context); + #elif ENABLE_VULKAN - window = std::make_unique(s_surf, vulkan_library); + window = std::make_unique(s_surface, vulkan_library); + secondary_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; @@ -208,7 +235,8 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) { InputManager::Init(); window->MakeCurrent(); - const Core::System::ResultStatus load_result{system.Load(*window, filepath)}; + const Core::System::ResultStatus load_result{ + system.Load(*window, filepath, secondary_window.get())}; if (load_result != Core::System::ResultStatus::Success) { return load_result; } @@ -262,6 +290,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 (secondary_window) secondary_window->PollEvents(); } } @@ -304,28 +333,68 @@ extern "C" { void Java_org_citra_citra_1emu_NativeLibrary_surfaceChanged(JNIEnv* env, [[maybe_unused]] jobject obj, jobject surf) { - s_surf = ANativeWindow_fromSurface(env, surf); + s_surface = ANativeWindow_fromSurface(env, surf); bool notify = false; if (window) { - notify = window->OnSurfaceChanged(s_surf); + notify = window->OnSurfaceChanged(s_surface); } auto& system = Core::System::GetInstance(); if (notify && system.IsPoweredOn()) { - system.GPU().Renderer().NotifySurfaceChanged(); + system.GPU().Renderer().NotifySurfaceChanged(false); } LOG_INFO(Frontend, "Surface changed"); } +void Java_org_citra_citra_1emu_NativeLibrary_secondarySurfaceChanged(JNIEnv* env, + [[maybe_unused]] jobject obj, + jobject surf) { + auto& system = Core::System::GetInstance(); + + if (s_secondary_surface) { + ANativeWindow_release(s_secondary_surface); + s_secondary_surface = nullptr; + } + s_secondary_surface = ANativeWindow_fromSurface(env, surf); + if (!s_secondary_surface) { + return; + } + + bool notify = false; + if (secondary_window) { + // Second window already created, so update it + notify = secondary_window->OnSurfaceChanged(s_secondary_surface); + } else { + LOG_WARNING(Frontend, + "Second Window does not exist in native.cpp but surface changed. Ignoring."); + } + + if (notify && system.IsPoweredOn()) { + system.GPU().Renderer().NotifySurfaceChanged(true); + } + + LOG_INFO(Frontend, "Secondary Surface changed"); +} + +void Java_org_citra_citra_1emu_NativeLibrary_secondarySurfaceDestroyed( + JNIEnv* env, [[maybe_unused]] jobject obj) { + if (s_secondary_surface != nullptr) { + ANativeWindow_release(s_secondary_surface); + s_secondary_surface = nullptr; + } + + LOG_INFO(Frontend, "Secondary Surface Destroyed"); +} + void Java_org_citra_citra_1emu_NativeLibrary_surfaceDestroyed([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) { - if (s_surf != nullptr) { - ANativeWindow_release(s_surf); - s_surf = nullptr; + if (s_surface != nullptr) { + ANativeWindow_release(s_surface); + s_surface = nullptr; if (window) { - window->OnSurfaceChanged(s_surf); + window->OnSurfaceChanged(s_surface); } } } @@ -338,6 +407,9 @@ void Java_org_citra_citra_1emu_NativeLibrary_doFrame([[maybe_unused]] JNIEnv* en if (window) { window->TryPresenting(); } + if (secondary_window) { + secondary_window->TryPresenting(); + } } void JNICALL Java_org_citra_citra_1emu_NativeLibrary_initializeGpuDriver( @@ -514,6 +586,9 @@ void Java_org_citra_citra_1emu_NativeLibrary_stopEmulation([[maybe_unused]] JNIE stop_run = true; pause_emulation = false; window->StopPresenting(); + if (secondary_window) { + secondary_window->StopPresenting(); + } running_cv.notify_all(); } diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index 543b59913..e29871840 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -35,12 +35,26 @@ @string/emulation_screen_layout_custom + + @string/emulation_secondary_display_default + @string/emulation_top_screen + @string/emulation_bottom_screen + @string/emulation_screen_layout_sidebyside + + 0 2 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 e04cdb0a9..c79be56c8 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -437,6 +437,8 @@ Aspect Ratio Landscape Screen Layout Portrait Screen Layout + Secondary Display Screen Layout + The layout used by a connected secondary screen, wired or wireless (Chromecast, Miracast) Large Screen Portrait Single Screen @@ -444,6 +446,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 e2ae6e4b4..f91d1ec58 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -114,6 +114,7 @@ void LogSettings() { } log_setting("Layout_LayoutOption", values.layout_option.GetValue()); log_setting("Layout_PortraitLayoutOption", values.portrait_layout_option.GetValue()); + log_setting("Layout_SecondaryDisplayLayout", values.secondary_display_layout.GetValue()); log_setting("Layout_SwapScreen", values.swap_screen.GetValue()); log_setting("Layout_UprightScreen", values.upright_screen.GetValue()); log_setting("Layout_ScreenGap", values.screen_gap.GetValue()); @@ -207,6 +208,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_display_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 e6576b748..0bb947269 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -54,6 +54,7 @@ enum class PortraitLayoutOption : u32 { PortraitOriginal }; +enum class SecondaryDisplayLayout : u32 { None, TopScreenOnly, BottomScreenOnly, SideBySide }; /** Defines where the small screen will appear relative to the large screen * when in Large Screen mode */ @@ -519,6 +520,8 @@ struct Values { SwitchableSetting layout_option{LayoutOption::Default, "layout_option"}; SwitchableSetting swap_screen{false, "swap_screen"}; SwitchableSetting upright_screen{false, "upright_screen"}; + SwitchableSetting secondary_display_layout{SecondaryDisplayLayout::None, + "secondary_display_layout"}; SwitchableSetting large_screen_proportion{4.f, 1.f, 16.f, "large_screen_proportion"}; SwitchableSetting screen_gap{0, "screen_gap"}; diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp index 70658cf29..c352dbdf0 100644 --- a/src/core/frontend/emu_window.cpp +++ b/src/core/frontend/emu_window.cpp @@ -271,6 +271,11 @@ void EmuWindow::UpdateCurrentFramebufferLayout(u32 width, u32 height, bool is_po break; } } +#ifdef ANDROID + if (is_secondary) { + layout = Layout::AndroidSecondaryLayout(width, height); + } +#endif UpdateMinimumWindowSize(min_size); if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::CardboardVR) { diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index 2752a3263..f217a7eac 100644 --- a/src/core/frontend/emu_window.h +++ b/src/core/frontend/emu_window.h @@ -1,4 +1,4 @@ -// Copyright 2014 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -254,6 +254,9 @@ public: bool is_portrait_mode = {}); std::unique_ptr mailbox = nullptr; + bool isSecondary() const { + return is_secondary; + } protected: EmuWindow(); diff --git a/src/core/frontend/framebuffer_layout.cpp b/src/core/frontend/framebuffer_layout.cpp index a347053fd..99628d118 100644 --- a/src/core/frontend/framebuffer_layout.cpp +++ b/src/core/frontend/framebuffer_layout.cpp @@ -79,7 +79,7 @@ FramebufferLayout SingleFrameLayout(u32 width, u32 height, bool swapped, bool up // TODO: This is kind of gross, make it platform agnostic. -OS #ifdef ANDROID - const float window_aspect_ratio = static_cast(height) / width; + const float window_aspect_ratio = static_cast(height) / static_cast(width); const auto aspect_ratio_setting = Settings::values.aspect_ratio.GetValue(); float emulation_aspect_ratio = (swapped) ? BOT_SCREEN_ASPECT_RATIO : TOP_SCREEN_ASPECT_RATIO; @@ -172,7 +172,7 @@ FramebufferLayout LargeFrameLayout(u32 width, u32 height, bool swapped, bool upr emulation_height = std::max(large_height, small_height); } - const float window_aspect_ratio = static_cast(height) / width; + const float window_aspect_ratio = static_cast(height) / static_cast(width); const float emulation_aspect_ratio = emulation_height / emulation_width; Common::Rectangle screen_window_area{0, 0, width, height}; @@ -281,7 +281,7 @@ FramebufferLayout HybridScreenLayout(u32 width, u32 height, bool swapped, bool u // Split the window into two parts. Give 2.25x width to the main screen, // and make a bar on the right side with 1x width top screen and 1.25x width bottom 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); const float scale_factor = 2.25f; float main_screen_aspect_ratio = TOP_SCREEN_ASPECT_RATIO; @@ -338,6 +338,24 @@ FramebufferLayout SeparateWindowsLayout(u32 width, u32 height, bool is_secondary return SingleFrameLayout(width, height, is_secondary, upright); } +FramebufferLayout AndroidSecondaryLayout(u32 width, u32 height) { + const Settings::SecondaryDisplayLayout layout = + Settings::values.secondary_display_layout.GetValue(); + switch (layout) { + + case Settings::SecondaryDisplayLayout::BottomScreenOnly: + return SingleFrameLayout(width, height, true, Settings::values.upright_screen.GetValue()); + case Settings::SecondaryDisplayLayout::SideBySide: + return LargeFrameLayout(width, height, false, Settings::values.upright_screen.GetValue(), + 1.0f, Settings::SmallScreenPosition::MiddleRight); + case Settings::SecondaryDisplayLayout::None: + // this should never happen, but if it does, somehow, send the top screen + case Settings::SecondaryDisplayLayout::TopScreenOnly: + default: + return SingleFrameLayout(width, height, false, Settings::values.upright_screen.GetValue()); + } +} + 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 6e4dc45a8..ad45cfe01 100644 --- a/src/core/frontend/framebuffer_layout.h +++ b/src/core/frontend/framebuffer_layout.h @@ -133,6 +133,14 @@ 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 diff --git a/src/video_core/renderer_base.cpp b/src/video_core/renderer_base.cpp index 16b59107a..e78ab9c91 100644 --- a/src/video_core/renderer_base.cpp +++ b/src/video_core/renderer_base.cpp @@ -1,4 +1,4 @@ -// Copyright 2015 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -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); } } @@ -66,5 +66,4 @@ void RendererBase::RequestScreenshot(void* data, std::function callb settings.screenshot_framebuffer_layout = layout; settings.screenshot_requested = true; } - } // namespace VideoCore diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h index a44d513fd..5a737a37f 100644 --- a/src/video_core/renderer_base.h +++ b/src/video_core/renderer_base.h @@ -61,7 +61,8 @@ public: virtual void CleanupVideoDumping() {} /// 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(); @@ -106,10 +107,12 @@ public: 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. - 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 + Frontend::EmuWindow& render_window; /// Reference to the render window handle. + Frontend::EmuWindow* secondary_window; /// Reference to the secondary render window handle. + +protected: + 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 }; } // namespace VideoCore diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 0dd68c55b..61aa18928 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -100,7 +100,15 @@ void RendererOpenGL::SwapBuffers() { const auto& main_layout = render_window.GetFramebufferLayout(); RenderToMailbox(main_layout, render_window.mailbox, false); -#ifndef ANDROID +#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(); + } +#else if (Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows) { ASSERT(secondary_window); const auto& secondary_layout = secondary_window->GetFramebufferLayout(); @@ -108,6 +116,7 @@ void RendererOpenGL::SwapBuffers() { secondary_window->PollEvents(); } #endif + if (frame_dumper.IsDumping()) { try { RenderToMailbox(frame_dumper.GetLayout(), frame_dumper.mailbox, true); diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index 95bb7bd4b..ee8b1910a 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -105,27 +105,33 @@ RendererVulkan::RendererVulkan(Core::System& system, Pica::PicaCore& pica_, : RendererBase{system, window, secondary_window}, memory{system.Memory()}, pica{pica_}, instance{window, Settings::values.physical_device.GetValue()}, scheduler{instance}, renderpass_cache{instance, scheduler}, - main_window{window, instance, scheduler, IsLowRefreshRate()}, + main_present_window{window, instance, scheduler, IsLowRefreshRate()}, vertex_buffer{instance, scheduler, vk::BufferUsageFlagBits::eVertexBuffer, VERTEX_BUFFER_SIZE}, - update_queue{instance}, - rasterizer{ - memory, pica, system.CustomTexManager(), *this, render_window, - instance, scheduler, renderpass_cache, update_queue, main_window.ImageCount()}, + update_queue{instance}, rasterizer{memory, + pica, + system.CustomTexManager(), + *this, + render_window, + instance, + scheduler, + renderpass_cache, + update_queue, + main_present_window.ImageCount()}, present_heap{instance, scheduler.GetMasterSemaphore(), PRESENT_BINDINGS, 32} { CompileShaders(); BuildLayouts(); BuildPipelines(); if (secondary_window) { - second_window = std::make_unique(*secondary_window, instance, scheduler, - IsLowRefreshRate()); + secondary_present_window_ptr = std::make_unique( + *secondary_window, instance, scheduler, IsLowRefreshRate()); } } RendererVulkan::~RendererVulkan() { vk::Device device = instance.GetDevice(); scheduler.Finish(); - main_window.WaitPresent(); + main_present_window.WaitPresent(); device.waitIdle(); device.destroyShaderModule(present_vertex_shader); @@ -177,7 +183,8 @@ void RendererVulkan::PrepareDraw(Frame* frame, const Layout::FramebufferLayout& } renderpass_cache.EndRendering(); - scheduler.Record([this, layout, frame, present_set, renderpass = main_window.Renderpass(), + scheduler.Record([this, layout, frame, present_set, + renderpass = main_present_window.Renderpass(), index = current_pipeline](vk::CommandBuffer cmdbuf) { const vk::Viewport viewport = { .x = 0.0f, @@ -434,7 +441,7 @@ void RendererVulkan::BuildPipelines() { .pColorBlendState = &color_blending, .pDynamicState = &dynamic_info, .layout = *present_pipeline_layout, - .renderPass = main_window.Renderpass(), + .renderPass = main_present_window.Renderpass(), }; const auto [result, pipeline] = @@ -887,19 +894,32 @@ void RendererVulkan::SwapBuffers() { const Layout::FramebufferLayout& layout = render_window.GetFramebufferLayout(); PrepareRendertarget(); RenderScreenshot(); - RenderToWindow(main_window, layout, false); + RenderToWindow(main_present_window, layout, false); #ifndef ANDROID if (Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows) { ASSERT(secondary_window); const auto& secondary_layout = secondary_window->GetFramebufferLayout(); - if (!second_window) { - second_window = std::make_unique(*secondary_window, instance, scheduler, - IsLowRefreshRate()); + if (!secondary_present_window_ptr) { + secondary_present_window_ptr = std::make_unique( + *secondary_window, instance, scheduler, IsLowRefreshRate()); } - RenderToWindow(*second_window, secondary_layout, false); + RenderToWindow(*secondary_present_window_ptr, secondary_layout, false); secondary_window->PollEvents(); } #endif + +#ifdef ANDROID + if (secondary_window) { + const auto& secondary_layout = secondary_window->GetFramebufferLayout(); + if (!secondary_present_window_ptr) { + secondary_present_window_ptr = std::make_unique( + *secondary_window, instance, scheduler, IsLowRefreshRate()); + } + RenderToWindow(*secondary_present_window_ptr, secondary_layout, false); + secondary_window->PollEvents(); + } +#endif + system.perf_stats->EndSwap(); rasterizer.TickFrame(); EndFrame(); @@ -954,7 +974,7 @@ void RendererVulkan::RenderScreenshotWithStagingCopy() { vk::Buffer staging_buffer{unsafe_buffer}; Frame frame{}; - main_window.RecreateFrame(&frame, width, height); + main_present_window.RecreateFrame(&frame, width, height); DrawScreens(&frame, layout, false); @@ -1127,7 +1147,7 @@ bool RendererVulkan::TryRenderScreenshotWithHostMemory() { device.bindBufferMemory(imported_buffer.get(), imported_memory.get(), 0); Frame frame{}; - main_window.RecreateFrame(&frame, width, height); + main_present_window.RecreateFrame(&frame, width, height); DrawScreens(&frame, layout, false); @@ -1190,4 +1210,14 @@ bool RendererVulkan::TryRenderScreenshotWithHostMemory() { return true; } +void RendererVulkan::NotifySurfaceChanged(bool is_second_window) { + if (is_second_window) { + if (secondary_present_window_ptr) { + secondary_present_window_ptr->NotifySurfaceChanged(); + } + } else { + main_present_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 a5ad3a72f..8486915e4 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.h +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h @@ -74,9 +74,7 @@ public: return &rasterizer; } - void NotifySurfaceChanged() override { - main_window.NotifySurfaceChanged(); - } + void NotifySurfaceChanged(bool second) override; void SwapBuffers() override; void TryPresent(int timeout_ms, bool is_secondary) override {} @@ -117,11 +115,11 @@ private: Instance instance; Scheduler scheduler; RenderManager renderpass_cache; - PresentWindow main_window; + PresentWindow main_present_window; StreamBuffer vertex_buffer; DescriptorUpdateQueue update_queue; RasterizerVulkan rasterizer; - std::unique_ptr second_window; + std::unique_ptr secondary_present_window_ptr; DescriptorHeap present_heap; vk::UniquePipelineLayout present_pipeline_layout;