mirror of
https://github.com/azahar-emu/azahar
synced 2025-11-06 23:19:57 +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 surfaceDestroyed()
|
||||||
external fun doFrame()
|
external fun doFrame()
|
||||||
|
|
||||||
|
// Second window
|
||||||
|
external fun secondarySurfaceChanged(secondary_surface: Surface)
|
||||||
|
external fun secondarySurfaceDestroyed()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unpauses emulation from a paused state.
|
* 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.contracts.OpenFileResultContract
|
||||||
import org.citra.citra_emu.databinding.ActivityEmulationBinding
|
import org.citra.citra_emu.databinding.ActivityEmulationBinding
|
||||||
import org.citra.citra_emu.display.ScreenAdjustmentUtil
|
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.hotkeys.HotkeyUtility
|
||||||
import org.citra.citra_emu.features.settings.model.BooleanSetting
|
import org.citra.citra_emu.features.settings.model.BooleanSetting
|
||||||
import org.citra.citra_emu.features.settings.model.IntSetting
|
import org.citra.citra_emu.features.settings.model.IntSetting
|
||||||
@ -59,6 +60,7 @@ class EmulationActivity : AppCompatActivity() {
|
|||||||
private lateinit var binding: ActivityEmulationBinding
|
private lateinit var binding: ActivityEmulationBinding
|
||||||
private lateinit var screenAdjustmentUtil: ScreenAdjustmentUtil
|
private lateinit var screenAdjustmentUtil: ScreenAdjustmentUtil
|
||||||
private lateinit var hotkeyUtility: HotkeyUtility
|
private lateinit var hotkeyUtility: HotkeyUtility
|
||||||
|
private lateinit var secondaryDisplay: SecondaryDisplay;
|
||||||
|
|
||||||
private val emulationFragment: EmulationFragment
|
private val emulationFragment: EmulationFragment
|
||||||
get() {
|
get() {
|
||||||
@ -73,10 +75,10 @@ class EmulationActivity : AppCompatActivity() {
|
|||||||
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||||
|
|
||||||
ThemeUtil.setTheme(this)
|
ThemeUtil.setTheme(this)
|
||||||
|
|
||||||
settingsViewModel.settings.loadSettings()
|
settingsViewModel.settings.loadSettings()
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
secondaryDisplay = SecondaryDisplay(this);
|
||||||
|
secondaryDisplay.updateDisplay();
|
||||||
|
|
||||||
binding = ActivityEmulationBinding.inflate(layoutInflater)
|
binding = ActivityEmulationBinding.inflate(layoutInflater)
|
||||||
screenAdjustmentUtil = ScreenAdjustmentUtil(this, windowManager, settingsViewModel.settings)
|
screenAdjustmentUtil = ScreenAdjustmentUtil(this, windowManager, settingsViewModel.settings)
|
||||||
@ -136,6 +138,11 @@ class EmulationActivity : AppCompatActivity() {
|
|||||||
applyOrientationSettings() // Check for orientation settings changes on runtime
|
applyOrientationSettings() // Check for orientation settings changes on runtime
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onStop() {
|
||||||
|
secondaryDisplay.releasePresentation()
|
||||||
|
super.onStop()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||||
super.onWindowFocusChanged(hasFocus)
|
super.onWindowFocusChanged(hasFocus)
|
||||||
enableFullscreenImmersive()
|
enableFullscreenImmersive()
|
||||||
@ -143,6 +150,7 @@ class EmulationActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
public override fun onRestart() {
|
public override fun onRestart() {
|
||||||
super.onRestart()
|
super.onRestart()
|
||||||
|
secondaryDisplay.updateDisplay()
|
||||||
NativeLibrary.reloadCameraDevices()
|
NativeLibrary.reloadCameraDevices()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,6 +169,9 @@ class EmulationActivity : AppCompatActivity() {
|
|||||||
NativeLibrary.playTimeManagerStop()
|
NativeLibrary.playTimeManagerStop()
|
||||||
isEmulationRunning = false
|
isEmulationRunning = false
|
||||||
instance = null
|
instance = null
|
||||||
|
secondaryDisplay.releasePresentation()
|
||||||
|
secondaryDisplay.releaseVD();
|
||||||
|
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -50,3 +50,17 @@ enum class PortraitScreenLayout(val int: Int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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),
|
LANDSCAPE_BOTTOM_HEIGHT("custom_bottom_height",Settings.SECTION_LAYOUT,480),
|
||||||
SCREEN_GAP("screen_gap",Settings.SECTION_LAYOUT,0),
|
SCREEN_GAP("screen_gap",Settings.SECTION_LAYOUT,0),
|
||||||
PORTRAIT_SCREEN_LAYOUT("portrait_layout_option",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_X("custom_portrait_top_x",Settings.SECTION_LAYOUT,0),
|
||||||
PORTRAIT_TOP_Y("custom_portrait_top_y",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),
|
PORTRAIT_TOP_WIDTH("custom_portrait_top_width",Settings.SECTION_LAYOUT,800),
|
||||||
|
|||||||
@ -14,11 +14,8 @@ import android.os.Build
|
|||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import kotlin.math.min
|
|
||||||
import org.citra.citra_emu.CitraApplication
|
import org.citra.citra_emu.CitraApplication
|
||||||
import org.citra.citra_emu.R
|
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.AbstractBooleanSetting
|
||||||
import org.citra.citra_emu.features.settings.model.AbstractIntSetting
|
import org.citra.citra_emu.features.settings.model.AbstractIntSetting
|
||||||
import org.citra.citra_emu.features.settings.model.AbstractSetting
|
import org.citra.citra_emu.features.settings.model.AbstractSetting
|
||||||
@ -1111,6 +1108,17 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||||||
IntSetting.PORTRAIT_SCREEN_LAYOUT.defaultValue
|
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(
|
add(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.ASPECT_RATIO,
|
IntSetting.ASPECT_RATIO,
|
||||||
|
|||||||
@ -209,6 +209,9 @@ void Config::ReadValues() {
|
|||||||
static_cast<Settings::PortraitLayoutOption>(sdl2_config->GetInteger(
|
static_cast<Settings::PortraitLayoutOption>(sdl2_config->GetInteger(
|
||||||
"Layout", "portrait_layout_option",
|
"Layout", "portrait_layout_option",
|
||||||
static_cast<int>(Settings::PortraitLayoutOption::PortraitTopFullWidth)));
|
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_x);
|
||||||
ReadSetting("Layout", Settings::values.custom_portrait_top_y);
|
ReadSetting("Layout", Settings::values.custom_portrait_top_y);
|
||||||
ReadSetting("Layout", Settings::values.custom_portrait_top_width);
|
ReadSetting("Layout", Settings::values.custom_portrait_top_width);
|
||||||
|
|||||||
@ -288,6 +288,15 @@ swap_screen =
|
|||||||
# 0 (default): Off, 1: On
|
# 0 (default): Off, 1: On
|
||||||
expand_to_cutout_area =
|
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)
|
# Screen placement settings when using Cardboard VR (render3d = 4)
|
||||||
# 30 - 100: Screen size as a percentage of the viewport. 85 (default)
|
# 30 - 100: Screen size as a percentage of the viewport. 85 (default)
|
||||||
cardboard_screen_size =
|
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
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// 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 bigger{window_width > window_height ? window_width : window_height};
|
||||||
const int smaller{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);
|
UpdateCurrentFramebufferLayout(smaller, bigger, is_portrait_mode);
|
||||||
} else {
|
} else {
|
||||||
UpdateCurrentFramebufferLayout(bigger, smaller, is_portrait_mode);
|
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");
|
LOG_DEBUG(Frontend, "Initializing EmuWindow_Android");
|
||||||
|
|
||||||
if (!surface) {
|
if (!surface) {
|
||||||
LOG_CRITICAL(Frontend, "surface is nullptr");
|
LOG_CRITICAL(Frontend, "surface is nullptr");
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
// Copyright 2019 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <EGL/egl.h>
|
||||||
#include "core/frontend/emu_window.h"
|
#include "core/frontend/emu_window.h"
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
@ -13,7 +14,7 @@ class System;
|
|||||||
|
|
||||||
class EmuWindow_Android : public Frontend::EmuWindow {
|
class EmuWindow_Android : public Frontend::EmuWindow {
|
||||||
public:
|
public:
|
||||||
EmuWindow_Android(ANativeWindow* surface);
|
EmuWindow_Android(ANativeWindow* surface, bool is_secondary = false);
|
||||||
~EmuWindow_Android();
|
~EmuWindow_Android();
|
||||||
|
|
||||||
/// Called by the onSurfaceChanges() method to change the surface
|
/// Called by the onSurfaceChanges() method to change the surface
|
||||||
@ -30,7 +31,12 @@ public:
|
|||||||
void DoneCurrent() override;
|
void DoneCurrent() override;
|
||||||
|
|
||||||
virtual void TryPresenting() {}
|
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() {}
|
virtual void StopPresenting() {}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2019 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
@ -72,8 +72,9 @@ private:
|
|||||||
EGLContext egl_context{};
|
EGLContext egl_context{};
|
||||||
};
|
};
|
||||||
|
|
||||||
EmuWindow_Android_OpenGL::EmuWindow_Android_OpenGL(Core::System& system_, ANativeWindow* surface)
|
EmuWindow_Android_OpenGL::EmuWindow_Android_OpenGL(Core::System& system_, ANativeWindow* surface,
|
||||||
: EmuWindow_Android{surface}, system{system_} {
|
bool is_secondary, EGLContext* sharedContext)
|
||||||
|
: EmuWindow_Android{surface, is_secondary}, system{system_} {
|
||||||
if (egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); egl_display == EGL_NO_DISPLAY) {
|
if (egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); egl_display == EGL_NO_DISPLAY) {
|
||||||
LOG_CRITICAL(Frontend, "eglGetDisplay() failed");
|
LOG_CRITICAL(Frontend, "eglGetDisplay() failed");
|
||||||
return;
|
return;
|
||||||
@ -96,8 +97,10 @@ EmuWindow_Android_OpenGL::EmuWindow_Android_OpenGL(Core::System& system_, ANativ
|
|||||||
if (eglQuerySurface(egl_display, egl_surface, EGL_HEIGHT, &window_height) != EGL_TRUE) {
|
if (eglQuerySurface(egl_display, egl_surface, EGL_HEIGHT, &window_height) != EGL_TRUE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (sharedContext) {
|
||||||
if (egl_context = eglCreateContext(egl_display, egl_config, 0, egl_context_attribs.data());
|
egl_context = *sharedContext;
|
||||||
|
} else if (egl_context =
|
||||||
|
eglCreateContext(egl_display, egl_config, 0, egl_context_attribs.data());
|
||||||
egl_context == EGL_NO_CONTEXT) {
|
egl_context == EGL_NO_CONTEXT) {
|
||||||
LOG_CRITICAL(Frontend, "eglCreateContext() failed");
|
LOG_CRITICAL(Frontend, "eglCreateContext() failed");
|
||||||
return;
|
return;
|
||||||
@ -127,6 +130,10 @@ EmuWindow_Android_OpenGL::EmuWindow_Android_OpenGL(Core::System& system_, ANativ
|
|||||||
OnFramebufferSizeChanged();
|
OnFramebufferSizeChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EGLContext* EmuWindow_Android_OpenGL::GetEGLContext() {
|
||||||
|
return &egl_context;
|
||||||
|
}
|
||||||
|
|
||||||
bool EmuWindow_Android_OpenGL::CreateWindowSurface() {
|
bool EmuWindow_Android_OpenGL::CreateWindowSurface() {
|
||||||
if (!host_window) {
|
if (!host_window) {
|
||||||
return true;
|
return true;
|
||||||
@ -204,14 +211,14 @@ void EmuWindow_Android_OpenGL::TryPresenting() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (presenting_state == PresentingState::Initial) [[unlikely]] {
|
if (presenting_state == PresentingState::Initial) [[unlikely]] {
|
||||||
eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context);
|
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
|
||||||
presenting_state = PresentingState::Running;
|
presenting_state = PresentingState::Running;
|
||||||
}
|
}
|
||||||
if (presenting_state != PresentingState::Running) [[unlikely]] {
|
if (presenting_state != PresentingState::Running) [[unlikely]] {
|
||||||
return;
|
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);
|
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);
|
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
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
@ -19,13 +19,14 @@ struct ANativeWindow;
|
|||||||
|
|
||||||
class EmuWindow_Android_OpenGL : public EmuWindow_Android {
|
class EmuWindow_Android_OpenGL : public EmuWindow_Android {
|
||||||
public:
|
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;
|
~EmuWindow_Android_OpenGL() override = default;
|
||||||
|
|
||||||
void TryPresenting() override;
|
void TryPresenting() override;
|
||||||
void StopPresenting() override;
|
void StopPresenting() override;
|
||||||
void PollEvents() override;
|
void PollEvents() override;
|
||||||
|
EGLContext* GetEGLContext() override;
|
||||||
std::unique_ptr<GraphicsContext> CreateSharedContext() const override;
|
std::unique_ptr<GraphicsContext> CreateSharedContext() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2019 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
@ -24,8 +24,9 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
EmuWindow_Android_Vulkan::EmuWindow_Android_Vulkan(
|
EmuWindow_Android_Vulkan::EmuWindow_Android_Vulkan(
|
||||||
ANativeWindow* surface, std::shared_ptr<Common::DynamicLibrary> driver_library_)
|
ANativeWindow* surface, std::shared_ptr<Common::DynamicLibrary> driver_library_,
|
||||||
: EmuWindow_Android{surface}, driver_library{driver_library_} {
|
bool is_secondary)
|
||||||
|
: EmuWindow_Android{surface, is_secondary}, driver_library{driver_library_} {
|
||||||
CreateWindowSurface();
|
CreateWindowSurface();
|
||||||
|
|
||||||
if (core_context = CreateSharedContext(); !core_context) {
|
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
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
@ -11,7 +11,8 @@ struct ANativeWindow;
|
|||||||
class EmuWindow_Android_Vulkan : public EmuWindow_Android {
|
class EmuWindow_Android_Vulkan : public EmuWindow_Android {
|
||||||
public:
|
public:
|
||||||
EmuWindow_Android_Vulkan(ANativeWindow* surface,
|
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;
|
~EmuWindow_Android_Vulkan() override = default;
|
||||||
|
|
||||||
void PollEvents() override {}
|
void PollEvents() override {}
|
||||||
|
|||||||
@ -16,11 +16,13 @@
|
|||||||
#include <core/hle/service/cfg/cfg.h>
|
#include <core/hle/service/cfg/cfg.h>
|
||||||
#include "audio_core/dsp_interface.h"
|
#include "audio_core/dsp_interface.h"
|
||||||
#include "common/arch.h"
|
#include "common/arch.h"
|
||||||
|
|
||||||
#if CITRA_ARCH(arm64)
|
#if CITRA_ARCH(arm64)
|
||||||
#include "common/aarch64/cpu_detect.h"
|
#include "common/aarch64/cpu_detect.h"
|
||||||
#elif CITRA_ARCH(x86_64)
|
#elif CITRA_ARCH(x86_64)
|
||||||
#include "common/x64/cpu_detect.h"
|
#include "common/x64/cpu_detect.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "common/common_paths.h"
|
#include "common/common_paths.h"
|
||||||
#include "common/dynamic_library/dynamic_library.h"
|
#include "common/dynamic_library/dynamic_library.h"
|
||||||
#include "common/file_util.h"
|
#include "common/file_util.h"
|
||||||
@ -47,12 +49,18 @@
|
|||||||
#include "jni/camera/ndk_camera.h"
|
#include "jni/camera/ndk_camera.h"
|
||||||
#include "jni/camera/still_image_camera.h"
|
#include "jni/camera/still_image_camera.h"
|
||||||
#include "jni/config.h"
|
#include "jni/config.h"
|
||||||
|
|
||||||
#ifdef ENABLE_OPENGL
|
#ifdef ENABLE_OPENGL
|
||||||
#include "jni/emu_window/emu_window_gl.h"
|
#include "jni/emu_window/emu_window_gl.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef ENABLE_VULKAN
|
#ifdef ENABLE_VULKAN
|
||||||
#include "jni/emu_window/emu_window_vk.h"
|
#include "jni/emu_window/emu_window_vk.h"
|
||||||
|
#if CITRA_ARCH(arm64)
|
||||||
|
#include <adrenotools/driver.h>
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "jni/id_cache.h"
|
#include "jni/id_cache.h"
|
||||||
#include "jni/input_manager.h"
|
#include "jni/input_manager.h"
|
||||||
#include "jni/ndk_motion.h"
|
#include "jni/ndk_motion.h"
|
||||||
@ -61,16 +69,14 @@
|
|||||||
#include "video_core/gpu.h"
|
#include "video_core/gpu.h"
|
||||||
#include "video_core/renderer_base.h"
|
#include "video_core/renderer_base.h"
|
||||||
|
|
||||||
#if defined(ENABLE_VULKAN) && CITRA_ARCH(arm64)
|
|
||||||
#include <adrenotools/driver.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
ANativeWindow* s_surf;
|
ANativeWindow* s_surface;
|
||||||
|
ANativeWindow* s_secondary_surface;
|
||||||
|
|
||||||
std::shared_ptr<Common::DynamicLibrary> vulkan_library{};
|
std::shared_ptr<Common::DynamicLibrary> vulkan_library{};
|
||||||
std::unique_ptr<EmuWindow_Android> window;
|
std::unique_ptr<EmuWindow_Android> window;
|
||||||
|
std::unique_ptr<EmuWindow_Android> secondary_window;
|
||||||
|
|
||||||
std::unique_ptr<PlayTime::PlayTimeManager> play_time_manager;
|
std::unique_ptr<PlayTime::PlayTimeManager> play_time_manager;
|
||||||
jlong ptm_current_title_id = std::numeric_limits<jlong>::max(); // Arbitrary default value
|
jlong ptm_current_title_id = std::numeric_limits<jlong>::max(); // Arbitrary default value
|
||||||
@ -124,8 +130,17 @@ static void TryShutdown() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
window->DoneCurrent();
|
window->DoneCurrent();
|
||||||
|
if (secondary_window) {
|
||||||
|
secondary_window->DoneCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
Core::System::GetInstance().Shutdown();
|
Core::System::GetInstance().Shutdown();
|
||||||
|
|
||||||
window.reset();
|
window.reset();
|
||||||
|
if (secondary_window) {
|
||||||
|
secondary_window.reset();
|
||||||
|
}
|
||||||
|
|
||||||
InputManager::Shutdown();
|
InputManager::Shutdown();
|
||||||
MicroProfileShutdown();
|
MicroProfileShutdown();
|
||||||
}
|
}
|
||||||
@ -151,15 +166,21 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
|
|||||||
Core::System& system{Core::System::GetInstance()};
|
Core::System& system{Core::System::GetInstance()};
|
||||||
|
|
||||||
const auto graphics_api = Settings::values.graphics_api.GetValue();
|
const auto graphics_api = Settings::values.graphics_api.GetValue();
|
||||||
|
EGLContext* shared_context;
|
||||||
switch (graphics_api) {
|
switch (graphics_api) {
|
||||||
#ifdef ENABLE_OPENGL
|
#ifdef ENABLE_OPENGL
|
||||||
case Settings::GraphicsAPI::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;
|
break;
|
||||||
#endif
|
#endif
|
||||||
#ifdef ENABLE_VULKAN
|
#ifdef ENABLE_VULKAN
|
||||||
case Settings::GraphicsAPI::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;
|
break;
|
||||||
#endif
|
#endif
|
||||||
default:
|
default:
|
||||||
@ -167,11 +188,17 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
|
|||||||
"Unknown or unsupported graphics API {}, falling back to available default",
|
"Unknown or unsupported graphics API {}, falling back to available default",
|
||||||
graphics_api);
|
graphics_api);
|
||||||
#ifdef ENABLE_OPENGL
|
#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
|
#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
|
#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."
|
#error "At least one renderer must be enabled."
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
@ -208,7 +235,8 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
|
|||||||
InputManager::Init();
|
InputManager::Init();
|
||||||
|
|
||||||
window->MakeCurrent();
|
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) {
|
if (load_result != Core::System::ResultStatus::Success) {
|
||||||
return load_result;
|
return load_result;
|
||||||
}
|
}
|
||||||
@ -262,6 +290,7 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
|
|||||||
std::unique_lock pause_lock{paused_mutex};
|
std::unique_lock pause_lock{paused_mutex};
|
||||||
running_cv.wait(pause_lock, [] { return !pause_emulation || stop_run; });
|
running_cv.wait(pause_lock, [] { return !pause_emulation || stop_run; });
|
||||||
window->PollEvents();
|
window->PollEvents();
|
||||||
|
// if (secondary_window) secondary_window->PollEvents();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,28 +333,68 @@ extern "C" {
|
|||||||
void Java_org_citra_citra_1emu_NativeLibrary_surfaceChanged(JNIEnv* env,
|
void Java_org_citra_citra_1emu_NativeLibrary_surfaceChanged(JNIEnv* env,
|
||||||
[[maybe_unused]] jobject obj,
|
[[maybe_unused]] jobject obj,
|
||||||
jobject surf) {
|
jobject surf) {
|
||||||
s_surf = ANativeWindow_fromSurface(env, surf);
|
s_surface = ANativeWindow_fromSurface(env, surf);
|
||||||
|
|
||||||
bool notify = false;
|
bool notify = false;
|
||||||
if (window) {
|
if (window) {
|
||||||
notify = window->OnSurfaceChanged(s_surf);
|
notify = window->OnSurfaceChanged(s_surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& system = Core::System::GetInstance();
|
auto& system = Core::System::GetInstance();
|
||||||
if (notify && system.IsPoweredOn()) {
|
if (notify && system.IsPoweredOn()) {
|
||||||
system.GPU().Renderer().NotifySurfaceChanged();
|
system.GPU().Renderer().NotifySurfaceChanged(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_INFO(Frontend, "Surface changed");
|
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,
|
void Java_org_citra_citra_1emu_NativeLibrary_surfaceDestroyed([[maybe_unused]] JNIEnv* env,
|
||||||
[[maybe_unused]] jobject obj) {
|
[[maybe_unused]] jobject obj) {
|
||||||
if (s_surf != nullptr) {
|
if (s_surface != nullptr) {
|
||||||
ANativeWindow_release(s_surf);
|
ANativeWindow_release(s_surface);
|
||||||
s_surf = nullptr;
|
s_surface = nullptr;
|
||||||
if (window) {
|
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) {
|
if (window) {
|
||||||
window->TryPresenting();
|
window->TryPresenting();
|
||||||
}
|
}
|
||||||
|
if (secondary_window) {
|
||||||
|
secondary_window->TryPresenting();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void JNICALL Java_org_citra_citra_1emu_NativeLibrary_initializeGpuDriver(
|
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;
|
stop_run = true;
|
||||||
pause_emulation = false;
|
pause_emulation = false;
|
||||||
window->StopPresenting();
|
window->StopPresenting();
|
||||||
|
if (secondary_window) {
|
||||||
|
secondary_window->StopPresenting();
|
||||||
|
}
|
||||||
running_cv.notify_all();
|
running_cv.notify_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -35,12 +35,26 @@
|
|||||||
<item>@string/emulation_screen_layout_custom</item>
|
<item>@string/emulation_screen_layout_custom</item>
|
||||||
</string-array>
|
</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">
|
<integer-array name="portraitLayoutValues">
|
||||||
<item>0</item>
|
<item>0</item>
|
||||||
<item>2</item>
|
<item>2</item>
|
||||||
<item>1</item>
|
<item>1</item>
|
||||||
</integer-array>
|
</integer-array>
|
||||||
|
|
||||||
|
<integer-array name="secondaryLayoutValues">
|
||||||
|
<item>0</item>
|
||||||
|
<item>1</item>
|
||||||
|
<item>2</item>
|
||||||
|
<item>3</item>
|
||||||
|
</integer-array>
|
||||||
|
|
||||||
<string-array name="smallScreenPositions">
|
<string-array name="smallScreenPositions">
|
||||||
<item>@string/small_screen_position_top_right</item>
|
<item>@string/small_screen_position_top_right</item>
|
||||||
<item>@string/small_screen_position_middle_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_aspect_ratio">Aspect Ratio</string>
|
||||||
<string name="emulation_switch_screen_layout">Landscape Screen Layout</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_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_largescreen">Large Screen</string>
|
||||||
<string name="emulation_screen_layout_portrait">Portrait</string>
|
<string name="emulation_screen_layout_portrait">Portrait</string>
|
||||||
<string name="emulation_screen_layout_single">Single Screen</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_hybrid">Hybrid Screens</string>
|
||||||
<string name="emulation_screen_layout_original">Original</string>
|
<string name="emulation_screen_layout_original">Original</string>
|
||||||
<string name="emulation_portrait_layout_top_full">Default</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_screen_layout_custom">Custom Layout</string>
|
||||||
<string name="emulation_small_screen_position">Small Screen Position</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>
|
<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_LayoutOption", values.layout_option.GetValue());
|
||||||
log_setting("Layout_PortraitLayoutOption", values.portrait_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_SwapScreen", values.swap_screen.GetValue());
|
||||||
log_setting("Layout_UprightScreen", values.upright_screen.GetValue());
|
log_setting("Layout_UprightScreen", values.upright_screen.GetValue());
|
||||||
log_setting("Layout_ScreenGap", values.screen_gap.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.delay_game_render_thread_us.SetGlobal(true);
|
||||||
values.layout_option.SetGlobal(true);
|
values.layout_option.SetGlobal(true);
|
||||||
values.portrait_layout_option.SetGlobal(true);
|
values.portrait_layout_option.SetGlobal(true);
|
||||||
|
values.secondary_display_layout.SetGlobal(true);
|
||||||
values.swap_screen.SetGlobal(true);
|
values.swap_screen.SetGlobal(true);
|
||||||
values.upright_screen.SetGlobal(true);
|
values.upright_screen.SetGlobal(true);
|
||||||
values.large_screen_proportion.SetGlobal(true);
|
values.large_screen_proportion.SetGlobal(true);
|
||||||
|
|||||||
@ -54,6 +54,7 @@ enum class PortraitLayoutOption : u32 {
|
|||||||
PortraitOriginal
|
PortraitOriginal
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class SecondaryDisplayLayout : u32 { None, TopScreenOnly, BottomScreenOnly, SideBySide };
|
||||||
/** Defines where the small screen will appear relative to the large screen
|
/** Defines where the small screen will appear relative to the large screen
|
||||||
* when in Large Screen mode
|
* when in Large Screen mode
|
||||||
*/
|
*/
|
||||||
@ -519,6 +520,8 @@ struct Values {
|
|||||||
SwitchableSetting<LayoutOption> layout_option{LayoutOption::Default, "layout_option"};
|
SwitchableSetting<LayoutOption> layout_option{LayoutOption::Default, "layout_option"};
|
||||||
SwitchableSetting<bool> swap_screen{false, "swap_screen"};
|
SwitchableSetting<bool> swap_screen{false, "swap_screen"};
|
||||||
SwitchableSetting<bool> upright_screen{false, "upright_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,
|
SwitchableSetting<float, true> large_screen_proportion{4.f, 1.f, 16.f,
|
||||||
"large_screen_proportion"};
|
"large_screen_proportion"};
|
||||||
SwitchableSetting<int> screen_gap{0, "screen_gap"};
|
SwitchableSetting<int> screen_gap{0, "screen_gap"};
|
||||||
|
|||||||
@ -271,6 +271,11 @@ void EmuWindow::UpdateCurrentFramebufferLayout(u32 width, u32 height, bool is_po
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#ifdef ANDROID
|
||||||
|
if (is_secondary) {
|
||||||
|
layout = Layout::AndroidSecondaryLayout(width, height);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
UpdateMinimumWindowSize(min_size);
|
UpdateMinimumWindowSize(min_size);
|
||||||
|
|
||||||
if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::CardboardVR) {
|
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
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
@ -254,6 +254,9 @@ public:
|
|||||||
bool is_portrait_mode = {});
|
bool is_portrait_mode = {});
|
||||||
|
|
||||||
std::unique_ptr<TextureMailbox> mailbox = nullptr;
|
std::unique_ptr<TextureMailbox> mailbox = nullptr;
|
||||||
|
bool isSecondary() const {
|
||||||
|
return is_secondary;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
EmuWindow();
|
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
|
// TODO: This is kind of gross, make it platform agnostic. -OS
|
||||||
#ifdef ANDROID
|
#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();
|
const auto aspect_ratio_setting = Settings::values.aspect_ratio.GetValue();
|
||||||
|
|
||||||
float emulation_aspect_ratio = (swapped) ? BOT_SCREEN_ASPECT_RATIO : TOP_SCREEN_ASPECT_RATIO;
|
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);
|
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;
|
const float emulation_aspect_ratio = emulation_height / emulation_width;
|
||||||
|
|
||||||
Common::Rectangle<u32> screen_window_area{0, 0, width, height};
|
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,
|
// 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
|
// 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
|
// 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;
|
const float scale_factor = 2.25f;
|
||||||
|
|
||||||
float main_screen_aspect_ratio = TOP_SCREEN_ASPECT_RATIO;
|
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);
|
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) {
|
FramebufferLayout CustomFrameLayout(u32 width, u32 height, bool is_swapped, bool is_portrait_mode) {
|
||||||
ASSERT(width > 0);
|
ASSERT(width > 0);
|
||||||
ASSERT(height > 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);
|
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
|
* Factory method for constructing a custom FramebufferLayout
|
||||||
* @param width Window framebuffer width in pixels
|
* @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
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// 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);
|
window.UpdateCurrentFramebufferLayout(layout.width, layout.height, is_portrait_mode);
|
||||||
};
|
};
|
||||||
update_layout(render_window);
|
update_layout(render_window);
|
||||||
if (secondary_window) {
|
if (secondary_window != nullptr) {
|
||||||
update_layout(*secondary_window);
|
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_framebuffer_layout = layout;
|
||||||
settings.screenshot_requested = true;
|
settings.screenshot_requested = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace VideoCore
|
} // namespace VideoCore
|
||||||
|
|||||||
@ -61,7 +61,8 @@ public:
|
|||||||
virtual void CleanupVideoDumping() {}
|
virtual void CleanupVideoDumping() {}
|
||||||
|
|
||||||
/// This is called to notify the rendering backend of a surface change
|
/// 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
|
/// Returns the resolution scale factor relative to the native 3DS screen resolution
|
||||||
u32 GetResolutionScaleFactor();
|
u32 GetResolutionScaleFactor();
|
||||||
@ -106,10 +107,12 @@ public:
|
|||||||
protected:
|
protected:
|
||||||
Core::System& system;
|
Core::System& system;
|
||||||
RendererSettings settings;
|
RendererSettings settings;
|
||||||
Frontend::EmuWindow& render_window; ///< Reference to the render window handle.
|
Frontend::EmuWindow& render_window; /// Reference to the render window handle.
|
||||||
Frontend::EmuWindow* secondary_window; ///< Reference to the secondary render window handle.
|
Frontend::EmuWindow* secondary_window; /// 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
|
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
|
} // namespace VideoCore
|
||||||
|
|||||||
@ -100,7 +100,15 @@ void RendererOpenGL::SwapBuffers() {
|
|||||||
const auto& main_layout = render_window.GetFramebufferLayout();
|
const auto& main_layout = render_window.GetFramebufferLayout();
|
||||||
RenderToMailbox(main_layout, render_window.mailbox, false);
|
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) {
|
if (Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows) {
|
||||||
ASSERT(secondary_window);
|
ASSERT(secondary_window);
|
||||||
const auto& secondary_layout = secondary_window->GetFramebufferLayout();
|
const auto& secondary_layout = secondary_window->GetFramebufferLayout();
|
||||||
@ -108,6 +116,7 @@ void RendererOpenGL::SwapBuffers() {
|
|||||||
secondary_window->PollEvents();
|
secondary_window->PollEvents();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (frame_dumper.IsDumping()) {
|
if (frame_dumper.IsDumping()) {
|
||||||
try {
|
try {
|
||||||
RenderToMailbox(frame_dumper.GetLayout(), frame_dumper.mailbox, true);
|
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_},
|
: RendererBase{system, window, secondary_window}, memory{system.Memory()}, pica{pica_},
|
||||||
instance{window, Settings::values.physical_device.GetValue()}, scheduler{instance},
|
instance{window, Settings::values.physical_device.GetValue()}, scheduler{instance},
|
||||||
renderpass_cache{instance, scheduler},
|
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{instance, scheduler, vk::BufferUsageFlagBits::eVertexBuffer,
|
||||||
VERTEX_BUFFER_SIZE},
|
VERTEX_BUFFER_SIZE},
|
||||||
update_queue{instance},
|
update_queue{instance}, rasterizer{memory,
|
||||||
rasterizer{
|
pica,
|
||||||
memory, pica, system.CustomTexManager(), *this, render_window,
|
system.CustomTexManager(),
|
||||||
instance, scheduler, renderpass_cache, update_queue, main_window.ImageCount()},
|
*this,
|
||||||
|
render_window,
|
||||||
|
instance,
|
||||||
|
scheduler,
|
||||||
|
renderpass_cache,
|
||||||
|
update_queue,
|
||||||
|
main_present_window.ImageCount()},
|
||||||
present_heap{instance, scheduler.GetMasterSemaphore(), PRESENT_BINDINGS, 32} {
|
present_heap{instance, scheduler.GetMasterSemaphore(), PRESENT_BINDINGS, 32} {
|
||||||
CompileShaders();
|
CompileShaders();
|
||||||
BuildLayouts();
|
BuildLayouts();
|
||||||
BuildPipelines();
|
BuildPipelines();
|
||||||
if (secondary_window) {
|
if (secondary_window) {
|
||||||
second_window = std::make_unique<PresentWindow>(*secondary_window, instance, scheduler,
|
secondary_present_window_ptr = std::make_unique<PresentWindow>(
|
||||||
IsLowRefreshRate());
|
*secondary_window, instance, scheduler, IsLowRefreshRate());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RendererVulkan::~RendererVulkan() {
|
RendererVulkan::~RendererVulkan() {
|
||||||
vk::Device device = instance.GetDevice();
|
vk::Device device = instance.GetDevice();
|
||||||
scheduler.Finish();
|
scheduler.Finish();
|
||||||
main_window.WaitPresent();
|
main_present_window.WaitPresent();
|
||||||
device.waitIdle();
|
device.waitIdle();
|
||||||
|
|
||||||
device.destroyShaderModule(present_vertex_shader);
|
device.destroyShaderModule(present_vertex_shader);
|
||||||
@ -177,7 +183,8 @@ void RendererVulkan::PrepareDraw(Frame* frame, const Layout::FramebufferLayout&
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderpass_cache.EndRendering();
|
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) {
|
index = current_pipeline](vk::CommandBuffer cmdbuf) {
|
||||||
const vk::Viewport viewport = {
|
const vk::Viewport viewport = {
|
||||||
.x = 0.0f,
|
.x = 0.0f,
|
||||||
@ -434,7 +441,7 @@ void RendererVulkan::BuildPipelines() {
|
|||||||
.pColorBlendState = &color_blending,
|
.pColorBlendState = &color_blending,
|
||||||
.pDynamicState = &dynamic_info,
|
.pDynamicState = &dynamic_info,
|
||||||
.layout = *present_pipeline_layout,
|
.layout = *present_pipeline_layout,
|
||||||
.renderPass = main_window.Renderpass(),
|
.renderPass = main_present_window.Renderpass(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto [result, pipeline] =
|
const auto [result, pipeline] =
|
||||||
@ -887,19 +894,32 @@ void RendererVulkan::SwapBuffers() {
|
|||||||
const Layout::FramebufferLayout& layout = render_window.GetFramebufferLayout();
|
const Layout::FramebufferLayout& layout = render_window.GetFramebufferLayout();
|
||||||
PrepareRendertarget();
|
PrepareRendertarget();
|
||||||
RenderScreenshot();
|
RenderScreenshot();
|
||||||
RenderToWindow(main_window, layout, false);
|
RenderToWindow(main_present_window, layout, false);
|
||||||
#ifndef ANDROID
|
#ifndef ANDROID
|
||||||
if (Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows) {
|
if (Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows) {
|
||||||
ASSERT(secondary_window);
|
ASSERT(secondary_window);
|
||||||
const auto& secondary_layout = secondary_window->GetFramebufferLayout();
|
const auto& secondary_layout = secondary_window->GetFramebufferLayout();
|
||||||
if (!second_window) {
|
if (!secondary_present_window_ptr) {
|
||||||
second_window = std::make_unique<PresentWindow>(*secondary_window, instance, scheduler,
|
secondary_present_window_ptr = std::make_unique<PresentWindow>(
|
||||||
IsLowRefreshRate());
|
*secondary_window, instance, scheduler, IsLowRefreshRate());
|
||||||
}
|
}
|
||||||
RenderToWindow(*second_window, secondary_layout, false);
|
RenderToWindow(*secondary_present_window_ptr, secondary_layout, false);
|
||||||
secondary_window->PollEvents();
|
secondary_window->PollEvents();
|
||||||
}
|
}
|
||||||
#endif
|
#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();
|
system.perf_stats->EndSwap();
|
||||||
rasterizer.TickFrame();
|
rasterizer.TickFrame();
|
||||||
EndFrame();
|
EndFrame();
|
||||||
@ -954,7 +974,7 @@ void RendererVulkan::RenderScreenshotWithStagingCopy() {
|
|||||||
vk::Buffer staging_buffer{unsafe_buffer};
|
vk::Buffer staging_buffer{unsafe_buffer};
|
||||||
|
|
||||||
Frame frame{};
|
Frame frame{};
|
||||||
main_window.RecreateFrame(&frame, width, height);
|
main_present_window.RecreateFrame(&frame, width, height);
|
||||||
|
|
||||||
DrawScreens(&frame, layout, false);
|
DrawScreens(&frame, layout, false);
|
||||||
|
|
||||||
@ -1127,7 +1147,7 @@ bool RendererVulkan::TryRenderScreenshotWithHostMemory() {
|
|||||||
device.bindBufferMemory(imported_buffer.get(), imported_memory.get(), 0);
|
device.bindBufferMemory(imported_buffer.get(), imported_memory.get(), 0);
|
||||||
|
|
||||||
Frame frame{};
|
Frame frame{};
|
||||||
main_window.RecreateFrame(&frame, width, height);
|
main_present_window.RecreateFrame(&frame, width, height);
|
||||||
|
|
||||||
DrawScreens(&frame, layout, false);
|
DrawScreens(&frame, layout, false);
|
||||||
|
|
||||||
@ -1190,4 +1210,14 @@ bool RendererVulkan::TryRenderScreenshotWithHostMemory() {
|
|||||||
return true;
|
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
|
} // namespace Vulkan
|
||||||
|
|||||||
@ -74,9 +74,7 @@ public:
|
|||||||
return &rasterizer;
|
return &rasterizer;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotifySurfaceChanged() override {
|
void NotifySurfaceChanged(bool second) override;
|
||||||
main_window.NotifySurfaceChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SwapBuffers() override;
|
void SwapBuffers() override;
|
||||||
void TryPresent(int timeout_ms, bool is_secondary) override {}
|
void TryPresent(int timeout_ms, bool is_secondary) override {}
|
||||||
@ -117,11 +115,11 @@ private:
|
|||||||
Instance instance;
|
Instance instance;
|
||||||
Scheduler scheduler;
|
Scheduler scheduler;
|
||||||
RenderManager renderpass_cache;
|
RenderManager renderpass_cache;
|
||||||
PresentWindow main_window;
|
PresentWindow main_present_window;
|
||||||
StreamBuffer vertex_buffer;
|
StreamBuffer vertex_buffer;
|
||||||
DescriptorUpdateQueue update_queue;
|
DescriptorUpdateQueue update_queue;
|
||||||
RasterizerVulkan rasterizer;
|
RasterizerVulkan rasterizer;
|
||||||
std::unique_ptr<PresentWindow> second_window;
|
std::unique_ptr<PresentWindow> secondary_present_window_ptr;
|
||||||
|
|
||||||
DescriptorHeap present_heap;
|
DescriptorHeap present_heap;
|
||||||
vk::UniquePipelineLayout present_pipeline_layout;
|
vk::UniquePipelineLayout present_pipeline_layout;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user