Compare commits
3 Commits
liz-dynarm
...
descriptor
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
513b81b965 | ||
|
|
955cc70796 | ||
|
|
b55c8bb2e7 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -37,8 +37,6 @@ CMakeLists.txt.user*
|
||||
# *nix related
|
||||
# Common convention for backup or temporary files
|
||||
*~
|
||||
*.core
|
||||
dtrace-out/
|
||||
|
||||
# Visual Studio CMake settings
|
||||
CMakeSettings.json
|
||||
|
||||
10
README.md
10
README.md
@@ -25,9 +25,9 @@ It is written in C++ with portability in mind, and we actively maintain builds f
|
||||
<img src="https://img.shields.io/discord/1367654015269339267?color=5865F2&label=Eden&logo=discord&logoColor=white"
|
||||
alt="Discord">
|
||||
</a>
|
||||
<a href="https://stt.gg/qKgFEAbH">
|
||||
<img src="https://img.shields.io/revolt/invite/qKgFEAbH?color=d61f3a&label=Stoat"
|
||||
alt="Stoat">
|
||||
<a href="https://rvlt.gg/qKgFEAbH">
|
||||
<img src="https://img.shields.io/revolt/invite/qKgFEAbH?color=d61f3a&label=Revolt"
|
||||
alt="Revolt">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@@ -52,10 +52,10 @@ Check out our [website](https://eden-emu.dev) for the latest news on exciting fe
|
||||
|
||||
## Development
|
||||
|
||||
Most of the development happens on our Git server. It is also where [our central repository](https://git.eden-emu.dev/eden-emu/eden) is hosted. For development discussions, please join us on [Discord](https://discord.gg/HstXbPch7X) or [Stoat](https://stt.gg/qKgFEAbH).
|
||||
Most of the development happens on our Git server. It is also where [our central repository](https://git.eden-emu.dev/eden-emu/eden) is hosted. For development discussions, please join us on [Discord](https://discord.gg/HstXbPch7X) or [Revolt](https://rvlt.gg/qKgFEAbH).
|
||||
You can also follow us on [X (Twitter)](https://nitter.poast.org/edenemuofficial) for updates and announcements.
|
||||
|
||||
If you would like to contribute, we are open to new developers and pull requests. Please ensure that your work is of a high standard and properly documented. You can also contact any of the developers on Discord or Stoat to learn more about the current state of the emulator.
|
||||
If you would like to contribute, we are open to new developers and pull requests. Please ensure that your work is of a high standard and properly documented. You can also contact any of the developers on Discord or Revolt to learn more about the current state of the emulator.
|
||||
|
||||
See the [sign-up instructions](docs/SIGNUP.md) for information on registration.
|
||||
|
||||
|
||||
@@ -137,7 +137,7 @@ If your initial configure failed:
|
||||
- Evaluate the error and find any related settings
|
||||
- See the [CPM docs](CPM.md) to see if you may need to forcefully bundle any packages
|
||||
|
||||
Otherwise, feel free to ask for help in Stoat or Discord.
|
||||
Otherwise, feel free to ask for help in Revolt or Discord.
|
||||
|
||||
## Caveats
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ import android.content.IntentFilter
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.Icon
|
||||
import android.hardware.input.InputManager
|
||||
import android.hardware.Sensor
|
||||
import android.hardware.SensorEvent
|
||||
import android.hardware.SensorEventListener
|
||||
@@ -64,12 +63,11 @@ import kotlin.math.roundToInt
|
||||
import org.yuzu.yuzu_emu.utils.ForegroundService
|
||||
import androidx.core.os.BundleCompat
|
||||
|
||||
class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager.InputDeviceListener {
|
||||
class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
private lateinit var binding: ActivityEmulationBinding
|
||||
|
||||
var isActivityRecreated = false
|
||||
private lateinit var nfcReader: NfcReader
|
||||
private lateinit var inputManager: InputManager
|
||||
|
||||
private var touchDownTime: Long = 0
|
||||
private val maxTapDuration = 500L
|
||||
@@ -142,9 +140,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
||||
nfcReader = NfcReader(this)
|
||||
nfcReader.initialize()
|
||||
|
||||
inputManager = getSystemService(INPUT_SERVICE) as InputManager
|
||||
inputManager.registerInputDeviceListener(this, null)
|
||||
|
||||
foregroundService = Intent(this, ForegroundService::class.java)
|
||||
startForegroundService(foregroundService)
|
||||
|
||||
@@ -211,9 +206,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
inputManager.unregisterInputDeviceListener(this)
|
||||
stopForegroundService(this)
|
||||
NativeLibrary.playTimeManagerStop()
|
||||
|
||||
}
|
||||
|
||||
override fun onUserLeaveHint() {
|
||||
@@ -249,10 +244,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
||||
val isPhysicalKeyboard = event.source and InputDevice.SOURCE_KEYBOARD == InputDevice.SOURCE_KEYBOARD &&
|
||||
event.device?.isVirtual == false
|
||||
|
||||
val isControllerInput = event.source and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK ||
|
||||
event.source and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD
|
||||
|
||||
if (!isControllerInput &&
|
||||
if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK &&
|
||||
event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD &&
|
||||
event.source and InputDevice.SOURCE_MOUSE != InputDevice.SOURCE_MOUSE &&
|
||||
!isPhysicalKeyboard
|
||||
) {
|
||||
@@ -263,18 +256,12 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
||||
return super.dispatchKeyEvent(event)
|
||||
}
|
||||
|
||||
if (isControllerInput && event.action == KeyEvent.ACTION_DOWN) {
|
||||
notifyControllerInput()
|
||||
}
|
||||
|
||||
return InputHandler.dispatchKeyEvent(event)
|
||||
}
|
||||
|
||||
override fun dispatchGenericMotionEvent(event: MotionEvent): Boolean {
|
||||
val isControllerInput = event.source and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK ||
|
||||
event.source and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD
|
||||
|
||||
if (!isControllerInput &&
|
||||
if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK &&
|
||||
event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD &&
|
||||
event.source and InputDevice.SOURCE_KEYBOARD != InputDevice.SOURCE_KEYBOARD &&
|
||||
event.source and InputDevice.SOURCE_MOUSE != InputDevice.SOURCE_MOUSE
|
||||
) {
|
||||
@@ -290,54 +277,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
||||
return true
|
||||
}
|
||||
|
||||
if (isControllerInput) {
|
||||
notifyControllerInput()
|
||||
}
|
||||
|
||||
return InputHandler.dispatchGenericMotionEvent(event)
|
||||
}
|
||||
|
||||
private fun notifyControllerInput() {
|
||||
val navHostFragment =
|
||||
supportFragmentManager.findFragmentById(R.id.fragment_container) as? NavHostFragment
|
||||
val emulationFragment =
|
||||
navHostFragment?.childFragmentManager?.fragments?.firstOrNull() as? org.yuzu.yuzu_emu.fragments.EmulationFragment
|
||||
emulationFragment?.onControllerInputDetected()
|
||||
}
|
||||
|
||||
private fun isGameController(deviceId: Int): Boolean {
|
||||
val device = InputDevice.getDevice(deviceId) ?: return false
|
||||
val sources = device.sources
|
||||
return sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD ||
|
||||
sources and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK
|
||||
}
|
||||
|
||||
override fun onInputDeviceAdded(deviceId: Int) {
|
||||
if (isGameController(deviceId)) {
|
||||
InputHandler.updateControllerData()
|
||||
val navHostFragment =
|
||||
supportFragmentManager.findFragmentById(R.id.fragment_container) as? NavHostFragment
|
||||
val emulationFragment =
|
||||
navHostFragment?.childFragmentManager?.fragments?.firstOrNull() as? org.yuzu.yuzu_emu.fragments.EmulationFragment
|
||||
emulationFragment?.onControllerConnected()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onInputDeviceRemoved(deviceId: Int) {
|
||||
InputHandler.updateControllerData()
|
||||
val navHostFragment =
|
||||
supportFragmentManager.findFragmentById(R.id.fragment_container) as? NavHostFragment
|
||||
val emulationFragment =
|
||||
navHostFragment?.childFragmentManager?.fragments?.firstOrNull() as? org.yuzu.yuzu_emu.fragments.EmulationFragment
|
||||
emulationFragment?.onControllerDisconnected()
|
||||
}
|
||||
|
||||
override fun onInputDeviceChanged(deviceId: Int) {
|
||||
if (isGameController(deviceId)) {
|
||||
InputHandler.updateControllerData()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSensorChanged(event: SensorEvent) {
|
||||
val rotation = this.display?.rotation
|
||||
if (rotation == Surface.ROTATION_90) {
|
||||
@@ -577,10 +519,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
||||
when (event.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
touchDownTime = System.currentTimeMillis()
|
||||
// show overlay immediately on touch and cancel timer when only auto-hide is enabled
|
||||
if (!emulationViewModel.drawerOpen.value &&
|
||||
BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.getBoolean() &&
|
||||
!BooleanSetting.HIDE_OVERLAY_ON_CONTROLLER_INPUT.getBoolean()) {
|
||||
// show overlay immediately on touch and cancel timer
|
||||
if (!emulationViewModel.drawerOpen.value) {
|
||||
fragment.handler.removeCallbacksAndMessages(null)
|
||||
fragment.showOverlay()
|
||||
}
|
||||
|
||||
@@ -52,7 +52,6 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
||||
|
||||
SOC_OVERLAY_BACKGROUND("soc_overlay_background"),
|
||||
ENABLE_INPUT_OVERLAY_AUTO_HIDE("enable_input_overlay_auto_hide"),
|
||||
HIDE_OVERLAY_ON_CONTROLLER_INPUT("hide_overlay_on_controller_input"),
|
||||
|
||||
PERF_OVERLAY_BACKGROUND("perf_overlay_background"),
|
||||
SHOW_PERFORMANCE_OVERLAY("show_performance_overlay"),
|
||||
|
||||
@@ -387,13 +387,6 @@ abstract class SettingsItem(
|
||||
valueHint = R.string.seconds
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.HIDE_OVERLAY_ON_CONTROLLER_INPUT,
|
||||
titleId = R.string.hide_overlay_on_controller_input,
|
||||
descriptionId = R.string.hide_overlay_on_controller_input_description
|
||||
)
|
||||
)
|
||||
|
||||
put(
|
||||
SwitchSetting(
|
||||
|
||||
@@ -274,7 +274,6 @@ class SettingsFragmentPresenter(
|
||||
sl.apply {
|
||||
add(BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.key)
|
||||
add(IntSetting.INPUT_OVERLAY_AUTO_HIDE.key)
|
||||
add(BooleanSetting.HIDE_OVERLAY_ON_CONTROLLER_INPUT.key)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ class AboutFragment : Fragment() {
|
||||
}
|
||||
|
||||
binding.buttonDiscord.setOnClickListener { openLink(getString(R.string.discord_link)) }
|
||||
binding.buttonStoat.setOnClickListener { openLink(getString(R.string.stoat_link)) }
|
||||
binding.buttonRevolt.setOnClickListener { openLink(getString(R.string.revolt_link)) }
|
||||
binding.buttonX.setOnClickListener { openLink(getString(R.string.x_link)) }
|
||||
binding.buttonWebsite.setOnClickListener { openLink(getString(R.string.website_link)) }
|
||||
binding.buttonGithub.setOnClickListener { openLink(getString(R.string.github_link)) }
|
||||
|
||||
@@ -93,6 +93,7 @@ import org.yuzu.yuzu_emu.utils.collect
|
||||
import org.yuzu.yuzu_emu.utils.CustomSettingsHandler
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import kotlin.coroutines.coroutineContext
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
@@ -105,7 +106,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
|
||||
val handler = Handler(Looper.getMainLooper())
|
||||
private var isOverlayVisible = true
|
||||
private var controllerInputReceived = false
|
||||
|
||||
private var _binding: FragmentEmulationBinding? = null
|
||||
|
||||
@@ -656,12 +656,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
BooleanSetting.SHOW_INPUT_OVERLAY.setBoolean(newState)
|
||||
updateQuickOverlayMenuEntry(newState)
|
||||
binding.surfaceInputOverlay.refreshControls()
|
||||
// Sync view visibility with the setting
|
||||
if (newState) {
|
||||
showOverlay()
|
||||
} else {
|
||||
hideOverlay()
|
||||
}
|
||||
NativeConfig.saveGlobalConfig()
|
||||
true
|
||||
}
|
||||
@@ -1907,7 +1901,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
|
||||
companion object {
|
||||
fun fromValue(value: Int): AmiiboState =
|
||||
entries.firstOrNull { it.value == value } ?: Disabled
|
||||
values().firstOrNull { it.value == value } ?: Disabled
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1920,7 +1914,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
|
||||
companion object {
|
||||
fun fromValue(value: Int): AmiiboLoadResult =
|
||||
entries.firstOrNull { it.value == value } ?: Unknown
|
||||
values().firstOrNull { it.value == value } ?: Unknown
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1977,8 +1971,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
fun showOverlay() {
|
||||
if (!isOverlayVisible) {
|
||||
isOverlayVisible = true
|
||||
// Reset controller input flag so controller can hide overlay again
|
||||
controllerInputReceived = false
|
||||
ViewUtils.showView(binding.surfaceInputOverlay, 500)
|
||||
}
|
||||
}
|
||||
@@ -1986,26 +1978,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
private fun hideOverlay() {
|
||||
if (isOverlayVisible) {
|
||||
isOverlayVisible = false
|
||||
ViewUtils.hideView(binding.surfaceInputOverlay)
|
||||
ViewUtils.hideView(binding.surfaceInputOverlay, 500)
|
||||
}
|
||||
}
|
||||
|
||||
fun onControllerInputDetected() {
|
||||
if (!BooleanSetting.HIDE_OVERLAY_ON_CONTROLLER_INPUT.getBoolean()) return
|
||||
if (!BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()) return
|
||||
if (controllerInputReceived) return
|
||||
controllerInputReceived = true
|
||||
hideOverlay()
|
||||
}
|
||||
|
||||
fun onControllerConnected() {
|
||||
controllerInputReceived = false
|
||||
}
|
||||
|
||||
fun onControllerDisconnected() {
|
||||
if (!BooleanSetting.HIDE_OVERLAY_ON_CONTROLLER_INPUT.getBoolean()) return
|
||||
if (!BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()) return
|
||||
controllerInputReceived = false
|
||||
showOverlay()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,11 +92,6 @@ namespace AndroidSettings {
|
||||
Settings::Setting<u32> input_overlay_auto_hide{linkage, 5, "input_overlay_auto_hide",
|
||||
Settings::Category::Overlay,
|
||||
Settings::Specialization::Default, true, true, &enable_input_overlay_auto_hide};
|
||||
Settings::Setting<bool> hide_overlay_on_controller_input{linkage, false,
|
||||
"hide_overlay_on_controller_input",
|
||||
Settings::Category::Overlay,
|
||||
Settings::Specialization::Default, true,
|
||||
true};
|
||||
Settings::Setting<bool> perf_overlay_background{linkage, false, "perf_overlay_background",
|
||||
Settings::Category::Overlay,
|
||||
Settings::Specialization::Default, true,
|
||||
|
||||
@@ -220,12 +220,12 @@
|
||||
app:iconPadding="0dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/button_stoat"
|
||||
android:id="@+id/button_revolt"
|
||||
style="@style/EdenButton.Secondary"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
app:icon="@drawable/ic_stoat"
|
||||
app:icon="@drawable/ic_revolt"
|
||||
app:iconGravity="textStart"
|
||||
app:iconSize="24dp"
|
||||
app:iconPadding="0dp" />
|
||||
@@ -270,4 +270,4 @@
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -215,12 +215,12 @@
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
style="@style/EdenButton.Secondary"
|
||||
android:id="@+id/button_stoat"
|
||||
android:id="@+id/button_revolt"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="8dp"
|
||||
app:icon="@drawable/ic_stoat"
|
||||
app:icon="@drawable/ic_revolt"
|
||||
app:iconSize="24dp"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="0dp" />
|
||||
@@ -235,7 +235,7 @@
|
||||
app:icon="@drawable/ic_x"
|
||||
app:iconSize="24dp"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="0dp" />
|
||||
app:iconPadding="0dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
style="@style/EdenButton.Secondary"
|
||||
@@ -267,4 +267,4 @@
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -26,8 +26,6 @@
|
||||
<string name="overlay_auto_hide">Overlay Auto Hide</string>
|
||||
<string name="overlay_auto_hide_description">Automatically hide the touch controls overlay after the specified time of inactivity.</string>
|
||||
<string name="enable_input_overlay_auto_hide">Enable Overlay Auto Hide</string>
|
||||
<string name="hide_overlay_on_controller_input">Hide Overlay on Controller Input</string>
|
||||
<string name="hide_overlay_on_controller_input_description">Automatically hide the touch controls overlay when a physical controller is used. Overlay reappears when controller is disconnected.</string>
|
||||
|
||||
<string name="input_overlay_options">Input Overlay</string>
|
||||
<string name="input_overlay_options_description">Configure on-screen controls</string>
|
||||
@@ -447,7 +445,7 @@
|
||||
<string name="user_data_export_cancelled">Export cancelled</string>
|
||||
<string name="user_data_import_failed_description">Make sure the user data folders are at the root of the zip folder and contain a config file at config/config.ini and try again.</string>
|
||||
<string name="discord_link" translatable="false">https://discord.gg/HstXbPch7X</string>
|
||||
<string name="stoat_link" translatable="false">https://stt.gg/qKgFEAbH</string>
|
||||
<string name="revolt_link" translatable="false">https://rvlt.gg/qKgFEAbH</string>
|
||||
<string name="x_link" translatable="false">https://nitter.poast.org/edenemuofficial</string>
|
||||
<string name="website_link" translatable="false">https://eden-emu.dev</string>
|
||||
<string name="github_link" translatable="false">https://git.eden-emu.dev/eden-emu</string>
|
||||
|
||||
@@ -17,8 +17,6 @@ add_library(core STATIC
|
||||
constants.h
|
||||
core.cpp
|
||||
core.h
|
||||
game_settings.cpp
|
||||
game_settings.h
|
||||
core_timing.cpp
|
||||
core_timing.h
|
||||
cpu_manager.cpp
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "game_settings.h"
|
||||
#include "audio_core/audio_core.h"
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/logging/log.h"
|
||||
@@ -293,6 +292,48 @@ struct System::Impl {
|
||||
return SystemResultStatus::Success;
|
||||
}
|
||||
|
||||
|
||||
void LoadOverrides(u64 programId) const {
|
||||
std::string vendor = gpu_core->Renderer().GetDeviceVendor();
|
||||
LOG_INFO(Core, "GPU Vendor: {}", vendor);
|
||||
|
||||
// Reset all per-game flags
|
||||
Settings::values.use_squashed_iterated_blend = false;
|
||||
|
||||
// Insert PC overrides here
|
||||
|
||||
#ifdef ANDROID
|
||||
// Example on how to set a setting based on the program ID and vendor
|
||||
if (programId == 0x010028600EBDA000 && vendor == "Mali") { // Mario 3d World
|
||||
// Settings::values.example = true;
|
||||
}
|
||||
|
||||
// Example array of program IDs
|
||||
const std::array<u64, 10> example_array = {
|
||||
//0xprogramId
|
||||
0x0004000000033400, // Game 1
|
||||
0x0004000000033500 // Game 2
|
||||
// And so on
|
||||
};
|
||||
|
||||
for (auto id : example_array) {
|
||||
if (programId == id) {
|
||||
// Settings::values.example = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Ninja Gaiden Ragebound
|
||||
constexpr u64 ngr = 0x0100781020710000ULL;
|
||||
|
||||
if (programId == ngr) {
|
||||
LOG_INFO(Core, "Enabling game specifc override: use_squashed_iterated_blend");
|
||||
Settings::values.use_squashed_iterated_blend = true;
|
||||
}
|
||||
}
|
||||
|
||||
SystemResultStatus Load(System& system, Frontend::EmuWindow& emu_window,
|
||||
const std::string& filepath,
|
||||
Service::AM::FrontendAppletParameters& params) {
|
||||
@@ -378,8 +419,7 @@ struct System::Impl {
|
||||
LOG_ERROR(Core, "Failed to find program id for ROM");
|
||||
}
|
||||
|
||||
|
||||
GameSettings::LoadOverrides(program_id, gpu_core->Renderer());
|
||||
LoadOverrides(program_id);
|
||||
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||
Network::GameInfo game_info;
|
||||
game_info.name = name;
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "core/game_settings.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <TargetConditionals.h>
|
||||
#endif
|
||||
|
||||
namespace Core::GameSettings {
|
||||
|
||||
static GPUVendor GetGPU(const std::string& gpu_vendor_string) {
|
||||
struct Entry { const char* name; GPUVendor vendor; };
|
||||
static constexpr Entry GpuVendor[] = {
|
||||
// NVIDIA
|
||||
{"NVIDIA", GPUVendor::Nvidia},
|
||||
{"Nouveau", GPUVendor::Nvidia},
|
||||
{"NVK", GPUVendor::Nvidia},
|
||||
{"Tegra", GPUVendor::Nvidia},
|
||||
// AMD
|
||||
{"AMD", GPUVendor::AMD},
|
||||
{"RadeonSI", GPUVendor::AMD},
|
||||
{"RADV", GPUVendor::AMD},
|
||||
{"AMDVLK", GPUVendor::AMD},
|
||||
{"R600", GPUVendor::AMD},
|
||||
// Intel
|
||||
{"Intel", GPUVendor::Intel},
|
||||
{"ANV", GPUVendor::Intel},
|
||||
{"i965", GPUVendor::Intel},
|
||||
{"i915", GPUVendor::Intel},
|
||||
{"OpenSWR", GPUVendor::Intel},
|
||||
// Apple
|
||||
{"Apple", GPUVendor::Apple},
|
||||
{"MoltenVK", GPUVendor::Apple},
|
||||
// Qualcomm / Adreno
|
||||
{"Qualcomm", GPUVendor::Qualcomm},
|
||||
{"Turnip", GPUVendor::Qualcomm},
|
||||
// ARM / Mali
|
||||
{"Mali", GPUVendor::ARM},
|
||||
{"PanVK", GPUVendor::ARM},
|
||||
// Imagination / PowerVR
|
||||
{"PowerVR", GPUVendor::Imagination},
|
||||
{"PVR", GPUVendor::Imagination},
|
||||
// Microsoft / WARP / D3D12 GL
|
||||
{"D3D12", GPUVendor::Microsoft},
|
||||
{"Microsoft", GPUVendor::Microsoft},
|
||||
{"WARP", GPUVendor::Microsoft},
|
||||
};
|
||||
|
||||
for (const auto& entry : GpuVendor) {
|
||||
if (gpu_vendor_string == entry.name) {
|
||||
return entry.vendor;
|
||||
}
|
||||
}
|
||||
|
||||
// legacy (shouldn't be needed anymore, but just in case)
|
||||
std::string gpu = gpu_vendor_string;
|
||||
std::transform(gpu.begin(), gpu.end(), gpu.begin(), [](unsigned char c){ return (char)std::tolower(c); });
|
||||
if (gpu.find("geforce") != std::string::npos) {
|
||||
return GPUVendor::Nvidia;
|
||||
}
|
||||
if (gpu.find("radeon") != std::string::npos || gpu.find("ati") != std::string::npos) {
|
||||
return GPUVendor::AMD;
|
||||
}
|
||||
|
||||
return GPUVendor::Unknown;
|
||||
}
|
||||
|
||||
static OS DetectOS() {
|
||||
#if defined(_WIN32)
|
||||
return OS::Windows;
|
||||
#elif defined(__FIREOS__)
|
||||
return OS::FireOS;
|
||||
#elif defined(__ANDROID__)
|
||||
return OS::Android;
|
||||
#elif defined(__OHOS__)
|
||||
return OS::HarmonyOS;
|
||||
#elif defined(__HAIKU__)
|
||||
return OS::HaikuOS;
|
||||
#elif defined(__DragonFly__)
|
||||
return OS::DragonFlyBSD;
|
||||
#elif defined(__NetBSD__)
|
||||
return OS::NetBSD;
|
||||
#elif defined(__OpenBSD__)
|
||||
return OS::OpenBSD;
|
||||
#elif defined(_AIX)
|
||||
return OS::AIX;
|
||||
#elif defined(__managarm__)
|
||||
return OS::Managarm;
|
||||
#elif defined(__redox__)
|
||||
return OS::RedoxOS;
|
||||
#elif defined(__APPLE__) && defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
|
||||
return OS::IOS;
|
||||
#elif defined(__APPLE__)
|
||||
return OS::MacOS;
|
||||
#elif defined(__FreeBSD__)
|
||||
return OS::FreeBSD;
|
||||
#elif defined(__sun) && defined(__SVR4)
|
||||
return OS::Solaris;
|
||||
#elif defined(__linux__)
|
||||
return OS::Linux;
|
||||
#else
|
||||
return OS::Unknown;
|
||||
#endif
|
||||
}
|
||||
|
||||
EnvironmentInfo DetectEnvironment(const VideoCore::RendererBase& renderer) {
|
||||
EnvironmentInfo env{};
|
||||
env.os = DetectOS();
|
||||
env.vendor_string = renderer.GetDeviceVendor();
|
||||
env.vendor = GetGPU(env.vendor_string);
|
||||
return env;
|
||||
}
|
||||
|
||||
void LoadOverrides(std::uint64_t program_id, const VideoCore::RendererBase& renderer) {
|
||||
const auto env = DetectEnvironment(renderer);
|
||||
|
||||
switch (static_cast<TitleID>(program_id)) {
|
||||
case TitleID::NinjaGaidenRagebound:
|
||||
Settings::values.use_squashed_iterated_blend = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
LOG_INFO(Core, "Applied game settings for title ID {:016X} on OS {}, GPU vendor {} ({})",
|
||||
program_id,
|
||||
static_cast<int>(env.os),
|
||||
static_cast<int>(env.vendor),
|
||||
env.vendor_string);
|
||||
}
|
||||
|
||||
} // namespace Core::GameSettings
|
||||
@@ -1,60 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace VideoCore { class RendererBase; }
|
||||
|
||||
namespace Core::GameSettings {
|
||||
|
||||
enum class OS {
|
||||
Windows,
|
||||
Linux,
|
||||
MacOS,
|
||||
IOS,
|
||||
Android,
|
||||
FireOS,
|
||||
HarmonyOS,
|
||||
FreeBSD,
|
||||
DragonFlyBSD,
|
||||
NetBSD,
|
||||
OpenBSD,
|
||||
HaikuOS,
|
||||
AIX,
|
||||
Managarm,
|
||||
RedoxOS,
|
||||
Solaris,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
enum class GPUVendor {
|
||||
Nvidia,
|
||||
AMD,
|
||||
Intel,
|
||||
Apple,
|
||||
Qualcomm,
|
||||
ARM,
|
||||
Imagination,
|
||||
Microsoft,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
enum class TitleID : std::uint64_t {
|
||||
NinjaGaidenRagebound = 0x0100781020710000ULL
|
||||
};
|
||||
|
||||
struct EnvironmentInfo {
|
||||
OS os{OS::Unknown};
|
||||
GPUVendor vendor{GPUVendor::Unknown};
|
||||
std::string vendor_string; // raw string from driver
|
||||
};
|
||||
|
||||
EnvironmentInfo DetectEnvironment(const VideoCore::RendererBase& renderer);
|
||||
|
||||
void LoadOverrides(std::uint64_t program_id, const VideoCore::RendererBase& renderer);
|
||||
|
||||
} // namespace Core::GameSettings
|
||||
@@ -36,6 +36,10 @@ void DisplayLayerManager::Initialize(Core::System& system, Kernel::KProcess* pro
|
||||
m_buffer_sharing_enabled = false;
|
||||
m_blending_enabled = mode == LibraryAppletMode::PartialForeground ||
|
||||
mode == LibraryAppletMode::PartialForegroundIndirectDisplay;
|
||||
|
||||
if (m_applet_id != AppletId::Application) {
|
||||
(void)this->IsSystemBufferSharingEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayLayerManager::Finalize() {
|
||||
@@ -76,10 +80,11 @@ Result DisplayLayerManager::CreateManagedDisplayLayer(u64* out_layer_id) {
|
||||
if (m_applet_id != AppletId::Application) {
|
||||
(void)m_manager_display_service->SetLayerBlending(m_blending_enabled, *out_layer_id);
|
||||
if (m_applet_id == AppletId::OverlayDisplay) {
|
||||
(void)m_manager_display_service->SetLayerZIndex(-1, *out_layer_id);
|
||||
(void)m_display_service->GetContainer()->SetLayerIsOverlay(*out_layer_id, true);
|
||||
static constexpr s32 kOverlayBackgroundZ = -1;
|
||||
(void)m_manager_display_service->SetLayerZIndex(kOverlayBackgroundZ, *out_layer_id);
|
||||
} else {
|
||||
(void)m_manager_display_service->SetLayerZIndex(1, *out_layer_id);
|
||||
static constexpr s32 kOverlayZ = 3;
|
||||
(void)m_manager_display_service->SetLayerZIndex(kOverlayZ, *out_layer_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +131,6 @@ Result DisplayLayerManager::IsSystemBufferSharingEnabled() {
|
||||
s32 initial_z = 1;
|
||||
if (m_applet_id == AppletId::OverlayDisplay) {
|
||||
initial_z = -1;
|
||||
(void)m_display_service->GetContainer()->SetLayerIsOverlay(m_system_shared_layer_id, true);
|
||||
}
|
||||
m_manager_display_service->SetLayerZIndex(initial_z, m_system_shared_layer_id);
|
||||
R_SUCCEED();
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Service::AM {
|
||||
{20, D<&IOverlayFunctions::SetHandlingHomeButtonShortPressedEnabled>, "SetHandlingHomeButtonShortPressedEnabled"},
|
||||
{21, nullptr, "SetHandlingTouchScreenInputEnabled"},
|
||||
{30, nullptr, "SetHealthWarningShowingState"},
|
||||
{31, D<&IOverlayFunctions::IsHealthWarningRequired>, "IsHealthWarningRequired"},
|
||||
{31, nullptr, "IsHealthWarningRequired"},
|
||||
{40, nullptr, "GetApplicationNintendoLogo"},
|
||||
{41, nullptr, "GetApplicationStartupMovie"},
|
||||
{50, nullptr, "SetGpuTimeSliceBoostForApplication"},
|
||||
@@ -69,33 +69,12 @@ namespace Service::AM {
|
||||
Result IOverlayFunctions::GetApplicationIdForLogo(Out<u64> out_application_id) {
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
|
||||
std::shared_ptr<Applet> target_applet;
|
||||
|
||||
auto* window_system = system.GetAppletManager().GetWindowSystem();
|
||||
if (window_system) {
|
||||
target_applet = window_system->GetMainApplet();
|
||||
if (target_applet) {
|
||||
std::scoped_lock lk{target_applet->lock};
|
||||
LOG_DEBUG(Service_AM, "applet_id={}, program_id={:016X}, type={}",
|
||||
static_cast<u32>(target_applet->applet_id), target_applet->program_id,
|
||||
static_cast<u32>(target_applet->type));
|
||||
|
||||
u64 id = target_applet->screen_shot_identity.application_id;
|
||||
if (id == 0) {
|
||||
id = target_applet->program_id;
|
||||
}
|
||||
LOG_DEBUG(Service_AM, "application_id={:016X}", id);
|
||||
*out_application_id = id;
|
||||
R_SUCCEED();
|
||||
}
|
||||
}
|
||||
|
||||
// Prefer explicit application_id if available, else fall back to program_id
|
||||
std::scoped_lock lk{m_applet->lock};
|
||||
u64 id = m_applet->screen_shot_identity.application_id;
|
||||
if (id == 0) {
|
||||
id = m_applet->program_id;
|
||||
}
|
||||
LOG_DEBUG(Service_AM, "application_id={:016X} (fallback)", id);
|
||||
*out_application_id = id;
|
||||
R_SUCCEED();
|
||||
}
|
||||
@@ -107,13 +86,6 @@ namespace Service::AM {
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IOverlayFunctions::IsHealthWarningRequired(Out<bool> is_required) {
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
std::scoped_lock lk{m_applet->lock};
|
||||
*is_required = false;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IOverlayFunctions::SetHandlingHomeButtonShortPressedEnabled(bool enabled) {
|
||||
LOG_DEBUG(Service_AM, "called, enabled={}", enabled);
|
||||
std::scoped_lock lk{m_applet->lock};
|
||||
|
||||
@@ -18,7 +18,6 @@ namespace Service::AM {
|
||||
Result EndToWatchShortHomeButtonMessage();
|
||||
Result GetApplicationIdForLogo(Out<u64> out_application_id);
|
||||
Result SetAutoSleepTimeAndDimmingTimeEnabled(bool enabled);
|
||||
Result IsHealthWarningRequired(Out<bool> is_required);
|
||||
Result SetHandlingHomeButtonShortPressedEnabled(bool enabled);
|
||||
Result Unknown70();
|
||||
|
||||
|
||||
@@ -186,6 +186,8 @@ void WindowSystem::OnSystemButtonPress(SystemButtonType type) {
|
||||
if (m_overlay_display) {
|
||||
std::scoped_lock lk_overlay{m_overlay_display->lock};
|
||||
m_overlay_display->overlay_in_foreground = !m_overlay_display->overlay_in_foreground;
|
||||
// Tie window visibility to foreground state so hidden when not active
|
||||
m_overlay_display->window_visible = m_overlay_display->overlay_in_foreground;
|
||||
LOG_INFO(Service_AM, "Overlay long-press toggle: overlay_in_foreground={} window_visible={}", m_overlay_display->overlay_in_foreground, m_overlay_display->window_visible);
|
||||
}
|
||||
SendButtonAppletMessageLocked(AppletMessage::DetectLongPressingHomeButton);
|
||||
@@ -391,7 +393,7 @@ void WindowSystem::UpdateAppletStateLocked(Applet* applet, bool is_foreground, b
|
||||
s32 z_index = 0;
|
||||
const bool now_foreground = inherited_foreground;
|
||||
if (applet->applet_id == AppletId::OverlayDisplay) {
|
||||
z_index = applet->overlay_in_foreground ? 100000 : -1;
|
||||
z_index = applet->overlay_in_foreground ? 100000 : -100000;
|
||||
} else if (now_foreground && !is_obscured) {
|
||||
z_index = 2;
|
||||
} else if (now_foreground) {
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -70,7 +67,7 @@ public:
|
||||
{20701, &IFriendService::GetPlayHistoryStatistics, "GetPlayHistoryStatistics"},
|
||||
{20800, &IFriendService::LoadUserSetting, "LoadUserSetting"},
|
||||
{20801, nullptr, "SyncUserSetting"},
|
||||
{20900, &IFriendService::RequestListSummaryOverlayNotification, "RequestListSummaryOverlayNotification"},
|
||||
{20900, nullptr, "RequestListSummaryOverlayNotification"},
|
||||
{21000, nullptr, "GetExternalApplicationCatalog"},
|
||||
{22000, nullptr, "GetReceivedFriendInvitationList"},
|
||||
{22001, nullptr, "GetReceivedFriendInvitationDetailedInfo"},
|
||||
@@ -320,13 +317,6 @@ private:
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
||||
void RequestListSummaryOverlayNotification(HLERequestContext& ctx) {
|
||||
LOG_INFO(Service_Friend, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
||||
void GetReceivedFriendInvitationCountCache(HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_Friend, "(STUBBED) called, check in out");
|
||||
|
||||
|
||||
@@ -10,8 +10,6 @@
|
||||
#include "core/hle/service/cmif_serialization.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/hle/service/ns/application_manager_interface.h"
|
||||
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/hle/service/ns/content_management_interface.h"
|
||||
#include "core/hle/service/ns/read_only_application_control_data_interface.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
@@ -56,7 +54,7 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_
|
||||
{37, nullptr, "ListRequiredVersion"},
|
||||
{38, D<&IApplicationManagerInterface::CheckApplicationLaunchVersion>, "CheckApplicationLaunchVersion"},
|
||||
{39, nullptr, "CheckApplicationLaunchRights"},
|
||||
{40, D<&IApplicationManagerInterface::GetApplicationLogoData>, "GetApplicationLogoData"},
|
||||
{40, nullptr, "GetApplicationLogoData"},
|
||||
{41, nullptr, "CalculateApplicationDownloadRequiredSize"},
|
||||
{42, nullptr, "CleanupSdCard"},
|
||||
{43, D<&IApplicationManagerInterface::CheckSdCardMountStatus>, "CheckSdCardMountStatus"},
|
||||
@@ -333,53 +331,6 @@ Result IApplicationManagerInterface::UnregisterNetworkServiceAccountWithUserSave
|
||||
LOG_DEBUG(Service_NS, "called, user_id={}", user_id.FormattedString());
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IApplicationManagerInterface::GetApplicationLogoData(
|
||||
Out<s64> out_size, OutBuffer<BufferAttr_HipcMapAlias> out_buffer, u64 application_id,
|
||||
InBuffer<BufferAttr_HipcMapAlias> logo_path_buffer) {
|
||||
const std::string path_view{reinterpret_cast<const char*>(logo_path_buffer.data()),
|
||||
logo_path_buffer.size()};
|
||||
|
||||
// Find null terminator and trim the path
|
||||
auto null_pos = path_view.find('\0');
|
||||
std::string path = (null_pos != std::string::npos) ? path_view.substr(0, null_pos) : path_view;
|
||||
|
||||
LOG_DEBUG(Service_NS, "called, application_id={:016X}, logo_path={}", application_id, path);
|
||||
|
||||
auto& content_provider = system.GetContentProviderUnion();
|
||||
|
||||
auto program = content_provider.GetEntry(application_id, FileSys::ContentRecordType::Program);
|
||||
if (!program) {
|
||||
LOG_WARNING(Service_NS, "Application program not found for id={:016X}", application_id);
|
||||
R_RETURN(ResultUnknown);
|
||||
}
|
||||
|
||||
const auto logo_dir = program->GetLogoPartition();
|
||||
if (!logo_dir) {
|
||||
LOG_WARNING(Service_NS, "Logo partition not found for id={:016X}", application_id);
|
||||
R_RETURN(ResultUnknown);
|
||||
}
|
||||
|
||||
const auto file = logo_dir->GetFile(path);
|
||||
if (!file) {
|
||||
LOG_WARNING(Service_NS, "Logo path not found: {} for id={:016X}", path,
|
||||
application_id);
|
||||
R_RETURN(ResultUnknown);
|
||||
}
|
||||
|
||||
const auto data = file->ReadAllBytes();
|
||||
if (data.size() > out_buffer.size()) {
|
||||
LOG_WARNING(Service_NS, "Logo buffer too small: have={}, need={}", out_buffer.size(),
|
||||
data.size());
|
||||
R_RETURN(ResultUnknown);
|
||||
}
|
||||
|
||||
std::memcpy(out_buffer.data(), data.data(), data.size());
|
||||
*out_size = static_cast<s64>(data.size());
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IApplicationManagerInterface::GetApplicationControlData(
|
||||
OutBuffer<BufferAttr_HipcMapAlias> out_buffer, Out<u32> out_actual_size,
|
||||
ApplicationControlSource application_control_source, u64 application_id) {
|
||||
|
||||
@@ -60,10 +60,6 @@ public:
|
||||
u64 application_id);
|
||||
Result CheckApplicationLaunchVersion(u64 application_id);
|
||||
Result GetApplicationTerminateResult(Out<Result> out_result, u64 application_id);
|
||||
Result GetApplicationLogoData(Out<s64> out_size,
|
||||
OutBuffer<BufferAttr_HipcMapAlias> out_buffer,
|
||||
u64 application_id,
|
||||
InBuffer<BufferAttr_HipcMapAlias> logo_path_buffer);
|
||||
Result Unknown4022(OutCopyHandle<Kernel::KReadableEvent> out_event);
|
||||
Result Unknown4023(Out<u64> out_result);
|
||||
Result Unknown4053();
|
||||
|
||||
@@ -179,9 +179,4 @@ struct ApplicationDisplayData {
|
||||
};
|
||||
static_assert(sizeof(ApplicationDisplayData) == 0x300, "ApplicationDisplayData has incorrect size.");
|
||||
|
||||
struct LogoPath {
|
||||
std::array<char, 0x300> path;
|
||||
};
|
||||
static_assert(std::is_trivially_copyable_v<LogoPath>, "LogoPath must be trivially copyable.");
|
||||
|
||||
} // namespace Service::NS
|
||||
|
||||
@@ -15,7 +15,7 @@ struct Layer {
|
||||
explicit Layer(std::shared_ptr<android::BufferItemConsumer> buffer_item_consumer_,
|
||||
s32 consumer_id_)
|
||||
: buffer_item_consumer(std::move(buffer_item_consumer_)), consumer_id(consumer_id_),
|
||||
blending(LayerBlending::None), visible(true), z_index(0), is_overlay(false) {}
|
||||
blending(LayerBlending::None), visible(true), z_index(0) {}
|
||||
~Layer() {
|
||||
buffer_item_consumer->Abandon();
|
||||
}
|
||||
@@ -25,7 +25,6 @@ struct Layer {
|
||||
LayerBlending blending;
|
||||
bool visible;
|
||||
s32 z_index;
|
||||
bool is_overlay;
|
||||
};
|
||||
|
||||
struct LayerStack {
|
||||
|
||||
@@ -78,25 +78,8 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display,
|
||||
for (auto& layer : display.stack.layers) {
|
||||
auto consumer_id = layer->consumer_id;
|
||||
|
||||
bool should_try_acquire = true;
|
||||
if (!layer->is_overlay) {
|
||||
auto fb_it = m_framebuffers.find(consumer_id);
|
||||
if (fb_it != m_framebuffers.end() && fb_it->second.is_acquired) {
|
||||
const u64 frames_since_last_acquire = m_frame_number - fb_it->second.last_acquire_frame;
|
||||
const s32 expected_interval = NormalizeSwapInterval(nullptr, fb_it->second.item.swap_interval);
|
||||
|
||||
if (frames_since_last_acquire < static_cast<u64>(expected_interval)) {
|
||||
should_try_acquire = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to fetch the framebuffer (either new or stale).
|
||||
const auto result = should_try_acquire
|
||||
? this->CacheFramebufferLocked(*layer, consumer_id)
|
||||
: (m_framebuffers.find(consumer_id) != m_framebuffers.end() && m_framebuffers[consumer_id].is_acquired
|
||||
? CacheStatus::CachedBufferReused
|
||||
: CacheStatus::NoBufferAvailable);
|
||||
const auto result = this->CacheFramebufferLocked(*layer, consumer_id);
|
||||
|
||||
// If we failed, skip this layer.
|
||||
if (result == CacheStatus::NoBufferAvailable) {
|
||||
@@ -128,12 +111,6 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display,
|
||||
});
|
||||
}
|
||||
|
||||
// Overlay layers run at their own framerate independently of the game.
|
||||
// Skip them when calculating the swap interval for the main game.
|
||||
if (layer->is_overlay) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We need to compose again either before this frame is supposed to
|
||||
// be released, or exactly on the vsync period it should be released.
|
||||
const s32 item_swap_interval = NormalizeSwapInterval(out_speed_scale, item.swap_interval);
|
||||
@@ -161,44 +138,33 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display,
|
||||
// Batch framebuffer releases, instead of one-into-one.
|
||||
std::vector<std::pair<Layer*, Framebuffer*>> to_release;
|
||||
for (auto& [layer_id, framebuffer] : m_framebuffers) {
|
||||
if (!framebuffer.is_acquired)
|
||||
if (framebuffer.release_frame_number > m_frame_number || !framebuffer.is_acquired)
|
||||
continue;
|
||||
|
||||
auto layer = display.stack.FindLayer(layer_id);
|
||||
if (!layer)
|
||||
continue;
|
||||
|
||||
// Overlay layers always release after every compose
|
||||
// Non-overlay layers release based on their swap interval
|
||||
if (layer->is_overlay || framebuffer.release_frame_number <= m_frame_number) {
|
||||
if (auto layer = display.stack.FindLayer(layer_id); layer)
|
||||
to_release.emplace_back(layer.get(), &framebuffer);
|
||||
}
|
||||
}
|
||||
for (auto& [layer, framebuffer] : to_release) {
|
||||
layer->buffer_item_consumer->ReleaseBuffer(framebuffer->item, android::Fence::NoFence());
|
||||
framebuffer->is_acquired = false;
|
||||
}
|
||||
|
||||
// Advance by 1 frame (60 FPS compositing)
|
||||
m_frame_number += 1;
|
||||
// Advance by at least one frame.
|
||||
const u32 frame_advance = swap_interval.value_or(1);
|
||||
m_frame_number += frame_advance;
|
||||
|
||||
// Release any necessary framebuffers (non-overlay layers only, as overlays are already released above).
|
||||
// Release any necessary framebuffers.
|
||||
for (auto& [layer_id, framebuffer] : m_framebuffers) {
|
||||
if (framebuffer.release_frame_number > m_frame_number) {
|
||||
// Not yet ready to release this framebuffer.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!framebuffer.is_acquired) {
|
||||
// Already released.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (framebuffer.release_frame_number > m_frame_number) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (const auto layer = display.stack.FindLayer(layer_id); layer != nullptr) {
|
||||
// Skip overlay layers as they were already released above
|
||||
if (layer->is_overlay) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: support release fence
|
||||
// This is needed to prevent screen tearing
|
||||
layer->buffer_item_consumer->ReleaseBuffer(framebuffer.item, android::Fence::NoFence());
|
||||
@@ -206,7 +172,7 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display,
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
return frame_advance;
|
||||
}
|
||||
|
||||
void HardwareComposer::RemoveLayerLocked(Display& display, ConsumerId consumer_id) {
|
||||
@@ -234,9 +200,8 @@ bool HardwareComposer::TryAcquireFramebufferLocked(Layer& layer, Framebuffer& fr
|
||||
}
|
||||
|
||||
// We succeeded, so set the new release frame info.
|
||||
const s32 swap_interval = layer.is_overlay ? 1 : NormalizeSwapInterval(nullptr, framebuffer.item.swap_interval);
|
||||
framebuffer.release_frame_number = m_frame_number + swap_interval;
|
||||
framebuffer.last_acquire_frame = m_frame_number;
|
||||
framebuffer.release_frame_number =
|
||||
NormalizeSwapInterval(nullptr, framebuffer.item.swap_interval);
|
||||
framebuffer.is_acquired = true;
|
||||
|
||||
return true;
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
@@ -37,7 +34,6 @@ private:
|
||||
struct Framebuffer {
|
||||
android::BufferItem item{};
|
||||
ReleaseFrameNumber release_frame_number{};
|
||||
u64 last_acquire_frame{0};
|
||||
bool is_acquired{false};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -104,13 +101,6 @@ void SurfaceFlinger::SetLayerBlending(s32 consumer_binder_id, LayerBlending blen
|
||||
}
|
||||
}
|
||||
|
||||
void SurfaceFlinger::SetLayerIsOverlay(s32 consumer_binder_id, bool is_overlay) {
|
||||
if (const auto layer = this->FindLayer(consumer_binder_id); layer != nullptr) {
|
||||
layer->is_overlay = is_overlay;
|
||||
LOG_DEBUG(Service_VI, "Layer {} marked as overlay: {}", consumer_binder_id, is_overlay);
|
||||
}
|
||||
}
|
||||
|
||||
Display* SurfaceFlinger::FindDisplay(u64 display_id) {
|
||||
for (auto& display : m_displays) {
|
||||
if (display.id == display_id) {
|
||||
|
||||
@@ -47,7 +47,6 @@ public:
|
||||
|
||||
void SetLayerVisibility(s32 consumer_binder_id, bool visible);
|
||||
void SetLayerBlending(s32 consumer_binder_id, LayerBlending blending);
|
||||
void SetLayerIsOverlay(s32 consumer_binder_id, bool is_overlay);
|
||||
|
||||
std::shared_ptr<Layer> FindLayer(s32 consumer_binder_id);
|
||||
|
||||
|
||||
@@ -36,29 +36,6 @@ struct SettingsHeader {
|
||||
u32 version;
|
||||
u32 reserved;
|
||||
};
|
||||
|
||||
void SyncGlobalLanguageFromCode(LanguageCode language_code) {
|
||||
const auto it = std::find_if(available_language_codes.begin(), available_language_codes.end(),
|
||||
[language_code](LanguageCode code) { return code == language_code; });
|
||||
if (it == available_language_codes.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::size_t index = static_cast<std::size_t>(std::distance(available_language_codes.begin(), it));
|
||||
if (index >= static_cast<std::size_t>(Settings::values.language_index.GetValue())) {
|
||||
Settings::values.language_index.SetValue(static_cast<Settings::Language>(index));
|
||||
}
|
||||
}
|
||||
|
||||
void SyncGlobalRegionFromCode(SystemRegionCode region_code) {
|
||||
const auto region_index = static_cast<std::size_t>(region_code);
|
||||
if (region_index > static_cast<std::size_t>(Settings::Region::Taiwan)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Settings::values.region_index.SetValue(static_cast<Settings::Region>(region_index));
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
Result GetFirmwareVersionImpl(FirmwareVersionFormat& out_firmware, Core::System& system,
|
||||
@@ -480,7 +457,6 @@ Result ISystemSettingsServer::SetLanguageCode(LanguageCode language_code) {
|
||||
LOG_INFO(Service_SET, "called, language_code={}", language_code);
|
||||
|
||||
m_system_settings.language_code = language_code;
|
||||
SyncGlobalLanguageFromCode(language_code);
|
||||
SetSaveNeeded();
|
||||
R_SUCCEED();
|
||||
}
|
||||
@@ -913,7 +889,6 @@ Result ISystemSettingsServer::SetRegionCode(SystemRegionCode region_code) {
|
||||
LOG_INFO(Service_SET, "called, region_code={}", region_code);
|
||||
|
||||
m_system_settings.region_code = region_code;
|
||||
SyncGlobalRegionFromCode(region_code);
|
||||
SetSaveNeeded();
|
||||
R_SUCCEED();
|
||||
}
|
||||
@@ -1249,7 +1224,6 @@ Result ISystemSettingsServer::SetKeyboardLayout(KeyboardLayout keyboard_layout)
|
||||
LOG_INFO(Service_SET, "called, keyboard_layout={}", keyboard_layout);
|
||||
|
||||
m_system_settings.keyboard_layout = keyboard_layout;
|
||||
SetSaveNeeded();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
|
||||
@@ -578,7 +578,7 @@ std::pair<s32, Errno> BSD::PollImpl(std::vector<u8>& write_buffer, std::span<con
|
||||
}
|
||||
|
||||
std::vector<Network::PollFD> host_pollfds(fds.size());
|
||||
std::transform(fds.begin(), fds.end(), host_pollfds.begin(), [](PollFD pollfd) {
|
||||
std::transform(fds.begin(), fds.end(), host_pollfds.begin(), [this](PollFD pollfd) {
|
||||
Network::PollFD result;
|
||||
result.socket = file_descriptors[pollfd.fd]->socket.get();
|
||||
result.events = Translate(pollfd.events);
|
||||
@@ -657,11 +657,7 @@ Errno BSD::ConnectImpl(s32 fd, std::span<const u8> addr) {
|
||||
|
||||
const auto result = Translate(file_descriptors[fd]->socket->Connect(Translate(addr_in)));
|
||||
if (result != Errno::SUCCESS) {
|
||||
if (result == Errno::INPROGRESS || result == Errno::AGAIN) {
|
||||
LOG_DEBUG(Service, "Connect fd={} in progress (non-blocking), errno={}", fd, static_cast<int>(result));
|
||||
} else {
|
||||
LOG_ERROR(Service, "Connect fd={} failed with errno={}", fd, static_cast<int>(result));
|
||||
}
|
||||
LOG_ERROR(Service, "Connect fd={} failed with errno={}", fd, static_cast<int>(result));
|
||||
} else {
|
||||
LOG_INFO(Service, "Connect fd={} succeeded", fd);
|
||||
}
|
||||
@@ -971,11 +967,7 @@ Expected<s32, Errno> BSD::DuplicateSocketImpl(s32 fd) {
|
||||
return Unexpected(Errno::MFILE);
|
||||
}
|
||||
|
||||
file_descriptors[new_fd] = FileDescriptor{
|
||||
.socket = file_descriptors[fd]->socket,
|
||||
.flags = file_descriptors[fd]->flags,
|
||||
.is_connection_based = file_descriptors[fd]->is_connection_based,
|
||||
};
|
||||
file_descriptors[new_fd] = file_descriptors[fd];
|
||||
return new_fd;
|
||||
}
|
||||
|
||||
|
||||
@@ -179,7 +179,7 @@ private:
|
||||
|
||||
void BuildErrnoResponse(HLERequestContext& ctx, Errno bsd_errno) const noexcept;
|
||||
|
||||
static inline std::array<std::optional<FileDescriptor>, MAX_FD> file_descriptors{};
|
||||
std::array<std::optional<FileDescriptor>, MAX_FD> file_descriptors;
|
||||
|
||||
/// Callback to parse and handle a received wifi packet.
|
||||
void OnProxyPacketReceived(const Network::ProxyPacket& packet);
|
||||
|
||||
@@ -157,24 +157,22 @@ private:
|
||||
auto bsd = system.ServiceManager().GetService<Service::Sockets::BSD>("bsd:u");
|
||||
ASSERT_OR_EXECUTE(bsd, { return ResultInternalError; });
|
||||
|
||||
auto res = bsd->DuplicateSocketImpl(fd);
|
||||
if (!res.has_value()) {
|
||||
LOG_ERROR(Service_SSL, "Failed to duplicate socket with fd {}", fd);
|
||||
return ResultInvalidSocket;
|
||||
}
|
||||
|
||||
const s32 duplicated_fd = *res;
|
||||
|
||||
// Based on https://switchbrew.org/wiki/SSL_services#SetSocketDescriptor
|
||||
if (do_not_close_socket) {
|
||||
*out_fd = duplicated_fd;
|
||||
auto res = bsd->DuplicateSocketImpl(fd);
|
||||
if (!res.has_value()) {
|
||||
LOG_ERROR(Service_SSL, "Failed to duplicate socket with fd {}", fd);
|
||||
return ResultInvalidSocket;
|
||||
}
|
||||
fd = *res;
|
||||
fd_to_close = fd;
|
||||
*out_fd = fd;
|
||||
} else {
|
||||
*out_fd = -1;
|
||||
fd_to_close = duplicated_fd;
|
||||
}
|
||||
|
||||
std::optional<std::shared_ptr<Network::SocketBase>> sock = bsd->GetSocket(duplicated_fd);
|
||||
std::optional<std::shared_ptr<Network::SocketBase>> sock = bsd->GetSocket(fd);
|
||||
if (!sock.has_value()) {
|
||||
LOG_ERROR(Service_SSL, "invalid socket fd {} after duplication", duplicated_fd);
|
||||
LOG_ERROR(Service_SSL, "invalid socket fd {}", fd);
|
||||
return ResultInvalidSocket;
|
||||
}
|
||||
socket = std::move(*sock);
|
||||
@@ -327,19 +325,7 @@ private:
|
||||
res = backend->GetServerCerts(&certs);
|
||||
if (res == ResultSuccess) {
|
||||
const std::vector<u8> certs_buf = SerializeServerCerts(certs);
|
||||
if (ctx.CanWriteBuffer()) {
|
||||
const size_t buffer_size = ctx.GetWriteBufferSize();
|
||||
if (certs_buf.size() <= buffer_size) {
|
||||
ctx.WriteBuffer(certs_buf);
|
||||
} else {
|
||||
LOG_WARNING(Service_SSL, "Certificate buffer too small: {} bytes needed, {} bytes available",
|
||||
certs_buf.size(), buffer_size);
|
||||
ctx.WriteBuffer(std::span<const u8>(certs_buf.data(), buffer_size));
|
||||
}
|
||||
} else {
|
||||
LOG_DEBUG(Service_SSL, "No output buffer provided for certificates ({} bytes)", certs_buf.size());
|
||||
}
|
||||
|
||||
ctx.WriteBuffer(certs_buf);
|
||||
out.certs_count = static_cast<u32>(certs.size());
|
||||
out.certs_size = static_cast<u32>(certs_buf.size());
|
||||
}
|
||||
@@ -678,119 +664,119 @@ class ISslServiceForSystem final : public ServiceFramework<ISslServiceForSystem>
|
||||
{103, D<&ISslServiceForSystem::VerifySignature>, "VerifySignature"}
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
||||
RegisterHandlers(functions);
|
||||
};
|
||||
|
||||
|
||||
Result CreateContext() {
|
||||
LOG_DEBUG(Service_SSL, "(STUBBED) called.");
|
||||
|
||||
|
||||
// TODO (jarrodnorwell)
|
||||
|
||||
|
||||
return ResultSuccess;
|
||||
};
|
||||
|
||||
|
||||
Result GetContextCount() {
|
||||
LOG_DEBUG(Service_SSL, "(STUBBED) called.");
|
||||
|
||||
|
||||
// TODO (jarrodnorwell)
|
||||
|
||||
|
||||
return ResultSuccess;
|
||||
};
|
||||
|
||||
|
||||
Result GetCertificates() {
|
||||
LOG_DEBUG(Service_SSL, "(STUBBED) called.");
|
||||
|
||||
|
||||
// TODO (jarrodnorwell)
|
||||
|
||||
|
||||
return ResultSuccess;
|
||||
};
|
||||
|
||||
|
||||
Result GetCertificateBufSize() {
|
||||
LOG_DEBUG(Service_SSL, "(STUBBED) called.");
|
||||
|
||||
|
||||
// TODO (jarrodnorwell)
|
||||
|
||||
|
||||
return ResultSuccess;
|
||||
};
|
||||
|
||||
|
||||
Result DebugIoctl() {
|
||||
LOG_DEBUG(Service_SSL, "(STUBBED) called.");
|
||||
|
||||
|
||||
// TODO (jarrodnorwell)
|
||||
|
||||
|
||||
return ResultSuccess;
|
||||
};
|
||||
|
||||
|
||||
Result SetInterfaceVersion() {
|
||||
LOG_DEBUG(Service_SSL, "(STUBBED) called.");
|
||||
|
||||
|
||||
// TODO (jarrodnorwell)
|
||||
|
||||
|
||||
return ResultSuccess;
|
||||
};
|
||||
|
||||
|
||||
Result FlushSessionCache() {
|
||||
LOG_DEBUG(Service_SSL, "(STUBBED) called.");
|
||||
|
||||
|
||||
// TODO (jarrodnorwell)
|
||||
|
||||
|
||||
return ResultSuccess;
|
||||
};
|
||||
|
||||
|
||||
Result SetDebugOption() {
|
||||
LOG_DEBUG(Service_SSL, "(STUBBED) called.");
|
||||
|
||||
|
||||
// TODO (jarrodnorwell)
|
||||
|
||||
|
||||
return ResultSuccess;
|
||||
};
|
||||
|
||||
|
||||
Result GetDebugOption() {
|
||||
LOG_DEBUG(Service_SSL, "(STUBBED) called.");
|
||||
|
||||
|
||||
// TODO (jarrodnorwell)
|
||||
|
||||
|
||||
return ResultSuccess;
|
||||
};
|
||||
|
||||
|
||||
Result ClearTls12FallbackFlag() {
|
||||
LOG_DEBUG(Service_SSL, "(STUBBED) called.");
|
||||
|
||||
|
||||
// TODO (jarrodnorwell)
|
||||
|
||||
|
||||
return ResultSuccess;
|
||||
};
|
||||
|
||||
|
||||
Result CreateContextForSystem() {
|
||||
LOG_DEBUG(Service_SSL, "(STUBBED) called.");
|
||||
|
||||
|
||||
// TODO (jarrodnorwell)
|
||||
|
||||
|
||||
return ResultSuccess;
|
||||
};
|
||||
|
||||
|
||||
Result SetThreadCoreMask() {
|
||||
LOG_DEBUG(Service_SSL, "(STUBBED) called.");
|
||||
|
||||
|
||||
// TODO (jarrodnorwell)
|
||||
|
||||
|
||||
return ResultSuccess;
|
||||
};
|
||||
|
||||
|
||||
Result GetThreadCoreMask() {
|
||||
LOG_DEBUG(Service_SSL, "(STUBBED) called.");
|
||||
|
||||
|
||||
// TODO (jarrodnorwell)
|
||||
|
||||
|
||||
return ResultSuccess;
|
||||
};
|
||||
|
||||
|
||||
Result VerifySignature() {
|
||||
LOG_DEBUG(Service_SSL, "(STUBBED) called.");
|
||||
|
||||
|
||||
// TODO (jarrodnorwell)
|
||||
|
||||
|
||||
return ResultSuccess;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
||||
@@ -166,16 +166,6 @@ Result Container::GetLayerZIndex(u64 layer_id, s32* out_z_index) {
|
||||
R_RETURN(VI::ResultNotFound);
|
||||
}
|
||||
|
||||
Result Container::SetLayerIsOverlay(u64 layer_id, bool is_overlay) {
|
||||
std::scoped_lock lk{m_lock};
|
||||
|
||||
auto* const layer = m_layers.GetLayerById(layer_id);
|
||||
R_UNLESS(layer != nullptr, VI::ResultNotFound);
|
||||
|
||||
m_surface_flinger->SetLayerIsOverlay(layer->GetConsumerBinderId(), is_overlay);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void Container::LinkVsyncEvent(u64 display_id, Event* event) {
|
||||
std::scoped_lock lk{m_lock};
|
||||
m_conductor->LinkVsyncEvent(display_id, event);
|
||||
|
||||
@@ -67,7 +67,6 @@ public:
|
||||
Result SetLayerBlending(u64 layer_id, bool enabled);
|
||||
Result SetLayerZIndex(u64 layer_id, s32 z_index);
|
||||
Result GetLayerZIndex(u64 layer_id, s32* out_z_index);
|
||||
Result SetLayerIsOverlay(u64 layer_id, bool is_overlay);
|
||||
|
||||
void LinkVsyncEvent(u64 display_id, Event* event);
|
||||
void UnlinkVsyncEvent(u64 display_id, Event* event);
|
||||
|
||||
@@ -46,8 +46,8 @@ Result AllocateSharedBufferMemory(std::unique_ptr<Kernel::KPageGroup>* out_page_
|
||||
u32* start = system.DeviceMemory().GetPointer<u32>(block.GetAddress());
|
||||
u32* end = system.DeviceMemory().GetPointer<u32>(block.GetAddress() + block.GetSize());
|
||||
|
||||
for (; start < end; start++) {
|
||||
*start = 0xFF0000FF;
|
||||
for (; start < end; ++start) {
|
||||
*start = 0x00000000; // ARGB/RGBA with alpha=0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,6 +257,7 @@ Result SharedBufferManager::CreateSession(Kernel::KProcess* owner_process, u64*
|
||||
|
||||
// Configure blending and z-index
|
||||
R_ASSERT(m_container.SetLayerBlending(session.layer_id, enable_blending));
|
||||
R_ASSERT(m_container.SetLayerZIndex(session.layer_id, 100000));
|
||||
|
||||
// Get the producer and set preallocated buffers.
|
||||
std::shared_ptr<android::BufferQueueProducer> producer;
|
||||
@@ -373,6 +374,11 @@ Result SharedBufferManager::PresentSharedFrameBuffer(android::Fence fence,
|
||||
android::Status::NoError,
|
||||
VI::ResultOperationFailed);
|
||||
|
||||
// Ensure the layer is visible when content is presented.
|
||||
// Re-assert overlay priority in case clients reset it.
|
||||
(void)m_container.SetLayerZIndex(layer_id, 100000);
|
||||
(void)m_container.SetLayerVisibility(layer_id, true);
|
||||
|
||||
// We succeeded.
|
||||
R_SUCCEED();
|
||||
}
|
||||
@@ -409,22 +415,51 @@ Result SharedBufferManager::WriteAppletCaptureBuffer(bool* out_was_written, s32*
|
||||
// TODO: this could be optimized
|
||||
s64 e = -1280 * 768 * 4;
|
||||
for (auto& block : *m_buffer_page_group) {
|
||||
u8* start = m_system.DeviceMemory().GetPointer<u8>(block.GetAddress());
|
||||
u8* end = m_system.DeviceMemory().GetPointer<u8>(block.GetAddress() + block.GetSize());
|
||||
u8* const block_start = m_system.DeviceMemory().GetPointer<u8>(block.GetAddress());
|
||||
u8* ptr = block_start;
|
||||
u8* const block_end = m_system.DeviceMemory().GetPointer<u8>(block.GetAddress() + block.GetSize());
|
||||
|
||||
for (; start < end; start++) {
|
||||
*start = 0;
|
||||
for (; ptr < block_end; ++ptr) {
|
||||
if (e >= 0 && e < static_cast<s64>(capture_buffer.size())) {
|
||||
*start = capture_buffer[e];
|
||||
*ptr = capture_buffer[static_cast<size_t>(e)];
|
||||
} else {
|
||||
*ptr = 0;
|
||||
}
|
||||
e++;
|
||||
++e;
|
||||
}
|
||||
|
||||
m_system.GPU().Host1x().MemoryManager().ApplyOpOnPointer(start, scratch, [&](DAddr addr) {
|
||||
m_system.GPU().InvalidateRegion(addr, end - start);
|
||||
m_system.GPU().Host1x().MemoryManager().ApplyOpOnPointer(block_start, scratch, [&](DAddr addr) {
|
||||
m_system.GPU().InvalidateRegion(addr, block_end - block_start);
|
||||
});
|
||||
}
|
||||
|
||||
// After writing, present a frame on each active shared layer so it becomes visible.
|
||||
for (auto& [aruid, session] : m_sessions) {
|
||||
std::shared_ptr<android::BufferQueueProducer> producer;
|
||||
if (R_FAILED(m_container.GetLayerProducerHandle(std::addressof(producer), session.layer_id))) {
|
||||
continue;
|
||||
}
|
||||
s32 slot = -1;
|
||||
android::Fence fence = android::Fence::NoFence();
|
||||
if (producer->DequeueBuffer(&slot, &fence, SharedBufferAsync != 0, SharedBufferWidth,
|
||||
SharedBufferHeight, SharedBufferBlockLinearFormat, 0) !=
|
||||
android::Status::NoError) {
|
||||
continue;
|
||||
}
|
||||
std::shared_ptr<android::GraphicBuffer> gb;
|
||||
if (producer->RequestBuffer(slot, &gb) != android::Status::NoError) {
|
||||
producer->CancelBuffer(slot, android::Fence::NoFence());
|
||||
continue;
|
||||
}
|
||||
android::QueueBufferInput qin{};
|
||||
android::QueueBufferOutput qout{};
|
||||
qin.crop = {0, 0, static_cast<s32>(SharedBufferWidth), static_cast<s32>(SharedBufferHeight)};
|
||||
qin.fence = android::Fence::NoFence();
|
||||
qin.transform = static_cast<android::NativeWindowTransform>(0);
|
||||
qin.swap_interval = 1;
|
||||
(void)producer->QueueBuffer(slot, qin, &qout);
|
||||
}
|
||||
|
||||
*out_was_written = true;
|
||||
*out_layer_index = 1;
|
||||
R_SUCCEED();
|
||||
|
||||
@@ -217,13 +217,13 @@ void A32EmitX64::ClearFastDispatchTable() {
|
||||
}
|
||||
|
||||
void A32EmitX64::GenTerminalHandlers() {
|
||||
// PC ends up in edi, location_descriptor ends up in rbx
|
||||
// PC ends up in ebp, location_descriptor ends up in rbx
|
||||
const auto calculate_location_descriptor = [this] {
|
||||
// This calculation has to match up with IREmitter::PushRSB
|
||||
code.mov(ebx, dword[code.ABI_JIT_PTR + offsetof(A32JitState, upper_location_descriptor)]);
|
||||
code.shl(rbx, 32);
|
||||
code.mov(ecx, MJitStateReg(A32::Reg::PC));
|
||||
code.mov(edi, ecx);
|
||||
code.mov(ebp, ecx);
|
||||
code.or_(rbx, rcx);
|
||||
};
|
||||
|
||||
@@ -238,7 +238,7 @@ void A32EmitX64::GenTerminalHandlers() {
|
||||
code.mov(dword[code.ABI_JIT_PTR + offsetof(A32JitState, rsb_ptr)], eax);
|
||||
code.cmp(rbx, qword[code.ABI_JIT_PTR + offsetof(A32JitState, rsb_location_descriptors) + rax * sizeof(u64)]);
|
||||
if (conf.HasOptimization(OptimizationFlag::FastDispatch)) {
|
||||
code.jne(rsb_cache_miss, code.T_NEAR);
|
||||
code.jne(rsb_cache_miss);
|
||||
} else {
|
||||
code.jne(code.GetReturnFromRunCodeAddress());
|
||||
}
|
||||
@@ -251,21 +251,20 @@ void A32EmitX64::GenTerminalHandlers() {
|
||||
terminal_handler_fast_dispatch_hint = code.getCurr<const void*>();
|
||||
calculate_location_descriptor();
|
||||
code.L(rsb_cache_miss);
|
||||
code.mov(r8, reinterpret_cast<u64>(fast_dispatch_table.data()));
|
||||
//code.mov(r12d, MJitStateReg(A32::Reg::PC));
|
||||
code.mov(r12, rbx);
|
||||
code.mov(r12, reinterpret_cast<u64>(fast_dispatch_table.data()));
|
||||
code.mov(rbp, rbx);
|
||||
if (code.HasHostFeature(HostFeature::SSE42)) {
|
||||
code.crc32(r12, r8);
|
||||
code.crc32(rbp, r12);
|
||||
}
|
||||
code.and_(r12d, fast_dispatch_table_mask);
|
||||
code.lea(r12, ptr[r8 + r12]);
|
||||
code.cmp(rbx, qword[r12 + offsetof(FastDispatchEntry, location_descriptor)]);
|
||||
code.jne(fast_dispatch_cache_miss, code.T_NEAR);
|
||||
code.jmp(ptr[r12 + offsetof(FastDispatchEntry, code_ptr)]);
|
||||
code.and_(ebp, fast_dispatch_table_mask);
|
||||
code.lea(rbp, ptr[r12 + rbp]);
|
||||
code.cmp(rbx, qword[rbp + offsetof(FastDispatchEntry, location_descriptor)]);
|
||||
code.jne(fast_dispatch_cache_miss);
|
||||
code.jmp(ptr[rbp + offsetof(FastDispatchEntry, code_ptr)]);
|
||||
code.L(fast_dispatch_cache_miss);
|
||||
code.mov(qword[r12 + offsetof(FastDispatchEntry, location_descriptor)], rbx);
|
||||
code.mov(qword[rbp + offsetof(FastDispatchEntry, location_descriptor)], rbx);
|
||||
code.LookupBlock();
|
||||
code.mov(ptr[r12 + offsetof(FastDispatchEntry, code_ptr)], rax);
|
||||
code.mov(ptr[rbp + offsetof(FastDispatchEntry, code_ptr)], rax);
|
||||
code.jmp(rax);
|
||||
PerfMapRegister(terminal_handler_fast_dispatch_hint, code.getCurr(), "a32_terminal_handler_fast_dispatch_hint");
|
||||
|
||||
|
||||
@@ -188,14 +188,13 @@ void A64EmitX64::ClearFastDispatchTable() {
|
||||
}
|
||||
|
||||
void A64EmitX64::GenTerminalHandlers() {
|
||||
// PC ends up in rcx, location_descriptor ends up in rbx
|
||||
static_assert(std::find(ABI_ALL_CALLEE_SAVE.begin(), ABI_ALL_CALLEE_SAVE.end(), HostLoc::R12) != ABI_ALL_CALLEE_SAVE.end());
|
||||
// PC ends up in rbp, location_descriptor ends up in rbx
|
||||
const auto calculate_location_descriptor = [this] {
|
||||
// This calculation has to match up with A64::LocationDescriptor::UniqueHash
|
||||
// TODO: Optimization is available here based on known state of fpcr.
|
||||
code.mov(rdi, qword[code.ABI_JIT_PTR + offsetof(A64JitState, pc)]);
|
||||
code.mov(rbp, qword[code.ABI_JIT_PTR + offsetof(A64JitState, pc)]);
|
||||
code.mov(rcx, A64::LocationDescriptor::pc_mask);
|
||||
code.and_(rcx, rdi);
|
||||
code.and_(rcx, rbp);
|
||||
code.mov(ebx, dword[code.ABI_JIT_PTR + offsetof(A64JitState, fpcr)]);
|
||||
code.and_(ebx, A64::LocationDescriptor::fpcr_mask);
|
||||
code.shl(rbx, A64::LocationDescriptor::fpcr_shift);
|
||||
@@ -227,21 +226,20 @@ void A64EmitX64::GenTerminalHandlers() {
|
||||
terminal_handler_fast_dispatch_hint = code.getCurr<const void*>();
|
||||
calculate_location_descriptor();
|
||||
code.L(rsb_cache_miss);
|
||||
code.mov(r8, reinterpret_cast<u64>(fast_dispatch_table.data()));
|
||||
//code.mov(r12, qword[code.ABI_JIT_PTR + offsetof(A64JitState, pc)]);
|
||||
code.mov(r12, rbx);
|
||||
code.mov(r12, reinterpret_cast<u64>(fast_dispatch_table.data()));
|
||||
code.mov(rbp, rbx);
|
||||
if (code.HasHostFeature(HostFeature::SSE42)) {
|
||||
code.crc32(r12, r8);
|
||||
code.crc32(rbp, r12);
|
||||
}
|
||||
code.and_(r12d, fast_dispatch_table_mask);
|
||||
code.lea(r12, ptr[r8 + r12]);
|
||||
code.cmp(rbx, qword[r12 + offsetof(FastDispatchEntry, location_descriptor)]);
|
||||
code.jne(fast_dispatch_cache_miss, code.T_NEAR);
|
||||
code.jmp(ptr[r12 + offsetof(FastDispatchEntry, code_ptr)]);
|
||||
code.and_(ebp, fast_dispatch_table_mask);
|
||||
code.lea(rbp, ptr[r12 + rbp]);
|
||||
code.cmp(rbx, qword[rbp + offsetof(FastDispatchEntry, location_descriptor)]);
|
||||
code.jne(fast_dispatch_cache_miss);
|
||||
code.jmp(ptr[rbp + offsetof(FastDispatchEntry, code_ptr)]);
|
||||
code.L(fast_dispatch_cache_miss);
|
||||
code.mov(qword[r12 + offsetof(FastDispatchEntry, location_descriptor)], rbx);
|
||||
code.mov(qword[rbp + offsetof(FastDispatchEntry, location_descriptor)], rbx);
|
||||
code.LookupBlock();
|
||||
code.mov(ptr[r12 + offsetof(FastDispatchEntry, code_ptr)], rax);
|
||||
code.mov(ptr[rbp + offsetof(FastDispatchEntry, code_ptr)], rax);
|
||||
code.jmp(rax);
|
||||
PerfMapRegister(terminal_handler_fast_dispatch_hint, code.getCurr(), "a64_terminal_handler_fast_dispatch_hint");
|
||||
|
||||
|
||||
@@ -194,8 +194,6 @@ HostFeature GetHostFeatures() {
|
||||
features |= HostFeature::LZCNT;
|
||||
if (cpu_info.has(Cpu::tGFNI))
|
||||
features |= HostFeature::GFNI;
|
||||
if (cpu_info.has(Cpu::tWAITPKG))
|
||||
features |= HostFeature::WAITPKG;
|
||||
|
||||
if (cpu_info.has(Cpu::tBMI2)) {
|
||||
// BMI2 instructions such as pdep and pext have been very slow up until Zen 3.
|
||||
@@ -372,7 +370,7 @@ void BlockOfCode::GenRunCode(std::function<void(BlockOfCode&)> rcp) {
|
||||
|
||||
cmp(dword[ABI_JIT_PTR + jsi.offsetof_halt_reason], 0);
|
||||
jne(return_to_caller_mxcsr_already_exited, T_NEAR);
|
||||
lock(); or_(dword[ABI_JIT_PTR + jsi.offsetof_halt_reason], u32(HaltReason::Step));
|
||||
lock(); or_(dword[ABI_JIT_PTR + jsi.offsetof_halt_reason], static_cast<u32>(HaltReason::Step));
|
||||
|
||||
SwitchMxcsrOnEntry();
|
||||
jmp(ABI_PARAM2);
|
||||
|
||||
@@ -420,11 +420,10 @@ void AxxEmitX64::EmitExclusiveWriteMemoryInline(AxxEmitContext& ctx, IR::Inst* i
|
||||
const Xbyak::Reg64 vaddr = ctx.reg_alloc.UseGpr(code, args[1]);
|
||||
const Xbyak::Reg32 status = ctx.reg_alloc.ScratchGpr(code).cvt32();
|
||||
const Xbyak::Reg64 tmp = ctx.reg_alloc.ScratchGpr(code);
|
||||
const Xbyak::Reg64 tmp2 = ctx.reg_alloc.ScratchGpr(code);
|
||||
|
||||
const auto wrapped_fn = exclusive_write_fallbacks[std::make_tuple(ordered, bitsize, vaddr.getIdx(), value.getIdx())];
|
||||
|
||||
EmitExclusiveLock(code, conf, tmp, tmp2.cvt32());
|
||||
EmitExclusiveLock(code, conf, tmp, eax);
|
||||
|
||||
SharedLabel end = GenSharedLabel();
|
||||
|
||||
|
||||
@@ -346,7 +346,7 @@ void EmitExclusiveLock(BlockOfCode& code, const UserConfig& conf, Xbyak::Reg64 p
|
||||
}
|
||||
|
||||
code.mov(pointer, std::bit_cast<u64>(GetExclusiveMonitorLockPointer(conf.global_monitor)));
|
||||
EmitSpinLockLock(code, pointer, tmp, code.HasHostFeature(HostFeature::WAITPKG));
|
||||
EmitSpinLockLock(code, pointer, tmp);
|
||||
}
|
||||
|
||||
template<typename UserConfig>
|
||||
|
||||
@@ -35,10 +35,9 @@ enum class HostFeature : u64 {
|
||||
BMI2 = 1ULL << 19,
|
||||
LZCNT = 1ULL << 20,
|
||||
GFNI = 1ULL << 21,
|
||||
WAITPKG = 1ULL << 22,
|
||||
|
||||
// Zen-based BMI2
|
||||
FastBMI2 = 1ULL << 23,
|
||||
FastBMI2 = 1ULL << 22,
|
||||
|
||||
// Orthographic AVX512 features on 128 and 256 vectors
|
||||
AVX512_Ortho = AVX512F | AVX512VL,
|
||||
|
||||
@@ -22,46 +22,17 @@ static const auto default_cg_mode = nullptr; //Allow RWE
|
||||
|
||||
namespace Dynarmic {
|
||||
|
||||
void EmitSpinLockLock(Xbyak::CodeGenerator& code, Xbyak::Reg64 ptr, Xbyak::Reg32 tmp, bool waitpkg) {
|
||||
// TODO: this is because we lack regalloc - so better to be safe :(
|
||||
if (waitpkg) {
|
||||
code.push(Xbyak::util::eax);
|
||||
code.push(Xbyak::util::ebx);
|
||||
code.push(Xbyak::util::edx);
|
||||
}
|
||||
void EmitSpinLockLock(Xbyak::CodeGenerator& code, Xbyak::Reg64 ptr, Xbyak::Reg32 tmp) {
|
||||
Xbyak::Label start, loop;
|
||||
|
||||
code.jmp(start, code.T_NEAR);
|
||||
code.L(loop);
|
||||
if (waitpkg) {
|
||||
// TODO: This clobbers EAX and EDX did we tell the regalloc?
|
||||
// ARM ptr for address-monitoring
|
||||
code.umonitor(ptr);
|
||||
// tmp.bit[0] = 0: C0.1 | Slow Wakup | Better Savings
|
||||
// tmp.bit[0] = 1: C0.2 | Fast Wakup | Lesser Savings
|
||||
// edx:eax is implicitly used as a 64-bit deadline timestamp
|
||||
// Use the maximum so that we use the operating system's maximum
|
||||
// allowed wait time within the IA32_UMWAIT_CONTROL register
|
||||
// Enter power state designated by tmp and wait for a write to lock_ptr
|
||||
code.mov(Xbyak::util::eax, 0xFFFFFFFF);
|
||||
code.mov(Xbyak::util::edx, Xbyak::util::eax);
|
||||
// TODO: We can only be here because tmp is 1 already - however we repeatedly overwrite it...
|
||||
code.mov(Xbyak::util::ebx, 1);
|
||||
code.umwait(Xbyak::util::ebx);
|
||||
// CF == 1 if we hit the OS-timeout in IA32_UMWAIT_CONTROL without a write
|
||||
// CF == 0 if we exited the wait for any other reason
|
||||
} else {
|
||||
code.pause();
|
||||
}
|
||||
code.pause();
|
||||
code.L(start);
|
||||
code.mov(tmp, 1);
|
||||
/*code.lock();*/ code.xchg(code.dword[ptr], tmp);
|
||||
code.test(tmp, tmp);
|
||||
code.jnz(loop, code.T_NEAR);
|
||||
if (waitpkg) {
|
||||
code.pop(Xbyak::util::edx);
|
||||
code.pop(Xbyak::util::ebx);
|
||||
code.pop(Xbyak::util::eax);
|
||||
}
|
||||
}
|
||||
|
||||
void EmitSpinLockUnlock(Xbyak::CodeGenerator& code, Xbyak::Reg64 ptr, Xbyak::Reg32 tmp) {
|
||||
@@ -89,7 +60,7 @@ void SpinLockImpl::Initialize() noexcept {
|
||||
Xbyak::Reg64 const ABI_PARAM1 = Backend::X64::HostLocToReg64(Backend::X64::ABI_PARAM1);
|
||||
code.align();
|
||||
lock = code.getCurr<void (*)(volatile int*)>();
|
||||
EmitSpinLockLock(code, ABI_PARAM1, code.eax, false);
|
||||
EmitSpinLockLock(code, ABI_PARAM1, code.eax);
|
||||
code.ret();
|
||||
code.align();
|
||||
unlock = code.getCurr<void (*)(volatile int*)>();
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
* Copyright (c) 2022 MerryMage
|
||||
* SPDX-License-Identifier: 0BSD
|
||||
@@ -12,7 +9,7 @@
|
||||
|
||||
namespace Dynarmic {
|
||||
|
||||
void EmitSpinLockLock(Xbyak::CodeGenerator& code, Xbyak::Reg64 ptr, Xbyak::Reg32 tmp, bool waitpkg);
|
||||
void EmitSpinLockLock(Xbyak::CodeGenerator& code, Xbyak::Reg64 ptr, Xbyak::Reg32 tmp);
|
||||
void EmitSpinLockUnlock(Xbyak::CodeGenerator& code, Xbyak::Reg64 ptr, Xbyak::Reg32 tmp);
|
||||
|
||||
} // namespace Dynarmic
|
||||
|
||||
@@ -37,9 +37,6 @@
|
||||
#include "dynarmic/ir/basic_block.h"
|
||||
#include "dynarmic/ir/opt_passes.h"
|
||||
|
||||
#include "./A32/testenv.h"
|
||||
#include "./A64/testenv.h"
|
||||
|
||||
using namespace Dynarmic;
|
||||
|
||||
std::string_view GetNameOfA32Instruction(u32 instruction) {
|
||||
@@ -68,10 +65,7 @@ void PrintA32Instruction(u32 instruction) {
|
||||
fmt::print("should_continue: {}\n\n", should_continue);
|
||||
fmt::print("IR:\n");
|
||||
fmt::print("{}\n", IR::DumpBlock(ir_block));
|
||||
ArmTestEnv jit_env{};
|
||||
Dynarmic::A32::UserConfig jit_user_config{};
|
||||
jit_user_config.callbacks = &jit_env;
|
||||
Optimization::Optimize(ir_block, jit_user_config, {});
|
||||
Optimization::Optimize(ir_block, A32::UserConfig{}, {});
|
||||
fmt::print("Optimized IR:\n");
|
||||
fmt::print("{}\n", IR::DumpBlock(ir_block));
|
||||
}
|
||||
@@ -86,10 +80,7 @@ void PrintA64Instruction(u32 instruction) {
|
||||
fmt::print("should_continue: {}\n\n", should_continue);
|
||||
fmt::print("IR:\n");
|
||||
fmt::print("{}\n", IR::DumpBlock(ir_block));
|
||||
A64TestEnv jit_env{};
|
||||
Dynarmic::A64::UserConfig jit_user_config{};
|
||||
jit_user_config.callbacks = &jit_env;
|
||||
Optimization::Optimize(ir_block, jit_user_config, {});
|
||||
Optimization::Optimize(ir_block, A64::UserConfig{}, {});
|
||||
fmt::print("Optimized IR:\n");
|
||||
fmt::print("{}\n", IR::DumpBlock(ir_block));
|
||||
}
|
||||
@@ -107,10 +98,7 @@ void PrintThumbInstruction(u32 instruction) {
|
||||
fmt::print("should_continue: {}\n\n", should_continue);
|
||||
fmt::print("IR:\n");
|
||||
fmt::print("{}\n", IR::DumpBlock(ir_block));
|
||||
ThumbTestEnv jit_env{};
|
||||
Dynarmic::A32::UserConfig jit_user_config{};
|
||||
jit_user_config.callbacks = &jit_env;
|
||||
Optimization::Optimize(ir_block, jit_user_config, {});
|
||||
Optimization::Optimize(ir_block, A32::UserConfig{}, {});
|
||||
fmt::print("Optimized IR:\n");
|
||||
fmt::print("{}\n", IR::DumpBlock(ir_block));
|
||||
}
|
||||
@@ -231,7 +219,7 @@ void ExecuteA32Instruction(u32 instruction) {
|
||||
*(iter->second) = *value;
|
||||
fmt::print("> {} = 0x{:08x}\n", reg_name, *value);
|
||||
}
|
||||
} else if (reg_name.starts_with("m")) {
|
||||
} else if (reg_name == "mem" || reg_name == "memory") {
|
||||
fmt::print("address: ");
|
||||
if (const auto address = get_value()) {
|
||||
fmt::print("value: ");
|
||||
@@ -240,7 +228,7 @@ void ExecuteA32Instruction(u32 instruction) {
|
||||
fmt::print("> mem[0x{:08x}] = 0x{:08x}\n", *address, *value);
|
||||
}
|
||||
}
|
||||
} else if (reg_name == "exit" || reg_name == "end" || reg_name.starts_with("q")) {
|
||||
} else if (reg_name == "end") {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -256,7 +244,6 @@ void ExecuteA32Instruction(u32 instruction) {
|
||||
env.MemoryWrite32(initial_pc + 4, 0xEAFFFFFE); // B +0
|
||||
|
||||
cpu.Run();
|
||||
fmt::print("{}", fmt::join(cpu.Disassemble(), "\n"));
|
||||
|
||||
fmt::print("Registers modified:\n");
|
||||
for (size_t i = 0; i < regs.size(); ++i) {
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
namespace Vulkan {
|
||||
|
||||
// Prefer small grow rates to avoid saturating the descriptor pool with barely used pipelines
|
||||
constexpr size_t SETS_GROW_RATE = 16;
|
||||
//constexpr size_t SETS_GROW_RATE = 32; //test difference between 16 and 32
|
||||
constexpr s32 SCORE_THRESHOLD = 3;
|
||||
|
||||
struct DescriptorBank {
|
||||
@@ -29,9 +29,12 @@ struct DescriptorBank {
|
||||
};
|
||||
|
||||
bool DescriptorBankInfo::IsSuperset(const DescriptorBankInfo& subset) const noexcept {
|
||||
return uniform_buffers >= subset.uniform_buffers && storage_buffers >= subset.storage_buffers &&
|
||||
texture_buffers >= subset.texture_buffers && image_buffers >= subset.image_buffers &&
|
||||
textures >= subset.textures && images >= subset.images;
|
||||
return uniform_buffers >= subset.uniform_buffers &&
|
||||
storage_buffers >= subset.storage_buffers &&
|
||||
texture_buffers >= subset.texture_buffers &&
|
||||
image_buffers >= subset.image_buffers &&
|
||||
textures >= subset.textures &&
|
||||
images >= subset.images;
|
||||
}
|
||||
|
||||
template <typename Descriptors>
|
||||
@@ -45,6 +48,19 @@ static u32 Accumulate(const Descriptors& descriptors) {
|
||||
|
||||
static DescriptorBankInfo MakeBankInfo(std::span<const Shader::Info> infos) {
|
||||
DescriptorBankInfo bank;
|
||||
if (infos.size() == 1) {
|
||||
const auto& info = infos.front();
|
||||
const auto acc = [](const auto& ds){ u32 c=0; for (const auto& d: ds) c+=d.count; return c; };
|
||||
bank.uniform_buffers += acc(info.constant_buffer_descriptors);
|
||||
bank.storage_buffers += acc(info.storage_buffers_descriptors);
|
||||
bank.texture_buffers += acc(info.texture_buffer_descriptors);
|
||||
bank.image_buffers += acc(info.image_buffer_descriptors);
|
||||
bank.textures += acc(info.texture_descriptors);
|
||||
bank.images += acc(info.image_descriptors);
|
||||
bank.score = bank.uniform_buffers + bank.storage_buffers + bank.texture_buffers +
|
||||
bank.image_buffers + bank.textures + bank.images;
|
||||
return bank;
|
||||
}
|
||||
for (const Shader::Info& info : infos) {
|
||||
bank.uniform_buffers += Accumulate(info.constant_buffer_descriptors);
|
||||
bank.storage_buffers += Accumulate(info.storage_buffers_descriptors);
|
||||
@@ -87,14 +103,35 @@ static void AllocatePool(const Device& device, DescriptorBank& bank) {
|
||||
}));
|
||||
}
|
||||
|
||||
static size_t GetGrowRate() {
|
||||
if (Settings::getDebugKnobAt(0))
|
||||
return 8;
|
||||
else if (Settings::getDebugKnobAt(1))
|
||||
return 16;
|
||||
else if (Settings::getDebugKnobAt(2))
|
||||
return 24;
|
||||
else if (Settings::getDebugKnobAt(3))
|
||||
return 32;
|
||||
else if (Settings::getDebugKnobAt(4))
|
||||
return 40;
|
||||
else if (Settings::getDebugKnobAt(5))
|
||||
return 48;
|
||||
else if (Settings::getDebugKnobAt(6))
|
||||
return 56;
|
||||
else if (Settings::getDebugKnobAt(7))
|
||||
return 64;
|
||||
else
|
||||
return 16;
|
||||
}
|
||||
|
||||
DescriptorAllocator::DescriptorAllocator(const Device& device_, MasterSemaphore& master_semaphore_,
|
||||
DescriptorBank& bank_, VkDescriptorSetLayout layout_)
|
||||
: ResourcePool(master_semaphore_, SETS_GROW_RATE), device{&device_}, bank{&bank_},
|
||||
layout{layout_} {}
|
||||
: ResourcePool(master_semaphore_, /*SETS_GROW_RATE*/ GetGrowRate()), device{&device_}, bank{&bank_},
|
||||
layout{layout_}, grow_rate{GetGrowRate()} {}
|
||||
|
||||
VkDescriptorSet DescriptorAllocator::Commit() {
|
||||
const size_t index = CommitResource();
|
||||
return sets[index / SETS_GROW_RATE][index % SETS_GROW_RATE];
|
||||
return sets[index / grow_rate][index % grow_rate];
|
||||
}
|
||||
|
||||
void DescriptorAllocator::Allocate(size_t begin, size_t end) {
|
||||
@@ -102,13 +139,22 @@ void DescriptorAllocator::Allocate(size_t begin, size_t end) {
|
||||
}
|
||||
|
||||
vk::DescriptorSets DescriptorAllocator::AllocateDescriptors(size_t count) {
|
||||
const std::vector<VkDescriptorSetLayout> layouts(count, layout);
|
||||
std::array<VkDescriptorSetLayout, 64> stack{};
|
||||
const VkDescriptorSetLayout* p_layouts = nullptr;
|
||||
std::vector<VkDescriptorSetLayout> heap;
|
||||
if (count <= stack.size()) {
|
||||
stack.fill(layout);
|
||||
p_layouts = stack.data();
|
||||
} else {
|
||||
heap.assign(count, layout);
|
||||
p_layouts = heap.data();
|
||||
}
|
||||
VkDescriptorSetAllocateInfo allocate_info{
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.descriptorPool = *bank->pools.back(),
|
||||
.descriptorSetCount = static_cast<u32>(count),
|
||||
.pSetLayouts = layouts.data(),
|
||||
.pSetLayouts = p_layouts,
|
||||
};
|
||||
vk::DescriptorSets new_sets = bank->pools.back().Allocate(allocate_info);
|
||||
if (!new_sets.IsOutOfPoolMemory()) {
|
||||
@@ -146,21 +192,58 @@ DescriptorAllocator DescriptorPool::Allocator(VkDescriptorSetLayout layout,
|
||||
}
|
||||
|
||||
DescriptorBank& DescriptorPool::Bank(const DescriptorBankInfo& reqs) {
|
||||
{
|
||||
std::scoped_lock lk(cache_mutex);
|
||||
DescriptorBank* best = nullptr; u64 best_stamp = 0;
|
||||
for (const auto& e : cache_) {
|
||||
if (!e.bank) continue;
|
||||
if (std::abs(e.info.score - reqs.score) < SCORE_THRESHOLD && e.info.IsSuperset(reqs)) {
|
||||
if (e.stamp >= best_stamp) { best_stamp = e.stamp; best = e.bank; }
|
||||
}
|
||||
}
|
||||
if (best) return *best;
|
||||
}
|
||||
std::shared_lock read_lock{banks_mutex};
|
||||
const auto it = std::ranges::find_if(bank_infos, [&reqs](const DescriptorBankInfo& bank) {
|
||||
return std::abs(bank.score - reqs.score) < SCORE_THRESHOLD && bank.IsSuperset(reqs);
|
||||
});
|
||||
if (it != bank_infos.end()) {
|
||||
return *banks[std::distance(bank_infos.begin(), it)].get();
|
||||
DescriptorBank& found = *banks[std::distance(bank_infos.begin(), it)].get();
|
||||
read_lock.unlock();
|
||||
// update cache
|
||||
std::scoped_lock lk(cache_mutex);
|
||||
size_t victim = 0; u64 oldest = UINT64_MAX;
|
||||
for (size_t i=0;i<cache_.size();++i) if (cache_[i].stamp < oldest) { oldest = cache_[i].stamp; victim = i; }
|
||||
cache_[victim] = CacheEntry{found.info, &found, ++cache_tick_};
|
||||
return found;
|
||||
}
|
||||
read_lock.unlock();
|
||||
|
||||
std::unique_lock write_lock{banks_mutex};
|
||||
auto it2 = std::ranges::find_if(bank_infos, [&reqs](const DescriptorBankInfo& bank) {
|
||||
return std::abs(bank.score - reqs.score) < SCORE_THRESHOLD && bank.IsSuperset(reqs);
|
||||
});
|
||||
if (it2 != bank_infos.end()) {
|
||||
DescriptorBank& found = *banks[std::distance(bank_infos.begin(), it2)].get();
|
||||
// update cache
|
||||
std::scoped_lock lk(cache_mutex);
|
||||
size_t victim = 0; u64 oldest = UINT64_MAX;
|
||||
for (size_t i=0;i<cache_.size();++i) if (cache_[i].stamp < oldest) { oldest = cache_[i].stamp; victim = i; }
|
||||
cache_[victim] = CacheEntry{found.info, &found, ++cache_tick_};
|
||||
return found;
|
||||
}
|
||||
bank_infos.push_back(reqs);
|
||||
|
||||
auto& bank = *banks.emplace_back(std::make_unique<DescriptorBank>());
|
||||
bank.info = reqs;
|
||||
AllocatePool(device, bank);
|
||||
// update cache
|
||||
{
|
||||
std::scoped_lock lk(cache_mutex);
|
||||
size_t victim = 0; u64 oldest = UINT64_MAX;
|
||||
for (size_t i=0;i<cache_.size();++i) if (cache_[i].stamp < oldest) { oldest = cache_[i].stamp; victim = i; }
|
||||
cache_[victim] = CacheEntry{bank.info, &bank, ++cache_tick_};
|
||||
}
|
||||
return bank;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -6,7 +9,8 @@
|
||||
#include <shared_mutex>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#include <array>
|
||||
#include <mutex>
|
||||
#include "shader_recompiler/shader_info.h"
|
||||
#include "video_core/renderer_vulkan/vk_resource_pool.h"
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
@@ -58,6 +62,7 @@ private:
|
||||
VkDescriptorSetLayout layout{};
|
||||
|
||||
std::vector<vk::DescriptorSets> sets;
|
||||
size_t grow_rate = 32;
|
||||
};
|
||||
|
||||
class DescriptorPool {
|
||||
@@ -75,6 +80,14 @@ public:
|
||||
|
||||
private:
|
||||
DescriptorBank& Bank(const DescriptorBankInfo& reqs);
|
||||
struct CacheEntry {
|
||||
DescriptorBankInfo info{};
|
||||
DescriptorBank* bank{nullptr};
|
||||
u64 stamp{0};
|
||||
};
|
||||
std::mutex cache_mutex{};
|
||||
std::array<CacheEntry, 8> cache_{}; //test and then adjust
|
||||
u64 cache_tick_{0};
|
||||
|
||||
const Device& device;
|
||||
MasterSemaphore& master_semaphore;
|
||||
|
||||
@@ -20,12 +20,18 @@
|
||||
namespace Vulkan {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] bool AreExtensionsSupported(const vk::InstanceDispatch& dld, std::vector<VkExtensionProperties> const& properties, std::span<const char* const> extensions) {
|
||||
[[nodiscard]] bool AreExtensionsSupported(const vk::InstanceDispatch& dld,
|
||||
std::span<const char* const> extensions) {
|
||||
const std::optional properties = vk::EnumerateInstanceExtensionProperties(dld);
|
||||
if (!properties) {
|
||||
LOG_ERROR(Render_Vulkan, "Failed to query extension properties");
|
||||
return false;
|
||||
}
|
||||
for (const char* extension : extensions) {
|
||||
const auto it = std::ranges::find_if(properties, [extension](const auto& prop) {
|
||||
const auto it = std::ranges::find_if(*properties, [extension](const auto& prop) {
|
||||
return std::strcmp(extension, prop.extensionName) == 0;
|
||||
});
|
||||
if (it == properties.end()) {
|
||||
if (it == properties->end()) {
|
||||
LOG_ERROR(Render_Vulkan, "Required instance extension {} is not available", extension);
|
||||
return false;
|
||||
}
|
||||
@@ -72,16 +78,14 @@ namespace {
|
||||
if (window_type != Core::Frontend::WindowSystemType::Headless) {
|
||||
extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
|
||||
}
|
||||
if (auto const properties = vk::EnumerateInstanceExtensionProperties(dld); properties) {
|
||||
#ifdef __APPLE__
|
||||
if (AreExtensionsSupported(dld, *properties, std::array{VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME}))
|
||||
extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
|
||||
if (AreExtensionsSupported(dld, std::array{VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME})) {
|
||||
extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
|
||||
}
|
||||
#endif
|
||||
if (enable_validation && AreExtensionsSupported(dld, *properties, std::array{VK_EXT_DEBUG_UTILS_EXTENSION_NAME}))
|
||||
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
||||
// VK_EXT_surface_maintenance1 is required for VK_EXT_swapchain_maintenance1
|
||||
if (window_type != Core::Frontend::WindowSystemType::Headless && AreExtensionsSupported(dld, *properties, std::array{VK_EXT_SURFACE_MAINTENANCE_1_EXTENSION_NAME}))
|
||||
extensions.push_back(VK_EXT_SURFACE_MAINTENANCE_1_EXTENSION_NAME);
|
||||
if (enable_validation &&
|
||||
AreExtensionsSupported(dld, std::array{VK_EXT_DEBUG_UTILS_EXTENSION_NAME})) {
|
||||
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
||||
}
|
||||
return extensions;
|
||||
}
|
||||
@@ -129,10 +133,11 @@ vk::Instance CreateInstance(const Common::DynamicLibrary& library, vk::InstanceD
|
||||
LOG_ERROR(Render_Vulkan, "Failed to load Vulkan function pointers");
|
||||
throw vk::Exception(VK_ERROR_INITIALIZATION_FAILED);
|
||||
}
|
||||
std::vector<const char*> const extensions = RequiredExtensions(dld, window_type, enable_validation);
|
||||
auto const properties = vk::EnumerateInstanceExtensionProperties(dld);
|
||||
if (!properties || !AreExtensionsSupported(dld, *properties, extensions))
|
||||
const std::vector<const char*> extensions =
|
||||
RequiredExtensions(dld, window_type, enable_validation);
|
||||
if (!AreExtensionsSupported(dld, extensions)) {
|
||||
throw vk::Exception(VK_ERROR_EXTENSION_NOT_PRESENT);
|
||||
}
|
||||
std::vector<const char*> layers = Layers(enable_validation);
|
||||
RemoveUnavailableLayers(dld, layers);
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ li.checked::marker { content: "\2612"; }
|
||||
<item>
|
||||
<widget class="QLabel" name="labelLinks">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><a href="https://eden-emulator.github.io/"><span style=" text-decoration: underline; color:#039be5;">Website</span></a> | <a href="https://git.eden-emu.dev"><span style=" text-decoration: underline; color:#039be5;">Source Code</span></a> | <a href="https://git.eden-emu.dev/eden-emu/eden/activity/contributors"><span style=" text-decoration: underline; color:#039be5;">Contributors</span></a> | <a href="https://discord.gg/HstXbPch7X"><span style=" text-decoration: underline; color:#039be5;">Discord</span></a> | <a href="https://stt.gg/qKgFEAbH"><span style=" text-decoration: underline; color:#039be5;">Stoat</span></a> | <a href="https://nitter.poast.org/edenemuofficial"><span style=" text-decoration: underline; color:#039be5;">Twitter</span></a> | <a href="https://git.eden-emu.dev/eden-emu/eden/src/branch/master/LICENSE.txt"><span style=" text-decoration: underline; color:#039be5;">License</span></a></p></body></html></string>
|
||||
<string><html><head/><body><p><a href="https://eden-emulator.github.io/"><span style=" text-decoration: underline; color:#039be5;">Website</span></a> | <a href="https://git.eden-emu.dev"><span style=" text-decoration: underline; color:#039be5;">Source Code</span></a> | <a href="https://git.eden-emu.dev/eden-emu/eden/activity/contributors"><span style=" text-decoration: underline; color:#039be5;">Contributors</span></a> | <a href="https://discord.gg/HstXbPch7X"><span style=" text-decoration: underline; color:#039be5;">Discord</span></a> | <a href="https://rvlt.gg/qKgFEAbH"><span style=" text-decoration: underline; color:#039be5;">Revolt</span></a> | <a href="https://nitter.poast.org/edenemuofficial"><span style=" text-decoration: underline; color:#039be5;">Twitter</span></a> | <a href="https://git.eden-emu.dev/eden-emu/eden/src/branch/master/LICENSE.txt"><span style=" text-decoration: underline; color:#039be5;">License</span></a></p></body></html></string>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
|
||||
@@ -1900,7 +1900,7 @@ bool MainWindow::LoadROM(const QString& filename, Service::AM::FrontendAppletPar
|
||||
tr("Error while loading ROM! %1", "%1 signifies a numeric error code.")
|
||||
.arg(QString::fromStdString(error_code));
|
||||
const auto description =
|
||||
tr("%1<br>Please redump your files or ask on Discord/Stoat for help.",
|
||||
tr("%1<br>Please redump your files or ask on Discord/Revolt for help.",
|
||||
"%1 signifies an error string.")
|
||||
.arg(QString::fromStdString(
|
||||
GetResultStatusString(static_cast<Loader::ResultStatus>(error_id))));
|
||||
|
||||
59
tools/dtrace-tool.sh
Executable file
59
tools/dtrace-tool.sh
Executable file
@@ -0,0 +1,59 @@
|
||||
#!/usr/local/bin/bash -ex
|
||||
|
||||
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# Basic script to run dtrace sampling over the program (requires Flamegraph)
|
||||
# Usage is either running as: ./dtrace-tool.sh pid (then input the pid of the process)
|
||||
# Or just run directly with: ./dtrace-tool.sh <command>
|
||||
|
||||
FLAMEGRAPH_DIR=".."
|
||||
fail() {
|
||||
printf '%s\n' "$1" >&2
|
||||
exit "${2-1}"
|
||||
}
|
||||
|
||||
[ -f $FLAMEGRAPH_DIR/FlameGraph/stackcollapse.pl ] || fail 'Where is flamegraph?'
|
||||
#[ which dtrace ] || fail 'Needs DTrace installed'
|
||||
|
||||
read -r "Sampling Hz [800]: " TRACE_CFG_HZ
|
||||
if [ -z "${TRACE_CFG_HZ}" ]; then
|
||||
TRACE_CFG_HZ=800
|
||||
fi
|
||||
|
||||
read -r "Sampling time [5] sec: " TRACE_CFG_TIME
|
||||
if [ -z "${TRACE_CFG_TIME}" ]; then
|
||||
TRACE_CFG_TIME=5
|
||||
fi
|
||||
|
||||
TRACE_FILE=dtrace-out.user_stacks
|
||||
TRACE_FOLD=dtrace-out.fold
|
||||
TRACE_SVG=dtrace-out.svg
|
||||
ps
|
||||
|
||||
if [ "$1" = 'pid' ]; then
|
||||
read -r "PID: " TRACE_CFG_PID
|
||||
sudo echo 'Sudo!'
|
||||
else
|
||||
if [ -f "$1" ] && [ "$1" ]; then
|
||||
fail 'Usage: ./tools/dtrace-profile.sh <path to program>'
|
||||
fi
|
||||
|
||||
printf "Executing: "
|
||||
echo "$@"
|
||||
sudo echo 'Sudo!'
|
||||
"$@" &
|
||||
TRACE_CFG_PID=$!
|
||||
fi
|
||||
|
||||
TRACE_PROBE="profile-${TRACE_CFG_HZ} /pid == ${TRACE_CFG_PID} && arg1/ { @[ustack()] = count(); } tick-${TRACE_CFG_TIME}s { exit(0); }"
|
||||
|
||||
rm -- $TRACE_SVG || echo 'Skip'
|
||||
|
||||
sudo dtrace -x ustackframes=100 -Z -n "$TRACE_PROBE" -o $TRACE_FILE 2>/dev/null || exit
|
||||
|
||||
perl $FLAMEGRAPH_DIR/FlameGraph/stackcollapse.pl $TRACE_FILE > $TRACE_FOLD || exit
|
||||
perl $FLAMEGRAPH_DIR/FlameGraph/flamegraph.pl $TRACE_FOLD > $TRACE_SVG || exit
|
||||
|
||||
sudo chmod 0666 $TRACE_FILE
|
||||
rm -- $TRACE_FILE $TRACE_FOLD
|
||||
Reference in New Issue
Block a user