mirror of
https://github.com/azahar-emu/azahar
synced 2025-11-06 15:09:58 +01:00
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 <opensauce04@gmail.com>
This commit is contained in:
parent
2697526f34
commit
aca8b45664
@ -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.
|
||||
*/
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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),
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -209,6 +209,9 @@ void Config::ReadValues() {
|
||||
static_cast<Settings::PortraitLayoutOption>(sdl2_config->GetInteger(
|
||||
"Layout", "portrait_layout_option",
|
||||
static_cast<int>(Settings::PortraitLayoutOption::PortraitTopFullWidth)));
|
||||
Settings::values.secondary_display_layout = static_cast<Settings::SecondaryDisplayLayout>(
|
||||
sdl2_config->GetInteger("Layout", "secondary_display_layout",
|
||||
static_cast<int>(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);
|
||||
|
||||
@ -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 =
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 <vector>
|
||||
#include <EGL/egl.h>
|
||||
#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:
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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<GraphicsContext> CreateSharedContext() const override;
|
||||
|
||||
private:
|
||||
|
||||
@ -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<Common::DynamicLibrary> driver_library_)
|
||||
: EmuWindow_Android{surface}, driver_library{driver_library_} {
|
||||
ANativeWindow* surface, std::shared_ptr<Common::DynamicLibrary> driver_library_,
|
||||
bool is_secondary)
|
||||
: EmuWindow_Android{surface, is_secondary}, driver_library{driver_library_} {
|
||||
CreateWindowSurface();
|
||||
|
||||
if (core_context = CreateSharedContext(); !core_context) {
|
||||
|
||||
@ -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<Common::DynamicLibrary> driver_library);
|
||||
std::shared_ptr<Common::DynamicLibrary> driver_library,
|
||||
bool is_secondary);
|
||||
~EmuWindow_Android_Vulkan() override = default;
|
||||
|
||||
void PollEvents() override {}
|
||||
|
||||
@ -16,11 +16,13 @@
|
||||
#include <core/hle/service/cfg/cfg.h>
|
||||
#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 <adrenotools/driver.h>
|
||||
#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 <adrenotools/driver.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
ANativeWindow* s_surf;
|
||||
ANativeWindow* s_surface;
|
||||
ANativeWindow* s_secondary_surface;
|
||||
|
||||
std::shared_ptr<Common::DynamicLibrary> vulkan_library{};
|
||||
std::unique_ptr<EmuWindow_Android> window;
|
||||
std::unique_ptr<EmuWindow_Android> secondary_window;
|
||||
|
||||
std::unique_ptr<PlayTime::PlayTimeManager> play_time_manager;
|
||||
jlong ptm_current_title_id = std::numeric_limits<jlong>::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<EmuWindow_Android_OpenGL>(system, s_surf);
|
||||
window = std::make_unique<EmuWindow_Android_OpenGL>(system, s_surface, false);
|
||||
shared_context = window->GetEGLContext();
|
||||
secondary_window = std::make_unique<EmuWindow_Android_OpenGL>(system, s_secondary_surface,
|
||||
true, shared_context);
|
||||
break;
|
||||
#endif
|
||||
#ifdef ENABLE_VULKAN
|
||||
case Settings::GraphicsAPI::Vulkan:
|
||||
window = std::make_unique<EmuWindow_Android_Vulkan>(s_surf, vulkan_library);
|
||||
window = std::make_unique<EmuWindow_Android_Vulkan>(s_surface, vulkan_library, false);
|
||||
secondary_window =
|
||||
std::make_unique<EmuWindow_Android_Vulkan>(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<EmuWindow_Android_OpenGL>(system, s_surf);
|
||||
window = std::make_unique<EmuWindow_Android_OpenGL>(system, s_surface, false);
|
||||
shared_context = window->GetEGLContext();
|
||||
secondary_window = std::make_unique<EmuWindow_Android_OpenGL>(system, s_secondary_surface,
|
||||
true, shared_context);
|
||||
|
||||
#elif ENABLE_VULKAN
|
||||
window = std::make_unique<EmuWindow_Android_Vulkan>(s_surf, vulkan_library);
|
||||
window = std::make_unique<EmuWindow_Android_Vulkan>(s_surface, vulkan_library);
|
||||
secondary_window =
|
||||
std::make_unique<EmuWindow_Android_Vulkan>(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();
|
||||
}
|
||||
|
||||
|
||||
@ -35,12 +35,26 @@
|
||||
<item>@string/emulation_screen_layout_custom</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="secondaryLayouts">
|
||||
<item>@string/emulation_secondary_display_default</item>
|
||||
<item>@string/emulation_top_screen</item>
|
||||
<item>@string/emulation_bottom_screen</item>
|
||||
<item>@string/emulation_screen_layout_sidebyside</item>
|
||||
</string-array>
|
||||
|
||||
<integer-array name="portraitLayoutValues">
|
||||
<item>0</item>
|
||||
<item>2</item>
|
||||
<item>1</item>
|
||||
</integer-array>
|
||||
|
||||
<integer-array name="secondaryLayoutValues">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
<item>3</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="smallScreenPositions">
|
||||
<item>@string/small_screen_position_top_right</item>
|
||||
<item>@string/small_screen_position_middle_right</item>
|
||||
|
||||
@ -437,6 +437,8 @@
|
||||
<string name="emulation_aspect_ratio">Aspect Ratio</string>
|
||||
<string name="emulation_switch_screen_layout">Landscape Screen Layout</string>
|
||||
<string name="emulation_switch_portrait_layout">Portrait Screen Layout</string>
|
||||
<string name="emulation_switch_secondary_layout">Secondary Display Screen Layout</string>
|
||||
<string name="emulation_switch_secondary_layout_description">The layout used by a connected secondary screen, wired or wireless (Chromecast, Miracast)</string>
|
||||
<string name="emulation_screen_layout_largescreen">Large Screen</string>
|
||||
<string name="emulation_screen_layout_portrait">Portrait</string>
|
||||
<string name="emulation_screen_layout_single">Single Screen</string>
|
||||
@ -444,6 +446,7 @@
|
||||
<string name="emulation_screen_layout_hybrid">Hybrid Screens</string>
|
||||
<string name="emulation_screen_layout_original">Original</string>
|
||||
<string name="emulation_portrait_layout_top_full">Default</string>
|
||||
<string name="emulation_secondary_display_default">System Default (mirror)</string>
|
||||
<string name="emulation_screen_layout_custom">Custom Layout</string>
|
||||
<string name="emulation_small_screen_position">Small Screen Position</string>
|
||||
<string name="small_screen_position_description">Where should the small screen appear relative to the large one in Large Screen Layout?</string>
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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<LayoutOption> layout_option{LayoutOption::Default, "layout_option"};
|
||||
SwitchableSetting<bool> swap_screen{false, "swap_screen"};
|
||||
SwitchableSetting<bool> upright_screen{false, "upright_screen"};
|
||||
SwitchableSetting<SecondaryDisplayLayout> secondary_display_layout{SecondaryDisplayLayout::None,
|
||||
"secondary_display_layout"};
|
||||
SwitchableSetting<float, true> large_screen_proportion{4.f, 1.f, 16.f,
|
||||
"large_screen_proportion"};
|
||||
SwitchableSetting<int> screen_gap{0, "screen_gap"};
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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<TextureMailbox> mailbox = nullptr;
|
||||
bool isSecondary() const {
|
||||
return is_secondary;
|
||||
}
|
||||
|
||||
protected:
|
||||
EmuWindow();
|
||||
|
||||
@ -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<float>(height) / width;
|
||||
const float window_aspect_ratio = static_cast<float>(height) / static_cast<float>(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<float>(height) / width;
|
||||
const float window_aspect_ratio = static_cast<float>(height) / static_cast<float>(width);
|
||||
const float emulation_aspect_ratio = emulation_height / emulation_width;
|
||||
|
||||
Common::Rectangle<u32> 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<float>(height) / width;
|
||||
const float window_aspect_ratio = static_cast<float>(height) / static_cast<float>(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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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<void(bool)> callb
|
||||
settings.screenshot_framebuffer_layout = layout;
|
||||
settings.screenshot_requested = true;
|
||||
}
|
||||
|
||||
} // namespace VideoCore
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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<PresentWindow>(*secondary_window, instance, scheduler,
|
||||
IsLowRefreshRate());
|
||||
secondary_present_window_ptr = std::make_unique<PresentWindow>(
|
||||
*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<PresentWindow>(*secondary_window, instance, scheduler,
|
||||
IsLowRefreshRate());
|
||||
if (!secondary_present_window_ptr) {
|
||||
secondary_present_window_ptr = std::make_unique<PresentWindow>(
|
||||
*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<PresentWindow>(
|
||||
*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
|
||||
|
||||
@ -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<PresentWindow> second_window;
|
||||
std::unique_ptr<PresentWindow> secondary_present_window_ptr;
|
||||
|
||||
DescriptorHeap present_heap;
|
||||
vk::UniquePipelineLayout present_pipeline_layout;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user