Compare commits

..

10 Commits

Author SHA1 Message Date
Caio Oliveira
112b14b564 [chore] fix build errors
Signed-off-by: Caio Oliveira <caiooliveirafarias0@gmail.com>
2025-12-17 21:46:22 -03:00
lizzie
754883db97 [core] pin core threads to logical CPUs 0-3
this basically allows the threads to exist in these logical CPUs, undisturbed, and without trashing each other's cache
this could improve performance, very tricky thing to pull off correctly, but again, this is mostly an experiment
will mainly benefit: Linux, Android, FreeBSD, Windows (not ARM)
Additionally, this means no context trashing :)

Signed-off-by: lizzie <lizzie@eden-emu.dev>
2025-12-17 21:23:30 -03:00
Caio Oliveira
057d566ff4 [FIXUP] Partially revert "[dynarmic] allow better dtrace diagnostics for code - do not clobber %rbp and save frame pointer (#2653)" (#3176)
This partially reverts commit 50f8d4130d.

Signed-off-by: Caio Oliveira <caiooliveirafarias0@gmail.com>

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3176
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Co-authored-by: Caio Oliveira <caiooliveirafarias0@gmail.com>
Co-committed-by: Caio Oliveira <caiooliveirafarias0@gmail.com>
2025-12-18 00:09:42 +01:00
Lizzie
be592f0ab3 [dist, docs] Revolt renames itself to Stoat, change rvlt.gg to stt.gg (#2656)
No badges.io yet, no new SVG logo from them

Signed-off-by: lizzie <lizzie@eden-emu.dev>

Co-authored-by: Maufeat <sahyno1996@gmail.com>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2656
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Reviewed-by: Caio Oliveira <caiooliveirafarias0@gmail.com>
Co-authored-by: Lizzie <lizzie@eden-emu.dev>
Co-committed-by: Lizzie <lizzie@eden-emu.dev>
2025-12-17 14:36:35 +01:00
Maufeat
bf68eede05 [bsd, ssl] fix connection between bsd:u and bsd:s and file descriptor copy (#3172)
as seen in repeated epic games api connection in sonic

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3172
Reviewed-by: Caio Oliveira <caiooliveirafarias0@gmail.com>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: Maufeat <sahyno1996@gmail.com>
Co-committed-by: Maufeat <sahyno1996@gmail.com>
2025-12-17 06:32:22 +01:00
Maufeat
1eed7efd09 [core, display, overlay] Add LayerIsOverlay to separate overlay on composer, stub RequestListSummaryOverlayNotification, sync emu settings when setting language (#3123)
This should fix the issue with, for example, ToTK running at 60 FPS when overlay applet is running.
This also should always run the overlay as actual overlay and not in the back.
Stubs RequestListSummaryOverlayNotifications in friends
Syncs Language of the Emulator, when setting language, this is used in Starter Applet

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3123
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: Caio Oliveira <caiooliveirafarias0@gmail.com>
Co-authored-by: Maufeat <sahyno1996@gmail.com>
Co-committed-by: Maufeat <sahyno1996@gmail.com>
2025-12-17 06:26:02 +01:00
lizzie
50f8d4130d [dynarmic] allow better dtrace diagnostics for code - do not clobber %rbp and save frame pointer (#2653)
Saving the %rbp pointer allows us to backref previous stackframes easily

Signed-off-by: lizzie <lizzie@eden-emu.dev>

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2653
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: Caio Oliveira <caiooliveirafarias0@gmail.com>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2025-12-17 05:41:11 +01:00
Producdevity
e4dccd5a5c [android] setting to auto hide overlay on controller input (#3127)
Setting `HIDE_OVERLAY_ON_CONTROLLER_INPUT` in *Advanced settings → Input Overlay*

**Behavior:**
  - First controller input  -> hides overlay
  - Controller disconnect → shows overlay again
  - Subsequent controller inputs → ignored (already hidden, so no retrigger needed)
  - Touch screen → does **not** show overlay (so you can use a controller and touchscreen to interact with games)
  - Sidebar "Show/Hide controller" button → still works as master toggle

**State reset: The "first input" detection resets when:**
  1. Controller disconnects
  2. Overlay is shown via sidebar button
  3. Controller reconnects

**Interaction with other settings:**
  - Requires `SHOW_INPUT_OVERLAY` to be enabled (basicaly a master switch)
  - Independent from `ENABLE_INPUT_OVERLAY_AUTO_HIDE` (timer-based hide, was already implemented)
  - When both are enabled, touch-to-show is disabled (controller-hide takes precedence)

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3127
Reviewed-by: Caio Oliveira <caiooliveirafarias0@gmail.com>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Co-authored-by: Producdevity <y.gherbi.dev@gmail.com>
Co-committed-by: Producdevity <y.gherbi.dev@gmail.com>
2025-12-17 03:59:46 +01:00
Kleidis
b9530ae80f [core] Add overridable game setting functionality (#2963)
Adds a place to override specific game settings for specific vendors

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2963
Reviewed-by: Caio Oliveira <caiooliveirafarias0@gmail.com>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Co-authored-by: Kleidis <kleidis1@protonmail.com>
Co-committed-by: Kleidis <kleidis1@protonmail.com>
2025-12-17 03:59:27 +01:00
lizzie
5130185d12 [vk] avoid calling vkENumerateInstanceFeatures multiple times in init code (#3147)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3147
Reviewed-by: Caio Oliveira <caiooliveirafarias0@gmail.com>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2025-12-17 03:58:30 +01:00
56 changed files with 753 additions and 399 deletions

2
.gitignore vendored
View File

@@ -37,6 +37,8 @@ CMakeLists.txt.user*
# *nix related
# Common convention for backup or temporary files
*~
*.core
dtrace-out/
# Visual Studio CMake settings
CMakeSettings.json

View File

@@ -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://rvlt.gg/qKgFEAbH">
<img src="https://img.shields.io/revolt/invite/qKgFEAbH?color=d61f3a&label=Revolt"
alt="Revolt">
<a href="https://stt.gg/qKgFEAbH">
<img src="https://img.shields.io/revolt/invite/qKgFEAbH?color=d61f3a&label=Stoat"
alt="Stoat">
</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 [Revolt](https://rvlt.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 [Stoat](https://stt.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 Revolt 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 Stoat to learn more about the current state of the emulator.
See the [sign-up instructions](docs/SIGNUP.md) for information on registration.

View File

@@ -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 Revolt or Discord.
Otherwise, feel free to ask for help in Stoat or Discord.
## Caveats

View File

@@ -18,6 +18,7 @@ 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
@@ -63,11 +64,12 @@ import kotlin.math.roundToInt
import org.yuzu.yuzu_emu.utils.ForegroundService
import androidx.core.os.BundleCompat
class EmulationActivity : AppCompatActivity(), SensorEventListener {
class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager.InputDeviceListener {
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
@@ -140,6 +142,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
nfcReader = NfcReader(this)
nfcReader.initialize()
inputManager = getSystemService(INPUT_SERVICE) as InputManager
inputManager.registerInputDeviceListener(this, null)
foregroundService = Intent(this, ForegroundService::class.java)
startForegroundService(foregroundService)
@@ -206,9 +211,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
override fun onDestroy() {
super.onDestroy()
inputManager.unregisterInputDeviceListener(this)
stopForegroundService(this)
NativeLibrary.playTimeManagerStop()
}
override fun onUserLeaveHint() {
@@ -244,8 +249,10 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
val isPhysicalKeyboard = event.source and InputDevice.SOURCE_KEYBOARD == InputDevice.SOURCE_KEYBOARD &&
event.device?.isVirtual == false
if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK &&
event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD &&
val isControllerInput = event.source and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK ||
event.source and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD
if (!isControllerInput &&
event.source and InputDevice.SOURCE_MOUSE != InputDevice.SOURCE_MOUSE &&
!isPhysicalKeyboard
) {
@@ -256,12 +263,18 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
return super.dispatchKeyEvent(event)
}
if (isControllerInput && event.action == KeyEvent.ACTION_DOWN) {
notifyControllerInput()
}
return InputHandler.dispatchKeyEvent(event)
}
override fun dispatchGenericMotionEvent(event: MotionEvent): Boolean {
if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK &&
event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD &&
val isControllerInput = event.source and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK ||
event.source and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD
if (!isControllerInput &&
event.source and InputDevice.SOURCE_KEYBOARD != InputDevice.SOURCE_KEYBOARD &&
event.source and InputDevice.SOURCE_MOUSE != InputDevice.SOURCE_MOUSE
) {
@@ -277,9 +290,54 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
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) {
@@ -519,8 +577,10 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
touchDownTime = System.currentTimeMillis()
// show overlay immediately on touch and cancel timer
if (!emulationViewModel.drawerOpen.value) {
// 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()) {
fragment.handler.removeCallbacksAndMessages(null)
fragment.showOverlay()
}

View File

@@ -52,6 +52,7 @@ 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"),

View File

@@ -387,6 +387,13 @@ 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(

View File

@@ -274,6 +274,7 @@ 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)
}
}

View File

@@ -100,7 +100,7 @@ class AboutFragment : Fragment() {
}
binding.buttonDiscord.setOnClickListener { openLink(getString(R.string.discord_link)) }
binding.buttonRevolt.setOnClickListener { openLink(getString(R.string.revolt_link)) }
binding.buttonStoat.setOnClickListener { openLink(getString(R.string.stoat_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)) }

View File

@@ -93,7 +93,6 @@ 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
@@ -106,6 +105,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
val handler = Handler(Looper.getMainLooper())
private var isOverlayVisible = true
private var controllerInputReceived = false
private var _binding: FragmentEmulationBinding? = null
@@ -656,6 +656,12 @@ 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
}
@@ -1901,7 +1907,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
companion object {
fun fromValue(value: Int): AmiiboState =
values().firstOrNull { it.value == value } ?: Disabled
entries.firstOrNull { it.value == value } ?: Disabled
}
}
@@ -1914,7 +1920,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
companion object {
fun fromValue(value: Int): AmiiboLoadResult =
values().firstOrNull { it.value == value } ?: Unknown
entries.firstOrNull { it.value == value } ?: Unknown
}
}
@@ -1971,6 +1977,8 @@ 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)
}
}
@@ -1978,7 +1986,26 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
private fun hideOverlay() {
if (isOverlayVisible) {
isOverlayVisible = false
ViewUtils.hideView(binding.surfaceInputOverlay, 500)
ViewUtils.hideView(binding.surfaceInputOverlay)
}
}
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()
}
}

View File

@@ -92,6 +92,11 @@ 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,

View File

@@ -220,12 +220,12 @@
app:iconPadding="0dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_revolt"
android:id="@+id/button_stoat"
style="@style/EdenButton.Secondary"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_marginEnd="12dp"
app:icon="@drawable/ic_revolt"
app:icon="@drawable/ic_stoat"
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>

View File

@@ -215,12 +215,12 @@
<com.google.android.material.button.MaterialButton
style="@style/EdenButton.Secondary"
android:id="@+id/button_revolt"
android:id="@+id/button_stoat"
android:layout_width="0dp"
android:layout_height="56dp"
android:layout_weight="1"
android:layout_marginEnd="8dp"
app:icon="@drawable/ic_revolt"
app:icon="@drawable/ic_stoat"
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>

View File

@@ -26,6 +26,8 @@
<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>
@@ -445,7 +447,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="revolt_link" translatable="false">https://rvlt.gg/qKgFEAbH</string>
<string name="stoat_link" translatable="false">https://stt.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>

View File

@@ -5,9 +5,11 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <string>
#include <thread>
#include "common/error.h"
#include "common/logging/log.h"
#include "common/assert.h"
#include "common/thread.h"
#ifdef __APPLE__
#include <mach/mach.h>
@@ -18,6 +20,8 @@
#include "common/string_util.h"
#else
#if defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
#include <sys/cpuset.h>
#include <sys/_cpuset.h>
#include <pthread_np.h>
#endif
#include <pthread.h>
@@ -28,7 +32,7 @@
#endif
#ifdef __FreeBSD__
#define cpu_set_t cpuset_t
# define cpu_set_t cpuset_t
#endif
namespace Common {
@@ -77,22 +81,14 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) {
#endif
}
void SetCurrentThreadName(const char* name) {
#ifdef _MSC_VER
// Sets the debugger-visible name of the current thread.
void SetCurrentThreadName(const char* name) {
static auto pf = (decltype(&SetThreadDescription))(void*)GetProcAddress(GetModuleHandle(TEXT("KernelBase.dll")), "SetThreadDescription");
if (pf)
// Sets the debugger-visible name of the current thread.
if (auto pf = (decltype(&SetThreadDescription))(void*)GetProcAddress(GetModuleHandle(TEXT("KernelBase.dll")), "SetThreadDescription"); pf)
pf(GetCurrentThread(), UTF8ToUTF16W(name).data()); // Windows 10+
}
#else // !MSVC_VER, so must be POSIX threads
// MinGW with the POSIX threading model does not support pthread_setname_np
void SetCurrentThreadName(const char* name) {
// See for reference
// https://gitlab.freedesktop.org/mesa/mesa/-/blame/main/src/util/u_thread.c?ref_type=heads#L75
#ifdef __APPLE__
else
; // No-op
#elif defined(__APPLE__)
pthread_setname_np(name);
#elif defined(__HAIKU__)
rename_thread(find_thread(NULL), name);
@@ -112,13 +108,33 @@ void SetCurrentThreadName(const char* name) {
pthread_setname_np(pthread_self(), buf);
}
#elif defined(_WIN32)
// mingw stub
// MinGW with the POSIX threading model does not support pthread_setname_np
// See for reference
// https://gitlab.freedesktop.org/mesa/mesa/-/blame/main/src/util/u_thread.c?ref_type=heads#L75
(void)name;
#else
pthread_setname_np(pthread_self(), name);
#endif
}
void PinCurrentThreadToPerformanceCore(size_t core_id) {
ASSERT(core_id < 4);
// If we set a flag for a CPU that doesn't exist, the thread may not be allowed to
// run in ANY processor!
auto const total_cores = std::thread::hardware_concurrency();
if (core_id < total_cores) {
#if defined(__linux__) || defined(__FreeBSD__)
cpu_set_t set;
CPU_ZERO(&set);
CPU_SET(core_id, &set);
pthread_setaffinity_np(pthread_self(), sizeof(set), &set);
#elif defined(_WIN32)
DWORD set = 1UL << core_id;
SetThreadAffinityMask(GetCurrentThread(), set);
#else
// No pin functionality implemented
#endif
}
}
} // namespace Common

View File

@@ -106,7 +106,7 @@ enum class ThreadPriority : u32 {
};
void SetCurrentThreadPriority(ThreadPriority new_priority);
void SetCurrentThreadName(const char* name);
void PinCurrentThreadToPerformanceCore(size_t core_id);
} // namespace Common

View File

@@ -17,6 +17,8 @@ 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

View File

@@ -6,6 +6,7 @@
#include <memory>
#include <utility>
#include "game_settings.h"
#include "audio_core/audio_core.h"
#include "common/fs/fs.h"
#include "common/logging/log.h"
@@ -292,48 +293,6 @@ 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) {
@@ -419,7 +378,8 @@ struct System::Impl {
LOG_ERROR(Core, "Failed to find program id for ROM");
}
LoadOverrides(program_id);
GameSettings::LoadOverrides(program_id, gpu_core->Renderer());
if (auto room_member = Network::GetRoomMember().lock()) {
Network::GameInfo game_info;
game_info.name = name;

View File

@@ -7,6 +7,7 @@
#include "common/fiber.h"
#include "common/scope_exit.h"
#include "common/thread.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/cpu_manager.h"
@@ -25,11 +26,8 @@ CpuManager::~CpuManager() = default;
void CpuManager::Initialize() {
num_cores = is_multicore ? Core::Hardware::NUM_CPU_CORES : 1;
gpu_barrier = std::make_unique<Common::Barrier>(num_cores + 1);
for (std::size_t core = 0; core < num_cores; core++) {
core_data[core].host_thread =
std::jthread([this, core](std::stop_token token) { RunThread(token, core); });
}
for (std::size_t core = 0; core < num_cores; core++)
core_data[core].host_thread = std::jthread([this, core](std::stop_token token) { RunThread(token, core); });
}
void CpuManager::Shutdown() {
@@ -188,14 +186,10 @@ void CpuManager::ShutdownThread() {
void CpuManager::RunThread(std::stop_token token, std::size_t core) {
/// Initialization
system.RegisterCoreThread(core);
std::string name;
if (is_multicore) {
name = "CPUCore_" + std::to_string(core);
} else {
name = "CPUThread";
}
std::string name = is_multicore ? ("CPUCore_" + std::to_string(core)) : std::string{"CPUThread"};
Common::SetCurrentThreadName(name.c_str());
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
Common::PinCurrentThreadToPerformanceCore(core);
auto& data = core_data[core];
data.host_context = Common::Fiber::ThreadToFiber();

140
src/core/game_settings.cpp Normal file
View File

@@ -0,0 +1,140 @@
// 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

60
src/core/game_settings.h Normal file
View File

@@ -0,0 +1,60 @@
// 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

View File

@@ -36,10 +36,6 @@ 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() {
@@ -80,11 +76,10 @@ 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) {
static constexpr s32 kOverlayBackgroundZ = -1;
(void)m_manager_display_service->SetLayerZIndex(kOverlayBackgroundZ, *out_layer_id);
(void)m_manager_display_service->SetLayerZIndex(-1, *out_layer_id);
(void)m_display_service->GetContainer()->SetLayerIsOverlay(*out_layer_id, true);
} else {
static constexpr s32 kOverlayZ = 3;
(void)m_manager_display_service->SetLayerZIndex(kOverlayZ, *out_layer_id);
(void)m_manager_display_service->SetLayerZIndex(1, *out_layer_id);
}
}
@@ -131,6 +126,7 @@ 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();

View File

@@ -24,7 +24,7 @@ namespace Service::AM {
{20, D<&IOverlayFunctions::SetHandlingHomeButtonShortPressedEnabled>, "SetHandlingHomeButtonShortPressedEnabled"},
{21, nullptr, "SetHandlingTouchScreenInputEnabled"},
{30, nullptr, "SetHealthWarningShowingState"},
{31, nullptr, "IsHealthWarningRequired"},
{31, D<&IOverlayFunctions::IsHealthWarningRequired>, "IsHealthWarningRequired"},
{40, nullptr, "GetApplicationNintendoLogo"},
{41, nullptr, "GetApplicationStartupMovie"},
{50, nullptr, "SetGpuTimeSliceBoostForApplication"},
@@ -69,12 +69,33 @@ namespace Service::AM {
Result IOverlayFunctions::GetApplicationIdForLogo(Out<u64> out_application_id) {
LOG_DEBUG(Service_AM, "called");
// Prefer explicit application_id if available, else fall back to program_id
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();
}
}
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();
}
@@ -86,6 +107,13 @@ 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};

View File

@@ -18,6 +18,7 @@ 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();

View File

@@ -186,8 +186,6 @@ 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);
@@ -393,7 +391,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 : -100000;
z_index = applet->overlay_in_foreground ? 100000 : -1;
} else if (now_foreground && !is_obscured) {
z_index = 2;
} else if (now_foreground) {

View File

@@ -1,3 +1,6 @@
// 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
@@ -67,7 +70,7 @@ public:
{20701, &IFriendService::GetPlayHistoryStatistics, "GetPlayHistoryStatistics"},
{20800, &IFriendService::LoadUserSetting, "LoadUserSetting"},
{20801, nullptr, "SyncUserSetting"},
{20900, nullptr, "RequestListSummaryOverlayNotification"},
{20900, &IFriendService::RequestListSummaryOverlayNotification, "RequestListSummaryOverlayNotification"},
{21000, nullptr, "GetExternalApplicationCatalog"},
{22000, nullptr, "GetReceivedFriendInvitationList"},
{22001, nullptr, "GetReceivedFriendInvitationDetailedInfo"},
@@ -317,6 +320,13 @@ 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");

View File

@@ -10,6 +10,8 @@
#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"
@@ -54,7 +56,7 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_
{37, nullptr, "ListRequiredVersion"},
{38, D<&IApplicationManagerInterface::CheckApplicationLaunchVersion>, "CheckApplicationLaunchVersion"},
{39, nullptr, "CheckApplicationLaunchRights"},
{40, nullptr, "GetApplicationLogoData"},
{40, D<&IApplicationManagerInterface::GetApplicationLogoData>, "GetApplicationLogoData"},
{41, nullptr, "CalculateApplicationDownloadRequiredSize"},
{42, nullptr, "CleanupSdCard"},
{43, D<&IApplicationManagerInterface::CheckSdCardMountStatus>, "CheckSdCardMountStatus"},
@@ -331,6 +333,53 @@ 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) {

View File

@@ -60,6 +60,10 @@ 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();

View File

@@ -179,4 +179,9 @@ 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

View File

@@ -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) {}
blending(LayerBlending::None), visible(true), z_index(0), is_overlay(false) {}
~Layer() {
buffer_item_consumer->Abandon();
}
@@ -25,6 +25,7 @@ struct Layer {
LayerBlending blending;
bool visible;
s32 z_index;
bool is_overlay;
};
struct LayerStack {

View File

@@ -78,8 +78,25 @@ 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 = this->CacheFramebufferLocked(*layer, consumer_id);
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);
// If we failed, skip this layer.
if (result == CacheStatus::NoBufferAvailable) {
@@ -111,6 +128,12 @@ 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);
@@ -138,33 +161,44 @@ 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.release_frame_number > m_frame_number || !framebuffer.is_acquired)
if (!framebuffer.is_acquired)
continue;
if (auto layer = display.stack.FindLayer(layer_id); layer)
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) {
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 at least one frame.
const u32 frame_advance = swap_interval.value_or(1);
m_frame_number += frame_advance;
// Advance by 1 frame (60 FPS compositing)
m_frame_number += 1;
// Release any necessary framebuffers.
// Release any necessary framebuffers (non-overlay layers only, as overlays are already released above).
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());
@@ -172,7 +206,7 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display,
}
}
return frame_advance;
return 1;
}
void HardwareComposer::RemoveLayerLocked(Display& display, ConsumerId consumer_id) {
@@ -200,8 +234,9 @@ bool HardwareComposer::TryAcquireFramebufferLocked(Layer& layer, Framebuffer& fr
}
// We succeeded, so set the new release frame info.
framebuffer.release_frame_number =
NormalizeSwapInterval(nullptr, framebuffer.item.swap_interval);
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.is_acquired = true;
return true;

View File

@@ -1,3 +1,6 @@
// 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
@@ -34,6 +37,7 @@ private:
struct Framebuffer {
android::BufferItem item{};
ReleaseFrameNumber release_frame_number{};
u64 last_acquire_frame{0};
bool is_acquired{false};
};

View File

@@ -1,3 +1,6 @@
// 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
@@ -101,6 +104,13 @@ 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) {

View File

@@ -47,6 +47,7 @@ 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);

View File

@@ -36,6 +36,29 @@ 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,
@@ -457,6 +480,7 @@ 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();
}
@@ -889,6 +913,7 @@ 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();
}
@@ -1224,6 +1249,7 @@ 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();
}

View File

@@ -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(), [this](PollFD pollfd) {
std::transform(fds.begin(), fds.end(), host_pollfds.begin(), [](PollFD pollfd) {
Network::PollFD result;
result.socket = file_descriptors[pollfd.fd]->socket.get();
result.events = Translate(pollfd.events);
@@ -657,7 +657,11 @@ 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) {
LOG_ERROR(Service, "Connect fd={} failed with errno={}", fd, static_cast<int>(result));
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));
}
} else {
LOG_INFO(Service, "Connect fd={} succeeded", fd);
}
@@ -967,7 +971,11 @@ Expected<s32, Errno> BSD::DuplicateSocketImpl(s32 fd) {
return Unexpected(Errno::MFILE);
}
file_descriptors[new_fd] = file_descriptors[fd];
file_descriptors[new_fd] = FileDescriptor{
.socket = file_descriptors[fd]->socket,
.flags = file_descriptors[fd]->flags,
.is_connection_based = file_descriptors[fd]->is_connection_based,
};
return new_fd;
}

View File

@@ -179,7 +179,7 @@ private:
void BuildErrnoResponse(HLERequestContext& ctx, Errno bsd_errno) const noexcept;
std::array<std::optional<FileDescriptor>, MAX_FD> file_descriptors;
static inline std::array<std::optional<FileDescriptor>, MAX_FD> file_descriptors{};
/// Callback to parse and handle a received wifi packet.
void OnProxyPacketReceived(const Network::ProxyPacket& packet);

View File

@@ -157,22 +157,24 @@ private:
auto bsd = system.ServiceManager().GetService<Service::Sockets::BSD>("bsd:u");
ASSERT_OR_EXECUTE(bsd, { return ResultInternalError; });
// Based on https://switchbrew.org/wiki/SSL_services#SetSocketDescriptor
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;
if (do_not_close_socket) {
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;
*out_fd = duplicated_fd;
} else {
*out_fd = -1;
fd_to_close = duplicated_fd;
}
std::optional<std::shared_ptr<Network::SocketBase>> sock = bsd->GetSocket(fd);
std::optional<std::shared_ptr<Network::SocketBase>> sock = bsd->GetSocket(duplicated_fd);
if (!sock.has_value()) {
LOG_ERROR(Service_SSL, "invalid socket fd {}", fd);
LOG_ERROR(Service_SSL, "invalid socket fd {} after duplication", duplicated_fd);
return ResultInvalidSocket;
}
socket = std::move(*sock);
@@ -325,7 +327,19 @@ private:
res = backend->GetServerCerts(&certs);
if (res == ResultSuccess) {
const std::vector<u8> certs_buf = SerializeServerCerts(certs);
ctx.WriteBuffer(certs_buf);
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());
}
out.certs_count = static_cast<u32>(certs.size());
out.certs_size = static_cast<u32>(certs_buf.size());
}
@@ -664,119 +678,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;
};
};

View File

@@ -1,3 +1,6 @@
// 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

View File

@@ -166,6 +166,16 @@ 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);

View File

@@ -67,6 +67,7 @@ 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);

View File

@@ -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 = 0x00000000; // ARGB/RGBA with alpha=0
for (; start < end; start++) {
*start = 0xFF0000FF;
}
}
@@ -257,7 +257,6 @@ 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;
@@ -374,11 +373,6 @@ 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();
}
@@ -415,51 +409,22 @@ 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* 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());
u8* start = m_system.DeviceMemory().GetPointer<u8>(block.GetAddress());
u8* end = m_system.DeviceMemory().GetPointer<u8>(block.GetAddress() + block.GetSize());
for (; ptr < block_end; ++ptr) {
for (; start < end; start++) {
*start = 0;
if (e >= 0 && e < static_cast<s64>(capture_buffer.size())) {
*ptr = capture_buffer[static_cast<size_t>(e)];
} else {
*ptr = 0;
*start = capture_buffer[e];
}
++e;
e++;
}
m_system.GPU().Host1x().MemoryManager().ApplyOpOnPointer(block_start, scratch, [&](DAddr addr) {
m_system.GPU().InvalidateRegion(addr, block_end - block_start);
m_system.GPU().Host1x().MemoryManager().ApplyOpOnPointer(start, scratch, [&](DAddr addr) {
m_system.GPU().InvalidateRegion(addr, end - 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();

View File

@@ -217,13 +217,13 @@ void A32EmitX64::ClearFastDispatchTable() {
}
void A32EmitX64::GenTerminalHandlers() {
// PC ends up in ebp, location_descriptor ends up in rbx
// PC ends up in edi, 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(ebp, ecx);
code.mov(edi, 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.jne(rsb_cache_miss, code.T_NEAR);
} else {
code.jne(code.GetReturnFromRunCodeAddress());
}
@@ -251,20 +251,21 @@ void A32EmitX64::GenTerminalHandlers() {
terminal_handler_fast_dispatch_hint = code.getCurr<const void*>();
calculate_location_descriptor();
code.L(rsb_cache_miss);
code.mov(r12, reinterpret_cast<u64>(fast_dispatch_table.data()));
code.mov(rbp, rbx);
code.mov(r8, reinterpret_cast<u64>(fast_dispatch_table.data()));
//code.mov(r12d, MJitStateReg(A32::Reg::PC));
code.mov(r12, rbx);
if (code.HasHostFeature(HostFeature::SSE42)) {
code.crc32(rbp, r12);
code.crc32(r12, r8);
}
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.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.L(fast_dispatch_cache_miss);
code.mov(qword[rbp + offsetof(FastDispatchEntry, location_descriptor)], rbx);
code.mov(qword[r12 + offsetof(FastDispatchEntry, location_descriptor)], rbx);
code.LookupBlock();
code.mov(ptr[rbp + offsetof(FastDispatchEntry, code_ptr)], rax);
code.mov(ptr[r12 + offsetof(FastDispatchEntry, code_ptr)], rax);
code.jmp(rax);
PerfMapRegister(terminal_handler_fast_dispatch_hint, code.getCurr(), "a32_terminal_handler_fast_dispatch_hint");

View File

@@ -188,13 +188,14 @@ void A64EmitX64::ClearFastDispatchTable() {
}
void A64EmitX64::GenTerminalHandlers() {
// PC ends up in rbp, location_descriptor ends up in rbx
// 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());
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(rbp, qword[code.ABI_JIT_PTR + offsetof(A64JitState, pc)]);
code.mov(rdi, qword[code.ABI_JIT_PTR + offsetof(A64JitState, pc)]);
code.mov(rcx, A64::LocationDescriptor::pc_mask);
code.and_(rcx, rbp);
code.and_(rcx, rdi);
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);
@@ -226,20 +227,21 @@ void A64EmitX64::GenTerminalHandlers() {
terminal_handler_fast_dispatch_hint = code.getCurr<const void*>();
calculate_location_descriptor();
code.L(rsb_cache_miss);
code.mov(r12, reinterpret_cast<u64>(fast_dispatch_table.data()));
code.mov(rbp, rbx);
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);
if (code.HasHostFeature(HostFeature::SSE42)) {
code.crc32(rbp, r12);
code.crc32(r12, r8);
}
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.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.L(fast_dispatch_cache_miss);
code.mov(qword[rbp + offsetof(FastDispatchEntry, location_descriptor)], rbx);
code.mov(qword[r12 + offsetof(FastDispatchEntry, location_descriptor)], rbx);
code.LookupBlock();
code.mov(ptr[rbp + offsetof(FastDispatchEntry, code_ptr)], rax);
code.mov(ptr[r12 + offsetof(FastDispatchEntry, code_ptr)], rax);
code.jmp(rax);
PerfMapRegister(terminal_handler_fast_dispatch_hint, code.getCurr(), "a64_terminal_handler_fast_dispatch_hint");

View File

@@ -370,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], static_cast<u32>(HaltReason::Step));
lock(); or_(dword[ABI_JIT_PTR + jsi.offsetof_halt_reason], u32(HaltReason::Step));
SwitchMxcsrOnEntry();
jmp(ABI_PARAM2);

View File

@@ -37,6 +37,9 @@
#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) {
@@ -65,7 +68,10 @@ void PrintA32Instruction(u32 instruction) {
fmt::print("should_continue: {}\n\n", should_continue);
fmt::print("IR:\n");
fmt::print("{}\n", IR::DumpBlock(ir_block));
Optimization::Optimize(ir_block, A32::UserConfig{}, {});
ArmTestEnv jit_env{};
Dynarmic::A32::UserConfig jit_user_config{};
jit_user_config.callbacks = &jit_env;
Optimization::Optimize(ir_block, jit_user_config, {});
fmt::print("Optimized IR:\n");
fmt::print("{}\n", IR::DumpBlock(ir_block));
}
@@ -80,7 +86,10 @@ void PrintA64Instruction(u32 instruction) {
fmt::print("should_continue: {}\n\n", should_continue);
fmt::print("IR:\n");
fmt::print("{}\n", IR::DumpBlock(ir_block));
Optimization::Optimize(ir_block, A64::UserConfig{}, {});
A64TestEnv jit_env{};
Dynarmic::A64::UserConfig jit_user_config{};
jit_user_config.callbacks = &jit_env;
Optimization::Optimize(ir_block, jit_user_config, {});
fmt::print("Optimized IR:\n");
fmt::print("{}\n", IR::DumpBlock(ir_block));
}
@@ -98,7 +107,10 @@ void PrintThumbInstruction(u32 instruction) {
fmt::print("should_continue: {}\n\n", should_continue);
fmt::print("IR:\n");
fmt::print("{}\n", IR::DumpBlock(ir_block));
Optimization::Optimize(ir_block, A32::UserConfig{}, {});
ThumbTestEnv jit_env{};
Dynarmic::A32::UserConfig jit_user_config{};
jit_user_config.callbacks = &jit_env;
Optimization::Optimize(ir_block, jit_user_config, {});
fmt::print("Optimized IR:\n");
fmt::print("{}\n", IR::DumpBlock(ir_block));
}
@@ -219,7 +231,7 @@ void ExecuteA32Instruction(u32 instruction) {
*(iter->second) = *value;
fmt::print("> {} = 0x{:08x}\n", reg_name, *value);
}
} else if (reg_name == "mem" || reg_name == "memory") {
} else if (reg_name.starts_with("m")) {
fmt::print("address: ");
if (const auto address = get_value()) {
fmt::print("value: ");
@@ -228,7 +240,7 @@ void ExecuteA32Instruction(u32 instruction) {
fmt::print("> mem[0x{:08x}] = 0x{:08x}\n", *address, *value);
}
}
} else if (reg_name == "end") {
} else if (reg_name == "exit" || reg_name == "end" || reg_name.starts_with("q")) {
break;
}
}
@@ -244,6 +256,7 @@ 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) {

View File

@@ -97,28 +97,8 @@ VkDescriptorSet DescriptorAllocator::Commit() {
return sets[index / SETS_GROW_RATE][index % SETS_GROW_RATE];
}
VkDescriptorSet DescriptorAllocator::CommitWithTracking(u64 current_frame, const void* descriptor_data) {
const size_t index = CommitResource();
const size_t group = index / SETS_GROW_RATE;
const size_t slot = index % SETS_GROW_RATE;
set_states[group][slot].set = sets[group][slot];
set_states[group][slot].last_update_frame = current_frame;
set_states[group][slot].last_data_ptr = descriptor_data;
return set_states[group][slot].set;
}
bool DescriptorAllocator::NeedsUpdate(VkDescriptorSet set, u64 current_frame, const void* descriptor_data) const {
for (const auto& group : set_states)
for (const auto& st : group)
if (st.set == set) // Update if pointer changed or the set hasn't been updated this frame
return st.last_data_ptr != descriptor_data || st.last_update_frame != current_frame;
return true;
}
void DescriptorAllocator::Allocate(size_t begin, size_t end) {
const size_t count = end - begin;
sets.push_back(AllocateDescriptors(count));
set_states.emplace_back(count); // create parallel state storage
sets.push_back(AllocateDescriptors(end - begin));
}
vk::DescriptorSets DescriptorAllocator::AllocateDescriptors(size_t count) {

View File

@@ -1,6 +1,3 @@
// 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
@@ -9,7 +6,7 @@
#include <shared_mutex>
#include <span>
#include <vector>
#include "common/common_types.h"
#include "shader_recompiler/shader_info.h"
#include "video_core/renderer_vulkan/vk_resource_pool.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
@@ -47,15 +44,7 @@ public:
DescriptorAllocator(const DescriptorAllocator&) = delete;
VkDescriptorSet Commit();
// commit + remember when/with what we last updated this set
struct DescriptorSetState {
VkDescriptorSet set = VK_NULL_HANDLE;
u64 last_update_frame = 0;
const void* last_data_ptr = nullptr; // fast pointer compare
};
VkDescriptorSet CommitWithTracking(u64 current_frame, const void* descriptor_data);
bool NeedsUpdate(VkDescriptorSet set, u64 current_frame, const void* descriptor_data) const;
private:
explicit DescriptorAllocator(const Device& device_, MasterSemaphore& master_semaphore_,
DescriptorBank& bank_, VkDescriptorSetLayout layout_);
@@ -69,7 +58,6 @@ private:
VkDescriptorSetLayout layout{};
std::vector<vk::DescriptorSets> sets;
std::vector<std::vector<DescriptorSetState>> set_states;
};
class DescriptorPool {

View File

@@ -515,20 +515,7 @@ void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling,
const bool update_rescaling{scheduler.UpdateRescaling(is_rescaling)};
const bool bind_pipeline{scheduler.UpdateGraphicsPipeline(this)};
const void* const descriptor_data{guest_descriptor_queue.UpdateData()};
// allocate/bind descriptor set and only update if needed
VkDescriptorSet descriptor_set = VK_NULL_HANDLE;
bool needs_update = false;
if (descriptor_set_layout && !uses_push_descriptor) {
descriptor_set = descriptor_allocator.CommitWithTracking(current_frame_number, descriptor_data);
needs_update = descriptor_allocator.NeedsUpdate(descriptor_set, current_frame_number, descriptor_data);
if (needs_update) {
const vk::Device& dev{device.GetLogical()};
dev.UpdateDescriptorSet(descriptor_set, *descriptor_update_template, descriptor_data);
}
}
scheduler.Record([this, descriptor_data, descriptor_set, bind_pipeline, rescaling_data = rescaling.Data(),
scheduler.Record([this, descriptor_data, bind_pipeline, rescaling_data = rescaling.Data(),
is_rescaling, update_rescaling,
uses_render_area = render_area.uses_render_area,
render_area_data = render_area.words](vk::CommandBuffer cmdbuf) {
@@ -554,8 +541,12 @@ void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling,
return;
}
if (uses_push_descriptor) {
cmdbuf.PushDescriptorSetWithTemplateKHR(*descriptor_update_template, *pipeline_layout, 0, descriptor_data);
cmdbuf.PushDescriptorSetWithTemplateKHR(*descriptor_update_template, *pipeline_layout,
0, descriptor_data);
} else {
const VkDescriptorSet descriptor_set{descriptor_allocator.Commit()};
const vk::Device& dev{device.GetLogical()};
dev.UpdateDescriptorSet(descriptor_set, *descriptor_update_template, descriptor_data);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline_layout, 0,
descriptor_set, nullptr);
}

View File

@@ -116,7 +116,7 @@ public:
maxwell3d = maxwell3d_;
gpu_memory = gpu_memory_;
}
void SetFrameNumber(u64 frame) { current_frame_number = frame; }
private:
template <typename Spec>
bool ConfigureImpl(bool is_indexed);
@@ -160,7 +160,6 @@ private:
std::mutex build_mutex;
std::atomic_bool is_built{false};
bool uses_push_descriptor{false};
u64 current_frame_number{0};
};
} // namespace Vulkan

View File

@@ -234,7 +234,6 @@ void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) {
std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
// update engine as channel may be different.
pipeline->SetEngine(maxwell3d, gpu_memory);
pipeline->SetFrameNumber(current_frame_number);
if (!pipeline->Configure(is_indexed))
return;
@@ -772,7 +771,6 @@ void RasterizerVulkan::FlushCommands() {
}
void RasterizerVulkan::TickFrame() {
current_frame_number++;
draw_counter = 0;
guest_descriptor_queue.TickFrame();
compute_pass_descriptor_queue.TickFrame();

View File

@@ -160,7 +160,7 @@ private:
void UpdateDynamicStates();
void HandleTransformFeedback();
u64 current_frame_number{0};
void UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateScissorsState(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateDepthBias(Tegra::Engines::Maxwell3D::Regs& regs);

View File

@@ -20,18 +20,12 @@
namespace Vulkan {
namespace {
[[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;
}
[[nodiscard]] bool AreExtensionsSupported(const vk::InstanceDispatch& dld, std::vector<VkExtensionProperties> const& properties, std::span<const char* const> extensions) {
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;
}
@@ -78,14 +72,16 @@ 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, std::array{VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME})) {
extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
}
if (AreExtensionsSupported(dld, *properties, std::array{VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME}))
extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
#endif
if (enable_validation &&
AreExtensionsSupported(dld, std::array{VK_EXT_DEBUG_UTILS_EXTENSION_NAME})) {
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
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);
}
return extensions;
}
@@ -133,11 +129,10 @@ 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);
}
const std::vector<const char*> extensions =
RequiredExtensions(dld, window_type, enable_validation);
if (!AreExtensionsSupported(dld, extensions)) {
std::vector<const char*> const extensions = RequiredExtensions(dld, window_type, enable_validation);
auto const properties = vk::EnumerateInstanceExtensionProperties(dld);
if (!properties || !AreExtensionsSupported(dld, *properties, extensions))
throw vk::Exception(VK_ERROR_EXTENSION_NOT_PRESENT);
}
std::vector<const char*> layers = Layers(enable_validation);
RemoveUnavailableLayers(dld, layers);

View File

@@ -128,7 +128,7 @@ li.checked::marker { content: &quot;\2612&quot;; }
<item>
<widget class="QLabel" name="labelLinks">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://eden-emulator.github.io/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://git.eden-emu.dev&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Source Code&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://git.eden-emu.dev/eden-emu/eden/activity/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributors&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://discord.gg/HstXbPch7X&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Discord&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://rvlt.gg/qKgFEAbH&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Revolt&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://nitter.poast.org/edenemuofficial&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Twitter&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://git.eden-emu.dev/eden-emu/eden/src/branch/master/LICENSE.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;License&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://eden-emulator.github.io/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://git.eden-emu.dev&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Source Code&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://git.eden-emu.dev/eden-emu/eden/activity/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributors&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://discord.gg/HstXbPch7X&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Discord&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://stt.gg/qKgFEAbH&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Stoat&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://nitter.poast.org/edenemuofficial&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Twitter&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://git.eden-emu.dev/eden-emu/eden/src/branch/master/LICENSE.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;License&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="openExternalLinks">
<bool>true</bool>

View File

@@ -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/Revolt for help.",
tr("%1<br>Please redump your files or ask on Discord/Stoat for help.",
"%1 signifies an error string.")
.arg(QString::fromStdString(
GetResultStatusString(static_cast<Loader::ResultStatus>(error_id))));

View File

@@ -1,59 +0,0 @@
#!/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