mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-29 09:58:05 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 17c5325109 | |||
| 2876c4ec52 |
+8
-24
@@ -43,7 +43,6 @@ This guide will walk you through adding a new boolean toggle setting to Eden's c
|
|||||||
Firstly add your desired toggle:
|
Firstly add your desired toggle:
|
||||||
|
|
||||||
Example: `src/common/setting.h`
|
Example: `src/common/setting.h`
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
SwitchableSetting<bool> your_setting_name{linkage, false, "your_setting_name", Category::RendererExtensions};
|
SwitchableSetting<bool> your_setting_name{linkage, false, "your_setting_name", Category::RendererExtensions};
|
||||||
```
|
```
|
||||||
@@ -68,7 +67,6 @@ Common Categories:
|
|||||||
Add the toggle to the Qt UI, where you wish for it to appear and place it there.
|
Add the toggle to the Qt UI, where you wish for it to appear and place it there.
|
||||||
|
|
||||||
Example: `src/qt_common/config/shared_translation.cpp`
|
Example: `src/qt_common/config/shared_translation.cpp`
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
INSERT(Settings,
|
INSERT(Settings,
|
||||||
your_setting_name,
|
your_setting_name,
|
||||||
@@ -93,7 +91,6 @@ INSERT(Settings,
|
|||||||
Add where it should be in the settings.
|
Add where it should be in the settings.
|
||||||
|
|
||||||
Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt`
|
Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt`
|
||||||
|
|
||||||
```kts
|
```kts
|
||||||
RENDERER_YOUR_SETTING_NAME("your_setting_name"),
|
RENDERER_YOUR_SETTING_NAME("your_setting_name"),
|
||||||
```
|
```
|
||||||
@@ -109,7 +106,6 @@ RENDERER_YOUR_SETTING_NAME("your_setting_name"),
|
|||||||
Add the toggle to the Kotlin (Android) UI
|
Add the toggle to the Kotlin (Android) UI
|
||||||
|
|
||||||
Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt`
|
Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt`
|
||||||
|
|
||||||
```kts
|
```kts
|
||||||
put(
|
put(
|
||||||
SwitchSetting(
|
SwitchSetting(
|
||||||
@@ -127,7 +123,6 @@ put(
|
|||||||
Add your setting within the right category.
|
Add your setting within the right category.
|
||||||
|
|
||||||
Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt`
|
Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt`
|
||||||
|
|
||||||
```kts
|
```kts
|
||||||
add(BooleanSetting.RENDERER_YOUR_SETTING_NAME.key)
|
add(BooleanSetting.RENDERER_YOUR_SETTING_NAME.key)
|
||||||
```
|
```
|
||||||
@@ -142,7 +137,6 @@ add(BooleanSetting.RENDERER_YOUR_SETTING_NAME.key)
|
|||||||
Add your setting and description in the appropriate place.
|
Add your setting and description in the appropriate place.
|
||||||
|
|
||||||
Example: `src/android/app/src/main/res/values/strings.xml`
|
Example: `src/android/app/src/main/res/values/strings.xml`
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<string name="your_setting_name">Your Setting Display Name</string>
|
<string name="your_setting_name">Your Setting Display Name</string>
|
||||||
<string name="your_setting_name_description">Detailed description of what this setting does. Explain any caveats, requirements, or warnings here.</string>
|
<string name="your_setting_name_description">Detailed description of what this setting does. Explain any caveats, requirements, or warnings here.</string>
|
||||||
@@ -156,7 +150,6 @@ Now the UI part is done find a place in the code for the toggle,
|
|||||||
And use it to your heart's desire!
|
And use it to your heart's desire!
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
const bool your_value = Settings::values.your_setting_name.GetValue();
|
const bool your_value = Settings::values.your_setting_name.GetValue();
|
||||||
|
|
||||||
@@ -203,31 +196,25 @@ Common advantages recap:
|
|||||||
|
|
||||||
#### Accessing Debug Knobs (dev side)
|
#### Accessing Debug Knobs (dev side)
|
||||||
|
|
||||||
Use the `Settings::GetDebugKnobAt(u8 i)` function to check if a specific bit is set:
|
Use the `Settings::getDebugKnobAt(u8 i)` function to check if a specific bit is set:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
//cpp side
|
//cpp side
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
|
|
||||||
//To use it as a general purpose uint var:
|
|
||||||
unsigned int debug_knobs = Settings::values.debug_knobs.GetValue();
|
|
||||||
|
|
||||||
// Check if bit 0 is set
|
// Check if bit 0 is set
|
||||||
bool feature_enabled = Settings::GetDebugKnobAt(0);
|
bool feature_enabled = Settings::getDebugKnobAt(0);
|
||||||
|
|
||||||
// Check if bit 15 is set
|
// Check if bit 15 is set
|
||||||
bool another_feature = Settings::GetDebugKnobAt(15);
|
bool another_feature = Settings::getDebugKnobAt(15);
|
||||||
```
|
```
|
||||||
|
|
||||||
```kts
|
```kts
|
||||||
//kotlin side
|
//kotlin side
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||||
|
|
||||||
//To use it as a general purpose uint var
|
|
||||||
val debug_knobs: Int = UShortSetting.DEBUG_KNOBS.getInt()
|
|
||||||
|
|
||||||
// Check if bit x is set
|
// Check if bit x is set
|
||||||
bool feature_enabled = Settings.GetDebugKnobAt(x); //x as integer from 0 to 15
|
bool feature_enabled = Settings.getDebugKnobAt(x); //x as integer from 0 to 15
|
||||||
```
|
```
|
||||||
|
|
||||||
The function returns `true` if the specified bit (0-15) is set in the `debug_knobs` value, `false` otherwise.
|
The function returns `true` if the specified bit (0-15) is set in the `debug_knobs` value, `false` otherwise.
|
||||||
@@ -260,7 +247,6 @@ There are two main confusions when talking about knobs:
|
|||||||
Sometimes when an user reports: knobs 1 and 2 gets better performance, dev may get confuse whether he means the knobs 1 and 2 literally, or the 1st and 2nd knobs (knobs 0 and 1).
|
Sometimes when an user reports: knobs 1 and 2 gets better performance, dev may get confuse whether he means the knobs 1 and 2 literally, or the 1st and 2nd knobs (knobs 0 and 1).
|
||||||
|
|
||||||
Debug knobs are **zero-based**, which means:
|
Debug knobs are **zero-based**, which means:
|
||||||
|
|
||||||
* The first knob is the knob(0) (or knob0 henceforth), and the last one is the 15 (knob15, likewise)
|
* The first knob is the knob(0) (or knob0 henceforth), and the last one is the 15 (knob15, likewise)
|
||||||
* You can talk: "knob0 is enabled/disabled", "In this video i was using only knobs 0 and 2", etc.
|
* You can talk: "knob0 is enabled/disabled", "In this video i was using only knobs 0 and 2", etc.
|
||||||
|
|
||||||
@@ -273,7 +259,6 @@ Whenever you're instructing tests or reporting results, be precise about whether
|
|||||||
|
|
||||||
ALWAYS use the word in PLURAL (knobs), without mentioning which one, to refer to the setting, aka multiple knobs at once:
|
ALWAYS use the word in PLURAL (knobs), without mentioning which one, to refer to the setting, aka multiple knobs at once:
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
- **knobs=0**: no knobs enabled
|
- **knobs=0**: no knobs enabled
|
||||||
- **knobs=1**: knob0 enabled, others disabled
|
- **knobs=1**: knob0 enabled, others disabled
|
||||||
- **knobs=2**: knob1 enabled, others disabled
|
- **knobs=2**: knob1 enabled, others disabled
|
||||||
@@ -285,7 +270,6 @@ Examples:
|
|||||||
|
|
||||||
Use the word in SINGULAR (knob), or in plural but referring which ones, when meaning multiple knobs at once:
|
Use the word in SINGULAR (knob), or in plural but referring which ones, when meaning multiple knobs at once:
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
- **knob0**: knob 0 enabled, others disabled
|
- **knob0**: knob 0 enabled, others disabled
|
||||||
- **knob1**: knob 1 enabled, others disabled
|
- **knob1**: knob 1 enabled, others disabled
|
||||||
- **knobs 0 and 1**: knobs 0 and 1 enabled, others disabled
|
- **knobs 0 and 1**: knobs 0 and 1 enabled, others disabled
|
||||||
@@ -298,12 +282,12 @@ Examples:
|
|||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
void SomeFunction() {
|
void SomeFunction() {
|
||||||
if (Settings::GetDebugKnobAt(0)) {
|
if (Settings::getDebugKnobAt(0)) {
|
||||||
LOG_DEBUG(Common, "Debug feature 0 is enabled");
|
LOG_DEBUG(Common, "Debug feature 0 is enabled");
|
||||||
// Additional debug code here
|
// Additional debug code here
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Settings::GetDebugKnobAt(1)) {
|
if (Settings::getDebugKnobAt(1)) {
|
||||||
LOG_DEBUG(Common, "Debug feature 1 is enabled");
|
LOG_DEBUG(Common, "Debug feature 1 is enabled");
|
||||||
// Different debug behavior
|
// Different debug behavior
|
||||||
}
|
}
|
||||||
@@ -315,7 +299,7 @@ void SomeFunction() {
|
|||||||
```cpp
|
```cpp
|
||||||
bool UseOptimizedPath() {
|
bool UseOptimizedPath() {
|
||||||
// Skip optimization if debug bit 2 is set for testing
|
// Skip optimization if debug bit 2 is set for testing
|
||||||
return !Settings::GetDebugKnobAt(2);
|
return !Settings::getDebugKnobAt(2);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -325,7 +309,7 @@ bool UseOptimizedPath() {
|
|||||||
void ExperimentalFeature() {
|
void ExperimentalFeature() {
|
||||||
static constexpr u8 EXPERIMENTAL_FEATURE_BIT = 3;
|
static constexpr u8 EXPERIMENTAL_FEATURE_BIT = 3;
|
||||||
|
|
||||||
if (!Settings::GetDebugKnobAt(EXPERIMENTAL_FEATURE_BIT)) {
|
if (!Settings::getDebugKnobAt(EXPERIMENTAL_FEATURE_BIT)) {
|
||||||
// Fallback to stable implementation
|
// Fallback to stable implementation
|
||||||
StableImplementation();
|
StableImplementation();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ object NativeLibrary {
|
|||||||
|
|
||||||
external fun refreshThreadPolicies()
|
external fun refreshThreadPolicies()
|
||||||
|
|
||||||
external fun GetDebugKnobAt(index: Int): Boolean
|
external fun getDebugKnobAt(index: Int): Boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the current speed limit to the configured turbo speed.
|
* Set the current speed limit to the configured turbo speed.
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ object Settings {
|
|||||||
fun getPlayerString(player: Int): String =
|
fun getPlayerString(player: Int): String =
|
||||||
YuzuApplication.appContext.getString(R.string.preferences_player, player)
|
YuzuApplication.appContext.getString(R.string.preferences_player, player)
|
||||||
|
|
||||||
fun GetDebugKnobAt(index: Int): Boolean {
|
fun getDebugKnobAt(index: Int): Boolean {
|
||||||
return org.yuzu.yuzu_emu.NativeLibrary.GetDebugKnobAt(index)
|
return org.yuzu.yuzu_emu.NativeLibrary.getDebugKnobAt(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
|
const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
|
||||||
|
|||||||
+2
-1
@@ -11,7 +11,8 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
|
|||||||
enum class ShortSetting(override val key: String) : AbstractShortSetting {
|
enum class ShortSetting(override val key: String) : AbstractShortSetting {
|
||||||
RENDERER_SPEED_LIMIT("speed_limit"),
|
RENDERER_SPEED_LIMIT("speed_limit"),
|
||||||
RENDERER_TURBO_SPEED_LIMIT("turbo_speed_limit"),
|
RENDERER_TURBO_SPEED_LIMIT("turbo_speed_limit"),
|
||||||
RENDERER_SLOW_SPEED_LIMIT("slow_speed_limit")
|
RENDERER_SLOW_SPEED_LIMIT("slow_speed_limit"),
|
||||||
|
DEBUG_KNOBS("debug_knobs")
|
||||||
;
|
;
|
||||||
|
|
||||||
override fun getShort(needsGlobal: Boolean): Short = NativeConfig.getShort(key, needsGlobal)
|
override fun getShort(needsGlobal: Boolean): Short = NativeConfig.getShort(key, needsGlobal)
|
||||||
|
|||||||
-30
@@ -1,30 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.model
|
|
||||||
|
|
||||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
|
||||||
|
|
||||||
enum class UShortSetting(override val key: String) : AbstractIntSetting {
|
|
||||||
DEBUG_KNOBS("debug_knobs")
|
|
||||||
;
|
|
||||||
|
|
||||||
override fun getInt(needsGlobal: Boolean): Int =
|
|
||||||
NativeConfig.getUnsignedShort(key, needsGlobal)
|
|
||||||
|
|
||||||
override fun setInt(value: Int) {
|
|
||||||
if (NativeConfig.isPerGameConfigLoaded()) {
|
|
||||||
global = false
|
|
||||||
}
|
|
||||||
NativeConfig.setUnsignedShort(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
override val defaultValue: Int by lazy { NativeConfig.getDefaultToString(key).toInt() }
|
|
||||||
|
|
||||||
override fun getValueAsString(needsGlobal: Boolean): String = getInt(needsGlobal).toString()
|
|
||||||
|
|
||||||
override fun reset() = NativeConfig.setUnsignedShort(key, defaultValue)
|
|
||||||
}
|
|
||||||
+1
-3
@@ -20,7 +20,6 @@ import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
|||||||
import org.yuzu.yuzu_emu.features.settings.model.LongSetting
|
import org.yuzu.yuzu_emu.features.settings.model.LongSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
|
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
|
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.UShortSetting
|
|
||||||
import org.yuzu.yuzu_emu.network.NetDataValidators
|
import org.yuzu.yuzu_emu.network.NetDataValidators
|
||||||
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
|
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
|
||||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
@@ -625,7 +624,6 @@ abstract class SettingsItem(
|
|||||||
IntSetting.FSR_SHARPENING_SLIDER,
|
IntSetting.FSR_SHARPENING_SLIDER,
|
||||||
titleId = R.string.fsr_sharpness,
|
titleId = R.string.fsr_sharpness,
|
||||||
descriptionId = R.string.fsr_sharpness_description,
|
descriptionId = R.string.fsr_sharpness_description,
|
||||||
max = 200,
|
|
||||||
units = "%"
|
units = "%"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -1035,7 +1033,7 @@ abstract class SettingsItem(
|
|||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SpinBoxSetting(
|
SpinBoxSetting(
|
||||||
UShortSetting.DEBUG_KNOBS,
|
ShortSetting.DEBUG_KNOBS,
|
||||||
titleId = R.string.debug_knobs,
|
titleId = R.string.debug_knobs,
|
||||||
descriptionId = R.string.debug_knobs_description,
|
descriptionId = R.string.debug_knobs_description,
|
||||||
valueHint = R.string.debug_knobs_hint,
|
valueHint = R.string.debug_knobs_hint,
|
||||||
|
|||||||
+3
-8
@@ -25,7 +25,6 @@ import org.yuzu.yuzu_emu.features.settings.model.Settings
|
|||||||
import org.yuzu.yuzu_emu.features.settings.model.Settings.MenuTag
|
import org.yuzu.yuzu_emu.features.settings.model.Settings.MenuTag
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
|
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
|
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.UShortSetting
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.*
|
import org.yuzu.yuzu_emu.features.settings.model.view.*
|
||||||
import org.yuzu.yuzu_emu.utils.InputHandler
|
import org.yuzu.yuzu_emu.utils.InputHandler
|
||||||
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
|
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
|
||||||
@@ -100,12 +99,7 @@ class SettingsFragmentPresenter(
|
|||||||
|
|
||||||
add(BooleanSetting.RENDERER_FRAME_GEN.key)
|
add(BooleanSetting.RENDERER_FRAME_GEN.key)
|
||||||
add(IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.key)
|
add(IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.key)
|
||||||
if (IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.getInt(
|
|
||||||
getNeedsGlobalForKey(IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.key)
|
|
||||||
) == 0
|
|
||||||
) {
|
|
||||||
add(IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.key)
|
add(IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.key)
|
||||||
}
|
|
||||||
add(IntSetting.RENDERER_FRAME_GEN_QUEUE_TARGET.key)
|
add(IntSetting.RENDERER_FRAME_GEN_QUEUE_TARGET.key)
|
||||||
add(BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.key)
|
add(BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.key)
|
||||||
if (!BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.getBoolean(
|
if (!BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.getBoolean(
|
||||||
@@ -313,6 +307,8 @@ class SettingsFragmentPresenter(
|
|||||||
// TODO(crueter): sub-submenus?
|
// TODO(crueter): sub-submenus?
|
||||||
private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
|
private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
|
||||||
sl.apply {
|
sl.apply {
|
||||||
|
// add(IntSetting.RENDERER_NVDEC_EMULATION.key)
|
||||||
|
|
||||||
add(IntSetting.RENDERER_RESOLUTION.key)
|
add(IntSetting.RENDERER_RESOLUTION.key)
|
||||||
add(IntSetting.RENDERER_VSYNC.key)
|
add(IntSetting.RENDERER_VSYNC.key)
|
||||||
add(IntSetting.RENDERER_SCALING_FILTER.key)
|
add(IntSetting.RENDERER_SCALING_FILTER.key)
|
||||||
@@ -329,7 +325,6 @@ class SettingsFragmentPresenter(
|
|||||||
add(IntSetting.MAX_ANISOTROPY.key)
|
add(IntSetting.MAX_ANISOTROPY.key)
|
||||||
add(IntSetting.RENDERER_VRAM_USAGE_MODE.key)
|
add(IntSetting.RENDERER_VRAM_USAGE_MODE.key)
|
||||||
add(IntSetting.RENDERER_ASTC_DECODE_METHOD.key)
|
add(IntSetting.RENDERER_ASTC_DECODE_METHOD.key)
|
||||||
add(IntSetting.RENDERER_NVDEC_EMULATION.key)
|
|
||||||
|
|
||||||
add(BooleanSetting.SYNC_MEMORY_OPERATIONS.key)
|
add(BooleanSetting.SYNC_MEMORY_OPERATIONS.key)
|
||||||
add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key)
|
add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key)
|
||||||
@@ -1327,7 +1322,7 @@ class SettingsFragmentPresenter(
|
|||||||
|
|
||||||
add(HeaderSetting(R.string.general))
|
add(HeaderSetting(R.string.general))
|
||||||
|
|
||||||
add(UShortSetting.DEBUG_KNOBS.key)
|
add(ShortSetting.DEBUG_KNOBS.key)
|
||||||
add(StringSetting.PROGRAM_ARGS.key)
|
add(StringSetting.PROGRAM_ARGS.key)
|
||||||
|
|
||||||
if (!NativeConfig.isPerGameConfigLoaded()) {
|
if (!NativeConfig.isPerGameConfigLoaded()) {
|
||||||
|
|||||||
@@ -1182,7 +1182,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
container,
|
container,
|
||||||
IntSetting.FSR_SHARPENING_SLIDER,
|
IntSetting.FSR_SHARPENING_SLIDER,
|
||||||
minValue = 0,
|
minValue = 0,
|
||||||
maxValue = 200,
|
maxValue = 100,
|
||||||
units = "%"
|
units = "%"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import androidx.activity.result.contract.ActivityResultContracts
|
|||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.doOnPreDraw
|
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.core.widget.doOnTextChanged
|
import androidx.core.widget.doOnTextChanged
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
@@ -60,10 +59,6 @@ class GamesFragment : Fragment() {
|
|||||||
|
|
||||||
private var lastViewType: Int = GameAdapter.VIEW_TYPE_GRID
|
private var lastViewType: Int = GameAdapter.VIEW_TYPE_GRID
|
||||||
private var fallbackBottomInset: Int = 0
|
private var fallbackBottomInset: Int = 0
|
||||||
private var pendingPostReloadListSettle = false
|
|
||||||
private var pendingPostReloadListSettleGeneration = 0
|
|
||||||
private var gameListSubmitGeneration = 0
|
|
||||||
private var committedGameListSubmitGeneration = 0
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val SEARCH_TEXT = "SearchText"
|
private const val SEARCH_TEXT = "SearchText"
|
||||||
@@ -173,9 +168,10 @@ class GamesFragment : Fragment() {
|
|||||||
|
|
||||||
gamesViewModel.shouldScrollAfterReload.collect(viewLifecycleOwner) { shouldScroll ->
|
gamesViewModel.shouldScrollAfterReload.collect(viewLifecycleOwner) { shouldScroll ->
|
||||||
if (shouldScroll) {
|
if (shouldScroll) {
|
||||||
pendingPostReloadListSettle = true
|
binding.gridGames.post {
|
||||||
pendingPostReloadListSettleGeneration = gameListSubmitGeneration
|
(binding.gridGames as? CarouselRecyclerView)?.pendingScrollAfterReload = true
|
||||||
schedulePostReloadListSettle()
|
gameAdapter.notifyDataSetChanged()
|
||||||
|
}
|
||||||
gamesViewModel.setShouldScrollAfterReload(false)
|
gamesViewModel.setShouldScrollAfterReload(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,42 +273,11 @@ class GamesFragment : Fragment() {
|
|||||||
lastSearchText = currentSearchText
|
lastSearchText = currentSearchText
|
||||||
lastFilter = currentFilter
|
lastFilter = currentFilter
|
||||||
} else {
|
} else {
|
||||||
submitGameList(games)
|
((binding.gridGames as? RecyclerView)?.adapter as? GameAdapter)?.submitList(games)
|
||||||
gamesViewModel.setFilteredGames(games)
|
gamesViewModel.setFilteredGames(games)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun submitGameList(games: List<Game>) {
|
|
||||||
val adapter = (binding.gridGames as? RecyclerView)?.adapter as? GameAdapter
|
|
||||||
if (adapter == null) {
|
|
||||||
schedulePostReloadListSettle()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val submitGeneration = ++gameListSubmitGeneration
|
|
||||||
adapter.submitList(games) {
|
|
||||||
if (committedGameListSubmitGeneration < submitGeneration) {
|
|
||||||
committedGameListSubmitGeneration = submitGeneration
|
|
||||||
}
|
|
||||||
schedulePostReloadListSettle()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private fun schedulePostReloadListSettle() {
|
|
||||||
if (!pendingPostReloadListSettle || _binding == null) return
|
|
||||||
|
|
||||||
binding.gridGames.doOnPreDraw {
|
|
||||||
if (!pendingPostReloadListSettle || _binding == null) return@doOnPreDraw
|
|
||||||
if (committedGameListSubmitGeneration < pendingPostReloadListSettleGeneration) {
|
|
||||||
schedulePostReloadListSettle()
|
|
||||||
return@doOnPreDraw
|
|
||||||
}
|
|
||||||
pendingPostReloadListSettle = false
|
|
||||||
|
|
||||||
(binding.gridGames as? CarouselRecyclerView)?.refreshView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private fun setupTopView() {
|
private fun setupTopView() {
|
||||||
binding.searchText.doOnTextChanged() { text: CharSequence?, _: Int, _: Int, _: Int ->
|
binding.searchText.doOnTextChanged() { text: CharSequence?, _: Int, _: Int, _: Int ->
|
||||||
if (text.toString().isNotEmpty()) {
|
if (text.toString().isNotEmpty()) {
|
||||||
@@ -449,7 +414,9 @@ class GamesFragment : Fragment() {
|
|||||||
|
|
||||||
val searchTerm = binding.searchText.text.toString().lowercase(Locale.getDefault())
|
val searchTerm = binding.searchText.text.toString().lowercase(Locale.getDefault())
|
||||||
if (searchTerm.isEmpty()) {
|
if (searchTerm.isEmpty()) {
|
||||||
submitGameList(filteredList)
|
((binding.gridGames as? RecyclerView)?.adapter as? GameAdapter)?.submitList(
|
||||||
|
filteredList
|
||||||
|
)
|
||||||
gamesViewModel.setFilteredGames(filteredList)
|
gamesViewModel.setFilteredGames(filteredList)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -465,7 +432,7 @@ class GamesFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}.sortedByDescending { it.score }.map { it.item }
|
}.sortedByDescending { it.score }.map { it.item }
|
||||||
|
|
||||||
submitGameList(sortedList)
|
((binding.gridGames as? RecyclerView)?.adapter as? GameAdapter)?.submitList(sortedList)
|
||||||
gamesViewModel.setFilteredGames(sortedList)
|
gamesViewModel.setFilteredGames(sortedList)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ object GameHelper {
|
|||||||
|
|
||||||
fun getGames(): List<Game> {
|
fun getGames(): List<Game> {
|
||||||
val games = mutableListOf<Game>()
|
val games = mutableListOf<Game>()
|
||||||
val gamesByProgramId = mutableMapOf<String, Game>()
|
|
||||||
val context = YuzuApplication.appContext
|
val context = YuzuApplication.appContext
|
||||||
preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
|
||||||
@@ -64,7 +63,6 @@ object GameHelper {
|
|||||||
|
|
||||||
addGamesRecursive(
|
addGamesRecursive(
|
||||||
games,
|
games,
|
||||||
gamesByProgramId,
|
|
||||||
FileUtil.listFiles(gameDirUri),
|
FileUtil.listFiles(gameDirUri),
|
||||||
scanDepth,
|
scanDepth,
|
||||||
mountedContainerUris
|
mountedContainerUris
|
||||||
@@ -138,7 +136,6 @@ object GameHelper {
|
|||||||
|
|
||||||
private fun addGamesRecursive(
|
private fun addGamesRecursive(
|
||||||
games: MutableList<Game>,
|
games: MutableList<Game>,
|
||||||
gamesByProgramId: MutableMap<String, Game>,
|
|
||||||
files: Array<MinimalDocumentFile>,
|
files: Array<MinimalDocumentFile>,
|
||||||
depth: Int,
|
depth: Int,
|
||||||
mountedContainerUris: MutableSet<String>
|
mountedContainerUris: MutableSet<String>
|
||||||
@@ -151,7 +148,6 @@ object GameHelper {
|
|||||||
if (it.isDirectory) {
|
if (it.isDirectory) {
|
||||||
addGamesRecursive(
|
addGamesRecursive(
|
||||||
games,
|
games,
|
||||||
gamesByProgramId,
|
|
||||||
FileUtil.listFiles(it.uri),
|
FileUtil.listFiles(it.uri),
|
||||||
depth - 1,
|
depth - 1,
|
||||||
mountedContainerUris
|
mountedContainerUris
|
||||||
@@ -160,9 +156,8 @@ object GameHelper {
|
|||||||
val extension = FileUtil.getExtension(it.uri).lowercase()
|
val extension = FileUtil.getExtension(it.uri).lowercase()
|
||||||
val filePath = it.uri.toString()
|
val filePath = it.uri.toString()
|
||||||
|
|
||||||
val mountedContainer = externalContentExtensions.contains(extension) &&
|
if (externalContentExtensions.contains(extension) &&
|
||||||
mountedContainerUris.add(filePath)
|
mountedContainerUris.add(filePath)) {
|
||||||
if (mountedContainer) {
|
|
||||||
NativeLibrary.addGameFolderFileToFilesystemProvider(filePath)
|
NativeLibrary.addGameFolderFileToFilesystemProvider(filePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,20 +165,6 @@ object GameHelper {
|
|||||||
val game = getGame(it.uri, true, false)
|
val game = getGame(it.uri, true, false)
|
||||||
if (game != null) {
|
if (game != null) {
|
||||||
games.add(game)
|
games.add(game)
|
||||||
if (game.programId != "0") {
|
|
||||||
gamesByProgramId[game.programId] = game
|
|
||||||
}
|
|
||||||
} else if (mountedContainer) {
|
|
||||||
GameMetadata.getProgramId(filePath).toLongOrNull()?.let { programId ->
|
|
||||||
gamesByProgramId[(programId and 0x800L.inv()).toString()]
|
|
||||||
}?.let { existingGame ->
|
|
||||||
NativeLibrary.getPatchesForFile(existingGame.path, existingGame.programId)
|
|
||||||
existingGame.version = GameMetadata.getVersion(
|
|
||||||
existingGame.path,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
GameIconUtils.refreshGameIcon(existingGame)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -27,15 +24,6 @@ import coil.request.Options
|
|||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.YuzuApplication
|
import org.yuzu.yuzu_emu.YuzuApplication
|
||||||
import org.yuzu.yuzu_emu.model.Game
|
import org.yuzu.yuzu_emu.model.Game
|
||||||
import java.util.Collections
|
|
||||||
import java.util.WeakHashMap
|
|
||||||
|
|
||||||
private val gameIconHashes = Collections.synchronizedMap(mutableMapOf<String, Int>())
|
|
||||||
private val gameIconTargets = Collections.synchronizedMap(WeakHashMap<ImageView, GameIconTarget>())
|
|
||||||
|
|
||||||
private fun Game.iconCacheKey(): String = "$path|$version"
|
|
||||||
|
|
||||||
private data class GameIconTarget(val game: Game, var iconHash: Int? = null)
|
|
||||||
|
|
||||||
class GameIconFetcher(
|
class GameIconFetcher(
|
||||||
private val game: Game,
|
private val game: Game,
|
||||||
@@ -43,15 +31,14 @@ class GameIconFetcher(
|
|||||||
) : Fetcher {
|
) : Fetcher {
|
||||||
override suspend fun fetch(): FetchResult {
|
override suspend fun fetch(): FetchResult {
|
||||||
return DrawableResult(
|
return DrawableResult(
|
||||||
drawable = decodeGameIcon(game)!!.toDrawable(options.context.resources),
|
drawable = decodeGameIcon(game.path)!!.toDrawable(options.context.resources),
|
||||||
isSampled = false,
|
isSampled = false,
|
||||||
dataSource = DataSource.DISK
|
dataSource = DataSource.DISK
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun decodeGameIcon(game: Game): Bitmap? {
|
private fun decodeGameIcon(uri: String): Bitmap? {
|
||||||
val data = GameMetadata.getIcon(game.path)
|
val data = GameMetadata.getIcon(uri)
|
||||||
gameIconHashes[game.iconCacheKey()] = data.contentHashCode()
|
|
||||||
return BitmapFactory.decodeByteArray(
|
return BitmapFactory.decodeByteArray(
|
||||||
data,
|
data,
|
||||||
0,
|
0,
|
||||||
@@ -67,7 +54,7 @@ class GameIconFetcher(
|
|||||||
}
|
}
|
||||||
|
|
||||||
class GameIconKeyer : Keyer<Game> {
|
class GameIconKeyer : Keyer<Game> {
|
||||||
override fun key(data: Game, options: Options): String = data.iconCacheKey()
|
override fun key(data: Game, options: Options): String = data.path
|
||||||
}
|
}
|
||||||
|
|
||||||
object GameIconUtils {
|
object GameIconUtils {
|
||||||
@@ -84,58 +71,14 @@ object GameIconUtils {
|
|||||||
.build()
|
.build()
|
||||||
|
|
||||||
fun loadGameIcon(game: Game, imageView: ImageView) {
|
fun loadGameIcon(game: Game, imageView: ImageView) {
|
||||||
gameIconTargets[imageView] = GameIconTarget(game)
|
|
||||||
val request = ImageRequest.Builder(YuzuApplication.appContext)
|
val request = ImageRequest.Builder(YuzuApplication.appContext)
|
||||||
.data(game)
|
.data(game)
|
||||||
.target(imageView)
|
.target(imageView)
|
||||||
.error(R.drawable.default_icon)
|
.error(R.drawable.default_icon)
|
||||||
.listener(
|
|
||||||
onSuccess = { _, _ ->
|
|
||||||
val target = gameIconTargets[imageView]
|
|
||||||
if (target?.game?.iconCacheKey() == game.iconCacheKey()) {
|
|
||||||
gameIconHashes[game.iconCacheKey()]?.let {
|
|
||||||
target.iconHash = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onError = { _, _ ->
|
|
||||||
gameIconTargets[imageView]?.iconHash = null
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.build()
|
.build()
|
||||||
imageLoader.enqueue(request)
|
imageLoader.enqueue(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun refreshGameIcon(game: Game) {
|
|
||||||
val targets = synchronized(gameIconTargets) {
|
|
||||||
gameIconTargets
|
|
||||||
.filterValues { it.game.path == game.path && it.game.programId == game.programId }
|
|
||||||
.keys
|
|
||||||
.toList()
|
|
||||||
}
|
|
||||||
if (targets.isEmpty()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val iconHash = GameMetadata.getIcon(game.path).contentHashCode()
|
|
||||||
val targetsToRefresh = targets.filter { gameIconTargets[it]?.iconHash != iconHash }
|
|
||||||
if (targetsToRefresh.isEmpty()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
imageLoader.memoryCache?.remove(MemoryCache.Key(game.iconCacheKey()))
|
|
||||||
targetsToRefresh.forEach { imageView ->
|
|
||||||
imageView.post {
|
|
||||||
val target = gameIconTargets[imageView] ?: return@post
|
|
||||||
if (target.game.path == game.path && target.game.programId == game.programId) {
|
|
||||||
if (target.iconHash != iconHash) {
|
|
||||||
loadGameIcon(game, imageView)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getGameIcon(lifecycleOwner: LifecycleOwner, game: Game): Bitmap {
|
suspend fun getGameIcon(lifecycleOwner: LifecycleOwner, game: Game): Bitmap {
|
||||||
val request = ImageRequest.Builder(YuzuApplication.appContext)
|
val request = ImageRequest.Builder(YuzuApplication.appContext)
|
||||||
.data(game)
|
.data(game)
|
||||||
|
|||||||
@@ -80,12 +80,6 @@ object NativeConfig {
|
|||||||
@Synchronized
|
@Synchronized
|
||||||
external fun setShort(key: String, value: Short)
|
external fun setShort(key: String, value: Short)
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
external fun getUnsignedShort(key: String, needsGlobal: Boolean): Int
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
external fun setUnsignedShort(key: String, value: Int)
|
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
external fun getInt(key: String, needsGlobal: Boolean): Int
|
external fun getInt(key: String, needsGlobal: Boolean): Int
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.ui
|
package org.yuzu.yuzu_emu.ui
|
||||||
@@ -11,8 +11,6 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
|||||||
import androidx.recyclerview.widget.PagerSnapHelper
|
import androidx.recyclerview.widget.PagerSnapHelper
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.math.cos
|
|
||||||
import kotlin.math.sin
|
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.adapters.GameAdapter
|
import org.yuzu.yuzu_emu.adapters.GameAdapter
|
||||||
import androidx.core.view.doOnNextLayout
|
import androidx.core.view.doOnNextLayout
|
||||||
@@ -36,7 +34,6 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
|||||||
private var overlapDecoration: OverlappingDecoration? = null
|
private var overlapDecoration: OverlappingDecoration? = null
|
||||||
private var pagerSnapHelper: PagerSnapHelper? = null
|
private var pagerSnapHelper: PagerSnapHelper? = null
|
||||||
private var scalingScrollListener: OnScrollListener? = null
|
private var scalingScrollListener: OnScrollListener? = null
|
||||||
private var savedItemAnimator: RecyclerView.ItemAnimator? = null
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val CAROUSEL_CARD_SIZE_FACTOR = "CarouselCardSizeMultiplier"
|
private const val CAROUSEL_CARD_SIZE_FACTOR = "CarouselCardSizeMultiplier"
|
||||||
@@ -45,13 +42,8 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
|||||||
private const val CAROUSEL_OVERLAP_FACTOR = "CarouselOverlapFactor"
|
private const val CAROUSEL_OVERLAP_FACTOR = "CarouselOverlapFactor"
|
||||||
private const val CAROUSEL_MAX_FLING_COUNT = "CarouselMaxFlingCount"
|
private const val CAROUSEL_MAX_FLING_COUNT = "CarouselMaxFlingCount"
|
||||||
private const val CAROUSEL_FLING_MULTIPLIER = "CarouselFlingMultiplier"
|
private const val CAROUSEL_FLING_MULTIPLIER = "CarouselFlingMultiplier"
|
||||||
private const val CAROUSEL_ARC_ANGLE_STEP_DEGREES = 15.0
|
private const val CAROUSEL_CARDS_SCALING_SHAPE = "CarouselCardsScalingShape"
|
||||||
private const val CAROUSEL_ARC_MAX_ANGLE_DEGREES = 165.0
|
private const val CAROUSEL_CARDS_ALPHA_SHAPE = "CarouselCardsAlphaShape"
|
||||||
private const val CAROUSEL_ARC_DEPTH_MAX_ANGLE_DEGREES = 85.0
|
|
||||||
private const val CAROUSEL_ARC_DEPTH_STRETCH = 5.0f
|
|
||||||
private const val CAROUSEL_ARC_X_DEPTH_FACTOR = 0.55f
|
|
||||||
private const val CAROUSEL_ARC_FADE_OUT_START_DEGREES = 60.0
|
|
||||||
private const val CAROUSEL_ARC_FADE_OUT_END_DEGREES = 95.0
|
|
||||||
const val CAROUSEL_LAST_SCROLL_POSITION = "CarouselLastScrollPosition"
|
const val CAROUSEL_LAST_SCROLL_POSITION = "CarouselLastScrollPosition"
|
||||||
const val CAROUSEL_VIEW_TYPE_PORTRAIT = "GamesViewTypePortrait"
|
const val CAROUSEL_VIEW_TYPE_PORTRAIT = "GamesViewTypePortrait"
|
||||||
const val CAROUSEL_VIEW_TYPE_LANDSCAPE = "GamesViewTypeLandscape"
|
const val CAROUSEL_VIEW_TYPE_LANDSCAPE = "GamesViewTypeLandscape"
|
||||||
@@ -168,52 +160,46 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun shapingFunction(x: Float, option: Int = 0): Float {
|
||||||
|
return when (option) {
|
||||||
|
0 -> 1f // Off
|
||||||
|
1 -> 1f - x // linear descending
|
||||||
|
2 -> (1f - x) * (1f - x) // Ease out
|
||||||
|
3 -> if (x < 0.05f) 1f else (1f - x) * 0.8f
|
||||||
|
4 -> kotlin.math.cos(x * Math.PI).toFloat() // Cosine
|
||||||
|
5 -> kotlin.math.cos((1.5f * x).coerceIn(0f, 1f) * Math.PI).toFloat() // Cosine 1.5x trimmed
|
||||||
|
else -> 1f // Default to Off
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun updateChildScaleAndAlphaForPosition(child: View) {
|
fun updateChildScaleAndAlphaForPosition(child: View) {
|
||||||
val cardSize = (adapter as? GameAdapter ?: return).cardSize
|
val cardSize = (adapter as? GameAdapter ?: return).cardSize
|
||||||
val position = getChildViewHolder(child).bindingAdapterPosition
|
val position = getChildViewHolder(child).bindingAdapterPosition
|
||||||
if (position == RecyclerView.NO_POSITION || cardSize <= 0) {
|
if (position == RecyclerView.NO_POSITION || cardSize <= 0) {
|
||||||
return // No valid position or card size
|
return // No valid position or card size
|
||||||
}
|
}
|
||||||
val layoutParams = child.layoutParams
|
child.layoutParams.width = cardSize
|
||||||
if (layoutParams.width != cardSize || layoutParams.height != cardSize) {
|
child.layoutParams.height = cardSize
|
||||||
child.layoutParams = layoutParams.apply {
|
|
||||||
width = cardSize
|
|
||||||
height = cardSize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val signedDistance = getChildDistanceToCenter(child)
|
|
||||||
val itemStep = (cardSize - overlapPx).toFloat().coerceAtLeast(1f)
|
|
||||||
val angleStep = Math.toRadians(CAROUSEL_ARC_ANGLE_STEP_DEGREES).toFloat()
|
|
||||||
val maxAngle = Math.toRadians(CAROUSEL_ARC_MAX_ANGLE_DEGREES).toFloat()
|
|
||||||
val depthMaxAngle = Math.toRadians(CAROUSEL_ARC_DEPTH_MAX_ANGLE_DEGREES).toFloat()
|
|
||||||
val fadeOutStartAngle = Math.toRadians(CAROUSEL_ARC_FADE_OUT_START_DEGREES).toFloat()
|
|
||||||
val fadeOutEndAngle = Math.toRadians(CAROUSEL_ARC_FADE_OUT_END_DEGREES).toFloat()
|
|
||||||
val angle = (signedDistance / itemStep * angleStep).coerceIn(-maxAngle, maxAngle)
|
|
||||||
val arcRadius = itemStep / angleStep
|
|
||||||
val arcX = sin(angle) * arcRadius
|
|
||||||
val absoluteAngle = abs(angle)
|
|
||||||
val rawDepthInput = ((1f - cos(absoluteAngle)) / (1f - cos(depthMaxAngle)))
|
|
||||||
.coerceIn(0f, 1f)
|
|
||||||
val easedDepthTail = Math.pow(
|
|
||||||
(1f - rawDepthInput).toDouble(),
|
|
||||||
CAROUSEL_ARC_DEPTH_STRETCH.toDouble()
|
|
||||||
).toFloat()
|
|
||||||
val depthInput = (1f - easedDepthTail).coerceIn(0f, 1f)
|
|
||||||
val projectedArcX = arcX * (1f - rawDepthInput * CAROUSEL_ARC_X_DEPTH_FACTOR)
|
|
||||||
|
|
||||||
child.animate().cancel()
|
|
||||||
child.translationX = projectedArcX - signedDistance
|
|
||||||
|
|
||||||
|
val center = getRecyclerViewCenter()
|
||||||
|
val distance = abs(getChildDistanceToCenter(child))
|
||||||
val internalBorderScale = resources.getFraction(R.fraction.carousel_bordercards_scale, 1, 1)
|
val internalBorderScale = resources.getFraction(R.fraction.carousel_bordercards_scale, 1, 1)
|
||||||
val borderScale = preferences.getFloat(CAROUSEL_BORDERCARDS_SCALE, internalBorderScale).coerceIn(
|
val borderScale = preferences.getFloat(CAROUSEL_BORDERCARDS_SCALE, internalBorderScale).coerceIn(
|
||||||
0f,
|
0f,
|
||||||
1f
|
1f
|
||||||
)
|
)
|
||||||
|
|
||||||
val shapedScaling = 1f - depthInput
|
val shapeInput = (distance / center).coerceIn(0f, 1f)
|
||||||
|
val internalShapeSetting = resources.getInteger(R.integer.carousel_cards_scaling_shape)
|
||||||
|
val scalingShapeSetting = preferences.getInt(
|
||||||
|
CAROUSEL_CARDS_SCALING_SHAPE,
|
||||||
|
internalShapeSetting
|
||||||
|
)
|
||||||
|
val shapedScaling = shapingFunction(shapeInput, scalingShapeSetting)
|
||||||
val scale = (borderScale + (1f - borderScale) * shapedScaling).coerceIn(0f, 1f)
|
val scale = (borderScale + (1f - borderScale) * shapedScaling).coerceIn(0f, 1f)
|
||||||
|
|
||||||
|
val maxDistance = width / 2f
|
||||||
|
val alphaInput = (distance / maxDistance).coerceIn(0f, 1f)
|
||||||
val internalBordersAlpha = resources.getFraction(
|
val internalBordersAlpha = resources.getFraction(
|
||||||
R.fraction.carousel_bordercards_alpha,
|
R.fraction.carousel_bordercards_alpha,
|
||||||
1,
|
1,
|
||||||
@@ -223,12 +209,15 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
|||||||
0f,
|
0f,
|
||||||
1f
|
1f
|
||||||
)
|
)
|
||||||
val shapedAlpha = cos(depthInput * Math.PI).toFloat()
|
val internalAlphaShapeSetting = resources.getInteger(R.integer.carousel_cards_alpha_shape)
|
||||||
val baseAlpha = (borderAlpha + (1f - borderAlpha) * shapedAlpha).coerceIn(0f, 1f)
|
val alphaShapeSetting = preferences.getInt(
|
||||||
val rearPresence = (1f - (absoluteAngle - fadeOutStartAngle) /
|
CAROUSEL_CARDS_ALPHA_SHAPE,
|
||||||
(fadeOutEndAngle - fadeOutStartAngle)).coerceIn(0f, 1f)
|
internalAlphaShapeSetting
|
||||||
val alpha = (baseAlpha * rearPresence).coerceIn(0f, 1f)
|
)
|
||||||
|
val shapedAlpha = shapingFunction(alphaInput, alphaShapeSetting)
|
||||||
|
val alpha = (borderAlpha + (1f - borderAlpha) * shapedAlpha).coerceIn(0f, 1f)
|
||||||
|
|
||||||
|
child.animate().cancel()
|
||||||
child.alpha = alpha
|
child.alpha = alpha
|
||||||
child.scaleX = scale
|
child.scaleX = scale
|
||||||
child.scaleY = scale
|
child.scaleY = scale
|
||||||
@@ -284,9 +273,7 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
|||||||
0f,
|
0f,
|
||||||
1f
|
1f
|
||||||
)
|
)
|
||||||
val scaledHeight = height * userFactor
|
return (userFactor * (height - bottomInset)).toInt()
|
||||||
val availableHeight = height - bottomInset
|
|
||||||
return minOf(scaledHeight.toInt(), availableHeight.toInt())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setupCarousel(enabled: Boolean) {
|
fun setupCarousel(enabled: Boolean) {
|
||||||
@@ -295,13 +282,6 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
|||||||
if (gameAdapter.cardSize == 0) return
|
if (gameAdapter.cardSize == 0) return
|
||||||
if (bottomInset < 0) return
|
if (bottomInset < 0) return
|
||||||
|
|
||||||
itemAnimator?.let {
|
|
||||||
if (savedItemAnimator == null) {
|
|
||||||
savedItemAnimator = it
|
|
||||||
}
|
|
||||||
itemAnimator = null
|
|
||||||
}
|
|
||||||
|
|
||||||
useCustomDrawingOrder = true
|
useCustomDrawingOrder = true
|
||||||
val cardSize = gameAdapter.cardSize
|
val cardSize = gameAdapter.cardSize
|
||||||
|
|
||||||
@@ -356,12 +336,6 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
|||||||
// Detach PagerSnapHelper
|
// Detach PagerSnapHelper
|
||||||
pagerSnapHelper?.attachToRecyclerView(null)
|
pagerSnapHelper?.attachToRecyclerView(null)
|
||||||
pagerSnapHelper = null
|
pagerSnapHelper = null
|
||||||
savedItemAnimator?.let {
|
|
||||||
if (itemAnimator == null) {
|
|
||||||
itemAnimator = it
|
|
||||||
}
|
|
||||||
savedItemAnimator = null
|
|
||||||
}
|
|
||||||
useCustomDrawingOrder = false
|
useCustomDrawingOrder = false
|
||||||
// Reset padding and fling
|
// Reset padding and fling
|
||||||
setPadding(0, 0, 0, 0)
|
setPadding(0, 0, 0, 0)
|
||||||
@@ -370,7 +344,6 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
|||||||
// Reset scaling
|
// Reset scaling
|
||||||
for (i in 0 until childCount) {
|
for (i in 0 until childCount) {
|
||||||
val child = getChildAt(i)
|
val child = getChildAt(i)
|
||||||
child?.translationX = 0f
|
|
||||||
child?.scaleX = 1f
|
child?.scaleX = 1f
|
||||||
child?.scaleY = 1f
|
child?.scaleY = 1f
|
||||||
child?.alpha = 1f
|
child?.alpha = 1f
|
||||||
|
|||||||
@@ -1245,8 +1245,8 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_refreshThreadPolicies(JNIEnv* env, jo
|
|||||||
Common::RefreshThreadPolicies();
|
Common::RefreshThreadPolicies();
|
||||||
}
|
}
|
||||||
|
|
||||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_GetDebugKnobAt(JNIEnv* env, jobject jobj, jint index) {
|
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_getDebugKnobAt(JNIEnv* env, jobject jobj, jint index) {
|
||||||
return static_cast<jboolean>(Settings::GetDebugKnobAt(static_cast<u8>(index)));
|
return static_cast<jboolean>(Settings::getDebugKnobAt(static_cast<u8>(index)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setTurboSpeedLimit(JNIEnv *env, jobject jobj, jboolean enabled) {
|
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setTurboSpeedLimit(JNIEnv *env, jobject jobj, jboolean enabled) {
|
||||||
|
|||||||
@@ -130,25 +130,6 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setShort(JNIEnv* env, jobject ob
|
|||||||
setting->SetValue(value);
|
setting->SetValue(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
jint Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getUnsignedShort(JNIEnv* env, jobject obj,
|
|
||||||
jstring jkey,
|
|
||||||
jboolean needGlobal) {
|
|
||||||
auto setting = getSetting<u16>(env, jkey);
|
|
||||||
if (setting == nullptr) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return static_cast<jint>(setting->GetValue(static_cast<bool>(needGlobal)));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setUnsignedShort(JNIEnv* env, jobject obj,
|
|
||||||
jstring jkey, jint value) {
|
|
||||||
auto setting = getSetting<u16>(env, jkey);
|
|
||||||
if (setting == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setting->SetValue(static_cast<u16>(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
jint Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getInt(JNIEnv* env, jobject obj, jstring jkey,
|
jint Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getInt(JNIEnv* env, jobject obj, jstring jkey,
|
||||||
jboolean needGlobal) {
|
jboolean needGlobal) {
|
||||||
auto setting = getSetting<int>(env, jkey);
|
auto setting = getSetting<int>(env, jkey);
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item android:state_pressed="true" android:color="?attr/colorControlHighlight" />
|
|
||||||
<item android:state_focused="true" android:color="@android:color/transparent" />
|
|
||||||
<item android:state_selected="true" android:color="@android:color/transparent" />
|
|
||||||
<item android:state_hovered="true" android:color="@android:color/transparent" />
|
|
||||||
<item android:color="@android:color/transparent" />
|
|
||||||
</selector>
|
|
||||||
@@ -10,7 +10,6 @@
|
|||||||
android:clipChildren="true"
|
android:clipChildren="true"
|
||||||
android:layout_margin="0dp"
|
android:layout_margin="0dp"
|
||||||
app:cardBackgroundColor="@color/eden_card_background"
|
app:cardBackgroundColor="@color/eden_card_background"
|
||||||
app:rippleColor="@color/game_card_ripple"
|
|
||||||
app:strokeWidth="1dp"
|
app:strokeWidth="1dp"
|
||||||
app:strokeColor="@color/eden_border">
|
app:strokeColor="@color/eden_border">
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
app:cardCornerRadius="16dp"
|
app:cardCornerRadius="16dp"
|
||||||
app:cardPreventCornerOverlap="true"
|
app:cardPreventCornerOverlap="true"
|
||||||
android:clipChildren="true"
|
android:clipChildren="true"
|
||||||
app:rippleColor="@color/game_card_ripple"
|
|
||||||
android:layout_margin="4dp">
|
android:layout_margin="4dp">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:transitionName="card_game"
|
android:transitionName="card_game"
|
||||||
app:cardCornerRadius="16dp"
|
app:cardCornerRadius="16dp"
|
||||||
app:rippleColor="@color/game_card_ripple"
|
|
||||||
android:foreground="@color/eden_border_gradient_start">
|
android:foreground="@color/eden_border_gradient_start">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:transitionName="card_game_compact"
|
android:transitionName="card_game_compact"
|
||||||
app:cardCornerRadius="16dp"
|
app:cardCornerRadius="16dp"
|
||||||
app:rippleColor="@color/game_card_ripple"
|
|
||||||
android:foreground="@color/eden_border_gradient_start">
|
android:foreground="@color/eden_border_gradient_start">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
app:cardCornerRadius="16dp"
|
app:cardCornerRadius="16dp"
|
||||||
app:cardElevation="0dp"
|
app:cardElevation="0dp"
|
||||||
app:cardBackgroundColor="@android:color/transparent"
|
app:cardBackgroundColor="@android:color/transparent"
|
||||||
app:rippleColor="@color/game_card_ripple"
|
|
||||||
app:strokeWidth="0dp">
|
app:strokeWidth="0dp">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
|||||||
@@ -1107,6 +1107,7 @@
|
|||||||
<string name="theme_mode_light">فاتح</string>
|
<string name="theme_mode_light">فاتح</string>
|
||||||
<string name="theme_mode_dark">داكن</string>
|
<string name="theme_mode_dark">داكن</string>
|
||||||
|
|
||||||
|
<string name="multiplier_none">لا شيء</string>
|
||||||
|
|
||||||
<!-- Black backgrounds theme -->
|
<!-- Black backgrounds theme -->
|
||||||
<string name="use_black_backgrounds">خلفيات سوداء</string>
|
<string name="use_black_backgrounds">خلفيات سوداء</string>
|
||||||
|
|||||||
@@ -991,6 +991,7 @@ Wirklich fortfahren?</string>
|
|||||||
<string name="theme_mode_light">Hell</string>
|
<string name="theme_mode_light">Hell</string>
|
||||||
<string name="theme_mode_dark">Dunkel</string>
|
<string name="theme_mode_dark">Dunkel</string>
|
||||||
|
|
||||||
|
<string name="multiplier_none">Keine</string>
|
||||||
|
|
||||||
<!-- Black backgrounds theme -->
|
<!-- Black backgrounds theme -->
|
||||||
<string name="use_black_backgrounds">Schwarze Hintergründe</string>
|
<string name="use_black_backgrounds">Schwarze Hintergründe</string>
|
||||||
|
|||||||
@@ -1092,6 +1092,7 @@
|
|||||||
<string name="theme_mode_light">Claro</string>
|
<string name="theme_mode_light">Claro</string>
|
||||||
<string name="theme_mode_dark">Oscuro</string>
|
<string name="theme_mode_dark">Oscuro</string>
|
||||||
|
|
||||||
|
<string name="multiplier_none">Nada</string>
|
||||||
|
|
||||||
<!-- Black backgrounds theme -->
|
<!-- Black backgrounds theme -->
|
||||||
<string name="use_black_backgrounds">Fondos oscuros</string>
|
<string name="use_black_backgrounds">Fondos oscuros</string>
|
||||||
|
|||||||
@@ -808,6 +808,9 @@
|
|||||||
<string name="multiplier_x4">x4</string>
|
<string name="multiplier_x4">x4</string>
|
||||||
<string name="multiplier_x8">x8</string>
|
<string name="multiplier_x8">x8</string>
|
||||||
<string name="multiplier_x16">x16</string>
|
<string name="multiplier_x16">x16</string>
|
||||||
|
<string name="multiplier_x32">x32</string>
|
||||||
|
<string name="multiplier_x64">x64</string>
|
||||||
|
<string name="multiplier_none">None</string>
|
||||||
|
|
||||||
<!-- Black backgrounds theme -->
|
<!-- Black backgrounds theme -->
|
||||||
<string name="use_black_backgrounds">پسزمینه مشکی</string>
|
<string name="use_black_backgrounds">پسزمینه مشکی</string>
|
||||||
|
|||||||
@@ -1004,6 +1004,7 @@
|
|||||||
<string name="theme_mode_light">Lumineux</string>
|
<string name="theme_mode_light">Lumineux</string>
|
||||||
<string name="theme_mode_dark">Sombre</string>
|
<string name="theme_mode_dark">Sombre</string>
|
||||||
|
|
||||||
|
<string name="multiplier_none">Aucun</string>
|
||||||
|
|
||||||
<!-- Black backgrounds theme -->
|
<!-- Black backgrounds theme -->
|
||||||
<string name="use_black_backgrounds">Arrière-plan noir</string>
|
<string name="use_black_backgrounds">Arrière-plan noir</string>
|
||||||
|
|||||||
@@ -935,6 +935,7 @@
|
|||||||
<string name="theme_mode_light">Jasny</string>
|
<string name="theme_mode_light">Jasny</string>
|
||||||
<string name="theme_mode_dark">Ciemny</string>
|
<string name="theme_mode_dark">Ciemny</string>
|
||||||
|
|
||||||
|
<string name="multiplier_none">Brak</string>
|
||||||
|
|
||||||
<!-- Black backgrounds theme -->
|
<!-- Black backgrounds theme -->
|
||||||
<string name="use_black_backgrounds">Czarne tła</string>
|
<string name="use_black_backgrounds">Czarne tła</string>
|
||||||
|
|||||||
@@ -891,6 +891,7 @@
|
|||||||
<string name="theme_mode_light">Claro</string>
|
<string name="theme_mode_light">Claro</string>
|
||||||
<string name="theme_mode_dark">Escuro</string>
|
<string name="theme_mode_dark">Escuro</string>
|
||||||
|
|
||||||
|
<string name="multiplier_none">Nenhum</string>
|
||||||
|
|
||||||
<!-- Black backgrounds theme -->
|
<!-- Black backgrounds theme -->
|
||||||
<string name="use_black_backgrounds">Planos de fundo pretos</string>
|
<string name="use_black_backgrounds">Planos de fundo pretos</string>
|
||||||
|
|||||||
@@ -1071,6 +1071,7 @@
|
|||||||
<string name="theme_mode_light">Светлая</string>
|
<string name="theme_mode_light">Светлая</string>
|
||||||
<string name="theme_mode_dark">Темная</string>
|
<string name="theme_mode_dark">Темная</string>
|
||||||
|
|
||||||
|
<string name="multiplier_none">Отключено</string>
|
||||||
|
|
||||||
<!-- Black backgrounds theme -->
|
<!-- Black backgrounds theme -->
|
||||||
<string name="use_black_backgrounds">Чёрный фон</string>
|
<string name="use_black_backgrounds">Чёрный фон</string>
|
||||||
|
|||||||
@@ -1053,6 +1053,7 @@
|
|||||||
<string name="theme_mode_light">Світла</string>
|
<string name="theme_mode_light">Світла</string>
|
||||||
<string name="theme_mode_dark">Темна</string>
|
<string name="theme_mode_dark">Темна</string>
|
||||||
|
|
||||||
|
<string name="multiplier_none">Жодного</string>
|
||||||
|
|
||||||
<!-- Black backgrounds theme -->
|
<!-- Black backgrounds theme -->
|
||||||
<string name="use_black_backgrounds">Чорний фон</string>
|
<string name="use_black_backgrounds">Чорний фон</string>
|
||||||
|
|||||||
@@ -1081,6 +1081,7 @@
|
|||||||
<string name="theme_mode_light">浅色</string>
|
<string name="theme_mode_light">浅色</string>
|
||||||
<string name="theme_mode_dark">深色</string>
|
<string name="theme_mode_dark">深色</string>
|
||||||
|
|
||||||
|
<string name="multiplier_none">无</string>
|
||||||
|
|
||||||
<!-- Black backgrounds theme -->
|
<!-- Black backgrounds theme -->
|
||||||
<string name="use_black_backgrounds">使用黑色背景</string>
|
<string name="use_black_backgrounds">使用黑色背景</string>
|
||||||
|
|||||||
@@ -1006,6 +1006,7 @@
|
|||||||
<string name="theme_mode_light">淺色</string>
|
<string name="theme_mode_light">淺色</string>
|
||||||
<string name="theme_mode_dark">深色</string>
|
<string name="theme_mode_dark">深色</string>
|
||||||
|
|
||||||
|
<string name="multiplier_none">無</string>
|
||||||
|
|
||||||
<!-- Black backgrounds theme -->
|
<!-- Black backgrounds theme -->
|
||||||
<string name="use_black_backgrounds">黑色背景</string>
|
<string name="use_black_backgrounds">黑色背景</string>
|
||||||
|
|||||||
@@ -111,38 +111,43 @@
|
|||||||
<item>1</item>
|
<item>1</item>
|
||||||
</integer-array>
|
</integer-array>
|
||||||
|
|
||||||
|
<!-- VRAM USAGE MODE CHOICES -->
|
||||||
<string-array name="vramUsageMethodNames">
|
<string-array name="vramUsageMethodNames">
|
||||||
<item>@string/vram_usage_conservative</item>
|
<item>@string/vram_usage_conservative</item>
|
||||||
<item>@string/vram_usage_aggressive</item>
|
<item>@string/vram_usage_aggressive</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<!-- VRAM USAGE MODE VALUES -->
|
||||||
<integer-array name="vramUsageMethodValues">
|
<integer-array name="vramUsageMethodValues">
|
||||||
<item>0</item>
|
<item>0</item> <!-- Conservative -->
|
||||||
<item>1</item>
|
<item>1</item> <!-- Aggressive -->
|
||||||
</integer-array>
|
</integer-array>
|
||||||
|
|
||||||
|
<!-- ASTC Decoding Method Choices -->
|
||||||
<string-array name="astcDecodingMethodNames">
|
<string-array name="astcDecodingMethodNames">
|
||||||
<item>@string/accelerate_astc_cpu</item>
|
<item>@string/accelerate_astc_cpu</item>
|
||||||
<item>@string/accelerate_astc_gpu</item>
|
<item>@string/accelerate_astc_gpu</item>
|
||||||
<item>@string/accelerate_astc_async</item>
|
<item>@string/accelerate_astc_async</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<!-- ASTC Decoding Method Values -->
|
||||||
<integer-array name="astcDecodingMethodValues">
|
<integer-array name="astcDecodingMethodValues">
|
||||||
<item>0</item>
|
<item>0</item> <!-- CPU -->
|
||||||
<item>1</item>
|
<item>1</item> <!-- GPU -->
|
||||||
<item>2</item>
|
<item>2</item> <!-- CPU Asynchronously -->
|
||||||
</integer-array>
|
</integer-array>
|
||||||
|
<!-- NVDEC Emulation Choices -->
|
||||||
<string-array name="rendererNvdecNames">
|
<string-array name="rendererNvdecNames">
|
||||||
<item>@string/nvdec_emulation_none</item>
|
<item>@string/nvdec_emulation_none</item> <!-- Off -->
|
||||||
<item>@string/nvdec_emulation_cpu</item>
|
<item>@string/nvdec_emulation_cpu</item> <!-- Cpu -->
|
||||||
<item>@string/nvdec_emulation_gpu</item>
|
<item>@string/nvdec_emulation_gpu</item> <!-- Gpu -->
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<!-- NVDEC Emulation Values -->
|
||||||
<integer-array name="rendererNvdecValues">
|
<integer-array name="rendererNvdecValues">
|
||||||
<item>0</item>
|
<item>3</item> <!-- Off value -->
|
||||||
<item>1</item>
|
<item>1</item> <!-- CPU value -->
|
||||||
<item>2</item>
|
<item>2</item> <!-- GPU value -->
|
||||||
</integer-array>
|
</integer-array>
|
||||||
|
|
||||||
<string-array name="rendererResolutionNames">
|
<string-array name="rendererResolutionNames">
|
||||||
@@ -508,6 +513,9 @@
|
|||||||
<item>@string/multiplier_x4</item>
|
<item>@string/multiplier_x4</item>
|
||||||
<item>@string/multiplier_x8</item>
|
<item>@string/multiplier_x8</item>
|
||||||
<item>@string/multiplier_x16</item>
|
<item>@string/multiplier_x16</item>
|
||||||
|
<item>@string/multiplier_x32</item>
|
||||||
|
<item>@string/multiplier_x64</item>
|
||||||
|
<item>@string/multiplier_none</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<integer-array name="anisoValues">
|
<integer-array name="anisoValues">
|
||||||
<item>0</item>
|
<item>0</item>
|
||||||
@@ -516,6 +524,9 @@
|
|||||||
<item>3</item>
|
<item>3</item>
|
||||||
<item>4</item>
|
<item>4</item>
|
||||||
<item>5</item>
|
<item>5</item>
|
||||||
|
<item>6</item>
|
||||||
|
<item>7</item>
|
||||||
|
<item>8</item>
|
||||||
</integer-array>
|
</integer-array>
|
||||||
|
|
||||||
<string-array name="verticalAlignmentEntries">
|
<string-array name="verticalAlignmentEntries">
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
<integer name="game_columns_grid">2</integer>
|
<integer name="game_columns_grid">2</integer>
|
||||||
<integer name="carousel_max_fling_count">4</integer>
|
<integer name="carousel_max_fling_count">4</integer>
|
||||||
<integer name="carousel_focus_search_repeat_threshold_ms">100</integer>
|
<integer name="carousel_focus_search_repeat_threshold_ms">100</integer>
|
||||||
|
<integer name="carousel_cards_scaling_shape">1</integer>
|
||||||
|
<integer name="carousel_cards_alpha_shape">4</integer>
|
||||||
|
|
||||||
<!-- Default SWITCH landscape layout -->
|
<!-- Default SWITCH landscape layout -->
|
||||||
<integer name="BUTTON_A_X">760</integer>
|
<integer name="BUTTON_A_X">760</integer>
|
||||||
|
|||||||
@@ -111,7 +111,7 @@
|
|||||||
|
|
||||||
<!-- NVDEC Emulation -->
|
<!-- NVDEC Emulation -->
|
||||||
<string name="nvdec_emulation">NVDEC Emulation</string>
|
<string name="nvdec_emulation">NVDEC Emulation</string>
|
||||||
<string name="nvdec_emulation_description">Change to CPU if a crash occurs on cinematics.</string>
|
<string name="nvdec_emulation_description">Select how video decoding (NVDEC) is handled during cutscenes and intros.</string>
|
||||||
<string name="nvdec_emulation_cpu" translatable="false">CPU</string>
|
<string name="nvdec_emulation_cpu" translatable="false">CPU</string>
|
||||||
<string name="nvdec_emulation_gpu" translatable="false">GPU</string>
|
<string name="nvdec_emulation_gpu" translatable="false">GPU</string>
|
||||||
<string name="nvdec_emulation_none">None</string>
|
<string name="nvdec_emulation_none">None</string>
|
||||||
@@ -1246,6 +1246,9 @@
|
|||||||
<string name="multiplier_x4" translatable="false">x4</string>
|
<string name="multiplier_x4" translatable="false">x4</string>
|
||||||
<string name="multiplier_x8" translatable="false">x8</string>
|
<string name="multiplier_x8" translatable="false">x8</string>
|
||||||
<string name="multiplier_x16" translatable="false">x16</string>
|
<string name="multiplier_x16" translatable="false">x16</string>
|
||||||
|
<string name="multiplier_x32" translatable="false">x32</string>
|
||||||
|
<string name="multiplier_x64" translatable="false">x64</string>
|
||||||
|
<string name="multiplier_none">None</string>
|
||||||
|
|
||||||
<!-- Black backgrounds theme -->
|
<!-- Black backgrounds theme -->
|
||||||
<string name="use_black_backgrounds">Black backgrounds</string>
|
<string name="use_black_backgrounds">Black backgrounds</string>
|
||||||
|
|||||||
@@ -19,23 +19,6 @@
|
|||||||
namespace AudioCore::Sink {
|
namespace AudioCore::Sink {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
[[nodiscard]] bool InitializeAudio() {
|
|
||||||
if (!SDL_WasInit(SDL_INIT_AUDIO)) {
|
|
||||||
// See https://github.com/PCSX2/pcsx2/pull/12312
|
|
||||||
// "SDL and cubeb backends previously resulted in different names for the output which
|
|
||||||
// caused them be identified as different applications by the OS."
|
|
||||||
//
|
|
||||||
// Keep in sync with cubeb_sink.cpp name.
|
|
||||||
SDL_SetHint("SDL_AUDIO_DEVICE_APP_NAME", "yuzu Latency Getter");
|
|
||||||
if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) {
|
|
||||||
LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_AudioDeviceID FindAudioDeviceByName(const std::string& device_name, bool capture) {
|
SDL_AudioDeviceID FindAudioDeviceByName(const std::string& device_name, bool capture) {
|
||||||
int device_count = 0;
|
int device_count = 0;
|
||||||
SDL_AudioDeviceID* devices = capture ? SDL_GetAudioRecordingDevices(&device_count)
|
SDL_AudioDeviceID* devices = capture ? SDL_GetAudioRecordingDevices(&device_count)
|
||||||
@@ -221,15 +204,21 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
SDLSink::SDLSink(std::string_view target_device_name) {
|
SDLSink::SDLSink(std::string_view target_device_name) {
|
||||||
if (InitializeAudio()) {
|
if (!SDL_WasInit(SDL_INIT_AUDIO)) {
|
||||||
|
if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) {
|
||||||
|
LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (target_device_name != auto_device_name && !target_device_name.empty()) {
|
if (target_device_name != auto_device_name && !target_device_name.empty()) {
|
||||||
output_device = target_device_name;
|
output_device = target_device_name;
|
||||||
} else {
|
} else {
|
||||||
output_device.clear();
|
output_device.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
device_channels = 2;
|
device_channels = 2;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
SDLSink::~SDLSink() = default;
|
SDLSink::~SDLSink() = default;
|
||||||
|
|
||||||
@@ -276,10 +265,15 @@ void SDLSink::SetSystemVolume(f32 volume) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> ListSDLSinkDevices(bool capture) {
|
std::vector<std::string> ListSDLSinkDevices(bool capture) {
|
||||||
if (!InitializeAudio())
|
|
||||||
return {}; //no devices
|
|
||||||
|
|
||||||
std::vector<std::string> device_list;
|
std::vector<std::string> device_list;
|
||||||
|
|
||||||
|
if (!SDL_WasInit(SDL_INIT_AUDIO)) {
|
||||||
|
if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) {
|
||||||
|
LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int device_count = 0;
|
int device_count = 0;
|
||||||
SDL_AudioDeviceID* devices =
|
SDL_AudioDeviceID* devices =
|
||||||
capture ? SDL_GetAudioRecordingDevices(&device_count)
|
capture ? SDL_GetAudioRecordingDevices(&device_count)
|
||||||
@@ -310,8 +304,13 @@ bool IsSDLSuitable() {
|
|||||||
return false;
|
return false;
|
||||||
#else
|
#else
|
||||||
// Check SDL can init
|
// Check SDL can init
|
||||||
if (!InitializeAudio()!
|
if (!SDL_WasInit(SDL_INIT_AUDIO)) {
|
||||||
|
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
|
||||||
|
LOG_ERROR(Audio_Sink, "SDL failed to init, it is not suitable. Error: {}",
|
||||||
|
SDL_GetError());
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// We can set any latency frequency we want with SDL, so no need to check that.
|
// We can set any latency frequency we want with SDL, so no need to check that.
|
||||||
|
|
||||||
|
|||||||
+45
-56
@@ -39,19 +39,6 @@
|
|||||||
|
|
||||||
namespace Common::Log {
|
namespace Common::Log {
|
||||||
|
|
||||||
/// @brief A log entry. Log entries are store in a structured format to permit more varied output
|
|
||||||
/// formatting on different frontends, as well as facilitating filtering and aggregation.
|
|
||||||
struct Entry {
|
|
||||||
char const* message = nullptr;
|
|
||||||
size_t message_len = 0;
|
|
||||||
std::chrono::microseconds timestamp;
|
|
||||||
Class log_class{};
|
|
||||||
Level log_level{};
|
|
||||||
const char* filename = nullptr;
|
|
||||||
const char* function = nullptr;
|
|
||||||
uint32_t line_num = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
/// @brief Returns the name of the passed log class as a C-string. Subclasses are separated by periods
|
/// @brief Returns the name of the passed log class as a C-string. Subclasses are separated by periods
|
||||||
@@ -83,6 +70,8 @@ const char* GetLevelName(Level log_level) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Some IDEs prefer <file>:<line> instead, so let's just do that :)
|
// Some IDEs prefer <file>:<line> instead, so let's just do that :)
|
||||||
std::string FormatLogMessage(const Entry& entry) noexcept {
|
std::string FormatLogMessage(const Entry& entry) noexcept {
|
||||||
if (!entry.filename) return "";
|
if (!entry.filename) return "";
|
||||||
@@ -90,9 +79,10 @@ std::string FormatLogMessage(const Entry& entry) noexcept {
|
|||||||
auto const time_fractional = uint32_t(entry.timestamp.count() % 1000000);
|
auto const time_fractional = uint32_t(entry.timestamp.count() % 1000000);
|
||||||
auto const class_name = GetLogClassName(entry.log_class);
|
auto const class_name = GetLogClassName(entry.log_class);
|
||||||
auto const level_name = GetLevelName(entry.log_level);
|
auto const level_name = GetLevelName(entry.log_level);
|
||||||
return fmt::format("[{:4d}.{:06d}] {} <{}> {}:{}:{}: {}\n", time_seconds, time_fractional, class_name, level_name, entry.filename, entry.line_num, entry.function, entry.message);
|
return fmt::format("[{:4d}.{:06d}] {} <{}> {}:{}:{}: {}", time_seconds, time_fractional, class_name, level_name, entry.filename, entry.line_num, entry.function, entry.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
template <typename It>
|
template <typename It>
|
||||||
Level GetLevelByName(const It begin, const It end) {
|
Level GetLevelByName(const It begin, const It end) {
|
||||||
for (u32 i = 0; i < u32(Level::Count); ++i) {
|
for (u32 i = 0; i < u32(Level::Count); ++i) {
|
||||||
@@ -137,6 +127,25 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
|
|||||||
instance.SetClassLevel(log_class, level);
|
instance.SetClassLevel(log_class, level);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
} // Anonymous namespace
|
||||||
|
|
||||||
|
void Filter::ParseFilterString(std::string_view filter_view) {
|
||||||
|
auto clause_begin = filter_view.cbegin();
|
||||||
|
while (clause_begin != filter_view.cend()) {
|
||||||
|
auto clause_end = std::find(clause_begin, filter_view.cend(), ' ');
|
||||||
|
// If clause isn't empty
|
||||||
|
if (clause_end != clause_begin) {
|
||||||
|
ParseFilterRule(*this, clause_begin, clause_end);
|
||||||
|
}
|
||||||
|
if (clause_end != filter_view.cend()) {
|
||||||
|
// Skip over the whitespace
|
||||||
|
++clause_end;
|
||||||
|
}
|
||||||
|
clause_begin = clause_end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
/// @brief Trims up to and including the last of ../, ..\, src/, src\ in a string
|
/// @brief Trims up to and including the last of ../, ..\, src/, src\ in a string
|
||||||
/// do not be fooled this isn't generating new strings on .rodata :)
|
/// do not be fooled this isn't generating new strings on .rodata :)
|
||||||
@@ -199,7 +208,7 @@ struct ColorConsoleBackend final : public Backend {
|
|||||||
}());
|
}());
|
||||||
SetConsoleTextAttribute(console_handle, color);
|
SetConsoleTextAttribute(console_handle, color);
|
||||||
auto const df = GetDirectFormatArgs(entry);
|
auto const df = GetDirectFormatArgs(entry);
|
||||||
std::fprintf(stdout, CCB_PRINTF_FMT "\n", df.time_seconds, df.time_fractional, df.class_name, df.level_name, entry.filename, entry.line_num, entry.function, entry.message);
|
std::fprintf(stdout, CCB_PRINTF_FMT "\n", df.time_seconds, df.time_fractional, df.class_name, df.level_name, entry.filename, entry.line_num, entry.function, entry.message.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void Flush() noexcept override {}
|
void Flush() noexcept override {}
|
||||||
@@ -211,24 +220,22 @@ struct ColorConsoleBackend final : public Backend {
|
|||||||
~ColorConsoleBackend() noexcept override {}
|
~ColorConsoleBackend() noexcept override {}
|
||||||
void Write(const Entry& entry) noexcept override {
|
void Write(const Entry& entry) noexcept override {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
|
#define ESC "\x1b"
|
||||||
auto const color_str = [&entry]() -> const char* {
|
auto const color_str = [&entry]() -> const char* {
|
||||||
switch (entry.log_level) {
|
switch (entry.log_level) {
|
||||||
case Level::Debug: return "[0;36m"; // Cyan
|
#define CCB_MAKE_COLOR_FMT(X) ESC X CCB_PRINTF_FMT ESC "[0m\n"
|
||||||
case Level::Info: return "[0;37m"; // Bright gray
|
case Level::Debug: return CCB_MAKE_COLOR_FMT("[0;36m"); // Cyan
|
||||||
case Level::Warning: return "[1;33m"; // Bright yellow
|
case Level::Info: return CCB_MAKE_COLOR_FMT("[0;37m"); // Bright gray
|
||||||
case Level::Error: return "[1;31m"; // Bright red
|
case Level::Warning: return CCB_MAKE_COLOR_FMT("[1;33m"); // Bright yellow
|
||||||
case Level::Critical: return "[1;35m"; // Bright magenta
|
case Level::Error: return CCB_MAKE_COLOR_FMT("[1;31m"); // Bright red
|
||||||
default: return "[1;30m"; // Grey
|
case Level::Critical: return CCB_MAKE_COLOR_FMT("[1;35m"); // Bright magenta
|
||||||
|
default: return CCB_MAKE_COLOR_FMT("[1;30m"); // Grey
|
||||||
|
#undef CCB_MAKE_COLOR_FMT
|
||||||
}
|
}
|
||||||
}();
|
}();
|
||||||
auto const df = GetDirectFormatArgs(entry);
|
auto const df = GetDirectFormatArgs(entry);
|
||||||
// more restrictive, because take for example this simple prelude:
|
std::fprintf(stdout, color_str, df.time_seconds, df.time_fractional, df.class_name, df.level_name, entry.filename, entry.line_num, entry.function, entry.message.c_str());
|
||||||
// [ 50.872256] Config <Info> common/settings.cpp:142:LogSettings:
|
#undef ESC
|
||||||
char buffer[256];
|
|
||||||
auto result = fmt::format_to_n(buffer, sizeof(buffer) - 1, "\x1b{}[{:4d}.{:06d}] {} <{}> {}:{}:{}: ", color_str, df.time_seconds, df.time_fractional, df.class_name, df.level_name, entry.filename, entry.line_num, entry.function, entry.message);
|
|
||||||
std::fwrite(buffer, 1, (std::min)(sizeof(buffer) - 1, result.size), stdout);
|
|
||||||
std::fwrite(entry.message, 1, entry.message_len, stdout);
|
|
||||||
std::fwrite("\x1b[0m\n", 1, sizeof("\x1b[0m\n"), stdout);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void Flush() noexcept override {}
|
void Flush() noexcept override {}
|
||||||
@@ -239,7 +246,7 @@ struct ColorConsoleBackend final : public Backend {
|
|||||||
#ifndef __OPENORBIS__
|
#ifndef __OPENORBIS__
|
||||||
/// @brief Backend that writes to a file passed into the constructor
|
/// @brief Backend that writes to a file passed into the constructor
|
||||||
struct FileBackend final : public Backend {
|
struct FileBackend final : public Backend {
|
||||||
explicit FileBackend(const std::filesystem::path filename) noexcept {
|
explicit FileBackend(const std::filesystem::path& filename) noexcept {
|
||||||
auto old_filename = filename;
|
auto old_filename = filename;
|
||||||
old_filename += ".old.txt";
|
old_filename += ".old.txt";
|
||||||
// Existence checks are done within the functions themselves.
|
// Existence checks are done within the functions themselves.
|
||||||
@@ -254,7 +261,7 @@ struct FileBackend final : public Backend {
|
|||||||
if (!enabled)
|
if (!enabled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto message = FormatLogMessage(entry);
|
auto message = FormatLogMessage(entry).append(1, '\n');
|
||||||
#ifndef __ANDROID__
|
#ifndef __ANDROID__
|
||||||
if (Settings::values.censor_username.GetValue()) {
|
if (Settings::values.censor_username.GetValue()) {
|
||||||
// This must be a static otherwise it would get checked on EVERY
|
// This must be a static otherwise it would get checked on EVERY
|
||||||
@@ -262,7 +269,8 @@ struct FileBackend final : public Backend {
|
|||||||
static std::string username = []() -> std::string {
|
static std::string username = []() -> std::string {
|
||||||
// in order of precedence
|
// in order of precedence
|
||||||
// LOGNAME usually works on UNIX, USERNAME on Windows
|
// LOGNAME usually works on UNIX, USERNAME on Windows
|
||||||
// Some UNIX systems suck and don't use LOGNAME so we also need USER :(
|
// Some UNIX systems suck and don't use LOGNAME so we also
|
||||||
|
// need USER :(
|
||||||
for (auto const var : { "LOGNAME", "USERNAME", "USER", })
|
for (auto const var : { "LOGNAME", "USERNAME", "USER", })
|
||||||
if (auto const s = ::getenv(var); s != nullptr)
|
if (auto const s = ::getenv(var); s != nullptr)
|
||||||
return std::string{s};
|
return std::string{s};
|
||||||
@@ -272,7 +280,7 @@ struct FileBackend final : public Backend {
|
|||||||
boost::replace_all(message, username, "user");
|
boost::replace_all(message, username, "user");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
bytes_written += file->WriteSpan(std::span<const char>{message.begin(), message.end()});
|
bytes_written += file->WriteString(message);
|
||||||
|
|
||||||
// Option to log each line rather than 4k buffers
|
// Option to log each line rather than 4k buffers
|
||||||
if (Settings::values.log_flush_line.GetValue())
|
if (Settings::values.log_flush_line.GetValue())
|
||||||
@@ -300,13 +308,14 @@ private:
|
|||||||
bool enabled = true;
|
bool enabled = true;
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
/// @brief Backend that writes to Visual Studio's output window
|
/// @brief Backend that writes to Visual Studio's output window
|
||||||
struct DebuggerBackend final : public Backend {
|
struct DebuggerBackend final : public Backend {
|
||||||
explicit DebuggerBackend() noexcept = default;
|
explicit DebuggerBackend() noexcept = default;
|
||||||
~DebuggerBackend() noexcept override = default;
|
~DebuggerBackend() noexcept override = default;
|
||||||
void Write(const Entry& entry) noexcept override {
|
void Write(const Entry& entry) noexcept override {
|
||||||
::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry)).c_str());
|
::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str());
|
||||||
}
|
}
|
||||||
void Flush() noexcept override {}
|
void Flush() noexcept override {}
|
||||||
};
|
};
|
||||||
@@ -329,7 +338,7 @@ struct LogcatBackend : public Backend {
|
|||||||
}
|
}
|
||||||
}();
|
}();
|
||||||
auto const df = GetDirectFormatArgs(entry);
|
auto const df = GetDirectFormatArgs(entry);
|
||||||
__android_log_print(android_log_priority, "YuzuNative", CCB_PRINTF_FMT, df.time_seconds, df.time_fractional, df.class_name, df.level_name, entry.filename, entry.line_num, entry.function, entry.message);
|
__android_log_print(android_log_priority, "YuzuNative", CCB_PRINTF_FMT, df.time_seconds, df.time_fractional, df.class_name, df.level_name, entry.filename, entry.line_num, entry.function, entry.message.c_str());
|
||||||
}
|
}
|
||||||
void Flush() noexcept override {}
|
void Flush() noexcept override {}
|
||||||
};
|
};
|
||||||
@@ -368,23 +377,7 @@ struct Impl {
|
|||||||
#endif
|
#endif
|
||||||
std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};
|
std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};
|
||||||
};
|
};
|
||||||
} // Anonymous namespace
|
} // namespace
|
||||||
|
|
||||||
void Filter::ParseFilterString(std::string_view filter_view) {
|
|
||||||
auto clause_begin = filter_view.cbegin();
|
|
||||||
while (clause_begin < filter_view.cend()) {
|
|
||||||
auto clause_end = std::find(clause_begin, filter_view.cend(), ' ');
|
|
||||||
// If clause isn't empty
|
|
||||||
if (clause_end != clause_begin) {
|
|
||||||
ParseFilterRule(*this, clause_begin, clause_end);
|
|
||||||
}
|
|
||||||
if (clause_end != filter_view.cend()) {
|
|
||||||
// Skip over the whitespace
|
|
||||||
++clause_end;
|
|
||||||
}
|
|
||||||
clause_begin = clause_end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Constructor shall NOT depend upon Settings() or whatever
|
// Constructor shall NOT depend upon Settings() or whatever
|
||||||
// it's ran at global static ctor() time... so BE CAREFUL MFER!
|
// it's ran at global static ctor() time... so BE CAREFUL MFER!
|
||||||
@@ -426,13 +419,9 @@ void SetColorConsoleBackendEnabled(bool enabled) {
|
|||||||
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, unsigned int line_num, const char* function, fmt::string_view format, const fmt::format_args& args) {
|
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, unsigned int line_num, const char* function, fmt::string_view format, const fmt::format_args& args) {
|
||||||
if (logging_instance && logging_instance->filter.CheckMessage(log_class, log_level)) {
|
if (logging_instance && logging_instance->filter.CheckMessage(log_class, log_level)) {
|
||||||
auto const flush = ::Settings::values.log_flush_line.GetValue();
|
auto const flush = ::Settings::values.log_flush_line.GetValue();
|
||||||
char buffer[BUFSIZ];
|
|
||||||
auto result = fmt::vformat_to_n(buffer, sizeof(buffer) - 1, format, args);
|
|
||||||
buffer[(std::min)(result.size, sizeof(buffer) - 1)] = '\0';
|
|
||||||
logging_instance->ForEachBackend([=](Backend& backend) {
|
logging_instance->ForEachBackend([=](Backend& backend) {
|
||||||
backend.Write(Entry{
|
backend.Write(Entry{
|
||||||
.message = buffer,
|
.message = fmt::vformat(format, args),
|
||||||
.message_len = (std::min)(result.size, sizeof(buffer) - 1),
|
|
||||||
.timestamp = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - logging_instance->time_origin),
|
.timestamp = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - logging_instance->time_origin),
|
||||||
.log_class = log_class,
|
.log_class = log_class,
|
||||||
.log_level = log_level,
|
.log_level = log_level,
|
||||||
|
|||||||
@@ -140,4 +140,25 @@ void Stop();
|
|||||||
void SetGlobalFilter(const Filter& filter);
|
void SetGlobalFilter(const Filter& filter);
|
||||||
void SetColorConsoleBackendEnabled(bool enabled);
|
void SetColorConsoleBackendEnabled(bool enabled);
|
||||||
|
|
||||||
|
/// @brief A log entry. Log entries are store in a structured format to permit more varied output
|
||||||
|
/// formatting on different frontends, as well as facilitating filtering and aggregation.
|
||||||
|
struct Entry {
|
||||||
|
std::string message;
|
||||||
|
std::chrono::microseconds timestamp;
|
||||||
|
Class log_class{};
|
||||||
|
Level log_level{};
|
||||||
|
const char* filename = nullptr;
|
||||||
|
const char* function = nullptr;
|
||||||
|
unsigned int line_num = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Formats a log entry into the provided text buffer.
|
||||||
|
std::string FormatLogMessage(const Entry& entry) noexcept;
|
||||||
|
|
||||||
|
/// Prints the same message as `PrintMessage`, but colored according to the severity level.
|
||||||
|
void PrintColoredMessage(const Entry& entry) noexcept;
|
||||||
|
|
||||||
|
/// Formats and prints a log entry to the android logcat.
|
||||||
|
void PrintMessageToLogcat(const Entry& entry) noexcept;
|
||||||
|
|
||||||
} // namespace Common::Log
|
} // namespace Common::Log
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ std::optional<std::string> MakeRequest(const std::string& url, const std::string
|
|||||||
response.status);
|
response.status);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
if (!response.has_header("content-type")) {
|
if (!response.headers.contains("content-type")) {
|
||||||
LOG_ERROR(Common, "GET to {}{} returned no content", url, path);
|
LOG_ERROR(Common, "GET to {}{} returned no content", url, path);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,9 +132,11 @@ void LogSettings() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LOG_INFO(Config, "Eden Configuration:");
|
|
||||||
|
std::string settings_str{};
|
||||||
for (auto const& e : settings_list)
|
for (auto const& e : settings_list)
|
||||||
LOG_INFO(Config, "{}", e);
|
settings_str += e;
|
||||||
|
LOG_INFO(Config, "Eden Configuration:\n{}", settings_str);
|
||||||
#define LOG_PATH(NAME) \
|
#define LOG_PATH(NAME) \
|
||||||
LOG_INFO(Config, #NAME ": {}", Common::FS::PathToUTF8String(Common::FS::GetEdenPath(Common::FS::EdenPath::NAME)))
|
LOG_INFO(Config, #NAME ": {}", Common::FS::PathToUTF8String(Common::FS::GetEdenPath(Common::FS::EdenPath::NAME)))
|
||||||
LOG_PATH(CacheDir);
|
LOG_PATH(CacheDir);
|
||||||
@@ -146,7 +148,7 @@ void LogSettings() {
|
|||||||
#undef LOG_PATH
|
#undef LOG_PATH
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GetDebugKnobAt(u8 i) {
|
bool getDebugKnobAt(u8 i) {
|
||||||
return (values.debug_knobs.GetValue() & (1 << (i & 0xF))) != 0;
|
return (values.debug_knobs.GetValue() & (1 << (i & 0xF))) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -904,7 +904,7 @@ struct Values {
|
|||||||
0,
|
0,
|
||||||
65535,
|
65535,
|
||||||
"debug_knobs",
|
"debug_knobs",
|
||||||
Category::System,
|
Category::Debugging,
|
||||||
Specialization::Countable,
|
Specialization::Countable,
|
||||||
true,
|
true,
|
||||||
true};
|
true};
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ ENUM(TimeZone, Auto, Default, Cet, Cst6Cdt, Cuba, Eet, Egypt, Eire, Est, Est5Edt
|
|||||||
GmtPlusZero, GmtMinusZero, GmtZero, Greenwich, Hongkong, Hst, Iceland, Iran, Israel, Jamaica,
|
GmtPlusZero, GmtMinusZero, GmtZero, Greenwich, Hongkong, Hst, Iceland, Iran, Israel, Jamaica,
|
||||||
Japan, Kwajalein, Libya, Met, Mst, Mst7Mdt, Navajo, Nz, NzChat, Poland, Portugal, Prc, Pst8Pdt,
|
Japan, Kwajalein, Libya, Met, Mst, Mst7Mdt, Navajo, Nz, NzChat, Poland, Portugal, Prc, Pst8Pdt,
|
||||||
Roc, Rok, Singapore, Turkey, Uct, Universal, Utc, WSu, Wet, Zulu);
|
Roc, Rok, Singapore, Turkey, Uct, Universal, Utc, WSu, Wet, Zulu);
|
||||||
ENUM(AnisotropyMode, Automatic, Default, X2, X4, X8, X16);
|
ENUM(AnisotropyMode, Automatic, Default, X2, X4, X8, X16, X32, X64, None);
|
||||||
ENUM(AstcDecodeMode, Cpu, Gpu, CpuAsynchronous);
|
ENUM(AstcDecodeMode, Cpu, Gpu, CpuAsynchronous);
|
||||||
ENUM(AstcRecompression, Uncompressed, Bc1, Bc3);
|
ENUM(AstcRecompression, Uncompressed, Bc1, Bc3);
|
||||||
ENUM(FramePacingMode, Target_Auto, Target_30, Target_60, Target_90, Target_120);
|
ENUM(FramePacingMode, Target_Auto, Target_30, Target_60, Target_90, Target_120);
|
||||||
|
|||||||
@@ -7,4 +7,6 @@
|
|||||||
#define STB_IMAGE_IMPLEMENTATION 1
|
#define STB_IMAGE_IMPLEMENTATION 1
|
||||||
#define STB_IMAGE_RESIZE_IMPLEMENTATION 1
|
#define STB_IMAGE_RESIZE_IMPLEMENTATION 1
|
||||||
#define STB_IMAGE_WRITE_IMPLEMENTATION 1
|
#define STB_IMAGE_WRITE_IMPLEMENTATION 1
|
||||||
|
#define STBI_ONLY_JPEG 1
|
||||||
|
|
||||||
#include "common/stb.h"
|
#include "common/stb.h"
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define STBI_ONLY_JPEG 1
|
#define STBI_ONLY_JPEG 1
|
||||||
#define STBI_WRITE_NO_STDIO 1
|
|
||||||
#include <stb_image.h>
|
#include <stb_image.h>
|
||||||
#include <stb_image_resize.h>
|
#include <stb_image_resize.h>
|
||||||
#include <stb_image_write.h>
|
#include <stb_image_write.h>
|
||||||
|
|||||||
@@ -1267,23 +1267,6 @@ if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64 OR ARCHITECTURE_riscv64 OR ARCHITE
|
|||||||
target_link_libraries(core PRIVATE dynarmic::dynarmic)
|
target_link_libraries(core PRIVATE dynarmic::dynarmic)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_sources(core PRIVATE hle/service/ssl/ssl_backend_openssl.cpp)
|
|
||||||
|
|
||||||
target_link_libraries(core PRIVATE OpenSSL::SSL OpenSSL::Crypto)
|
target_link_libraries(core PRIVATE OpenSSL::SSL OpenSSL::Crypto)
|
||||||
|
|
||||||
# TODO
|
|
||||||
|
|
||||||
# elseif (APPLE)
|
|
||||||
# target_sources(core PRIVATE
|
|
||||||
# hle/service/ssl/ssl_backend_securetransport.cpp)
|
|
||||||
# target_link_libraries(core PRIVATE "-framework Security")
|
|
||||||
# elseif (WIN32)
|
|
||||||
# target_sources(core PRIVATE
|
|
||||||
# hle/service/ssl/ssl_backend_schannel.cpp)
|
|
||||||
# target_link_libraries(core PRIVATE crypt32 secur32)
|
|
||||||
# else()
|
|
||||||
# target_sources(core PRIVATE
|
|
||||||
# hle/service/ssl/ssl_backend_none.cpp)
|
|
||||||
# endif()
|
|
||||||
|
|
||||||
create_target_directory_groups(core)
|
create_target_directory_groups(core)
|
||||||
|
|||||||
@@ -286,7 +286,6 @@ void ArmDynarmic32::MakeJit(Common::PageTable* page_table) {
|
|||||||
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
|
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
|
||||||
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreStandardFPCRValue;
|
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreStandardFPCRValue;
|
||||||
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN;
|
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN;
|
||||||
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreGlobalMonitor;
|
|
||||||
break;
|
break;
|
||||||
// Paranoia mode for debugging optimizations
|
// Paranoia mode for debugging optimizations
|
||||||
case Settings::CpuAccuracy::Paranoid:
|
case Settings::CpuAccuracy::Paranoid:
|
||||||
|
|||||||
@@ -338,7 +338,6 @@ void ArmDynarmic64::MakeJit(Common::PageTable* page_table, std::size_t address_s
|
|||||||
config.unsafe_optimizations = true;
|
config.unsafe_optimizations = true;
|
||||||
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
|
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
|
||||||
config.fastmem_address_space_bits = 64;
|
config.fastmem_address_space_bits = 64;
|
||||||
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreGlobalMonitor;
|
|
||||||
break;
|
break;
|
||||||
// Paranoia mode for debugging optimizations
|
// Paranoia mode for debugging optimizations
|
||||||
case Settings::CpuAccuracy::Paranoid:
|
case Settings::CpuAccuracy::Paranoid:
|
||||||
|
|||||||
@@ -324,7 +324,6 @@ Result IApplicationFunctions::NotifyRunning(Out<bool> out_became_running) {
|
|||||||
|
|
||||||
Result IApplicationFunctions::GetPseudoDeviceId(Out<Common::UUID> out_pseudo_device_id) {
|
Result IApplicationFunctions::GetPseudoDeviceId(Out<Common::UUID> out_pseudo_device_id) {
|
||||||
LOG_WARNING(Service_AM, "(stubbed)");
|
LOG_WARNING(Service_AM, "(stubbed)");
|
||||||
R_UNLESS(out_pseudo_device_id, ResultUnknown);
|
|
||||||
|
|
||||||
// This should be hashed with the device specific hash
|
// This should be hashed with the device specific hash
|
||||||
// for now this will do
|
// for now this will do
|
||||||
|
|||||||
@@ -5,136 +5,33 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <mutex>
|
#include <ctime>
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/hle/kernel/k_event.h"
|
#include "core/hle/kernel/k_event.h"
|
||||||
#include "core/hle/service/cmif_serialization.h"
|
|
||||||
#include "core/hle/service/cmif_types.h"
|
|
||||||
#include "core/hle/service/ipc_helpers.h"
|
#include "core/hle/service/ipc_helpers.h"
|
||||||
#include "core/hle/service/kernel_helpers.h"
|
#include "core/hle/service/kernel_helpers.h"
|
||||||
#include "core/hle/service/nim/nim.h"
|
#include "core/hle/service/nim/nim.h"
|
||||||
#include "core/hle/service/os/event.h"
|
|
||||||
#include "core/hle/service/server_manager.h"
|
#include "core/hle/service/server_manager.h"
|
||||||
#include "core/hle/service/service.h"
|
#include "core/hle/service/service.h"
|
||||||
|
|
||||||
namespace Service::NIM {
|
namespace Service::NIM {
|
||||||
|
|
||||||
class IShopServiceAsync final : public ServiceFramework<IShopServiceAsync> {
|
class IShopServiceAsync final : public ServiceFramework<IShopServiceAsync> {
|
||||||
public:
|
public:
|
||||||
explicit IShopServiceAsync(Core::System& system_)
|
explicit IShopServiceAsync(Core::System& system_)
|
||||||
: ServiceFramework{system_, "IShopServiceAsync"},
|
: ServiceFramework{system_, "IShopServiceAsync"} {
|
||||||
service_context{system_, "IShopServiceAsync"} {
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
static const FunctionInfo functions[] = {
|
static const FunctionInfo functions[] = {
|
||||||
{0, D<&IShopServiceAsync::Cancel>, "Cancel"},
|
{0, nullptr, "Cancel"},
|
||||||
{1, D<&IShopServiceAsync::GetSize>, "GetSize"},
|
{1, nullptr, "GetSize"},
|
||||||
{2, D<&IShopServiceAsync::Read>, "Read"},
|
{2, nullptr, "Read"},
|
||||||
{3, D<&IShopServiceAsync::GetErrorCode>, "GetErrorCode"},
|
{3, nullptr, "GetErrorCode"},
|
||||||
{4, D<&IShopServiceAsync::Request>, "Request"},
|
{4, nullptr, "Request"},
|
||||||
{5, D<&IShopServiceAsync::Prepare>, "Prepare"},
|
{5, nullptr, "Prepare"},
|
||||||
};
|
};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
RegisterHandlers(functions);
|
RegisterHandlers(functions);
|
||||||
|
|
||||||
completion_event = service_context.CreateEvent("IShopServiceAsync:Completion");
|
|
||||||
}
|
|
||||||
|
|
||||||
~IShopServiceAsync() override {
|
|
||||||
CancelImpl();
|
|
||||||
service_context.CloseEvent(completion_event);
|
|
||||||
}
|
|
||||||
|
|
||||||
Kernel::KReadableEvent* GetEvent() const {
|
|
||||||
return &completion_event->GetReadableEvent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
KernelHelpers::ServiceContext service_context;
|
|
||||||
Kernel::KEvent* completion_event;
|
|
||||||
|
|
||||||
std::jthread worker;
|
|
||||||
std::atomic<u32> error_code{0};
|
|
||||||
|
|
||||||
std::mutex data_mutex;
|
|
||||||
std::vector<u8> download_data;
|
|
||||||
|
|
||||||
void CancelImpl() {
|
|
||||||
worker.request_stop();
|
|
||||||
if (worker.joinable()) {
|
|
||||||
worker.join();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Result Cancel() {
|
|
||||||
LOG_DEBUG(Service_NIM, "called");
|
|
||||||
CancelImpl();
|
|
||||||
R_SUCCEED();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result GetSize(Out<u64> out_size) {
|
|
||||||
LOG_DEBUG(Service_NIM, "called");
|
|
||||||
std::scoped_lock lock{data_mutex};
|
|
||||||
*out_size = download_data.size();
|
|
||||||
R_SUCCEED();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result Read(Out<u64> out_size, u64 offset, OutBuffer<BufferAttr_HipcAutoSelect> out_buffer) {
|
|
||||||
std::scoped_lock lock{data_mutex};
|
|
||||||
|
|
||||||
u64 actual_read = 0;
|
|
||||||
if (offset < download_data.size()) {
|
|
||||||
actual_read = std::min<u64>(out_buffer.size(), download_data.size() - offset);
|
|
||||||
std::memcpy(out_buffer.data(), download_data.data() + offset, actual_read);
|
|
||||||
}
|
|
||||||
|
|
||||||
*out_size = actual_read;
|
|
||||||
R_SUCCEED();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result GetErrorCode(Out<u32> out_error_code) {
|
|
||||||
LOG_DEBUG(Service_NIM, "called");
|
|
||||||
*out_error_code = error_code.load();
|
|
||||||
R_SUCCEED();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result Request() {
|
|
||||||
LOG_DEBUG(Service_NIM, "(STUBBED) called");
|
|
||||||
CancelImpl();
|
|
||||||
|
|
||||||
error_code.store(0);
|
|
||||||
completion_event->Clear(system.Kernel());
|
|
||||||
|
|
||||||
{
|
|
||||||
std::scoped_lock lock{data_mutex};
|
|
||||||
download_data.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
worker = std::jthread([this](const std::stop_token& stop_token) {
|
|
||||||
if (stop_token.stop_requested()) {
|
|
||||||
error_code.store(1);
|
|
||||||
} else {
|
|
||||||
std::scoped_lock lock{data_mutex};
|
|
||||||
// Dummy JSON response, else it fails...
|
|
||||||
const std::string dummy_response = "{}";
|
|
||||||
download_data.assign(dummy_response.begin(), dummy_response.end());
|
|
||||||
error_code.store(0);
|
|
||||||
}
|
|
||||||
completion_event->Signal(system.Kernel());
|
|
||||||
});
|
|
||||||
|
|
||||||
R_SUCCEED();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result Prepare(InArray<char, BufferAttr_HipcMapAlias> in_path, InArray<char, BufferAttr_HipcMapAlias> in_post) {
|
|
||||||
LOG_DEBUG(Service_NIM, "called");
|
|
||||||
if (!in_path.empty()) {
|
|
||||||
std::string url(in_path.data(), in_path.size());
|
|
||||||
LOG_INFO(Service_NIM, "Preparing request for URL: {}", url);
|
|
||||||
}
|
|
||||||
R_SUCCEED();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -152,13 +49,11 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void CreateAsyncInterface(HLERequestContext& ctx) {LOG_DEBUG(Service_NIM, "called");
|
void CreateAsyncInterface(HLERequestContext& ctx) {
|
||||||
auto async_interface = std::make_shared<IShopServiceAsync>(system);
|
LOG_WARNING(Service_NIM, "(STUBBED) called");
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||||
IPC::ResponseBuilder rb{ctx, 2, 1, 1};
|
|
||||||
rb.Push(ResultSuccess);
|
rb.Push(ResultSuccess);
|
||||||
rb.PushCopyObjects(ctx, async_interface->GetEvent());
|
rb.PushIpcInterface<IShopServiceAsync>(ctx, system);
|
||||||
rb.PushIpcInterface<IShopServiceAsync>(ctx, std::move(async_interface));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,11 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "common/stb.h"
|
#define STBI_ONLY_JPEG 1
|
||||||
|
#include <stb_image.h>
|
||||||
|
#include <stb_image_resize.h>
|
||||||
|
#include <stb_image_write.h>
|
||||||
|
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
#include "core/file_sys/control_metadata.h"
|
#include "core/file_sys/control_metadata.h"
|
||||||
#include "core/file_sys/patch_manager.h"
|
#include "core/file_sys/patch_manager.h"
|
||||||
|
|||||||
@@ -105,12 +105,7 @@ void LoopProcess(Core::System& system) {
|
|||||||
auto server_manager = std::make_unique<ServerManager>(system);
|
auto server_manager = std::make_unique<ServerManager>(system);
|
||||||
auto module = std::make_shared<Module>(system);
|
auto module = std::make_shared<Module>(system);
|
||||||
server_manager->RegisterNamedService("nvdrv", std::make_shared<NVDRV>(system, module, "nvdrv"));
|
server_manager->RegisterNamedService("nvdrv", std::make_shared<NVDRV>(system, module, "nvdrv"));
|
||||||
|
server_manager->RegisterNamedService("nvdrv:a", std::make_shared<NVDRV>(system, module, "nvdrv:a"));
|
||||||
const auto NvdrvInterfaceFactoryForApplets = [&, module] {
|
|
||||||
return std::make_shared<NVDRV>(system, module, "nvdrv:a");
|
|
||||||
};
|
|
||||||
|
|
||||||
server_manager->RegisterNamedService("nvdrv:a", NvdrvInterfaceFactoryForApplets);
|
|
||||||
server_manager->RegisterNamedService("nvdrv:s", std::make_shared<NVDRV>(system, module, "nvdrv:s"));
|
server_manager->RegisterNamedService("nvdrv:s", std::make_shared<NVDRV>(system, module, "nvdrv:s"));
|
||||||
server_manager->RegisterNamedService("nvdrv:t", std::make_shared<NVDRV>(system, module, "nvdrv:t"));
|
server_manager->RegisterNamedService("nvdrv:t", std::make_shared<NVDRV>(system, module, "nvdrv:t"));
|
||||||
server_manager->RegisterNamedService("nvmemp", std::make_shared<NVMEMP>(system));
|
server_manager->RegisterNamedService("nvmemp", std::make_shared<NVMEMP>(system));
|
||||||
|
|||||||
@@ -588,12 +588,7 @@ void LoopProcess(Core::System& system) {
|
|||||||
auto server_manager = std::make_unique<ServerManager>(system);
|
auto server_manager = std::make_unique<ServerManager>(system);
|
||||||
|
|
||||||
auto ro = std::make_shared<RoContext>();
|
auto ro = std::make_shared<RoContext>();
|
||||||
|
server_manager->RegisterNamedService("ldr:ro", std::make_shared<RoInterface>(system, "ldr:ro", ro, NrrKind::User));
|
||||||
const auto RoInterfaceFactoryForUser = [&, ro] {
|
|
||||||
return std::make_shared<RoInterface>(system, "ldr:ro", ro, NrrKind::User);
|
|
||||||
};
|
|
||||||
|
|
||||||
server_manager->RegisterNamedService("ldr:ro", std::move(RoInterfaceFactoryForUser));
|
|
||||||
server_manager->RegisterNamedService("ro:1", std::make_shared<RoInterface>(system, "ro:1", ro, NrrKind::JitPlugin));
|
server_manager->RegisterNamedService("ro:1", std::make_shared<RoInterface>(system, "ro:1", ro, NrrKind::JitPlugin));
|
||||||
server_manager->RegisterNamedService("ro:dmnt", std::make_shared<IDebugMonitorInterface>(system));
|
server_manager->RegisterNamedService("ro:dmnt", std::make_shared<IDebugMonitorInterface>(system));
|
||||||
ServerManager::RunServer(std::move(server_manager));
|
ServerManager::RunServer(std::move(server_manager));
|
||||||
|
|||||||
@@ -4,6 +4,18 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
#include <openssl/bio.h>
|
||||||
|
#include <openssl/err.h>
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
#include <openssl/x509.h>
|
||||||
|
#ifdef YUZU_BUNDLED_OPENSSL
|
||||||
|
#include <openssl/cert.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "common/fs/file.h"
|
||||||
|
#include "common/hex_util.h"
|
||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
|
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
@@ -22,6 +34,377 @@
|
|||||||
|
|
||||||
namespace Service::SSL {
|
namespace Service::SSL {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::once_flag one_time_init_flag;
|
||||||
|
bool one_time_init_success = false;
|
||||||
|
SSL_CTX* ssl_ctx = nullptr;
|
||||||
|
BIO_METHOD* bio_meth = nullptr;
|
||||||
|
Common::FS::IOFile key_log_file; // only open if SSLKEYLOGFILE set in environment
|
||||||
|
|
||||||
|
Result CheckOpenSSLErrors();
|
||||||
|
void OneTimeInit();
|
||||||
|
void OneTimeInitLogFile();
|
||||||
|
bool OneTimeInitBIO();
|
||||||
|
|
||||||
|
#ifdef YUZU_BUNDLED_OPENSSL
|
||||||
|
// This is ported from httplib
|
||||||
|
struct scope_exit {
|
||||||
|
explicit scope_exit(std::function<void(void)> &&f)
|
||||||
|
: exit_function(std::move(f)), execute_on_destruction{true} {}
|
||||||
|
|
||||||
|
scope_exit(scope_exit &&rhs) noexcept
|
||||||
|
: exit_function(std::move(rhs.exit_function)),
|
||||||
|
execute_on_destruction{rhs.execute_on_destruction} {
|
||||||
|
rhs.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
~scope_exit() {
|
||||||
|
if (execute_on_destruction) { this->exit_function(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
void release() { this->execute_on_destruction = false; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
scope_exit(const scope_exit &) = delete;
|
||||||
|
void operator=(const scope_exit &) = delete;
|
||||||
|
scope_exit &operator=(scope_exit &&) = delete;
|
||||||
|
|
||||||
|
std::function<void(void)> exit_function;
|
||||||
|
bool execute_on_destruction;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline X509_STORE *CreateCaCertStore(const char *ca_cert,
|
||||||
|
std::size_t size) {
|
||||||
|
auto mem = BIO_new_mem_buf(ca_cert, static_cast<int>(size));
|
||||||
|
auto se = scope_exit([&] { BIO_free_all(mem); });
|
||||||
|
if (!mem) { return nullptr; }
|
||||||
|
|
||||||
|
auto inf = PEM_X509_INFO_read_bio(mem, nullptr, nullptr, nullptr);
|
||||||
|
if (!inf) { return nullptr; }
|
||||||
|
|
||||||
|
auto cts = X509_STORE_new();
|
||||||
|
if (cts) {
|
||||||
|
for (auto i = 0; i < static_cast<int>(sk_X509_INFO_num(inf)); i++) {
|
||||||
|
auto itmp = sk_X509_INFO_value(inf, i);
|
||||||
|
if (!itmp) { continue; }
|
||||||
|
|
||||||
|
if (itmp->x509) { X509_STORE_add_cert(cts, itmp->x509); }
|
||||||
|
if (itmp->crl) { X509_STORE_add_crl(cts, itmp->crl); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sk_X509_INFO_pop_free(inf, X509_INFO_free);
|
||||||
|
return cts;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void SetCaCertStore(SSL_CTX *ctx, X509_STORE *ca_cert_store) {
|
||||||
|
if (ca_cert_store) {
|
||||||
|
if (ctx) {
|
||||||
|
if (SSL_CTX_get_cert_store(ctx) != ca_cert_store) {
|
||||||
|
// Free memory allocated for old cert and use new store `ca_cert_store`
|
||||||
|
SSL_CTX_set_cert_store(ctx, ca_cert_store);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
X509_STORE_free(ca_cert_store);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void LoadCaCertStore(SSL_CTX* ctx, const char* ca_cert, std::size_t size)
|
||||||
|
{
|
||||||
|
SetCaCertStore(ctx, CreateCaCertStore(ca_cert, size));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
class SSLConnectionBackend final {
|
||||||
|
public:
|
||||||
|
Result Init() {
|
||||||
|
// on bundled OpenSSL, load ca cert store
|
||||||
|
#ifdef YUZU_BUNDLED_OPENSSL
|
||||||
|
LoadCaCertStore(ssl_ctx, kCert, sizeof(kCert));
|
||||||
|
#endif
|
||||||
|
std::call_once(one_time_init_flag, OneTimeInit);
|
||||||
|
|
||||||
|
if (!one_time_init_success) {
|
||||||
|
LOG_ERROR(Service_SSL, "Can't create SSL connection because OpenSSL one-time initialization failed");
|
||||||
|
return ResultInternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssl = SSL_new(ssl_ctx);
|
||||||
|
if (!ssl) {
|
||||||
|
LOG_ERROR(Service_SSL, "SSL_new failed");
|
||||||
|
return CheckOpenSSLErrors();
|
||||||
|
}
|
||||||
|
SSL_set_connect_state(ssl);
|
||||||
|
bio = BIO_new(bio_meth);
|
||||||
|
if (!bio) {
|
||||||
|
LOG_ERROR(Service_SSL, "BIO_new failed");
|
||||||
|
return CheckOpenSSLErrors();
|
||||||
|
}
|
||||||
|
BIO_set_data(bio, this);
|
||||||
|
BIO_set_init(bio, 1);
|
||||||
|
SSL_set_bio(ssl, bio, bio);
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result SetHostName(const std::string& hostname) {
|
||||||
|
if (!skip_cert_verification) {
|
||||||
|
if (!SSL_set1_host(ssl, hostname.c_str())) {
|
||||||
|
LOG_ERROR(Service_SSL, "SSL_set1_host({}) failed", hostname);
|
||||||
|
return CheckOpenSSLErrors();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!SSL_set_tlsext_host_name(ssl, hostname.c_str())) { // hostname for SNI
|
||||||
|
LOG_ERROR(Service_SSL, "SSL_set_tlsext_host_name({}) failed", hostname);
|
||||||
|
return CheckOpenSSLErrors();
|
||||||
|
}
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetVerifyOption(u32 option) {
|
||||||
|
skip_cert_verification = (option == 0);
|
||||||
|
LOG_WARNING(Service_SSL, "option={} skip_verification={}", option,
|
||||||
|
skip_cert_verification);
|
||||||
|
if (skip_cert_verification) {
|
||||||
|
SSL_set_verify(ssl, SSL_VERIFY_NONE, nullptr);
|
||||||
|
SSL_set1_host(ssl, nullptr);
|
||||||
|
SSL_set_hostflags(ssl, 0);
|
||||||
|
} else {
|
||||||
|
SSL_set_verify(ssl, SSL_VERIFY_PEER, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DoHandshake() {
|
||||||
|
SSL_set_verify_result(ssl, X509_V_OK);
|
||||||
|
const int ret = SSL_do_handshake(ssl);
|
||||||
|
|
||||||
|
if (!skip_cert_verification) {
|
||||||
|
const long verify_result = SSL_get_verify_result(ssl);
|
||||||
|
if (verify_result != X509_V_OK) {
|
||||||
|
LOG_ERROR(Service_SSL, "SSL cert verification failed because: {}",
|
||||||
|
X509_verify_cert_error_string(verify_result));
|
||||||
|
return CheckOpenSSLErrors();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret <= 0) {
|
||||||
|
const int ssl_err = SSL_get_error(ssl, ret);
|
||||||
|
if (ssl_err == SSL_ERROR_ZERO_RETURN ||
|
||||||
|
(ssl_err == SSL_ERROR_SYSCALL && got_read_eof)) {
|
||||||
|
LOG_ERROR(Service_SSL, "SSL handshake failed because server hung up");
|
||||||
|
return ResultInternalError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return HandleReturn("SSL_do_handshake", 0, ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result HandleReturn(const char* what, size_t* actual, int ret) {
|
||||||
|
const int ssl_err = SSL_get_error(ssl, ret);
|
||||||
|
CheckOpenSSLErrors();
|
||||||
|
switch (ssl_err) {
|
||||||
|
case SSL_ERROR_NONE:
|
||||||
|
return ResultSuccess;
|
||||||
|
case SSL_ERROR_ZERO_RETURN:
|
||||||
|
LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_ZERO_RETURN", what);
|
||||||
|
// DoHandshake special-cases this, but for Read and Write:
|
||||||
|
*actual = 0;
|
||||||
|
return ResultSuccess;
|
||||||
|
case SSL_ERROR_WANT_READ:
|
||||||
|
LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_WANT_READ", what);
|
||||||
|
return ResultWouldBlock;
|
||||||
|
case SSL_ERROR_WANT_WRITE:
|
||||||
|
LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_WANT_WRITE", what);
|
||||||
|
return ResultWouldBlock;
|
||||||
|
default:
|
||||||
|
if (ssl_err == SSL_ERROR_SYSCALL && got_read_eof) {
|
||||||
|
LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_SYSCALL because server hung up", what);
|
||||||
|
*actual = 0;
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
LOG_ERROR(Service_SSL, "{} => other SSL_get_error return value {}", what, ssl_err);
|
||||||
|
return ResultInternalError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~SSLConnectionBackend() {
|
||||||
|
// this is null-tolerant:
|
||||||
|
SSL_free(ssl);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void KeyLogCallback(const ::SSL* ssl, const char* line) {
|
||||||
|
std::string str(line);
|
||||||
|
str.push_back('\n');
|
||||||
|
// Do this in a single WriteString for atomicity if multiple instances
|
||||||
|
// are running on different threads (though that can't currently
|
||||||
|
// happen).
|
||||||
|
if (key_log_file.WriteString(str) != str.size() || !key_log_file.Flush()) {
|
||||||
|
LOG_CRITICAL(Service_SSL, "Failed to write to SSLKEYLOGFILE");
|
||||||
|
}
|
||||||
|
LOG_DEBUG(Service_SSL, "Wrote to SSLKEYLOGFILE: {}", line);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int WriteCallback(BIO* bio, const char* buf, size_t len, size_t* actual_p) {
|
||||||
|
auto self = static_cast<SSLConnectionBackend*>(BIO_get_data(bio));
|
||||||
|
ASSERT_OR_EXECUTE_MSG(
|
||||||
|
self->socket, { return 0; }, "OpenSSL asked to send but we have no socket");
|
||||||
|
BIO_clear_retry_flags(bio);
|
||||||
|
auto [actual, err] = self->socket->Send({reinterpret_cast<const u8*>(buf), len}, 0);
|
||||||
|
switch (err) {
|
||||||
|
case Network::Errno::SUCCESS:
|
||||||
|
*actual_p = actual;
|
||||||
|
return 1;
|
||||||
|
case Network::Errno::AGAIN:
|
||||||
|
BIO_set_flags(bio, BIO_FLAGS_WRITE | BIO_FLAGS_SHOULD_RETRY);
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
LOG_ERROR(Service_SSL, "Socket send returned Network::Errno {}", err);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ReadCallback(BIO* bio, char* buf, size_t len, size_t* actual_p) {
|
||||||
|
auto self = static_cast<SSLConnectionBackend*>(BIO_get_data(bio));
|
||||||
|
ASSERT_OR_EXECUTE_MSG(
|
||||||
|
self->socket, { return 0; }, "OpenSSL asked to recv but we have no socket");
|
||||||
|
BIO_clear_retry_flags(bio);
|
||||||
|
auto [actual, err] = self->socket->Recv(0, {reinterpret_cast<u8*>(buf), len});
|
||||||
|
switch (err) {
|
||||||
|
case Network::Errno::SUCCESS:
|
||||||
|
*actual_p = actual;
|
||||||
|
if (actual == 0) {
|
||||||
|
self->got_read_eof = true;
|
||||||
|
}
|
||||||
|
return actual ? 1 : 0;
|
||||||
|
case Network::Errno::AGAIN:
|
||||||
|
BIO_set_flags(bio, BIO_FLAGS_READ | BIO_FLAGS_SHOULD_RETRY);
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
LOG_ERROR(Service_SSL, "Socket recv returned Network::Errno {}", err);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static long CtrlCallback(BIO* bio, int cmd, long l_arg, void* p_arg) {
|
||||||
|
switch (cmd) {
|
||||||
|
case BIO_CTRL_FLUSH:
|
||||||
|
// Nothing to flush.
|
||||||
|
return 1;
|
||||||
|
case BIO_CTRL_PUSH:
|
||||||
|
case BIO_CTRL_POP:
|
||||||
|
#ifdef BIO_CTRL_GET_KTLS_SEND
|
||||||
|
case BIO_CTRL_GET_KTLS_SEND:
|
||||||
|
case BIO_CTRL_GET_KTLS_RECV:
|
||||||
|
#endif
|
||||||
|
// We don't support these operations, but don't bother logging them
|
||||||
|
// as they're nothing unusual.
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
LOG_DEBUG(Service_SSL, "OpenSSL BIO got ctrl({}, {}, {})", cmd, l_arg, p_arg);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::SSL* ssl = nullptr;
|
||||||
|
BIO* bio = nullptr;
|
||||||
|
bool got_read_eof = false;
|
||||||
|
bool skip_cert_verification = false;
|
||||||
|
std::shared_ptr<Network::SocketBase> socket;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend) {
|
||||||
|
auto conn = std::make_unique<SSLConnectionBackend>();
|
||||||
|
R_TRY(conn->Init());
|
||||||
|
*out_backend = std::move(conn);
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result CheckOpenSSLErrors() {
|
||||||
|
unsigned long rc;
|
||||||
|
const char* file;
|
||||||
|
int line;
|
||||||
|
const char* func;
|
||||||
|
const char* data;
|
||||||
|
int flags;
|
||||||
|
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
|
||||||
|
while ((rc = ERR_get_error_all(&file, &line, &func, &data, &flags)))
|
||||||
|
#else
|
||||||
|
// Can't get function names from OpenSSL on this version, so use mine:
|
||||||
|
func = __func__;
|
||||||
|
while ((rc = ERR_get_error_line_data(&file, &line, &data, &flags)))
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
std::string msg;
|
||||||
|
msg.resize(1024, '\0');
|
||||||
|
ERR_error_string_n(rc, msg.data(), msg.size());
|
||||||
|
msg.resize(strlen(msg.data()), '\0');
|
||||||
|
if (flags & ERR_TXT_STRING) {
|
||||||
|
msg.append(" | ");
|
||||||
|
msg.append(data);
|
||||||
|
}
|
||||||
|
Common::Log::FmtLogMessage(Common::Log::Class::Service_SSL, Common::Log::Level::Error,
|
||||||
|
file, line, func, "OpenSSL: {}",
|
||||||
|
msg);
|
||||||
|
}
|
||||||
|
return ResultInternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OneTimeInit() {
|
||||||
|
ssl_ctx = SSL_CTX_new(TLS_client_method());
|
||||||
|
if (!ssl_ctx) {
|
||||||
|
LOG_ERROR(Service_SSL, "SSL_CTX_new failed");
|
||||||
|
CheckOpenSSLErrors();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, nullptr);
|
||||||
|
|
||||||
|
if (!SSL_CTX_set_default_verify_paths(ssl_ctx)) {
|
||||||
|
LOG_ERROR(Service_SSL, "SSL_CTX_set_default_verify_paths failed");
|
||||||
|
CheckOpenSSLErrors();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
OneTimeInitLogFile();
|
||||||
|
|
||||||
|
if (!OneTimeInitBIO()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
one_time_init_success = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OneTimeInitLogFile() {
|
||||||
|
const char* logfile = getenv("SSLKEYLOGFILE");
|
||||||
|
if (logfile) {
|
||||||
|
key_log_file.Open(logfile, Common::FS::FileAccessMode::Append, Common::FS::FileType::TextFile, Common::FS::FileShareFlag::ShareWriteOnly);
|
||||||
|
if (key_log_file.IsOpen()) {
|
||||||
|
SSL_CTX_set_keylog_callback(ssl_ctx, &SSLConnectionBackend::KeyLogCallback);
|
||||||
|
} else {
|
||||||
|
LOG_CRITICAL(Service_SSL, "SSLKEYLOGFILE was set but file could not be opened; not logging keys!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OneTimeInitBIO() {
|
||||||
|
bio_meth =
|
||||||
|
BIO_meth_new(BIO_get_new_index() | BIO_TYPE_SOURCE_SINK, "SSLConnectionBackend");
|
||||||
|
if (!bio_meth ||
|
||||||
|
!BIO_meth_set_write_ex(bio_meth, &SSLConnectionBackend::WriteCallback) ||
|
||||||
|
!BIO_meth_set_read_ex(bio_meth, &SSLConnectionBackend::ReadCallback) ||
|
||||||
|
!BIO_meth_set_ctrl(bio_meth, &SSLConnectionBackend::CtrlCallback)) {
|
||||||
|
LOG_ERROR(Service_SSL, "Failed to create BIO_METHOD");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
// This is nn::ssl::sf::CertificateFormat
|
// This is nn::ssl::sf::CertificateFormat
|
||||||
enum class CertificateFormat : u32 {
|
enum class CertificateFormat : u32 {
|
||||||
Pem = 1,
|
Pem = 1,
|
||||||
@@ -162,20 +545,17 @@ private:
|
|||||||
|
|
||||||
auto const res_v = bsd->DuplicateSocketImpl(fd);
|
auto const res_v = bsd->DuplicateSocketImpl(fd);
|
||||||
if (auto *res = std::get_if<s32>(&res_v)) {
|
if (auto *res = std::get_if<s32>(&res_v)) {
|
||||||
const s32 duplicated_fd = *res;
|
const s32 dup_fd = *res;
|
||||||
if (do_not_close_socket) {
|
*out_fd = do_not_close_socket ? dup_fd : -1;
|
||||||
*out_fd = duplicated_fd;
|
if (!do_not_close_socket)
|
||||||
} else {
|
fd_to_close = dup_fd;
|
||||||
*out_fd = -1;
|
auto const sock = bsd->GetSocket(dup_fd);
|
||||||
fd_to_close = duplicated_fd;
|
|
||||||
}
|
|
||||||
std::optional<std::shared_ptr<Network::SocketBase>> sock = bsd->GetSocket(duplicated_fd);
|
|
||||||
if (!sock.has_value()) {
|
if (!sock.has_value()) {
|
||||||
LOG_ERROR(Service_SSL, "invalid socket fd {} after duplication", duplicated_fd);
|
LOG_ERROR(Service_SSL, "invalid socket fd {} after duplication", dup_fd);
|
||||||
return ResultInvalidSocket;
|
return ResultInvalidSocket;
|
||||||
}
|
}
|
||||||
socket = std::move(*sock);
|
socket = std::move(*sock);
|
||||||
backend->SetSocket(socket);
|
backend->socket = std::move(socket);
|
||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
LOG_ERROR(Service_SSL, "Failed to duplicate socket with fd {}", fd);
|
LOG_ERROR(Service_SSL, "Failed to duplicate socket with fd {}", fd);
|
||||||
@@ -189,11 +569,11 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
Result SetVerifyOptionImpl(u32 option) {
|
Result SetVerifyOptionImpl(u32 option) {
|
||||||
ASSERT(!did_handshake);
|
|
||||||
LOG_DEBUG(Service_SSL, "called. option={} (forcing 0)", option);
|
LOG_DEBUG(Service_SSL, "called. option={} (forcing 0)", option);
|
||||||
|
ASSERT(!did_handshake);
|
||||||
verify_option = 0;
|
verify_option = 0;
|
||||||
backend->SetVerifyOption(0);
|
backend->SetVerifyOption(0);
|
||||||
return ResultSuccess;
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
Result SetIoModeImpl(u32 input_mode) {
|
Result SetIoModeImpl(u32 input_mode) {
|
||||||
@@ -206,13 +586,13 @@ private:
|
|||||||
if (error != Network::Errno::SUCCESS) {
|
if (error != Network::Errno::SUCCESS) {
|
||||||
LOG_ERROR(Service_SSL, "Failed to set native socket non-block flag to {}", non_block);
|
LOG_ERROR(Service_SSL, "Failed to set native socket non-block flag to {}", non_block);
|
||||||
}
|
}
|
||||||
return ResultSuccess;
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
Result SetSessionCacheModeImpl(u32 mode) {
|
Result SetSessionCacheModeImpl(u32 mode) {
|
||||||
ASSERT(!did_handshake);
|
ASSERT(!did_handshake);
|
||||||
LOG_WARNING(Service_SSL, "(STUBBED) called. value={}", mode);
|
LOG_WARNING(Service_SSL, "(STUBBED) called. value={}", mode);
|
||||||
return ResultSuccess;
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
Result DoHandshakeImpl() {
|
Result DoHandshakeImpl() {
|
||||||
@@ -234,19 +614,17 @@ private:
|
|||||||
};
|
};
|
||||||
if (!get_server_cert_chain) {
|
if (!get_server_cert_chain) {
|
||||||
// Just return the first one, unencoded.
|
// Just return the first one, unencoded.
|
||||||
ASSERT_OR_EXECUTE_MSG(
|
ASSERT_OR_EXECUTE_MSG(!certs.empty(), { return {}; }, "Should be at least one server cert");
|
||||||
!certs.empty(), { return {}; }, "Should be at least one server cert");
|
|
||||||
return certs[0];
|
return certs[0];
|
||||||
}
|
}
|
||||||
std::vector<u8> ret;
|
std::vector<u8> ret;
|
||||||
Header header{0x4E4D684374726543, static_cast<u32>(certs.size()), 0};
|
Header header{0x4E4D684374726543, u32(certs.size()), 0};
|
||||||
ret.insert(ret.end(), reinterpret_cast<u8*>(&header), reinterpret_cast<u8*>(&header + 1));
|
ret.insert(ret.end(), reinterpret_cast<u8*>(&header), reinterpret_cast<u8*>(&header + 1));
|
||||||
size_t data_offset = sizeof(Header) + certs.size() * sizeof(EntryHeader);
|
size_t data_offset = sizeof(Header) + certs.size() * sizeof(EntryHeader);
|
||||||
for (auto& cert : certs) {
|
for (auto& cert : certs) {
|
||||||
EntryHeader entry_header{static_cast<u32>(cert.size()), static_cast<u32>(data_offset)};
|
EntryHeader entry_header{u32(cert.size()), u32(data_offset)};
|
||||||
data_offset += cert.size();
|
data_offset += cert.size();
|
||||||
ret.insert(ret.end(), reinterpret_cast<u8*>(&entry_header),
|
ret.insert(ret.end(), reinterpret_cast<u8*>(&entry_header), reinterpret_cast<u8*>(&entry_header + 1));
|
||||||
reinterpret_cast<u8*>(&entry_header + 1));
|
|
||||||
}
|
}
|
||||||
for (auto& cert : certs) {
|
for (auto& cert : certs) {
|
||||||
ret.insert(ret.end(), cert.begin(), cert.end());
|
ret.insert(ret.end(), cert.begin(), cert.end());
|
||||||
@@ -257,7 +635,8 @@ private:
|
|||||||
Result ReadImpl(std::vector<u8>* out_data) {
|
Result ReadImpl(std::vector<u8>* out_data) {
|
||||||
ASSERT_OR_EXECUTE(did_handshake, { return ResultInternalError; });
|
ASSERT_OR_EXECUTE(did_handshake, { return ResultInternalError; });
|
||||||
size_t actual_size{};
|
size_t actual_size{};
|
||||||
Result res = backend->Read(&actual_size, *out_data);
|
const int ret = SSL_read_ex(backend->ssl, out_data->data(), out_data->size(), &actual_size);
|
||||||
|
Result res = backend->HandleReturn("SSL_read_ex", &actual_size, ret);
|
||||||
if (res != ResultSuccess) {
|
if (res != ResultSuccess) {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@@ -267,12 +646,13 @@ private:
|
|||||||
|
|
||||||
Result WriteImpl(size_t* out_size, std::span<const u8> data) {
|
Result WriteImpl(size_t* out_size, std::span<const u8> data) {
|
||||||
ASSERT_OR_EXECUTE(did_handshake, { return ResultInternalError; });
|
ASSERT_OR_EXECUTE(did_handshake, { return ResultInternalError; });
|
||||||
return backend->Write(out_size, data);
|
const int ret = SSL_write_ex(backend->ssl, data.data(), data.size(), out_size);
|
||||||
|
return backend->HandleReturn("SSL_write_ex", out_size, ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result PendingImpl(s32* out_pending) {
|
Result PendingImpl(s32* out_pending) {
|
||||||
LOG_WARNING(Service_SSL, "(STUBBED) called.");
|
LOG_WARNING(Service_SSL, "(STUBBED) called.");
|
||||||
*out_pending = 0;
|
*out_pending = SSL_pending(backend->ssl);
|
||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,24 +706,39 @@ private:
|
|||||||
OutputParameters out{};
|
OutputParameters out{};
|
||||||
if (res == ResultSuccess) {
|
if (res == ResultSuccess) {
|
||||||
std::vector<std::vector<u8>> certs;
|
std::vector<std::vector<u8>> certs;
|
||||||
res = backend->GetServerCerts(&certs);
|
STACK_OF(X509)* chain = SSL_get_peer_cert_chain(backend->ssl);
|
||||||
if (res == ResultSuccess) {
|
if (chain) {
|
||||||
|
int count = sk_X509_num(chain);
|
||||||
|
ASSERT(count >= 0);
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
X509* x509 = sk_X509_value(chain, i);
|
||||||
|
ASSERT_OR_EXECUTE(x509 != nullptr, { continue; });
|
||||||
|
unsigned char* buf = nullptr;
|
||||||
|
int len = i2d_X509(x509, &buf);
|
||||||
|
ASSERT_OR_EXECUTE(len >= 0 && buf, { continue; });
|
||||||
|
certs.emplace_back(buf, buf + len);
|
||||||
|
OPENSSL_free(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
// succeed!
|
||||||
const std::vector<u8> certs_buf = SerializeServerCerts(certs);
|
const std::vector<u8> certs_buf = SerializeServerCerts(certs);
|
||||||
if (ctx.CanWriteBuffer()) {
|
if (ctx.CanWriteBuffer()) {
|
||||||
const size_t buffer_size = ctx.GetWriteBufferSize();
|
const size_t buffer_size = ctx.GetWriteBufferSize();
|
||||||
if (certs_buf.size() <= buffer_size) {
|
if (certs_buf.size() <= buffer_size) {
|
||||||
ctx.WriteBuffer(certs_buf);
|
ctx.WriteBuffer(certs_buf);
|
||||||
} else {
|
} else {
|
||||||
LOG_WARNING(Service_SSL, "Certificate buffer too small: {} bytes needed, {} bytes available",
|
LOG_WARNING(Service_SSL, "Certificate buffer too small: {} bytes needed, {} bytes available", certs_buf.size(), buffer_size);
|
||||||
certs_buf.size(), buffer_size);
|
|
||||||
ctx.WriteBuffer(std::span<const u8>(certs_buf.data(), buffer_size));
|
ctx.WriteBuffer(std::span<const u8>(certs_buf.data(), buffer_size));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LOG_DEBUG(Service_SSL, "No output buffer provided for certificates ({} bytes)", certs_buf.size());
|
LOG_DEBUG(Service_SSL, "No output buffer provided for certificates ({} bytes)", certs_buf.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
out.certs_count = static_cast<u32>(certs.size());
|
out.certs_count = u32(certs.size());
|
||||||
out.certs_size = static_cast<u32>(certs_buf.size());
|
out.certs_size = u32(certs_buf.size());
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(Service_SSL, "SSL_get_peer_cert_chain returned nullptr");
|
||||||
|
res = ResultInternalError;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
IPC::ResponseBuilder rb{ctx, 4};
|
IPC::ResponseBuilder rb{ctx, 4};
|
||||||
|
|||||||
@@ -32,18 +32,4 @@ constexpr Result ResultInternalError{ErrorModule::SSLSrv, 999}; // made up
|
|||||||
// polling for read (with a timeout).
|
// polling for read (with a timeout).
|
||||||
constexpr Result ResultWouldBlock{ErrorModule::SSLSrv, 204};
|
constexpr Result ResultWouldBlock{ErrorModule::SSLSrv, 204};
|
||||||
|
|
||||||
class SSLConnectionBackend {
|
|
||||||
public:
|
|
||||||
virtual ~SSLConnectionBackend() {}
|
|
||||||
virtual void SetSocket(std::shared_ptr<Network::SocketBase> socket) = 0;
|
|
||||||
virtual Result SetHostName(const std::string& hostname) = 0;
|
|
||||||
virtual void SetVerifyOption(u32 option) = 0;
|
|
||||||
virtual Result DoHandshake() = 0;
|
|
||||||
virtual Result Read(size_t* out_size, std::span<u8> data) = 0;
|
|
||||||
virtual Result Write(size_t* out_size, std::span<const u8> data) = 0;
|
|
||||||
virtual Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend);
|
|
||||||
|
|
||||||
} // namespace Service::SSL
|
} // namespace Service::SSL
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#include "common/logging.h"
|
|
||||||
|
|
||||||
#include "core/hle/service/ssl/ssl_backend.h"
|
|
||||||
|
|
||||||
namespace Service::SSL {
|
|
||||||
|
|
||||||
Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend) {
|
|
||||||
LOG_ERROR(Service_SSL,
|
|
||||||
"Can't create SSL connection because no SSL backend is available on this platform");
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Service::SSL
|
|
||||||
@@ -1,450 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
#include <openssl/bio.h>
|
|
||||||
#include <openssl/err.h>
|
|
||||||
#include <openssl/ssl.h>
|
|
||||||
#include <openssl/x509.h>
|
|
||||||
|
|
||||||
#include "common/fs/file.h"
|
|
||||||
#include "common/hex_util.h"
|
|
||||||
#include "common/string_util.h"
|
|
||||||
|
|
||||||
#include "core/hle/service/ssl/ssl_backend.h"
|
|
||||||
#include "core/internal_network/network.h"
|
|
||||||
#include "core/internal_network/sockets.h"
|
|
||||||
|
|
||||||
#ifdef YUZU_BUNDLED_OPENSSL
|
|
||||||
#include <openssl/cert.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
using namespace Common::FS;
|
|
||||||
|
|
||||||
namespace Service::SSL {
|
|
||||||
|
|
||||||
// Import OpenSSL's `SSL` type into the namespace. This is needed because the
|
|
||||||
// namespace is also named `SSL`.
|
|
||||||
using ::SSL;
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
std::once_flag one_time_init_flag;
|
|
||||||
bool one_time_init_success = false;
|
|
||||||
|
|
||||||
SSL_CTX* ssl_ctx;
|
|
||||||
IOFile key_log_file; // only open if SSLKEYLOGFILE set in environment
|
|
||||||
BIO_METHOD* bio_meth;
|
|
||||||
|
|
||||||
Result CheckOpenSSLErrors();
|
|
||||||
void OneTimeInit();
|
|
||||||
void OneTimeInitLogFile();
|
|
||||||
bool OneTimeInitBIO();
|
|
||||||
|
|
||||||
#ifdef YUZU_BUNDLED_OPENSSL
|
|
||||||
// This is ported from httplib
|
|
||||||
struct scope_exit {
|
|
||||||
explicit scope_exit(std::function<void(void)> &&f)
|
|
||||||
: exit_function(std::move(f)), execute_on_destruction{true} {}
|
|
||||||
|
|
||||||
scope_exit(scope_exit &&rhs) noexcept
|
|
||||||
: exit_function(std::move(rhs.exit_function)),
|
|
||||||
execute_on_destruction{rhs.execute_on_destruction} {
|
|
||||||
rhs.release();
|
|
||||||
}
|
|
||||||
|
|
||||||
~scope_exit() {
|
|
||||||
if (execute_on_destruction) { this->exit_function(); }
|
|
||||||
}
|
|
||||||
|
|
||||||
void release() { this->execute_on_destruction = false; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
scope_exit(const scope_exit &) = delete;
|
|
||||||
void operator=(const scope_exit &) = delete;
|
|
||||||
scope_exit &operator=(scope_exit &&) = delete;
|
|
||||||
|
|
||||||
std::function<void(void)> exit_function;
|
|
||||||
bool execute_on_destruction;
|
|
||||||
};
|
|
||||||
|
|
||||||
inline X509_STORE *CreateCaCertStore(const char *ca_cert,
|
|
||||||
std::size_t size) {
|
|
||||||
auto mem = BIO_new_mem_buf(ca_cert, static_cast<int>(size));
|
|
||||||
auto se = scope_exit([&] { BIO_free_all(mem); });
|
|
||||||
if (!mem) { return nullptr; }
|
|
||||||
|
|
||||||
auto inf = PEM_X509_INFO_read_bio(mem, nullptr, nullptr, nullptr);
|
|
||||||
if (!inf) { return nullptr; }
|
|
||||||
|
|
||||||
auto cts = X509_STORE_new();
|
|
||||||
if (cts) {
|
|
||||||
for (auto i = 0; i < static_cast<int>(sk_X509_INFO_num(inf)); i++) {
|
|
||||||
auto itmp = sk_X509_INFO_value(inf, i);
|
|
||||||
if (!itmp) { continue; }
|
|
||||||
|
|
||||||
if (itmp->x509) { X509_STORE_add_cert(cts, itmp->x509); }
|
|
||||||
if (itmp->crl) { X509_STORE_add_crl(cts, itmp->crl); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sk_X509_INFO_pop_free(inf, X509_INFO_free);
|
|
||||||
return cts;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void SetCaCertStore(SSL_CTX *ctx, X509_STORE *ca_cert_store) {
|
|
||||||
if (ca_cert_store) {
|
|
||||||
if (ctx) {
|
|
||||||
if (SSL_CTX_get_cert_store(ctx) != ca_cert_store) {
|
|
||||||
// Free memory allocated for old cert and use new store `ca_cert_store`
|
|
||||||
SSL_CTX_set_cert_store(ctx, ca_cert_store);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
X509_STORE_free(ca_cert_store);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void LoadCaCertStore(SSL_CTX* ctx, const char* ca_cert, std::size_t size)
|
|
||||||
{
|
|
||||||
SetCaCertStore(ctx, CreateCaCertStore(ca_cert, size));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
class SSLConnectionBackendOpenSSL final : public SSLConnectionBackend {
|
|
||||||
public:
|
|
||||||
Result Init() {
|
|
||||||
// on bundled OpenSSL, load ca cert store
|
|
||||||
#ifdef YUZU_BUNDLED_OPENSSL
|
|
||||||
LoadCaCertStore(ssl_ctx, kCert, sizeof(kCert));
|
|
||||||
#endif
|
|
||||||
std::call_once(one_time_init_flag, OneTimeInit);
|
|
||||||
|
|
||||||
if (!one_time_init_success) {
|
|
||||||
LOG_ERROR(Service_SSL,
|
|
||||||
"Can't create SSL connection because OpenSSL one-time initialization failed");
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
|
|
||||||
ssl = SSL_new(ssl_ctx);
|
|
||||||
if (!ssl) {
|
|
||||||
LOG_ERROR(Service_SSL, "SSL_new failed");
|
|
||||||
return CheckOpenSSLErrors();
|
|
||||||
}
|
|
||||||
|
|
||||||
SSL_set_connect_state(ssl);
|
|
||||||
|
|
||||||
bio = BIO_new(bio_meth);
|
|
||||||
if (!bio) {
|
|
||||||
LOG_ERROR(Service_SSL, "BIO_new failed");
|
|
||||||
return CheckOpenSSLErrors();
|
|
||||||
}
|
|
||||||
|
|
||||||
BIO_set_data(bio, this);
|
|
||||||
BIO_set_init(bio, 1);
|
|
||||||
SSL_set_bio(ssl, bio, bio);
|
|
||||||
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetSocket(std::shared_ptr<Network::SocketBase> socket_in) override {
|
|
||||||
socket = std::move(socket_in);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SetHostName(const std::string& hostname) override {
|
|
||||||
if (!skip_cert_verification) {
|
|
||||||
if (!SSL_set1_host(ssl, hostname.c_str())) {
|
|
||||||
LOG_ERROR(Service_SSL, "SSL_set1_host({}) failed", hostname);
|
|
||||||
return CheckOpenSSLErrors();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!SSL_set_tlsext_host_name(ssl, hostname.c_str())) { // hostname for SNI
|
|
||||||
LOG_ERROR(Service_SSL, "SSL_set_tlsext_host_name({}) failed", hostname);
|
|
||||||
return CheckOpenSSLErrors();
|
|
||||||
}
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetVerifyOption(u32 option) override {
|
|
||||||
skip_cert_verification = (option == 0);
|
|
||||||
LOG_WARNING(Service_SSL, "option={} skip_verification={}", option,
|
|
||||||
skip_cert_verification);
|
|
||||||
if (skip_cert_verification) {
|
|
||||||
SSL_set_verify(ssl, SSL_VERIFY_NONE, nullptr);
|
|
||||||
SSL_set1_host(ssl, nullptr);
|
|
||||||
SSL_set_hostflags(ssl, 0);
|
|
||||||
} else {
|
|
||||||
SSL_set_verify(ssl, SSL_VERIFY_PEER, nullptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DoHandshake() override {
|
|
||||||
SSL_set_verify_result(ssl, X509_V_OK);
|
|
||||||
const int ret = SSL_do_handshake(ssl);
|
|
||||||
|
|
||||||
if (!skip_cert_verification) {
|
|
||||||
const long verify_result = SSL_get_verify_result(ssl);
|
|
||||||
if (verify_result != X509_V_OK) {
|
|
||||||
LOG_ERROR(Service_SSL, "SSL cert verification failed because: {}",
|
|
||||||
X509_verify_cert_error_string(verify_result));
|
|
||||||
return CheckOpenSSLErrors();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ret <= 0) {
|
|
||||||
const int ssl_err = SSL_get_error(ssl, ret);
|
|
||||||
if (ssl_err == SSL_ERROR_ZERO_RETURN ||
|
|
||||||
(ssl_err == SSL_ERROR_SYSCALL && got_read_eof)) {
|
|
||||||
LOG_ERROR(Service_SSL, "SSL handshake failed because server hung up");
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return HandleReturn("SSL_do_handshake", 0, ret);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result Read(size_t* out_size, std::span<u8> data) override {
|
|
||||||
const int ret = SSL_read_ex(ssl, data.data(), data.size(), out_size);
|
|
||||||
return HandleReturn("SSL_read_ex", out_size, ret);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result Write(size_t* out_size, std::span<const u8> data) override {
|
|
||||||
const int ret = SSL_write_ex(ssl, data.data(), data.size(), out_size);
|
|
||||||
return HandleReturn("SSL_write_ex", out_size, ret);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result HandleReturn(const char* what, size_t* actual, int ret) {
|
|
||||||
const int ssl_err = SSL_get_error(ssl, ret);
|
|
||||||
CheckOpenSSLErrors();
|
|
||||||
switch (ssl_err) {
|
|
||||||
case SSL_ERROR_NONE:
|
|
||||||
return ResultSuccess;
|
|
||||||
case SSL_ERROR_ZERO_RETURN:
|
|
||||||
LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_ZERO_RETURN", what);
|
|
||||||
// DoHandshake special-cases this, but for Read and Write:
|
|
||||||
*actual = 0;
|
|
||||||
return ResultSuccess;
|
|
||||||
case SSL_ERROR_WANT_READ:
|
|
||||||
LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_WANT_READ", what);
|
|
||||||
return ResultWouldBlock;
|
|
||||||
case SSL_ERROR_WANT_WRITE:
|
|
||||||
LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_WANT_WRITE", what);
|
|
||||||
return ResultWouldBlock;
|
|
||||||
default:
|
|
||||||
if (ssl_err == SSL_ERROR_SYSCALL && got_read_eof) {
|
|
||||||
LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_SYSCALL because server hung up", what);
|
|
||||||
*actual = 0;
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
LOG_ERROR(Service_SSL, "{} => other SSL_get_error return value {}", what, ssl_err);
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) override {
|
|
||||||
STACK_OF(X509)* chain = SSL_get_peer_cert_chain(ssl);
|
|
||||||
if (!chain) {
|
|
||||||
LOG_ERROR(Service_SSL, "SSL_get_peer_cert_chain returned nullptr");
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
int count = sk_X509_num(chain);
|
|
||||||
ASSERT(count >= 0);
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
X509* x509 = sk_X509_value(chain, i);
|
|
||||||
ASSERT_OR_EXECUTE(x509 != nullptr, { continue; });
|
|
||||||
unsigned char* buf = nullptr;
|
|
||||||
int len = i2d_X509(x509, &buf);
|
|
||||||
ASSERT_OR_EXECUTE(len >= 0 && buf, { continue; });
|
|
||||||
out_certs->emplace_back(buf, buf + len);
|
|
||||||
OPENSSL_free(buf);
|
|
||||||
}
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
~SSLConnectionBackendOpenSSL() {
|
|
||||||
// this is null-tolerant:
|
|
||||||
SSL_free(ssl);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void KeyLogCallback(const SSL* ssl, const char* line) {
|
|
||||||
std::string str(line);
|
|
||||||
str.push_back('\n');
|
|
||||||
// Do this in a single WriteString for atomicity if multiple instances
|
|
||||||
// are running on different threads (though that can't currently
|
|
||||||
// happen).
|
|
||||||
if (key_log_file.WriteString(str) != str.size() || !key_log_file.Flush()) {
|
|
||||||
LOG_CRITICAL(Service_SSL, "Failed to write to SSLKEYLOGFILE");
|
|
||||||
}
|
|
||||||
LOG_DEBUG(Service_SSL, "Wrote to SSLKEYLOGFILE: {}", line);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int WriteCallback(BIO* bio, const char* buf, size_t len, size_t* actual_p) {
|
|
||||||
auto self = static_cast<SSLConnectionBackendOpenSSL*>(BIO_get_data(bio));
|
|
||||||
ASSERT_OR_EXECUTE_MSG(
|
|
||||||
self->socket, { return 0; }, "OpenSSL asked to send but we have no socket");
|
|
||||||
BIO_clear_retry_flags(bio);
|
|
||||||
auto [actual, err] = self->socket->Send({reinterpret_cast<const u8*>(buf), len}, 0);
|
|
||||||
switch (err) {
|
|
||||||
case Network::Errno::SUCCESS:
|
|
||||||
*actual_p = actual;
|
|
||||||
return 1;
|
|
||||||
case Network::Errno::AGAIN:
|
|
||||||
BIO_set_flags(bio, BIO_FLAGS_WRITE | BIO_FLAGS_SHOULD_RETRY);
|
|
||||||
return 0;
|
|
||||||
default:
|
|
||||||
LOG_ERROR(Service_SSL, "Socket send returned Network::Errno {}", err);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int ReadCallback(BIO* bio, char* buf, size_t len, size_t* actual_p) {
|
|
||||||
auto self = static_cast<SSLConnectionBackendOpenSSL*>(BIO_get_data(bio));
|
|
||||||
ASSERT_OR_EXECUTE_MSG(
|
|
||||||
self->socket, { return 0; }, "OpenSSL asked to recv but we have no socket");
|
|
||||||
BIO_clear_retry_flags(bio);
|
|
||||||
auto [actual, err] = self->socket->Recv(0, {reinterpret_cast<u8*>(buf), len});
|
|
||||||
switch (err) {
|
|
||||||
case Network::Errno::SUCCESS:
|
|
||||||
*actual_p = actual;
|
|
||||||
if (actual == 0) {
|
|
||||||
self->got_read_eof = true;
|
|
||||||
}
|
|
||||||
return actual ? 1 : 0;
|
|
||||||
case Network::Errno::AGAIN:
|
|
||||||
BIO_set_flags(bio, BIO_FLAGS_READ | BIO_FLAGS_SHOULD_RETRY);
|
|
||||||
return 0;
|
|
||||||
default:
|
|
||||||
LOG_ERROR(Service_SSL, "Socket recv returned Network::Errno {}", err);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static long CtrlCallback(BIO* bio, int cmd, long l_arg, void* p_arg) {
|
|
||||||
switch (cmd) {
|
|
||||||
case BIO_CTRL_FLUSH:
|
|
||||||
// Nothing to flush.
|
|
||||||
return 1;
|
|
||||||
case BIO_CTRL_PUSH:
|
|
||||||
case BIO_CTRL_POP:
|
|
||||||
#ifdef BIO_CTRL_GET_KTLS_SEND
|
|
||||||
case BIO_CTRL_GET_KTLS_SEND:
|
|
||||||
case BIO_CTRL_GET_KTLS_RECV:
|
|
||||||
#endif
|
|
||||||
// We don't support these operations, but don't bother logging them
|
|
||||||
// as they're nothing unusual.
|
|
||||||
return 0;
|
|
||||||
default:
|
|
||||||
LOG_DEBUG(Service_SSL, "OpenSSL BIO got ctrl({}, {}, {})", cmd, l_arg, p_arg);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SSL* ssl = nullptr;
|
|
||||||
BIO* bio = nullptr;
|
|
||||||
bool got_read_eof = false;
|
|
||||||
bool skip_cert_verification = false;
|
|
||||||
|
|
||||||
std::shared_ptr<Network::SocketBase> socket;
|
|
||||||
};
|
|
||||||
|
|
||||||
Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend) {
|
|
||||||
auto conn = std::make_unique<SSLConnectionBackendOpenSSL>();
|
|
||||||
|
|
||||||
R_TRY(conn->Init());
|
|
||||||
|
|
||||||
*out_backend = std::move(conn);
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
Result CheckOpenSSLErrors() {
|
|
||||||
unsigned long rc;
|
|
||||||
const char* file;
|
|
||||||
int line;
|
|
||||||
const char* func;
|
|
||||||
const char* data;
|
|
||||||
int flags;
|
|
||||||
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
|
|
||||||
while ((rc = ERR_get_error_all(&file, &line, &func, &data, &flags)))
|
|
||||||
#else
|
|
||||||
// Can't get function names from OpenSSL on this version, so use mine:
|
|
||||||
func = __func__;
|
|
||||||
while ((rc = ERR_get_error_line_data(&file, &line, &data, &flags)))
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
std::string msg;
|
|
||||||
msg.resize(1024, '\0');
|
|
||||||
ERR_error_string_n(rc, msg.data(), msg.size());
|
|
||||||
msg.resize(strlen(msg.data()), '\0');
|
|
||||||
if (flags & ERR_TXT_STRING) {
|
|
||||||
msg.append(" | ");
|
|
||||||
msg.append(data);
|
|
||||||
}
|
|
||||||
Common::Log::FmtLogMessage(Common::Log::Class::Service_SSL, Common::Log::Level::Error,
|
|
||||||
file, line, func, "OpenSSL: {}",
|
|
||||||
msg);
|
|
||||||
}
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OneTimeInit() {
|
|
||||||
ssl_ctx = SSL_CTX_new(TLS_client_method());
|
|
||||||
if (!ssl_ctx) {
|
|
||||||
LOG_ERROR(Service_SSL, "SSL_CTX_new failed");
|
|
||||||
CheckOpenSSLErrors();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, nullptr);
|
|
||||||
|
|
||||||
if (!SSL_CTX_set_default_verify_paths(ssl_ctx)) {
|
|
||||||
LOG_ERROR(Service_SSL, "SSL_CTX_set_default_verify_paths failed");
|
|
||||||
CheckOpenSSLErrors();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
OneTimeInitLogFile();
|
|
||||||
|
|
||||||
if (!OneTimeInitBIO()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
one_time_init_success = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OneTimeInitLogFile() {
|
|
||||||
const char* logfile = getenv("SSLKEYLOGFILE");
|
|
||||||
if (logfile) {
|
|
||||||
key_log_file.Open(logfile, FileAccessMode::Append, FileType::TextFile,
|
|
||||||
FileShareFlag::ShareWriteOnly);
|
|
||||||
if (key_log_file.IsOpen()) {
|
|
||||||
SSL_CTX_set_keylog_callback(ssl_ctx, &SSLConnectionBackendOpenSSL::KeyLogCallback);
|
|
||||||
} else {
|
|
||||||
LOG_CRITICAL(Service_SSL,
|
|
||||||
"SSLKEYLOGFILE was set but file could not be opened; not logging keys!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OneTimeInitBIO() {
|
|
||||||
bio_meth =
|
|
||||||
BIO_meth_new(BIO_get_new_index() | BIO_TYPE_SOURCE_SINK, "SSLConnectionBackendOpenSSL");
|
|
||||||
if (!bio_meth ||
|
|
||||||
!BIO_meth_set_write_ex(bio_meth, &SSLConnectionBackendOpenSSL::WriteCallback) ||
|
|
||||||
!BIO_meth_set_read_ex(bio_meth, &SSLConnectionBackendOpenSSL::ReadCallback) ||
|
|
||||||
!BIO_meth_set_ctrl(bio_meth, &SSLConnectionBackendOpenSSL::CtrlCallback)) {
|
|
||||||
LOG_ERROR(Service_SSL, "Failed to create BIO_METHOD");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
} // namespace Service::SSL
|
|
||||||
@@ -1,563 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
#include "common/error.h"
|
|
||||||
#include "common/fs/file.h"
|
|
||||||
#include "common/hex_util.h"
|
|
||||||
#include "common/string_util.h"
|
|
||||||
|
|
||||||
#include "core/hle/service/ssl/ssl_backend.h"
|
|
||||||
#include "core/internal_network/network.h"
|
|
||||||
#include "core/internal_network/sockets.h"
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
// These includes are inside the namespace to avoid a conflict on MinGW where
|
|
||||||
// the headers define an enum containing Network and Service as enumerators
|
|
||||||
// (which clash with the correspondingly named namespaces).
|
|
||||||
#define SECURITY_WIN32
|
|
||||||
#include <schnlsp.h>
|
|
||||||
#include <security.h>
|
|
||||||
#include <wincrypt.h>
|
|
||||||
|
|
||||||
std::once_flag one_time_init_flag;
|
|
||||||
bool one_time_init_success = false;
|
|
||||||
|
|
||||||
SCHANNEL_CRED schannel_cred{};
|
|
||||||
CredHandle cred_handle;
|
|
||||||
|
|
||||||
static void OneTimeInit() {
|
|
||||||
schannel_cred.dwVersion = SCHANNEL_CRED_VERSION;
|
|
||||||
schannel_cred.dwFlags =
|
|
||||||
SCH_USE_STRONG_CRYPTO | // don't allow insecure protocols
|
|
||||||
SCH_CRED_NO_SERVERNAME_CHECK | // don't validate server names
|
|
||||||
SCH_CRED_NO_DEFAULT_CREDS; // don't automatically present a client certificate
|
|
||||||
// ^ I'm assuming that nobody would want to connect Yuzu to a
|
|
||||||
// service that requires some OS-provided corporate client
|
|
||||||
// certificate, and presenting one to some arbitrary server
|
|
||||||
// might be a privacy concern? Who knows, though.
|
|
||||||
|
|
||||||
const SECURITY_STATUS ret =
|
|
||||||
AcquireCredentialsHandle(nullptr, const_cast<LPTSTR>(UNISP_NAME), SECPKG_CRED_OUTBOUND,
|
|
||||||
nullptr, &schannel_cred, nullptr, nullptr, &cred_handle, nullptr);
|
|
||||||
if (ret != SEC_E_OK) {
|
|
||||||
// SECURITY_STATUS codes are a type of HRESULT and can be used with NativeErrorToString.
|
|
||||||
LOG_ERROR(Service_SSL, "AcquireCredentialsHandle failed: {}",
|
|
||||||
Common::NativeErrorToString(ret));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getenv("SSLKEYLOGFILE")) {
|
|
||||||
LOG_CRITICAL(Service_SSL, "SSLKEYLOGFILE was set but Schannel does not support exporting "
|
|
||||||
"keys; not logging keys!");
|
|
||||||
// Not fatal.
|
|
||||||
}
|
|
||||||
|
|
||||||
one_time_init_success = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace Service::SSL {
|
|
||||||
|
|
||||||
class SSLConnectionBackendSchannel final : public SSLConnectionBackend {
|
|
||||||
public:
|
|
||||||
Result Init() {
|
|
||||||
std::call_once(one_time_init_flag, OneTimeInit);
|
|
||||||
|
|
||||||
if (!one_time_init_success) {
|
|
||||||
LOG_ERROR(
|
|
||||||
Service_SSL,
|
|
||||||
"Can't create SSL connection because Schannel one-time initialization failed");
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetSocket(std::shared_ptr<Network::SocketBase> socket_in) override {
|
|
||||||
socket = std::move(socket_in);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SetHostName(const std::string& hostname_in) override {
|
|
||||||
hostname = hostname_in;
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetVerifyOption(u32 option) override {
|
|
||||||
skip_cert_verification = (option == 0);
|
|
||||||
LOG_WARNING(Service_SSL, "option={} skip_verification={}", option,
|
|
||||||
skip_cert_verification);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DoHandshake() override {
|
|
||||||
while (1) {
|
|
||||||
Result r;
|
|
||||||
switch (handshake_state) {
|
|
||||||
case HandshakeState::Initial:
|
|
||||||
if ((r = FlushCiphertextWriteBuf()) != ResultSuccess ||
|
|
||||||
(r = CallInitializeSecurityContext()) != ResultSuccess) {
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
// CallInitializeSecurityContext updated `handshake_state`.
|
|
||||||
continue;
|
|
||||||
case HandshakeState::ContinueNeeded:
|
|
||||||
case HandshakeState::IncompleteMessage:
|
|
||||||
if ((r = FlushCiphertextWriteBuf()) != ResultSuccess ||
|
|
||||||
(r = FillCiphertextReadBuf()) != ResultSuccess) {
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
if (ciphertext_read_buf.empty()) {
|
|
||||||
LOG_ERROR(Service_SSL, "SSL handshake failed because server hung up");
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
if ((r = CallInitializeSecurityContext()) != ResultSuccess) {
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
// CallInitializeSecurityContext updated `handshake_state`.
|
|
||||||
continue;
|
|
||||||
case HandshakeState::DoneAfterFlush:
|
|
||||||
if ((r = FlushCiphertextWriteBuf()) != ResultSuccess) {
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
handshake_state = HandshakeState::Connected;
|
|
||||||
return ResultSuccess;
|
|
||||||
case HandshakeState::Connected:
|
|
||||||
LOG_ERROR(Service_SSL, "Called DoHandshake but we already handshook");
|
|
||||||
return ResultInternalError;
|
|
||||||
case HandshakeState::Error:
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Result FillCiphertextReadBuf() {
|
|
||||||
const size_t fill_size = read_buf_fill_size ? read_buf_fill_size : 4096;
|
|
||||||
read_buf_fill_size = 0;
|
|
||||||
// This unnecessarily zeroes the buffer; oh well.
|
|
||||||
const size_t offset = ciphertext_read_buf.size();
|
|
||||||
ASSERT_OR_EXECUTE(offset + fill_size >= offset, { return ResultInternalError; });
|
|
||||||
ciphertext_read_buf.resize(offset + fill_size, 0);
|
|
||||||
const auto read_span = std::span(ciphertext_read_buf).subspan(offset, fill_size);
|
|
||||||
const auto [actual, err] = socket->Recv(0, read_span);
|
|
||||||
switch (err) {
|
|
||||||
case Network::Errno::SUCCESS:
|
|
||||||
ASSERT(static_cast<size_t>(actual) <= fill_size);
|
|
||||||
ciphertext_read_buf.resize(offset + actual);
|
|
||||||
return ResultSuccess;
|
|
||||||
case Network::Errno::AGAIN:
|
|
||||||
ciphertext_read_buf.resize(offset);
|
|
||||||
return ResultWouldBlock;
|
|
||||||
default:
|
|
||||||
ciphertext_read_buf.resize(offset);
|
|
||||||
LOG_ERROR(Service_SSL, "Socket recv returned Network::Errno {}", err);
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns success if the write buffer has been completely emptied.
|
|
||||||
Result FlushCiphertextWriteBuf() {
|
|
||||||
while (!ciphertext_write_buf.empty()) {
|
|
||||||
const auto [actual, err] = socket->Send(ciphertext_write_buf, 0);
|
|
||||||
switch (err) {
|
|
||||||
case Network::Errno::SUCCESS:
|
|
||||||
ASSERT(static_cast<size_t>(actual) <= ciphertext_write_buf.size());
|
|
||||||
ciphertext_write_buf.erase(ciphertext_write_buf.begin(),
|
|
||||||
ciphertext_write_buf.begin() + actual);
|
|
||||||
break;
|
|
||||||
case Network::Errno::AGAIN:
|
|
||||||
return ResultWouldBlock;
|
|
||||||
default:
|
|
||||||
LOG_ERROR(Service_SSL, "Socket send returned Network::Errno {}", err);
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result CallInitializeSecurityContext() {
|
|
||||||
unsigned long req = ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_CONFIDENTIALITY |
|
|
||||||
ISC_REQ_INTEGRITY | ISC_REQ_REPLAY_DETECT |
|
|
||||||
ISC_REQ_SEQUENCE_DETECT | ISC_REQ_STREAM |
|
|
||||||
ISC_REQ_USE_SUPPLIED_CREDS;
|
|
||||||
|
|
||||||
if (skip_cert_verification) {
|
|
||||||
req |= ISC_REQ_MANUAL_CRED_VALIDATION;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned long attr;
|
|
||||||
// https://learn.microsoft.com/en-us/windows/win32/secauthn/initializesecuritycontext--schannel
|
|
||||||
std::array<SecBuffer, 2> input_buffers{{
|
|
||||||
// only used if `initial_call_done`
|
|
||||||
{
|
|
||||||
// [0]
|
|
||||||
.cbBuffer = static_cast<unsigned long>(ciphertext_read_buf.size()),
|
|
||||||
.BufferType = SECBUFFER_TOKEN,
|
|
||||||
.pvBuffer = ciphertext_read_buf.data(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// [1] (will be replaced by SECBUFFER_MISSING when SEC_E_INCOMPLETE_MESSAGE is
|
|
||||||
// returned, or SECBUFFER_EXTRA when SEC_E_CONTINUE_NEEDED is returned if the
|
|
||||||
// whole buffer wasn't used)
|
|
||||||
.cbBuffer = 0,
|
|
||||||
.BufferType = SECBUFFER_EMPTY,
|
|
||||||
.pvBuffer = nullptr,
|
|
||||||
},
|
|
||||||
}};
|
|
||||||
std::array<SecBuffer, 2> output_buffers{{
|
|
||||||
{
|
|
||||||
.cbBuffer = 0,
|
|
||||||
.BufferType = SECBUFFER_TOKEN,
|
|
||||||
.pvBuffer = nullptr,
|
|
||||||
}, // [0]
|
|
||||||
{
|
|
||||||
.cbBuffer = 0,
|
|
||||||
.BufferType = SECBUFFER_ALERT,
|
|
||||||
.pvBuffer = nullptr,
|
|
||||||
}, // [1]
|
|
||||||
}};
|
|
||||||
SecBufferDesc input_desc{
|
|
||||||
.ulVersion = SECBUFFER_VERSION,
|
|
||||||
.cBuffers = static_cast<unsigned long>(input_buffers.size()),
|
|
||||||
.pBuffers = input_buffers.data(),
|
|
||||||
};
|
|
||||||
SecBufferDesc output_desc{
|
|
||||||
.ulVersion = SECBUFFER_VERSION,
|
|
||||||
.cBuffers = static_cast<unsigned long>(output_buffers.size()),
|
|
||||||
.pBuffers = output_buffers.data(),
|
|
||||||
};
|
|
||||||
ASSERT_OR_EXECUTE_MSG(
|
|
||||||
input_buffers[0].cbBuffer == ciphertext_read_buf.size(),
|
|
||||||
{ return ResultInternalError; }, "read buffer too large");
|
|
||||||
|
|
||||||
bool initial_call_done = handshake_state != HandshakeState::Initial;
|
|
||||||
if (initial_call_done) {
|
|
||||||
LOG_DEBUG(Service_SSL, "Passing {} bytes into InitializeSecurityContext",
|
|
||||||
ciphertext_read_buf.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
char* hostname_ptr = hostname ? const_cast<char*>(hostname->c_str()) : nullptr;
|
|
||||||
const SECURITY_STATUS ret = InitializeSecurityContextA(
|
|
||||||
&cred_handle, initial_call_done ? &ctxt : nullptr, hostname_ptr, req,
|
|
||||||
0, // Reserved1
|
|
||||||
0, // TargetDataRep not used with Schannel
|
|
||||||
initial_call_done ? &input_desc : nullptr,
|
|
||||||
0, // Reserved2
|
|
||||||
initial_call_done ? nullptr : &ctxt, &output_desc, &attr,
|
|
||||||
nullptr); // ptsExpiry
|
|
||||||
|
|
||||||
if (output_buffers[0].pvBuffer) {
|
|
||||||
const std::span span(static_cast<u8*>(output_buffers[0].pvBuffer),
|
|
||||||
output_buffers[0].cbBuffer);
|
|
||||||
ciphertext_write_buf.insert(ciphertext_write_buf.end(), span.begin(), span.end());
|
|
||||||
FreeContextBuffer(output_buffers[0].pvBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (output_buffers[1].pvBuffer) {
|
|
||||||
const std::span span(static_cast<u8*>(output_buffers[1].pvBuffer),
|
|
||||||
output_buffers[1].cbBuffer);
|
|
||||||
// The documentation doesn't explain what format this data is in.
|
|
||||||
LOG_DEBUG(Service_SSL, "Got a {}-byte alert buffer: {}", span.size(),
|
|
||||||
Common::HexToString(span));
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (ret) {
|
|
||||||
case SEC_I_CONTINUE_NEEDED:
|
|
||||||
LOG_DEBUG(Service_SSL, "InitializeSecurityContext => SEC_I_CONTINUE_NEEDED");
|
|
||||||
if (input_buffers[1].BufferType == SECBUFFER_EXTRA) {
|
|
||||||
LOG_DEBUG(Service_SSL, "EXTRA of size {}", input_buffers[1].cbBuffer);
|
|
||||||
ASSERT(input_buffers[1].cbBuffer <= ciphertext_read_buf.size());
|
|
||||||
ciphertext_read_buf.erase(ciphertext_read_buf.begin(),
|
|
||||||
ciphertext_read_buf.end() - input_buffers[1].cbBuffer);
|
|
||||||
} else {
|
|
||||||
ASSERT(input_buffers[1].BufferType == SECBUFFER_EMPTY);
|
|
||||||
ciphertext_read_buf.clear();
|
|
||||||
}
|
|
||||||
handshake_state = HandshakeState::ContinueNeeded;
|
|
||||||
return ResultSuccess;
|
|
||||||
case SEC_E_INCOMPLETE_MESSAGE:
|
|
||||||
LOG_DEBUG(Service_SSL, "InitializeSecurityContext => SEC_E_INCOMPLETE_MESSAGE");
|
|
||||||
ASSERT(input_buffers[1].BufferType == SECBUFFER_MISSING);
|
|
||||||
read_buf_fill_size = input_buffers[1].cbBuffer;
|
|
||||||
handshake_state = HandshakeState::IncompleteMessage;
|
|
||||||
return ResultSuccess;
|
|
||||||
case SEC_E_OK:
|
|
||||||
LOG_DEBUG(Service_SSL, "InitializeSecurityContext => SEC_E_OK");
|
|
||||||
ciphertext_read_buf.clear();
|
|
||||||
handshake_state = HandshakeState::DoneAfterFlush;
|
|
||||||
return GrabStreamSizes();
|
|
||||||
default:
|
|
||||||
LOG_ERROR(Service_SSL,
|
|
||||||
"InitializeSecurityContext failed (probably certificate/protocol issue): {}",
|
|
||||||
Common::NativeErrorToString(ret));
|
|
||||||
handshake_state = HandshakeState::Error;
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Result GrabStreamSizes() {
|
|
||||||
const SECURITY_STATUS ret =
|
|
||||||
QueryContextAttributes(&ctxt, SECPKG_ATTR_STREAM_SIZES, &stream_sizes);
|
|
||||||
if (ret != SEC_E_OK) {
|
|
||||||
LOG_ERROR(Service_SSL, "QueryContextAttributes(SECPKG_ATTR_STREAM_SIZES) failed: {}",
|
|
||||||
Common::NativeErrorToString(ret));
|
|
||||||
handshake_state = HandshakeState::Error;
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result Read(size_t* out_size, std::span<u8> data) override {
|
|
||||||
*out_size = 0;
|
|
||||||
if (handshake_state != HandshakeState::Connected) {
|
|
||||||
LOG_ERROR(Service_SSL, "Called Read but we did not successfully handshake");
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
if (data.size() == 0 || got_read_eof) {
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
while (1) {
|
|
||||||
if (!cleartext_read_buf.empty()) {
|
|
||||||
*out_size = (std::min)(cleartext_read_buf.size(), data.size());
|
|
||||||
std::memcpy(data.data(), cleartext_read_buf.data(), *out_size);
|
|
||||||
cleartext_read_buf.erase(cleartext_read_buf.begin(),
|
|
||||||
cleartext_read_buf.begin() + *out_size);
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
if (!ciphertext_read_buf.empty()) {
|
|
||||||
SecBuffer empty{
|
|
||||||
.cbBuffer = 0,
|
|
||||||
.BufferType = SECBUFFER_EMPTY,
|
|
||||||
.pvBuffer = nullptr,
|
|
||||||
};
|
|
||||||
std::array<SecBuffer, 5> buffers{{
|
|
||||||
{
|
|
||||||
.cbBuffer = static_cast<unsigned long>(ciphertext_read_buf.size()),
|
|
||||||
.BufferType = SECBUFFER_DATA,
|
|
||||||
.pvBuffer = ciphertext_read_buf.data(),
|
|
||||||
},
|
|
||||||
empty,
|
|
||||||
empty,
|
|
||||||
empty,
|
|
||||||
}};
|
|
||||||
ASSERT_OR_EXECUTE_MSG(
|
|
||||||
buffers[0].cbBuffer == ciphertext_read_buf.size(),
|
|
||||||
{ return ResultInternalError; }, "read buffer too large");
|
|
||||||
SecBufferDesc desc{
|
|
||||||
.ulVersion = SECBUFFER_VERSION,
|
|
||||||
.cBuffers = static_cast<unsigned long>(buffers.size()),
|
|
||||||
.pBuffers = buffers.data(),
|
|
||||||
};
|
|
||||||
SECURITY_STATUS ret =
|
|
||||||
DecryptMessage(&ctxt, &desc, /*MessageSeqNo*/ 0, /*pfQOP*/ nullptr);
|
|
||||||
switch (ret) {
|
|
||||||
case SEC_E_OK:
|
|
||||||
ASSERT_OR_EXECUTE(buffers[0].BufferType == SECBUFFER_STREAM_HEADER,
|
|
||||||
{ return ResultInternalError; });
|
|
||||||
ASSERT_OR_EXECUTE(buffers[1].BufferType == SECBUFFER_DATA,
|
|
||||||
{ return ResultInternalError; });
|
|
||||||
ASSERT_OR_EXECUTE(buffers[2].BufferType == SECBUFFER_STREAM_TRAILER,
|
|
||||||
{ return ResultInternalError; });
|
|
||||||
cleartext_read_buf.assign(static_cast<u8*>(buffers[1].pvBuffer),
|
|
||||||
static_cast<u8*>(buffers[1].pvBuffer) +
|
|
||||||
buffers[1].cbBuffer);
|
|
||||||
if (buffers[3].BufferType == SECBUFFER_EXTRA) {
|
|
||||||
ASSERT(buffers[3].cbBuffer <= ciphertext_read_buf.size());
|
|
||||||
ciphertext_read_buf.erase(ciphertext_read_buf.begin(),
|
|
||||||
ciphertext_read_buf.end() - buffers[3].cbBuffer);
|
|
||||||
} else {
|
|
||||||
ASSERT(buffers[3].BufferType == SECBUFFER_EMPTY);
|
|
||||||
ciphertext_read_buf.clear();
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
case SEC_E_INCOMPLETE_MESSAGE:
|
|
||||||
break;
|
|
||||||
case SEC_I_CONTEXT_EXPIRED:
|
|
||||||
// Server hung up by sending close_notify.
|
|
||||||
got_read_eof = true;
|
|
||||||
*out_size = 0;
|
|
||||||
return ResultSuccess;
|
|
||||||
default:
|
|
||||||
LOG_ERROR(Service_SSL, "DecryptMessage failed: {}",
|
|
||||||
Common::NativeErrorToString(ret));
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const Result r = FillCiphertextReadBuf();
|
|
||||||
if (r != ResultSuccess) {
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
if (ciphertext_read_buf.empty()) {
|
|
||||||
got_read_eof = true;
|
|
||||||
*out_size = 0;
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Result Write(size_t* out_size, std::span<const u8> data) override {
|
|
||||||
*out_size = 0;
|
|
||||||
|
|
||||||
if (handshake_state != HandshakeState::Connected) {
|
|
||||||
LOG_ERROR(Service_SSL, "Called Write but we did not successfully handshake");
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
if (data.size() == 0) {
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
data = data.subspan(0, std::min<size_t>(data.size(), stream_sizes.cbMaximumMessage));
|
|
||||||
if (!cleartext_write_buf.empty()) {
|
|
||||||
// Already in the middle of a write. It wouldn't make sense to not
|
|
||||||
// finish sending the entire buffer since TLS has
|
|
||||||
// header/MAC/padding/etc.
|
|
||||||
if (data.size() != cleartext_write_buf.size() ||
|
|
||||||
std::memcmp(data.data(), cleartext_write_buf.data(), data.size())) {
|
|
||||||
LOG_ERROR(Service_SSL, "Called Write but buffer does not match previous buffer");
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
return WriteAlreadyEncryptedData(out_size);
|
|
||||||
} else {
|
|
||||||
cleartext_write_buf.assign(data.begin(), data.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<u8> header_buf(stream_sizes.cbHeader, 0);
|
|
||||||
std::vector<u8> tmp_data_buf = cleartext_write_buf;
|
|
||||||
std::vector<u8> trailer_buf(stream_sizes.cbTrailer, 0);
|
|
||||||
|
|
||||||
std::array<SecBuffer, 3> buffers{{
|
|
||||||
{
|
|
||||||
.cbBuffer = stream_sizes.cbHeader,
|
|
||||||
.BufferType = SECBUFFER_STREAM_HEADER,
|
|
||||||
.pvBuffer = header_buf.data(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.cbBuffer = static_cast<unsigned long>(tmp_data_buf.size()),
|
|
||||||
.BufferType = SECBUFFER_DATA,
|
|
||||||
.pvBuffer = tmp_data_buf.data(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.cbBuffer = stream_sizes.cbTrailer,
|
|
||||||
.BufferType = SECBUFFER_STREAM_TRAILER,
|
|
||||||
.pvBuffer = trailer_buf.data(),
|
|
||||||
},
|
|
||||||
}};
|
|
||||||
ASSERT_OR_EXECUTE_MSG(
|
|
||||||
buffers[1].cbBuffer == tmp_data_buf.size(), { return ResultInternalError; },
|
|
||||||
"temp buffer too large");
|
|
||||||
SecBufferDesc desc{
|
|
||||||
.ulVersion = SECBUFFER_VERSION,
|
|
||||||
.cBuffers = static_cast<unsigned long>(buffers.size()),
|
|
||||||
.pBuffers = buffers.data(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const SECURITY_STATUS ret = EncryptMessage(&ctxt, /*fQOP*/ 0, &desc, /*MessageSeqNo*/ 0);
|
|
||||||
if (ret != SEC_E_OK) {
|
|
||||||
LOG_ERROR(Service_SSL, "EncryptMessage failed: {}", Common::NativeErrorToString(ret));
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
ciphertext_write_buf.insert(ciphertext_write_buf.end(), header_buf.begin(),
|
|
||||||
header_buf.end());
|
|
||||||
ciphertext_write_buf.insert(ciphertext_write_buf.end(), tmp_data_buf.begin(),
|
|
||||||
tmp_data_buf.end());
|
|
||||||
ciphertext_write_buf.insert(ciphertext_write_buf.end(), trailer_buf.begin(),
|
|
||||||
trailer_buf.end());
|
|
||||||
return WriteAlreadyEncryptedData(out_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result WriteAlreadyEncryptedData(size_t* out_size) {
|
|
||||||
const Result r = FlushCiphertextWriteBuf();
|
|
||||||
if (r != ResultSuccess) {
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
// write buf is empty
|
|
||||||
*out_size = cleartext_write_buf.size();
|
|
||||||
cleartext_write_buf.clear();
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) override {
|
|
||||||
PCCERT_CONTEXT returned_cert = nullptr;
|
|
||||||
const SECURITY_STATUS ret =
|
|
||||||
QueryContextAttributes(&ctxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &returned_cert);
|
|
||||||
if (ret != SEC_E_OK) {
|
|
||||||
LOG_ERROR(Service_SSL,
|
|
||||||
"QueryContextAttributes(SECPKG_ATTR_REMOTE_CERT_CONTEXT) failed: {}",
|
|
||||||
Common::NativeErrorToString(ret));
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
PCCERT_CONTEXT some_cert = nullptr;
|
|
||||||
while ((some_cert = CertEnumCertificatesInStore(returned_cert->hCertStore, some_cert)) !=
|
|
||||||
nullptr) {
|
|
||||||
out_certs->emplace_back(static_cast<u8*>(some_cert->pbCertEncoded),
|
|
||||||
static_cast<u8*>(some_cert->pbCertEncoded) +
|
|
||||||
some_cert->cbCertEncoded);
|
|
||||||
}
|
|
||||||
std::reverse(out_certs->begin(),
|
|
||||||
out_certs->end()); // Windows returns certs in reverse order from what we want
|
|
||||||
CertFreeCertificateContext(returned_cert);
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
~SSLConnectionBackendSchannel() {
|
|
||||||
if (handshake_state != HandshakeState::Initial) {
|
|
||||||
DeleteSecurityContext(&ctxt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class HandshakeState {
|
|
||||||
// Haven't called anything yet.
|
|
||||||
Initial,
|
|
||||||
// `SEC_I_CONTINUE_NEEDED` was returned by
|
|
||||||
// `InitializeSecurityContext`; must finish sending data (if any) in
|
|
||||||
// the write buffer, then read at least one byte before calling
|
|
||||||
// `InitializeSecurityContext` again.
|
|
||||||
ContinueNeeded,
|
|
||||||
// `SEC_E_INCOMPLETE_MESSAGE` was returned by
|
|
||||||
// `InitializeSecurityContext`; hopefully the write buffer is empty;
|
|
||||||
// must read at least one byte before calling
|
|
||||||
// `InitializeSecurityContext` again.
|
|
||||||
IncompleteMessage,
|
|
||||||
// `SEC_E_OK` was returned by `InitializeSecurityContext`; must
|
|
||||||
// finish sending data in the write buffer before having `DoHandshake`
|
|
||||||
// report success.
|
|
||||||
DoneAfterFlush,
|
|
||||||
// We finished the above and are now connected. At this point, writing
|
|
||||||
// and reading are separate 'state machines' represented by the
|
|
||||||
// nonemptiness of the ciphertext and cleartext read and write buffers.
|
|
||||||
Connected,
|
|
||||||
// Another error was returned and we shouldn't allow initialization
|
|
||||||
// to continue.
|
|
||||||
Error,
|
|
||||||
} handshake_state = HandshakeState::Initial;
|
|
||||||
|
|
||||||
CtxtHandle ctxt;
|
|
||||||
SecPkgContext_StreamSizes stream_sizes;
|
|
||||||
|
|
||||||
std::shared_ptr<Network::SocketBase> socket;
|
|
||||||
std::optional<std::string> hostname;
|
|
||||||
|
|
||||||
std::vector<u8> ciphertext_read_buf;
|
|
||||||
std::vector<u8> ciphertext_write_buf;
|
|
||||||
std::vector<u8> cleartext_read_buf;
|
|
||||||
std::vector<u8> cleartext_write_buf;
|
|
||||||
|
|
||||||
bool got_read_eof = false;
|
|
||||||
bool skip_cert_verification = false;
|
|
||||||
size_t read_buf_fill_size = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend) {
|
|
||||||
auto conn = std::make_unique<SSLConnectionBackendSchannel>();
|
|
||||||
|
|
||||||
R_TRY(conn->Init());
|
|
||||||
|
|
||||||
*out_backend = std::move(conn);
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Service::SSL
|
|
||||||
@@ -1,236 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
// SecureTransport has been deprecated in its entirety in favor of
|
|
||||||
// Network.framework, but that does not allow layering TLS on top of an
|
|
||||||
// arbitrary socket.
|
|
||||||
#if defined(__GNUC__) || defined(__clang__)
|
|
||||||
#pragma GCC diagnostic push
|
|
||||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
|
||||||
#include <Security/SecureTransport.h>
|
|
||||||
#pragma GCC diagnostic pop
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "core/hle/service/ssl/ssl_backend.h"
|
|
||||||
#include "core/internal_network/network.h"
|
|
||||||
#include "core/internal_network/sockets.h"
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
struct CFReleaser {
|
|
||||||
T ptr;
|
|
||||||
|
|
||||||
YUZU_NON_COPYABLE(CFReleaser);
|
|
||||||
constexpr CFReleaser() : ptr(nullptr) {}
|
|
||||||
constexpr CFReleaser(T ptr) : ptr(ptr) {}
|
|
||||||
constexpr operator T() {
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
~CFReleaser() {
|
|
||||||
if (ptr) {
|
|
||||||
CFRelease(ptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::string CFStringToString(CFStringRef cfstr) {
|
|
||||||
CFReleaser<CFDataRef> cfdata(
|
|
||||||
CFStringCreateExternalRepresentation(nullptr, cfstr, kCFStringEncodingUTF8, 0));
|
|
||||||
ASSERT_OR_EXECUTE(cfdata, { return "???"; });
|
|
||||||
return std::string(reinterpret_cast<const char*>(CFDataGetBytePtr(cfdata)),
|
|
||||||
CFDataGetLength(cfdata));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string OSStatusToString(OSStatus status) {
|
|
||||||
CFReleaser<CFStringRef> cfstr(SecCopyErrorMessageString(status, nullptr));
|
|
||||||
if (!cfstr) {
|
|
||||||
return "[unknown error]";
|
|
||||||
}
|
|
||||||
return CFStringToString(cfstr);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace Service::SSL {
|
|
||||||
|
|
||||||
class SSLConnectionBackendSecureTransport final : public SSLConnectionBackend {
|
|
||||||
public:
|
|
||||||
Result Init() {
|
|
||||||
static std::once_flag once_flag;
|
|
||||||
std::call_once(once_flag, []() {
|
|
||||||
if (getenv("SSLKEYLOGFILE")) {
|
|
||||||
LOG_CRITICAL(Service_SSL, "SSLKEYLOGFILE was set but SecureTransport does not "
|
|
||||||
"support exporting keys; not logging keys!");
|
|
||||||
// Not fatal.
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
context.ptr = SSLCreateContext(nullptr, kSSLClientSide, kSSLStreamType);
|
|
||||||
if (!context) {
|
|
||||||
LOG_ERROR(Service_SSL, "SSLCreateContext failed");
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
|
|
||||||
OSStatus status;
|
|
||||||
if ((status = SSLSetIOFuncs(context, ReadCallback, WriteCallback)) ||
|
|
||||||
(status = SSLSetConnection(context, this))) {
|
|
||||||
LOG_ERROR(Service_SSL, "SSLContext initialization failed: {}",
|
|
||||||
OSStatusToString(status));
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetSocket(std::shared_ptr<Network::SocketBase> in_socket) override {
|
|
||||||
socket = std::move(in_socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SetHostName(const std::string& hostname) override {
|
|
||||||
OSStatus status = SSLSetPeerDomainName(context, hostname.c_str(), hostname.size());
|
|
||||||
if (status) {
|
|
||||||
LOG_ERROR(Service_SSL, "SSLSetPeerDomainName failed: {}", OSStatusToString(status));
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetVerifyOption(u32 option) override {
|
|
||||||
skip_cert_verification = (option == 0);
|
|
||||||
LOG_WARNING(Service_SSL, "option={} skip_verification={}", option,
|
|
||||||
skip_cert_verification);
|
|
||||||
if (skip_cert_verification) {
|
|
||||||
SSLSetSessionOption(context, kSSLSessionOptionBreakOnServerAuth, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DoHandshake() override {
|
|
||||||
OSStatus status = SSLHandshake(context);
|
|
||||||
|
|
||||||
if (skip_cert_verification && status == errSSLServerAuthCompleted) {
|
|
||||||
LOG_DEBUG(Service_SSL, "Skipping certificate verification as requested");
|
|
||||||
status = SSLHandshake(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
return HandleReturn("SSLHandshake", 0, status);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result Read(size_t* out_size, std::span<u8> data) override {
|
|
||||||
OSStatus status = SSLRead(context, data.data(), data.size(), out_size);
|
|
||||||
return HandleReturn("SSLRead", out_size, status);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result Write(size_t* out_size, std::span<const u8> data) override {
|
|
||||||
OSStatus status = SSLWrite(context, data.data(), data.size(), out_size);
|
|
||||||
return HandleReturn("SSLWrite", out_size, status);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result HandleReturn(const char* what, size_t* actual, OSStatus status) {
|
|
||||||
switch (status) {
|
|
||||||
case 0:
|
|
||||||
return ResultSuccess;
|
|
||||||
case errSSLWouldBlock:
|
|
||||||
return ResultWouldBlock;
|
|
||||||
default: {
|
|
||||||
std::string reason;
|
|
||||||
if (got_read_eof) {
|
|
||||||
reason = "server hung up";
|
|
||||||
} else {
|
|
||||||
reason = OSStatusToString(status);
|
|
||||||
}
|
|
||||||
LOG_ERROR(Service_SSL, "{} failed: {}", what, reason);
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) override {
|
|
||||||
CFReleaser<SecTrustRef> trust;
|
|
||||||
OSStatus status = SSLCopyPeerTrust(context, &trust.ptr);
|
|
||||||
if (status) {
|
|
||||||
LOG_ERROR(Service_SSL, "SSLCopyPeerTrust failed: {}", OSStatusToString(status));
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
for (CFIndex i = 0, count = SecTrustGetCertificateCount(trust); i < count; i++) {
|
|
||||||
SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust, i);
|
|
||||||
CFReleaser<CFDataRef> data(SecCertificateCopyData(cert));
|
|
||||||
ASSERT_OR_EXECUTE(data, { return ResultInternalError; });
|
|
||||||
const u8* ptr = CFDataGetBytePtr(data);
|
|
||||||
out_certs->emplace_back(ptr, ptr + CFDataGetLength(data));
|
|
||||||
}
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
static OSStatus ReadCallback(SSLConnectionRef connection, void* data, size_t* dataLength) {
|
|
||||||
return ReadOrWriteCallback(connection, data, dataLength, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
static OSStatus WriteCallback(SSLConnectionRef connection, const void* data,
|
|
||||||
size_t* dataLength) {
|
|
||||||
return ReadOrWriteCallback(connection, const_cast<void*>(data), dataLength, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
static OSStatus ReadOrWriteCallback(SSLConnectionRef connection, void* data, size_t* dataLength,
|
|
||||||
bool is_read) {
|
|
||||||
auto self =
|
|
||||||
static_cast<SSLConnectionBackendSecureTransport*>(const_cast<void*>(connection));
|
|
||||||
ASSERT_OR_EXECUTE_MSG(
|
|
||||||
self->socket, { return 0; }, "SecureTransport asked to {} but we have no socket",
|
|
||||||
is_read ? "read" : "write");
|
|
||||||
|
|
||||||
// SecureTransport callbacks (unlike OpenSSL BIO callbacks) are
|
|
||||||
// expected to read/write the full requested dataLength or return an
|
|
||||||
// error, so we have to add a loop ourselves.
|
|
||||||
size_t requested_len = *dataLength;
|
|
||||||
size_t offset = 0;
|
|
||||||
while (offset < requested_len) {
|
|
||||||
std::span cur(reinterpret_cast<u8*>(data) + offset, requested_len - offset);
|
|
||||||
auto [actual, err] = is_read ? self->socket->Recv(0, cur) : self->socket->Send(cur, 0);
|
|
||||||
LOG_CRITICAL(Service_SSL, "op={}, offset={} actual={}/{} err={}", is_read, offset,
|
|
||||||
actual, cur.size(), static_cast<s32>(err));
|
|
||||||
switch (err) {
|
|
||||||
case Network::Errno::SUCCESS:
|
|
||||||
offset += actual;
|
|
||||||
if (actual == 0) {
|
|
||||||
ASSERT(is_read);
|
|
||||||
self->got_read_eof = true;
|
|
||||||
return errSecEndOfData;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Network::Errno::AGAIN:
|
|
||||||
*dataLength = offset;
|
|
||||||
return errSSLWouldBlock;
|
|
||||||
default:
|
|
||||||
LOG_ERROR(Service_SSL, "Socket {} returned Network::Errno {}",
|
|
||||||
is_read ? "recv" : "send", err);
|
|
||||||
return errSecIO;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ASSERT(offset == requested_len);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
CFReleaser<SSLContextRef> context = nullptr;
|
|
||||||
bool got_read_eof = false;
|
|
||||||
bool skip_cert_verification = false;
|
|
||||||
|
|
||||||
std::shared_ptr<Network::SocketBase> socket;
|
|
||||||
};
|
|
||||||
|
|
||||||
Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend) {
|
|
||||||
auto conn = std::make_unique<SSLConnectionBackendSecureTransport>();
|
|
||||||
|
|
||||||
R_TRY(conn->Init());
|
|
||||||
|
|
||||||
*out_backend = std::move(conn);
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Service::SSL
|
|
||||||
@@ -114,13 +114,13 @@ std::vector<Network::ScanData> ScanWifiNetworks(std::chrono::milliseconds deadli
|
|||||||
char ifname[IFNAMSIZ] = {0};
|
char ifname[IFNAMSIZ] = {0};
|
||||||
char *args[1] = {ifname};
|
char *args[1] = {ifname};
|
||||||
|
|
||||||
iw_enum_devices(sock, [](int f_skfd, char* f_ifname, char* f_args[], int) -> int {
|
iw_enum_devices(sock, [](int skfd, char* ifname, char* args[], int count) -> int {
|
||||||
iwrange range;
|
iwrange range;
|
||||||
int res = iw_get_range_info(f_skfd, f_ifname, &range);
|
int res = iw_get_range_info(skfd, ifname, &range);
|
||||||
LOG_INFO(Network, "ifname {} returned {} on iw_get_range_info", f_ifname, res);
|
LOG_INFO(Network, "ifname {} returned {} on iw_get_range_info", ifname, res);
|
||||||
if (res >= 0) {
|
if (res >= 0) {
|
||||||
strncpy(f_args[0], f_ifname, IFNAMSIZ - 1);
|
strncpy(args[0], ifname, IFNAMSIZ - 1);
|
||||||
f_args[0][IFNAMSIZ - 1] = 0;
|
args[0][IFNAMSIZ - 1] = 0;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/logging.h"
|
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "dynarmic/backend/exception_handler.h"
|
#include "dynarmic/backend/exception_handler.h"
|
||||||
#include "dynarmic/common/context.h"
|
#include "dynarmic/common/context.h"
|
||||||
@@ -54,21 +53,23 @@ class SigHandler {
|
|||||||
return e.first <= offset && e.first + e.second.size > offset;
|
return e.first <= offset && e.first + e.second.size > offset;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
static void SigAction(int sig, siginfo_t* info, void* raw_context);
|
||||||
|
|
||||||
|
bool supports_fast_mem = true;
|
||||||
|
void* signal_stack_memory = nullptr;
|
||||||
ankerl::unordered_dense::map<u64, CodeBlockInfo> code_block_infos;
|
ankerl::unordered_dense::map<u64, CodeBlockInfo> code_block_infos;
|
||||||
std::shared_mutex code_block_infos_mutex;
|
std::shared_mutex code_block_infos_mutex;
|
||||||
struct sigaction old_sa_segv;
|
struct sigaction old_sa_segv;
|
||||||
struct sigaction old_sa_bus;
|
struct sigaction old_sa_bus;
|
||||||
std::unique_ptr<uint8_t[]> signal_stack_memory;
|
std::size_t signal_stack_size;
|
||||||
bool supports_fast_mem = true;
|
|
||||||
public:
|
public:
|
||||||
SigHandler() noexcept {
|
SigHandler() noexcept {
|
||||||
auto const stack_size = std::max<size_t>(SIGSTKSZ, 2 * 1024 * 1024);
|
signal_stack_size = std::max<size_t>(SIGSTKSZ, 2 * 1024 * 1024);
|
||||||
signal_stack_memory = std::make_unique<uint8_t[]>(stack_size);
|
signal_stack_memory = mmap(nullptr, signal_stack_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||||
|
|
||||||
stack_t signal_stack{};
|
stack_t signal_stack{};
|
||||||
signal_stack.ss_sp = signal_stack_memory.get();
|
signal_stack.ss_sp = signal_stack_memory;
|
||||||
signal_stack.ss_size = stack_size;
|
signal_stack.ss_size = signal_stack_size;
|
||||||
signal_stack.ss_flags = 0;
|
signal_stack.ss_flags = 0;
|
||||||
if (sigaltstack(&signal_stack, nullptr) != 0) {
|
if (sigaltstack(&signal_stack, nullptr) != 0) {
|
||||||
fmt::print(stderr, "dynarmic: POSIX SigHandler: init failure at sigaltstack\n");
|
fmt::print(stderr, "dynarmic: POSIX SigHandler: init failure at sigaltstack\n");
|
||||||
@@ -86,7 +87,7 @@ public:
|
|||||||
supports_fast_mem = false;
|
supports_fast_mem = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#if defined(__APPLE__)
|
#ifdef __APPLE__
|
||||||
if (sigaction(SIGBUS, &sa, &old_sa_bus) != 0) {
|
if (sigaction(SIGBUS, &sa, &old_sa_bus) != 0) {
|
||||||
fmt::print(stderr, "dynarmic: POSIX SigHandler: could not set SIGBUS handler\n");
|
fmt::print(stderr, "dynarmic: POSIX SigHandler: could not set SIGBUS handler\n");
|
||||||
supports_fast_mem = false;
|
supports_fast_mem = false;
|
||||||
@@ -95,6 +96,10 @@ public:
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
~SigHandler() noexcept {
|
||||||
|
munmap(signal_stack_memory, signal_stack_size);
|
||||||
|
}
|
||||||
|
|
||||||
void AddCodeBlock(u64 offset, CodeBlockInfo cbi) noexcept {
|
void AddCodeBlock(u64 offset, CodeBlockInfo cbi) noexcept {
|
||||||
std::unique_lock guard(code_block_infos_mutex);
|
std::unique_lock guard(code_block_infos_mutex);
|
||||||
code_block_infos.insert_or_assign(offset, cbi);
|
code_block_infos.insert_or_assign(offset, cbi);
|
||||||
@@ -104,17 +109,14 @@ public:
|
|||||||
code_block_infos.erase(offset);
|
code_block_infos.erase(offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] inline bool SupportsFastmem() const noexcept {
|
bool SupportsFastmem() const noexcept { return supports_fast_mem; }
|
||||||
return supports_fast_mem;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void RegisterHandler();
|
|
||||||
static void SigAction(int sig, siginfo_t* info, void* raw_context);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
std::mutex handler_lock;
|
||||||
std::optional<SigHandler> sig_handler;
|
std::optional<SigHandler> sig_handler;
|
||||||
|
|
||||||
void SigHandler::RegisterHandler() {
|
void RegisterHandler() {
|
||||||
|
std::lock_guard<std::mutex> guard(handler_lock);
|
||||||
if (!sig_handler) {
|
if (!sig_handler) {
|
||||||
sig_handler.emplace();
|
sig_handler.emplace();
|
||||||
}
|
}
|
||||||
@@ -123,27 +125,51 @@ void SigHandler::RegisterHandler() {
|
|||||||
void SigHandler::SigAction(int sig, siginfo_t* info, void* raw_context) {
|
void SigHandler::SigAction(int sig, siginfo_t* info, void* raw_context) {
|
||||||
DEBUG_ASSERT(sig == SIGSEGV || sig == SIGBUS);
|
DEBUG_ASSERT(sig == SIGSEGV || sig == SIGBUS);
|
||||||
CTX_DECLARE(raw_context);
|
CTX_DECLARE(raw_context);
|
||||||
|
#if defined(ARCHITECTURE_x86_64)
|
||||||
{
|
{
|
||||||
std::shared_lock guard(sig_handler->code_block_infos_mutex);
|
std::shared_lock guard(sig_handler->code_block_infos_mutex);
|
||||||
if (auto const iter = sig_handler->FindCodeBlockInfo(CTX_PC); iter != sig_handler->code_block_infos.end()) {
|
if (auto const iter = sig_handler->FindCodeBlockInfo(CTX_PC); iter != sig_handler->code_block_infos.end()) {
|
||||||
FakeCall fc = iter->second.cb(CTX_PC);
|
FakeCall fc = iter->second.cb(CTX_PC);
|
||||||
#if defined(ARCHITECTURE_x86_64)
|
|
||||||
CTX_SP -= sizeof(u64);
|
CTX_SP -= sizeof(u64);
|
||||||
*std::bit_cast<u64*>(CTX_SP) = fc.ret_rip;
|
*std::bit_cast<u64*>(CTX_SP) = fc.ret_rip;
|
||||||
CTX_PC = fc.call_rip;
|
CTX_PC = fc.call_rip;
|
||||||
#elif defined(ARCHITECTURE_arm64)
|
|
||||||
CTX_PC = fc.call_pc;
|
|
||||||
#elif defined(ARCHITECTURE_riscv64)
|
|
||||||
CTX_PC = fc.call_sepc;
|
|
||||||
#elif defined(ARCHITECTURE_loongarch64)
|
|
||||||
CTX_PC = fc.call_pc;
|
|
||||||
#else
|
|
||||||
ASSERT(false);
|
|
||||||
#endif
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LOG_ERROR(Core, "Unhandled {} at {:#018x}\n", sig == SIGSEGV ? "SIGSEGV" : "SIGBUS", CTX_PC);
|
fmt::print(stderr, "Unhandled {} at rip {:#018x}\n", sig == SIGSEGV ? "SIGSEGV" : "SIGBUS", CTX_PC);
|
||||||
|
#elif defined(ARCHITECTURE_arm64)
|
||||||
|
{
|
||||||
|
std::shared_lock guard(sig_handler->code_block_infos_mutex);
|
||||||
|
if (const auto iter = sig_handler->FindCodeBlockInfo(CTX_PC); iter != sig_handler->code_block_infos.end()) {
|
||||||
|
FakeCall fc = iter->second.cb(CTX_PC);
|
||||||
|
CTX_PC = fc.call_pc;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt::print(stderr, "Unhandled {} at pc {:#018x}\n", sig == SIGSEGV ? "SIGSEGV" : "SIGBUS", CTX_PC);
|
||||||
|
#elif defined(ARCHITECTURE_riscv64)
|
||||||
|
{
|
||||||
|
std::shared_lock guard(sig_handler->code_block_infos_mutex);
|
||||||
|
if (const auto iter = sig_handler->FindCodeBlockInfo(CTX_SEPC); iter != sig_handler->code_block_infos.end()) {
|
||||||
|
FakeCall fc = iter->second.cb(CTX_SEPC);
|
||||||
|
CTX_SEPC = fc.call_sepc;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt::print(stderr, "Unhandled {} at pc {:#018x}\n", sig == SIGSEGV ? "SIGSEGV" : "SIGBUS", CTX_SEPC);
|
||||||
|
#elif defined(ARCHITECTURE_loongarch64)
|
||||||
|
{
|
||||||
|
std::shared_lock guard(sig_handler->code_block_infos_mutex);
|
||||||
|
if (const auto iter = sig_handler->FindCodeBlockInfo(CTX_PC); iter != sig_handler->code_block_infos.end()) {
|
||||||
|
FakeCall fc = iter->second.cb(CTX_PC);
|
||||||
|
CTX_PC = fc.call_pc;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt::print(stderr, "Unhandled {} at pc {:#018x}\n", sig == SIGSEGV ? "SIGSEGV" : "SIGBUS", CTX_PC);
|
||||||
|
#else
|
||||||
|
# error "Invalid architecture"
|
||||||
|
#endif
|
||||||
|
|
||||||
struct sigaction* retry_sa = sig == SIGSEGV ? &sig_handler->old_sa_segv : &sig_handler->old_sa_bus;
|
struct sigaction* retry_sa = sig == SIGSEGV ? &sig_handler->old_sa_segv : &sig_handler->old_sa_bus;
|
||||||
if (retry_sa->sa_flags & SA_SIGINFO) {
|
if (retry_sa->sa_flags & SA_SIGINFO) {
|
||||||
@@ -165,9 +191,8 @@ void SigHandler::SigAction(int sig, siginfo_t* info, void* raw_context) {
|
|||||||
struct ExceptionHandler::Impl final {
|
struct ExceptionHandler::Impl final {
|
||||||
Impl(u64 offset_, u64 size_)
|
Impl(u64 offset_, u64 size_)
|
||||||
: offset(offset_)
|
: offset(offset_)
|
||||||
, size(size_)
|
, size(size_) {
|
||||||
{
|
RegisterHandler();
|
||||||
SigHandler::RegisterHandler();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetCallback(std::function<FakeCall(u64)> cb) {
|
void SetCallback(std::function<FakeCall(u64)> cb) {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <variant>
|
|
||||||
#include "dynarmic/backend/loongarch64/emit_loongarch64.h"
|
#include "dynarmic/backend/loongarch64/emit_loongarch64.h"
|
||||||
|
|
||||||
#include "dynarmic/backend/loongarch64/a32_jitstate.h"
|
#include "dynarmic/backend/loongarch64/a32_jitstate.h"
|
||||||
@@ -96,8 +95,7 @@ EmittedBlockInfo EmitLoongArch64(lagoon_assembler_t& as, IR::Block block, const
|
|||||||
|
|
||||||
// TODO: Emit Terminal
|
// TODO: Emit Terminal
|
||||||
const auto term = block.GetTerminal();
|
const auto term = block.GetTerminal();
|
||||||
const IR::Term::LeafTerminal* leaft_term = std::get_if<IR::Term::LeafTerminal>(&term);
|
const IR::Term::LinkBlock* link_block_term = boost::get<IR::Term::LinkBlock>(&term);
|
||||||
const IR::Term::LinkBlock* link_block_term = std::get_if<IR::Term::LinkBlock>(leaft_term);
|
|
||||||
ASSERT(link_block_term);
|
ASSERT(link_block_term);
|
||||||
la_load_immediate64(&as, Xscratch0, link_block_term->next.Value());
|
la_load_immediate64(&as, Xscratch0, link_block_term->next.Value());
|
||||||
la_st_w(&as, Xscratch0, Xstate, static_cast<int32_t>(offsetof(A32JitState, regs) + sizeof(u32) * 15));
|
la_st_w(&as, Xscratch0, Xstate, static_cast<int32_t>(offsetof(A32JitState, regs) + sizeof(u32) * 15));
|
||||||
|
|||||||
@@ -136,14 +136,14 @@
|
|||||||
# endif
|
# endif
|
||||||
#elif defined(ARCHITECTURE_riscv64)
|
#elif defined(ARCHITECTURE_riscv64)
|
||||||
# if defined(__FreeBSD__)
|
# if defined(__FreeBSD__)
|
||||||
# define CTX_PC (mctx.mc_gpregs.gp_sepc)
|
# define CTX_SEPC (mctx.mc_gpregs.gp_sepc)
|
||||||
# define CTX_SP (mctx.mc_gpregs.gp_sp)
|
# define CTX_SP (mctx.mc_gpregs.gp_sp)
|
||||||
# elif defined(__linux__)
|
# elif defined(__linux__)
|
||||||
# define CTX_PC (mctx.__gregs[REG_PC])
|
# define CTX_SEPC (mctx.__gregs[REG_PC])
|
||||||
# define CTX_SP (mctx.__gregs[REG_SP])
|
# define CTX_SP (mctx.__gregs[REG_SP])
|
||||||
# elif defined(__OpenBSD__)
|
# elif defined(__OpenBSD__)
|
||||||
// https://github.com/openbsd/src/blob/master/sys/arch/riscv64/include/signal.h
|
// https://github.com/openbsd/src/blob/master/sys/arch/riscv64/include/signal.h
|
||||||
# define CTX_PC (ucontext->sc_sepc)
|
# define CTX_SEPC (ucontext->sc_sepc)
|
||||||
# define CTX_SP (ucontext->sc_sp)
|
# define CTX_SP (ucontext->sc_sp)
|
||||||
# else
|
# else
|
||||||
# error "unknown platform"
|
# error "unknown platform"
|
||||||
|
|||||||
@@ -522,6 +522,9 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent) {
|
|||||||
PAIR(AnisotropyMode, X4, tr("4x")),
|
PAIR(AnisotropyMode, X4, tr("4x")),
|
||||||
PAIR(AnisotropyMode, X8, tr("8x")),
|
PAIR(AnisotropyMode, X8, tr("8x")),
|
||||||
PAIR(AnisotropyMode, X16, tr("16x")),
|
PAIR(AnisotropyMode, X16, tr("16x")),
|
||||||
|
PAIR(AnisotropyMode, X32, tr("32x")),
|
||||||
|
PAIR(AnisotropyMode, X64, tr("64x")),
|
||||||
|
PAIR(AnisotropyMode, None, tr("None")),
|
||||||
}});
|
}});
|
||||||
translations->insert(
|
translations->insert(
|
||||||
{Settings::EnumMetadata<Settings::Language>::Index(),
|
{Settings::EnumMetadata<Settings::Language>::Index(),
|
||||||
|
|||||||
@@ -74,8 +74,8 @@ static constexpr char DEFAULT_DISCORD_IMAGE[] =
|
|||||||
"https://git.eden-emu.dev/eden-emu/eden/raw/branch/master/dist/qt_themes/default/icons/256x256/"
|
"https://git.eden-emu.dev/eden-emu/eden/raw/branch/master/dist/qt_themes/default/icons/256x256/"
|
||||||
"eden.png";
|
"eden.png";
|
||||||
|
|
||||||
void DiscordImpl::UpdateGameStatus(std::string_view game_url, bool has_boxart) {
|
void DiscordImpl::UpdateGameStatus(bool use_default) {
|
||||||
const std::string url = std::string{has_boxart ? game_url : DEFAULT_DISCORD_IMAGE};
|
const std::string url = use_default ? std::string{DEFAULT_DISCORD_IMAGE} : game_url;
|
||||||
s64 start_time = std::chrono::duration_cast<std::chrono::seconds>(
|
s64 start_time = std::chrono::duration_cast<std::chrono::seconds>(
|
||||||
std::chrono::system_clock::now().time_since_epoch())
|
std::chrono::system_clock::now().time_since_epoch())
|
||||||
.count();
|
.count();
|
||||||
@@ -98,7 +98,7 @@ void DiscordImpl::Update() {
|
|||||||
|
|
||||||
// Used to format Icon URL for yuzu website game compatibility page
|
// Used to format Icon URL for yuzu website game compatibility page
|
||||||
std::string icon_name = GetGameString(game_title);
|
std::string icon_name = GetGameString(game_title);
|
||||||
auto const game_url = fmt::format(
|
game_url = fmt::format(
|
||||||
"https://raw.githubusercontent.com/eden-emulator/boxart/refs/heads/master/img/{}.png",
|
"https://raw.githubusercontent.com/eden-emulator/boxart/refs/heads/master/img/{}.png",
|
||||||
icon_name);
|
icon_name);
|
||||||
|
|
||||||
@@ -117,7 +117,7 @@ void DiscordImpl::Update() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
auto res = client.send(request);
|
auto res = client.send(request);
|
||||||
UpdateGameStatus(game_url, res && res->status == 200);
|
UpdateGameStatus(res && res->status == 200);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: 2018 Citra Emulator Project
|
// SPDX-FileCopyrightText: 2018 Citra Emulator Project
|
||||||
@@ -26,9 +26,11 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
std::string GetGameString(const std::string& title);
|
std::string GetGameString(const std::string& title);
|
||||||
void UpdateGameStatus(std::string_view game_url, bool use_default);
|
void UpdateGameStatus(bool use_default);
|
||||||
|
|
||||||
|
std::string game_url{};
|
||||||
std::string game_title{};
|
std::string game_title{};
|
||||||
|
|
||||||
Core::System& system;
|
Core::System& system;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
@@ -665,8 +665,6 @@ void EmitShuffleDown(EmitContext& ctx, IR::Inst& inst, ScalarU32 value, ScalarU3
|
|||||||
const IR::Value& clamp, const IR::Value& segmentation_mask);
|
const IR::Value& clamp, const IR::Value& segmentation_mask);
|
||||||
void EmitShuffleButterfly(EmitContext& ctx, IR::Inst& inst, ScalarU32 value, ScalarU32 index,
|
void EmitShuffleButterfly(EmitContext& ctx, IR::Inst& inst, ScalarU32 value, ScalarU32 index,
|
||||||
const IR::Value& clamp, const IR::Value& segmentation_mask);
|
const IR::Value& clamp, const IR::Value& segmentation_mask);
|
||||||
void EmitQuadBroadcast(EmitContext& ctx, IR::Inst& inst, ScalarU32 value, ScalarU32 lane);
|
|
||||||
void EmitQuadSwap(EmitContext& ctx, IR::Inst& inst, ScalarU32 value, ScalarU32 direction);
|
|
||||||
void EmitFSwizzleAdd(EmitContext& ctx, IR::Inst& inst, ScalarF32 op_a, ScalarF32 op_b,
|
void EmitFSwizzleAdd(EmitContext& ctx, IR::Inst& inst, ScalarF32 op_a, ScalarF32 op_b,
|
||||||
ScalarU32 swizzle);
|
ScalarU32 swizzle);
|
||||||
void EmitDPdxFine(EmitContext& ctx, IR::Inst& inst, ScalarF32 op_a);
|
void EmitDPdxFine(EmitContext& ctx, IR::Inst& inst, ScalarF32 op_a);
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -100,24 +97,6 @@ void EmitShuffleButterfly(EmitContext& ctx, IR::Inst& inst, ScalarU32 value, Sca
|
|||||||
Shuffle(ctx, inst, value, index, clamp, segmentation_mask, "XOR");
|
Shuffle(ctx, inst, value, index, clamp, segmentation_mask, "XOR");
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmitQuadBroadcast(EmitContext& ctx, IR::Inst& inst, ScalarU32 value, ScalarU32 lane) {
|
|
||||||
const Register ret{ctx.reg_alloc.Define(inst)};
|
|
||||||
ctx.Add("AND.U RC.x,{}.threadid,~3;"
|
|
||||||
"AND.U RC.y,{},3;"
|
|
||||||
"OR.U RC.x,RC.x,RC.y;"
|
|
||||||
"SHFIDX.U {},{},RC.x,0x1C03;"
|
|
||||||
"MOV.U {}.x,{}.y;",
|
|
||||||
ctx.stage_name, lane, ret, value, ret, ret);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EmitQuadSwap(EmitContext& ctx, IR::Inst& inst, ScalarU32 value, ScalarU32 direction) {
|
|
||||||
const Register ret{ctx.reg_alloc.Define(inst)};
|
|
||||||
ctx.Add("ADD.U RC.x,{},1;"
|
|
||||||
"SHFXOR.U {},{},RC.x,0x1C03;"
|
|
||||||
"MOV.U {}.x,{}.y;",
|
|
||||||
direction, ret, value, ret, ret);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EmitFSwizzleAdd(EmitContext& ctx, IR::Inst& inst, ScalarF32 op_a, ScalarF32 op_b,
|
void EmitFSwizzleAdd(EmitContext& ctx, IR::Inst& inst, ScalarF32 op_a, ScalarF32 op_b,
|
||||||
ScalarU32 swizzle) {
|
ScalarU32 swizzle) {
|
||||||
const auto ret{ctx.reg_alloc.Define(inst)};
|
const auto ret{ctx.reg_alloc.Define(inst)};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
@@ -743,10 +743,6 @@ void EmitShuffleDown(EmitContext& ctx, IR::Inst& inst, std::string_view value,
|
|||||||
void EmitShuffleButterfly(EmitContext& ctx, IR::Inst& inst, std::string_view value,
|
void EmitShuffleButterfly(EmitContext& ctx, IR::Inst& inst, std::string_view value,
|
||||||
std::string_view index, std::string_view clamp,
|
std::string_view index, std::string_view clamp,
|
||||||
std::string_view segmentation_mask);
|
std::string_view segmentation_mask);
|
||||||
void EmitQuadBroadcast(EmitContext& ctx, IR::Inst& inst, std::string_view value,
|
|
||||||
std::string_view lane);
|
|
||||||
void EmitQuadSwap(EmitContext& ctx, IR::Inst& inst, std::string_view value,
|
|
||||||
std::string_view direction);
|
|
||||||
void EmitFSwizzleAdd(EmitContext& ctx, IR::Inst& inst, std::string_view op_a, std::string_view op_b,
|
void EmitFSwizzleAdd(EmitContext& ctx, IR::Inst& inst, std::string_view op_a, std::string_view op_b,
|
||||||
std::string_view swizzle);
|
std::string_view swizzle);
|
||||||
void EmitDPdxFine(EmitContext& ctx, IR::Inst& inst, std::string_view op_a);
|
void EmitDPdxFine(EmitContext& ctx, IR::Inst& inst, std::string_view op_a);
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -203,18 +200,6 @@ void EmitShuffleButterfly(EmitContext& ctx, IR::Inst& inst, std::string_view val
|
|||||||
ctx.AddU32("{}=shfl_in_bounds?shfl_result:{};", inst, value);
|
ctx.AddU32("{}=shfl_in_bounds?shfl_result:{};", inst, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmitQuadBroadcast(EmitContext& ctx, IR::Inst& inst, std::string_view value,
|
|
||||||
std::string_view lane) {
|
|
||||||
const auto src_thread_id{fmt::format("(({}&~3)|({}& 3))", THREAD_ID, lane)};
|
|
||||||
ctx.AddU32("{}=readInvocationARB({},{});", inst, value, src_thread_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EmitQuadSwap(EmitContext& ctx, IR::Inst& inst, std::string_view value,
|
|
||||||
std::string_view direction) {
|
|
||||||
const auto src_thread_id{fmt::format("({}^({}+1))", THREAD_ID, direction)};
|
|
||||||
ctx.AddU32("{}=readInvocationARB({},{});", inst, value, src_thread_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EmitFSwizzleAdd(EmitContext& ctx, IR::Inst& inst, std::string_view op_a, std::string_view op_b,
|
void EmitFSwizzleAdd(EmitContext& ctx, IR::Inst& inst, std::string_view op_a, std::string_view op_b,
|
||||||
std::string_view swizzle) {
|
std::string_view swizzle) {
|
||||||
const auto mask{fmt::format("({}>>((gl_SubGroupInvocationARB&3)<<1))&3", swizzle)};
|
const auto mask{fmt::format("({}>>((gl_SubGroupInvocationARB&3)<<1))&3", swizzle)};
|
||||||
|
|||||||
@@ -322,11 +322,6 @@ void DefineEntryPoint(const IR::Program& program, EmitContext& ctx, Id main) {
|
|||||||
if (ctx.runtime_info.force_early_z) {
|
if (ctx.runtime_info.force_early_z) {
|
||||||
ctx.AddExecutionMode(main, spv::ExecutionMode::EarlyFragmentTests);
|
ctx.AddExecutionMode(main, spv::ExecutionMode::EarlyFragmentTests);
|
||||||
}
|
}
|
||||||
if (ctx.profile.support_shader_quad_control && program.info.uses_quad_shuffles) {
|
|
||||||
ctx.AddExtension("SPV_KHR_quad_control");
|
|
||||||
ctx.AddCapability(spv::Capability::QuadControlKHR);
|
|
||||||
ctx.AddExecutionMode(main, spv::ExecutionMode::RequireFullQuadsKHR);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw NotImplementedException("Stage {}", program.stage);
|
throw NotImplementedException("Stage {}", program.stage);
|
||||||
@@ -340,7 +335,7 @@ void SetupDenormControl(const Profile& profile, const IR::Program& program, Emit
|
|||||||
if (info.uses_fp32_denorms_flush && info.uses_fp32_denorms_preserve) {
|
if (info.uses_fp32_denorms_flush && info.uses_fp32_denorms_preserve) {
|
||||||
LOG_DEBUG(Shader_SPIRV, "Fp32 denorm flush and preserve on the same shader");
|
LOG_DEBUG(Shader_SPIRV, "Fp32 denorm flush and preserve on the same shader");
|
||||||
} else if (info.uses_fp32_denorms_flush) {
|
} else if (info.uses_fp32_denorms_flush) {
|
||||||
if (profile.support_fp32_denorm_flush && !profile.has_broken_fp32_denorm_flush) {
|
if (profile.support_fp32_denorm_flush) {
|
||||||
ctx.AddCapability(spv::Capability::DenormFlushToZero);
|
ctx.AddCapability(spv::Capability::DenormFlushToZero);
|
||||||
ctx.AddExecutionMode(main_func, spv::ExecutionMode::DenormFlushToZero, 32U);
|
ctx.AddExecutionMode(main_func, spv::ExecutionMode::DenormFlushToZero, 32U);
|
||||||
} else {
|
} else {
|
||||||
@@ -448,12 +443,6 @@ void SetupCapabilities(const Profile& profile, const Info& info, EmitContext& ct
|
|||||||
ctx.AddCapability(spv::Capability::GroupNonUniformVote);
|
ctx.AddCapability(spv::Capability::GroupNonUniformVote);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (info.uses_quad_shuffles) {
|
|
||||||
if (profile.support_quad_shuffles) {
|
|
||||||
ctx.AddCapability(spv::Capability::GroupNonUniformQuad);
|
|
||||||
}
|
|
||||||
ctx.AddCapability(spv::Capability::GroupNonUniformShuffle);
|
|
||||||
}
|
|
||||||
if (info.uses_int64_bit_atomics && profile.support_int64_atomics) {
|
if (info.uses_int64_bit_atomics && profile.support_int64_atomics) {
|
||||||
ctx.AddCapability(spv::Capability::Int64Atomics);
|
ctx.AddCapability(spv::Capability::Int64Atomics);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
@@ -622,8 +622,6 @@ Id EmitShuffleDown(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clam
|
|||||||
Id segmentation_mask);
|
Id segmentation_mask);
|
||||||
Id EmitShuffleButterfly(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
|
Id EmitShuffleButterfly(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
|
||||||
Id segmentation_mask);
|
Id segmentation_mask);
|
||||||
Id EmitQuadBroadcast(EmitContext& ctx, Id value, Id lane);
|
|
||||||
Id EmitQuadSwap(EmitContext& ctx, Id value, Id direction);
|
|
||||||
Id EmitFSwizzleAdd(EmitContext& ctx, Id op_a, Id op_b, Id swizzle);
|
Id EmitFSwizzleAdd(EmitContext& ctx, Id op_a, Id op_b, Id swizzle);
|
||||||
Id EmitDPdxFine(EmitContext& ctx, Id op_a);
|
Id EmitDPdxFine(EmitContext& ctx, Id op_a);
|
||||||
Id EmitDPdyFine(EmitContext& ctx, Id op_a);
|
Id EmitDPdyFine(EmitContext& ctx, Id op_a);
|
||||||
|
|||||||
@@ -260,21 +260,6 @@ Id EmitShuffleButterfly(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id
|
|||||||
return SelectValue(ctx, in_range, value, src_thread_id);
|
return SelectValue(ctx, in_range, value, src_thread_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Id EmitQuadBroadcast(EmitContext& ctx, Id value, Id lane) {
|
|
||||||
if (ctx.profile.support_quad_shuffles) {
|
|
||||||
return ctx.OpGroupNonUniformQuadBroadcast(ctx.U32[1], SubgroupScope(ctx), value, lane);
|
|
||||||
}
|
|
||||||
const Id base{ctx.OpBitwiseAnd(ctx.U32[1], GetThreadId(ctx), ctx.Const(~3u))};
|
|
||||||
const Id local_lane{ctx.OpBitwiseAnd(ctx.U32[1], lane, ctx.Const(3u))};
|
|
||||||
const Id src_thread_id{ctx.OpBitwiseOr(ctx.U32[1], base, local_lane)};
|
|
||||||
return ctx.OpGroupNonUniformShuffle(ctx.U32[1], SubgroupScope(ctx), value, src_thread_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
Id EmitQuadSwap(EmitContext& ctx, Id value, Id direction) {
|
|
||||||
const Id xor_mask{ctx.OpIAdd(ctx.U32[1], direction, ctx.Const(1u))};
|
|
||||||
return ctx.OpGroupNonUniformShuffleXor(ctx.U32[1], SubgroupScope(ctx), value, xor_mask);
|
|
||||||
}
|
|
||||||
|
|
||||||
Id EmitFSwizzleAdd(EmitContext& ctx, Id op_a, Id op_b, Id swizzle) {
|
Id EmitFSwizzleAdd(EmitContext& ctx, Id op_a, Id op_b, Id swizzle) {
|
||||||
const Id three{ctx.Const(3U)};
|
const Id three{ctx.Const(3U)};
|
||||||
Id mask{GetThreadId(ctx)};
|
Id mask{GetThreadId(ctx)};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
@@ -2100,14 +2100,6 @@ U32 IREmitter::ShuffleButterfly(const IR::U32& value, const IR::U32& index, cons
|
|||||||
return Inst<U32>(Opcode::ShuffleButterfly, value, index, clamp, seg_mask);
|
return Inst<U32>(Opcode::ShuffleButterfly, value, index, clamp, seg_mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
U32 IREmitter::QuadBroadcast(const IR::U32& value, const IR::U32& lane) {
|
|
||||||
return Inst<U32>(Opcode::QuadBroadcast, value, lane);
|
|
||||||
}
|
|
||||||
|
|
||||||
U32 IREmitter::QuadSwap(const IR::U32& value, const IR::U32& direction) {
|
|
||||||
return Inst<U32>(Opcode::QuadSwap, value, direction);
|
|
||||||
}
|
|
||||||
|
|
||||||
F32 IREmitter::FSwizzleAdd(const F32& a, const F32& b, const U32& swizzle, FpControl control) {
|
F32 IREmitter::FSwizzleAdd(const F32& a, const F32& b, const U32& swizzle, FpControl control) {
|
||||||
return Inst<F32>(Opcode::FSwizzleAdd, Flags{control}, a, b, swizzle);
|
return Inst<F32>(Opcode::FSwizzleAdd, Flags{control}, a, b, swizzle);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
@@ -394,8 +394,6 @@ public:
|
|||||||
const IR::U32& seg_mask);
|
const IR::U32& seg_mask);
|
||||||
[[nodiscard]] U32 ShuffleButterfly(const IR::U32& value, const IR::U32& index,
|
[[nodiscard]] U32 ShuffleButterfly(const IR::U32& value, const IR::U32& index,
|
||||||
const IR::U32& clamp, const IR::U32& seg_mask);
|
const IR::U32& clamp, const IR::U32& seg_mask);
|
||||||
[[nodiscard]] U32 QuadBroadcast(const IR::U32& value, const IR::U32& lane);
|
|
||||||
[[nodiscard]] U32 QuadSwap(const IR::U32& value, const IR::U32& direction);
|
|
||||||
[[nodiscard]] F32 FSwizzleAdd(const F32& a, const F32& b, const U32& swizzle,
|
[[nodiscard]] F32 FSwizzleAdd(const F32& a, const F32& b, const U32& swizzle,
|
||||||
FpControl control = {});
|
FpControl control = {});
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace Shader::IR {
|
|||||||
|
|
||||||
namespace Detail {
|
namespace Detail {
|
||||||
|
|
||||||
OpcodeMeta META_TABLE[534] = {
|
OpcodeMeta META_TABLE[532] = {
|
||||||
#define OPCODE(name_token, type_token, ...) \
|
#define OPCODE(name_token, type_token, ...) \
|
||||||
{ \
|
{ \
|
||||||
.name{#name_token}, \
|
.name{#name_token}, \
|
||||||
@@ -21,7 +21,7 @@ OpcodeMeta META_TABLE[534] = {
|
|||||||
#undef OPCODE
|
#undef OPCODE
|
||||||
};
|
};
|
||||||
|
|
||||||
u8 NUM_ARGS[534] = {
|
u8 NUM_ARGS[532] = {
|
||||||
#define OPCODE(name_token, type_token, ...) u8(CalculateNumArgsOf(Opcode::name_token)),
|
#define OPCODE(name_token, type_token, ...) u8(CalculateNumArgsOf(Opcode::name_token)),
|
||||||
#include "opcodes.inc"
|
#include "opcodes.inc"
|
||||||
#undef OPCODE
|
#undef OPCODE
|
||||||
|
|||||||
@@ -57,12 +57,12 @@ static constexpr Type F64x2{Type::F64x2};
|
|||||||
static constexpr Type F64x3{Type::F64x3};
|
static constexpr Type F64x3{Type::F64x3};
|
||||||
static constexpr Type F64x4{Type::F64x4};
|
static constexpr Type F64x4{Type::F64x4};
|
||||||
|
|
||||||
extern OpcodeMeta META_TABLE[534];
|
extern OpcodeMeta META_TABLE[532];
|
||||||
constexpr size_t CalculateNumArgsOf(Opcode op) noexcept {
|
constexpr size_t CalculateNumArgsOf(Opcode op) noexcept {
|
||||||
const auto& arg_types = META_TABLE[size_t(op)].arg_types;
|
const auto& arg_types = META_TABLE[size_t(op)].arg_types;
|
||||||
return size_t(std::distance(arg_types.begin(), std::ranges::find(arg_types, Type::Void)));
|
return size_t(std::distance(arg_types.begin(), std::ranges::find(arg_types, Type::Void)));
|
||||||
}
|
}
|
||||||
extern u8 NUM_ARGS[534];
|
extern u8 NUM_ARGS[532];
|
||||||
} // namespace Detail
|
} // namespace Detail
|
||||||
|
|
||||||
/// Get return type of an opcode
|
/// Get return type of an opcode
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -582,8 +579,6 @@ OPCODE(ShuffleIndex, U32, U32,
|
|||||||
OPCODE(ShuffleUp, U32, U32, U32, U32, U32, )
|
OPCODE(ShuffleUp, U32, U32, U32, U32, U32, )
|
||||||
OPCODE(ShuffleDown, U32, U32, U32, U32, U32, )
|
OPCODE(ShuffleDown, U32, U32, U32, U32, U32, )
|
||||||
OPCODE(ShuffleButterfly, U32, U32, U32, U32, U32, )
|
OPCODE(ShuffleButterfly, U32, U32, U32, U32, U32, )
|
||||||
OPCODE(QuadBroadcast, U32, U32, U32, )
|
|
||||||
OPCODE(QuadSwap, U32, U32, U32, )
|
|
||||||
OPCODE(FSwizzleAdd, F32, F32, F32, U32, )
|
OPCODE(FSwizzleAdd, F32, F32, F32, U32, )
|
||||||
OPCODE(DPdxFine, F32, F32, )
|
OPCODE(DPdxFine, F32, F32, )
|
||||||
OPCODE(DPdyFine, F32, F32, )
|
OPCODE(DPdyFine, F32, F32, )
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
@@ -36,10 +36,7 @@ enum class ShuffleMode : u64 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr u32 QUAD_MASK = (28u << 8) | 3u;
|
void Shuffle(TranslatorVisitor& v, u64 insn, const IR::U32& index, const IR::U32& mask) {
|
||||||
|
|
||||||
void Shuffle(TranslatorVisitor& v, u64 insn, const IR::U32& index, const IR::U32& mask,
|
|
||||||
bool index_is_imm, u32 index_imm, bool mask_is_imm, u32 mask_imm) {
|
|
||||||
union {
|
union {
|
||||||
u64 insn;
|
u64 insn;
|
||||||
BitField<0, 8, IR::Reg> dest_reg;
|
BitField<0, 8, IR::Reg> dest_reg;
|
||||||
@@ -48,21 +45,6 @@ void Shuffle(TranslatorVisitor& v, u64 insn, const IR::U32& index, const IR::U32
|
|||||||
BitField<48, 3, IR::Pred> pred;
|
BitField<48, 3, IR::Pred> pred;
|
||||||
} const shfl{insn};
|
} const shfl{insn};
|
||||||
|
|
||||||
const bool is_quad_candidate{mask_is_imm && mask_imm == QUAD_MASK && index_is_imm &&
|
|
||||||
v.env.ShaderStage() == Stage::Fragment};
|
|
||||||
if (is_quad_candidate) {
|
|
||||||
if (shfl.mode == ShuffleMode::IDX && index_imm <= 3) {
|
|
||||||
v.X(shfl.dest_reg, v.ir.QuadBroadcast(v.X(shfl.src_reg), v.ir.Imm32(index_imm)));
|
|
||||||
v.ir.SetPred(shfl.pred, v.ir.Imm1(true));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (shfl.mode == ShuffleMode::BFLY && index_imm >= 1 && index_imm <= 3) {
|
|
||||||
v.X(shfl.dest_reg, v.ir.QuadSwap(v.X(shfl.src_reg), v.ir.Imm32(index_imm - 1)));
|
|
||||||
v.ir.SetPred(shfl.pred, v.ir.Imm1(true));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const IR::U32 result{ShuffleOperation(v.ir, v.X(shfl.src_reg), index, mask, shfl.mode)};
|
const IR::U32 result{ShuffleOperation(v.ir, v.X(shfl.src_reg), index, mask, shfl.mode)};
|
||||||
v.ir.SetPred(shfl.pred, v.ir.GetInBoundsFromOp(result));
|
v.ir.SetPred(shfl.pred, v.ir.GetInBoundsFromOp(result));
|
||||||
v.X(shfl.dest_reg, result);
|
v.X(shfl.dest_reg, result);
|
||||||
@@ -77,14 +59,11 @@ void TranslatorVisitor::SHFL(u64 insn) {
|
|||||||
BitField<29, 1, u64> src_b_flag;
|
BitField<29, 1, u64> src_b_flag;
|
||||||
BitField<34, 13, u64> src_b_imm;
|
BitField<34, 13, u64> src_b_imm;
|
||||||
} const flags{insn};
|
} const flags{insn};
|
||||||
const bool index_is_imm{flags.src_a_flag != 0};
|
const IR::U32 src_a{flags.src_a_flag != 0 ? ir.Imm32(static_cast<u32>(flags.src_a_imm))
|
||||||
const bool mask_is_imm{flags.src_b_flag != 0};
|
|
||||||
const IR::U32 src_a{index_is_imm ? ir.Imm32(static_cast<u32>(flags.src_a_imm))
|
|
||||||
: GetReg20(insn)};
|
: GetReg20(insn)};
|
||||||
const IR::U32 src_b{mask_is_imm ? ir.Imm32(static_cast<u32>(flags.src_b_imm))
|
const IR::U32 src_b{flags.src_b_flag != 0 ? ir.Imm32(static_cast<u32>(flags.src_b_imm))
|
||||||
: GetReg39(insn)};
|
: GetReg39(insn)};
|
||||||
Shuffle(*this, insn, src_a, src_b, index_is_imm, static_cast<u32>(flags.src_a_imm),
|
Shuffle(*this, insn, src_a, src_b);
|
||||||
mask_is_imm, static_cast<u32>(flags.src_b_imm));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Shader::Maxwell
|
} // namespace Shader::Maxwell
|
||||||
|
|||||||
@@ -498,10 +498,6 @@ void VisitUsages(Info& info, IR::Inst& inst) {
|
|||||||
case IR::Opcode::ShuffleButterfly:
|
case IR::Opcode::ShuffleButterfly:
|
||||||
info.uses_subgroup_shuffles = true;
|
info.uses_subgroup_shuffles = true;
|
||||||
break;
|
break;
|
||||||
case IR::Opcode::QuadBroadcast:
|
|
||||||
case IR::Opcode::QuadSwap:
|
|
||||||
info.uses_quad_shuffles = true;
|
|
||||||
break;
|
|
||||||
case IR::Opcode::GetCbufU8:
|
case IR::Opcode::GetCbufU8:
|
||||||
case IR::Opcode::GetCbufS8:
|
case IR::Opcode::GetCbufS8:
|
||||||
case IR::Opcode::GetCbufU16:
|
case IR::Opcode::GetCbufU16:
|
||||||
|
|||||||
@@ -37,8 +37,6 @@ struct Profile {
|
|||||||
bool support_explicit_workgroup_layout{};
|
bool support_explicit_workgroup_layout{};
|
||||||
bool support_workgroup_layout_8bit_access{};
|
bool support_workgroup_layout_8bit_access{};
|
||||||
bool support_workgroup_layout_16bit_access{};
|
bool support_workgroup_layout_16bit_access{};
|
||||||
bool support_shader_quad_control{};
|
|
||||||
bool support_quad_shuffles{};
|
|
||||||
bool support_vote{};
|
bool support_vote{};
|
||||||
u32 supported_subgroup_stages{0x7F};
|
u32 supported_subgroup_stages{0x7F};
|
||||||
bool support_viewport_index_layer_non_geometry{};
|
bool support_viewport_index_layer_non_geometry{};
|
||||||
@@ -88,8 +86,6 @@ struct Profile {
|
|||||||
bool has_broken_signed_operations{};
|
bool has_broken_signed_operations{};
|
||||||
/// Float controls break when fp16 is enabled
|
/// Float controls break when fp16 is enabled
|
||||||
bool has_broken_fp16_float_controls{};
|
bool has_broken_fp16_float_controls{};
|
||||||
/// Declaring fp32 denorm flush to zero miscompiles on some drivers
|
|
||||||
bool has_broken_fp32_denorm_flush{};
|
|
||||||
/// Dynamic vec4 indexing is broken on some OpenGL drivers
|
/// Dynamic vec4 indexing is broken on some OpenGL drivers
|
||||||
bool has_gl_component_indexing_bug{};
|
bool has_gl_component_indexing_bug{};
|
||||||
/// The precise type qualifier is broken in the fragment stage of some drivers
|
/// The precise type qualifier is broken in the fragment stage of some drivers
|
||||||
|
|||||||
@@ -252,7 +252,6 @@ struct Info {
|
|||||||
bool uses_is_helper_invocation{};
|
bool uses_is_helper_invocation{};
|
||||||
bool uses_subgroup_invocation_id{};
|
bool uses_subgroup_invocation_id{};
|
||||||
bool uses_subgroup_shuffles{};
|
bool uses_subgroup_shuffles{};
|
||||||
bool uses_quad_shuffles{};
|
|
||||||
std::array<bool, 30> uses_patches{};
|
std::array<bool, 30> uses_patches{};
|
||||||
|
|
||||||
std::array<Interpolation, 32> interpolation{};
|
std::array<Interpolation, 32> interpolation{};
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ set(SHADER_FILES
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/blit_color_float.frag
|
${CMAKE_CURRENT_SOURCE_DIR}/blit_color_float.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_2d.comp
|
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_2d.comp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/blit_color_msaa.frag
|
${CMAKE_CURRENT_SOURCE_DIR}/blit_color_msaa.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/blit_depth.frag
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/blit_depth_msaa.frag
|
${CMAKE_CURRENT_SOURCE_DIR}/blit_depth_msaa.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/blit_depth_stencil_msaa.frag
|
${CMAKE_CURRENT_SOURCE_DIR}/blit_depth_stencil_msaa.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_3d.comp
|
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_3d.comp
|
||||||
@@ -31,12 +30,8 @@ set(SHADER_FILES
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/convert_float_to_depth.frag
|
${CMAKE_CURRENT_SOURCE_DIR}/convert_float_to_depth.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/convert_msaa_to_non_msaa.comp
|
${CMAKE_CURRENT_SOURCE_DIR}/convert_msaa_to_non_msaa.comp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/convert_msaa_to_non_msaa.frag
|
${CMAKE_CURRENT_SOURCE_DIR}/convert_msaa_to_non_msaa.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/convert_msaa_to_non_msaa_depth.frag
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/convert_msaa_to_non_msaa_depth_stencil.frag
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/convert_non_msaa_to_msaa.comp
|
${CMAKE_CURRENT_SOURCE_DIR}/convert_non_msaa_to_msaa.comp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/convert_non_msaa_to_msaa.frag
|
${CMAKE_CURRENT_SOURCE_DIR}/convert_non_msaa_to_msaa.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/convert_non_msaa_to_msaa_depth.frag
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/convert_non_msaa_to_msaa_depth_stencil.frag
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/convert_s8d24_to_abgr8.frag
|
${CMAKE_CURRENT_SOURCE_DIR}/convert_s8d24_to_abgr8.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/full_screen_triangle.vert
|
${CMAKE_CURRENT_SOURCE_DIR}/full_screen_triangle.vert
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/fxaa.frag
|
${CMAKE_CURRENT_SOURCE_DIR}/fxaa.frag
|
||||||
@@ -168,44 +163,6 @@ foreach(SOURCE_FILE IN ITEMS ${SHADER_FILES})
|
|||||||
endif()
|
endif()
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
# Integer variants of the MSAA conversion shaders. They only differ from the float
|
|
||||||
# source in the sampler and output types, so they are generated from it via defines.
|
|
||||||
set(SHADER_TYPE_VARIANTS
|
|
||||||
"convert_msaa_to_non_msaa.frag|sint|isampler2DMS|ivec4"
|
|
||||||
"convert_msaa_to_non_msaa.frag|uint|usampler2DMS|uvec4"
|
|
||||||
"convert_non_msaa_to_msaa.frag|sint|isampler2D|ivec4"
|
|
||||||
"convert_non_msaa_to_msaa.frag|uint|usampler2D|uvec4"
|
|
||||||
)
|
|
||||||
|
|
||||||
foreach(VARIANT IN ITEMS ${SHADER_TYPE_VARIANTS})
|
|
||||||
string(REPLACE "|" ";" VARIANT_PARTS ${VARIANT})
|
|
||||||
list(GET VARIANT_PARTS 0 VARIANT_FILENAME)
|
|
||||||
list(GET VARIANT_PARTS 1 VARIANT_SUFFIX)
|
|
||||||
list(GET VARIANT_PARTS 2 VARIANT_SAMPLER)
|
|
||||||
list(GET VARIANT_PARTS 3 VARIANT_TEXEL)
|
|
||||||
|
|
||||||
set(VARIANT_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/${VARIANT_FILENAME})
|
|
||||||
get_filename_component(VARIANT_STEM ${VARIANT_FILENAME} NAME_WE)
|
|
||||||
get_filename_component(VARIANT_EXT ${VARIANT_FILENAME} EXT)
|
|
||||||
string(REPLACE "." "" VARIANT_EXT ${VARIANT_EXT})
|
|
||||||
set(VARIANT_NAME ${VARIANT_STEM}_${VARIANT_SUFFIX}_${VARIANT_EXT})
|
|
||||||
|
|
||||||
string(TOUPPER ${VARIANT_NAME}_SPV VARIANT_VARIABLE_NAME)
|
|
||||||
set(VARIANT_HEADER_FILE ${SHADER_DIR}/${VARIANT_NAME}_spv.h)
|
|
||||||
add_custom_command(
|
|
||||||
OUTPUT
|
|
||||||
${VARIANT_HEADER_FILE}
|
|
||||||
COMMAND
|
|
||||||
${GLSLANGVALIDATOR} -V ${QUIET_FLAG} -I"${FIDELITYFX_INCLUDE_DIR}" ${GLSL_FLAGS}
|
|
||||||
-DSAMPLER_TYPE=${VARIANT_SAMPLER} -DTEXEL_TYPE=${VARIANT_TEXEL}
|
|
||||||
--variable-name ${VARIANT_VARIABLE_NAME} -o ${VARIANT_HEADER_FILE} ${VARIANT_SOURCE}
|
|
||||||
--target-env ${SPIR_V_VERSION}
|
|
||||||
MAIN_DEPENDENCY
|
|
||||||
${VARIANT_SOURCE}
|
|
||||||
)
|
|
||||||
set(SHADER_HEADERS ${SHADER_HEADERS} ${VARIANT_HEADER_FILE})
|
|
||||||
endforeach()
|
|
||||||
|
|
||||||
foreach(FILEPATH IN ITEMS ${FIDELITYFX_FILES})
|
foreach(FILEPATH IN ITEMS ${FIDELITYFX_FILES})
|
||||||
get_filename_component(FILENAME ${FILEPATH} NAME)
|
get_filename_component(FILENAME ${FILEPATH} NAME)
|
||||||
string(REPLACE "." "_" HEADER_NAME ${FILENAME})
|
string(REPLACE "." "_" HEADER_NAME ${FILENAME})
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
#version 450 core
|
|
||||||
|
|
||||||
layout(binding = 0) uniform sampler2D depth_tex;
|
|
||||||
|
|
||||||
layout(location = 0) in vec2 texcoord;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_FragDepth = textureLod(depth_tex, texcoord, 0).r;
|
|
||||||
}
|
|
||||||
@@ -8,5 +8,5 @@ layout(binding = 0) uniform sampler2DMS depth_tex;
|
|||||||
layout(location = 0) in vec2 texcoord;
|
layout(location = 0) in vec2 texcoord;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
gl_FragDepth = texelFetch(depth_tex, ivec2(texcoord), gl_SampleID).r;
|
gl_FragDepth = texelFetch(depth_tex, ivec2(texcoord), 0).r;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,6 @@ layout(binding = 1) uniform usampler2DMS stencil_tex;
|
|||||||
layout(location = 0) in vec2 texcoord;
|
layout(location = 0) in vec2 texcoord;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
gl_FragDepth = texelFetch(depth_tex, ivec2(texcoord), gl_SampleID).r;
|
gl_FragDepth = texelFetch(depth_tex, ivec2(texcoord), 0).r;
|
||||||
gl_FragStencilRefARB = int(texelFetch(stencil_tex, ivec2(texcoord), gl_SampleID).r);
|
gl_FragStencilRefARB = int(texelFetch(stencil_tex, ivec2(texcoord), 0).r);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,7 @@
|
|||||||
|
|
||||||
#version 450 core
|
#version 450 core
|
||||||
|
|
||||||
#ifndef SAMPLER_TYPE
|
layout(binding = 0) uniform sampler2DMS msaa_in;
|
||||||
#define SAMPLER_TYPE sampler2DMS
|
|
||||||
#endif
|
|
||||||
#ifndef TEXEL_TYPE
|
|
||||||
#define TEXEL_TYPE vec4
|
|
||||||
#endif
|
|
||||||
|
|
||||||
layout(binding = 0) uniform SAMPLER_TYPE msaa_in;
|
|
||||||
|
|
||||||
layout(push_constant) uniform PushConstants {
|
layout(push_constant) uniform PushConstants {
|
||||||
ivec2 dst_offset;
|
ivec2 dst_offset;
|
||||||
@@ -18,7 +11,7 @@ layout(push_constant) uniform PushConstants {
|
|||||||
ivec2 scale;
|
ivec2 scale;
|
||||||
};
|
};
|
||||||
|
|
||||||
layout(location = 0) out TEXEL_TYPE frag_color;
|
layout(location = 0) out vec4 frag_color;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
const ivec2 coord = ivec2(gl_FragCoord.xy) - dst_offset + src_offset;
|
const ivec2 coord = ivec2(gl_FragCoord.xy) - dst_offset + src_offset;
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
#version 450 core
|
|
||||||
|
|
||||||
layout(binding = 0) uniform sampler2DMS msaa_in;
|
|
||||||
|
|
||||||
layout(push_constant) uniform PushConstants {
|
|
||||||
ivec2 dst_offset;
|
|
||||||
ivec2 src_offset;
|
|
||||||
ivec2 scale;
|
|
||||||
};
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
const ivec2 coord = ivec2(gl_FragCoord.xy) - dst_offset + src_offset;
|
|
||||||
const ivec2 msaa_coord = coord / scale;
|
|
||||||
const ivec2 sample_offset = coord % scale;
|
|
||||||
const int sample_id = sample_offset.x + scale.x * sample_offset.y;
|
|
||||||
gl_FragDepth = texelFetch(msaa_in, msaa_coord, sample_id).r;
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
#version 450 core
|
|
||||||
#extension GL_ARB_shader_stencil_export : require
|
|
||||||
|
|
||||||
layout(binding = 0) uniform sampler2DMS depth_tex;
|
|
||||||
layout(binding = 1) uniform usampler2DMS stencil_tex;
|
|
||||||
|
|
||||||
layout(push_constant) uniform PushConstants {
|
|
||||||
ivec2 dst_offset;
|
|
||||||
ivec2 src_offset;
|
|
||||||
ivec2 scale;
|
|
||||||
};
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
const ivec2 coord = ivec2(gl_FragCoord.xy) - dst_offset + src_offset;
|
|
||||||
const ivec2 msaa_coord = coord / scale;
|
|
||||||
const ivec2 sample_offset = coord % scale;
|
|
||||||
const int sample_id = sample_offset.x + scale.x * sample_offset.y;
|
|
||||||
gl_FragDepth = texelFetch(depth_tex, msaa_coord, sample_id).r;
|
|
||||||
gl_FragStencilRefARB = int(texelFetch(stencil_tex, msaa_coord, sample_id).r);
|
|
||||||
}
|
|
||||||
@@ -3,14 +3,7 @@
|
|||||||
|
|
||||||
#version 450 core
|
#version 450 core
|
||||||
|
|
||||||
#ifndef SAMPLER_TYPE
|
layout(binding = 0) uniform sampler2D img_in;
|
||||||
#define SAMPLER_TYPE sampler2D
|
|
||||||
#endif
|
|
||||||
#ifndef TEXEL_TYPE
|
|
||||||
#define TEXEL_TYPE vec4
|
|
||||||
#endif
|
|
||||||
|
|
||||||
layout(binding = 0) uniform SAMPLER_TYPE img_in;
|
|
||||||
|
|
||||||
layout(push_constant) uniform PushConstants {
|
layout(push_constant) uniform PushConstants {
|
||||||
ivec2 dst_offset;
|
ivec2 dst_offset;
|
||||||
@@ -18,7 +11,7 @@ layout(push_constant) uniform PushConstants {
|
|||||||
ivec2 scale;
|
ivec2 scale;
|
||||||
};
|
};
|
||||||
|
|
||||||
layout(location = 0) out TEXEL_TYPE frag_color;
|
layout(location = 0) out vec4 frag_color;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
const ivec2 msaa_coord = ivec2(gl_FragCoord.xy) - dst_offset;
|
const ivec2 msaa_coord = ivec2(gl_FragCoord.xy) - dst_offset;
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
#version 450 core
|
|
||||||
|
|
||||||
layout(binding = 0) uniform sampler2D img_in;
|
|
||||||
|
|
||||||
layout(push_constant) uniform PushConstants {
|
|
||||||
ivec2 dst_offset;
|
|
||||||
ivec2 src_offset;
|
|
||||||
ivec2 scale;
|
|
||||||
};
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
const ivec2 msaa_coord = ivec2(gl_FragCoord.xy) - dst_offset;
|
|
||||||
const ivec2 sample_offset = ivec2(gl_SampleID % scale.x, gl_SampleID / scale.x);
|
|
||||||
const ivec2 coord = msaa_coord * scale + sample_offset + src_offset;
|
|
||||||
gl_FragDepth = texelFetch(img_in, coord, 0).r;
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
#version 450 core
|
|
||||||
#extension GL_ARB_shader_stencil_export : require
|
|
||||||
|
|
||||||
layout(binding = 0) uniform sampler2D depth_tex;
|
|
||||||
layout(binding = 1) uniform usampler2D stencil_tex;
|
|
||||||
|
|
||||||
layout(push_constant) uniform PushConstants {
|
|
||||||
ivec2 dst_offset;
|
|
||||||
ivec2 src_offset;
|
|
||||||
ivec2 scale;
|
|
||||||
};
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
const ivec2 msaa_coord = ivec2(gl_FragCoord.xy) - dst_offset;
|
|
||||||
const ivec2 sample_offset = ivec2(gl_SampleID % scale.x, gl_SampleID / scale.x);
|
|
||||||
const ivec2 coord = msaa_coord * scale + sample_offset + src_offset;
|
|
||||||
gl_FragDepth = texelFetch(depth_tex, coord, 0).r;
|
|
||||||
gl_FragStencilRefARB = int(texelFetch(stencil_tex, coord, 0).r);
|
|
||||||
}
|
|
||||||
@@ -7,7 +7,6 @@ layout(push_constant) uniform constants {
|
|||||||
vec2 scale;
|
vec2 scale;
|
||||||
vec2 size;
|
vec2 size;
|
||||||
vec2 resize_factor;
|
vec2 resize_factor;
|
||||||
vec2 crop_offset;
|
|
||||||
float edge_sharpness;
|
float edge_sharpness;
|
||||||
};
|
};
|
||||||
layout(location = 0) out highp vec2 texcoord;
|
layout(location = 0) out highp vec2 texcoord;
|
||||||
@@ -16,5 +15,5 @@ void main() {
|
|||||||
float x = float((gl_VertexIndex & 1) << 2);
|
float x = float((gl_VertexIndex & 1) << 2);
|
||||||
float y = float((gl_VertexIndex & 2) << 1);
|
float y = float((gl_VertexIndex & 2) << 1);
|
||||||
gl_Position = vec4(x - 1.0f, y - 1.0f, 0.0, 1.0f) * vec4(sign(resize_factor), 1.f, 1.f);
|
gl_Position = vec4(x - 1.0f, y - 1.0f, 0.0, 1.0f) * vec4(sign(resize_factor), 1.f, 1.f);
|
||||||
texcoord = crop_offset + vec2(x, y) * abs(resize_factor) * 0.5;
|
texcoord = vec2(x, y) * abs(resize_factor) * 0.5;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ layout(push_constant) uniform constants {
|
|||||||
vec2 scale;
|
vec2 scale;
|
||||||
vec2 size;
|
vec2 size;
|
||||||
vec2 resize_factor;
|
vec2 resize_factor;
|
||||||
vec2 crop_offset;
|
|
||||||
float edge_sharpness;
|
float edge_sharpness;
|
||||||
};
|
};
|
||||||
layout(set = 0, binding = 0) uniform sampler2D sampler0;
|
layout(set = 0, binding = 0) uniform sampler2D sampler0;
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
layout( push_constant ) uniform constants {
|
layout( push_constant ) uniform constants {
|
||||||
vec4 ViewportInfo[1];
|
vec4 ViewportInfo[1];
|
||||||
vec2 ResizeFactor;
|
vec2 ResizeFactor;
|
||||||
vec2 CropOffset;
|
|
||||||
float EdgeSharpness;
|
float EdgeSharpness;
|
||||||
};
|
};
|
||||||
layout(set = 0, binding = 0) uniform sampler2D ps0;
|
layout(set = 0, binding = 0) uniform sampler2D ps0;
|
||||||
|
|||||||
@@ -231,7 +231,6 @@ ShaderCache::ShaderCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
|
|||||||
.has_broken_unsigned_image_offsets = true,
|
.has_broken_unsigned_image_offsets = true,
|
||||||
.has_broken_signed_operations = true,
|
.has_broken_signed_operations = true,
|
||||||
.has_broken_fp16_float_controls = false,
|
.has_broken_fp16_float_controls = false,
|
||||||
.has_broken_fp32_denorm_flush = false,
|
|
||||||
.has_gl_component_indexing_bug = device.HasComponentIndexingBug(),
|
.has_gl_component_indexing_bug = device.HasComponentIndexingBug(),
|
||||||
.has_gl_precise_bug = device.HasPreciseBug(),
|
.has_gl_precise_bug = device.HasPreciseBug(),
|
||||||
.has_gl_cbuf_ftou_bug = device.HasCbufFtouBug(),
|
.has_gl_cbuf_ftou_bug = device.HasCbufFtouBug(),
|
||||||
|
|||||||
@@ -75,8 +75,6 @@ public:
|
|||||||
|
|
||||||
void Finish();
|
void Finish();
|
||||||
|
|
||||||
void FlushDeferredClear() {}
|
|
||||||
|
|
||||||
StagingBufferMap UploadStagingBuffer(size_t size, bool deferred = false);
|
StagingBufferMap UploadStagingBuffer(size_t size, bool deferred = false);
|
||||||
|
|
||||||
StagingBufferMap DownloadStagingBuffer(size_t size, bool deferred = false);
|
StagingBufferMap DownloadStagingBuffer(size_t size, bool deferred = false);
|
||||||
@@ -372,7 +370,6 @@ struct TextureCacheParams {
|
|||||||
static constexpr bool HAS_EMULATED_COPIES = true;
|
static constexpr bool HAS_EMULATED_COPIES = true;
|
||||||
static constexpr bool HAS_DEVICE_MEMORY_INFO = true;
|
static constexpr bool HAS_DEVICE_MEMORY_INFO = true;
|
||||||
static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = true;
|
static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = true;
|
||||||
static constexpr bool HAS_MSAA_DOWNLOADS = false;
|
|
||||||
|
|
||||||
using Runtime = OpenGL::TextureCacheRuntime;
|
using Runtime = OpenGL::TextureCacheRuntime;
|
||||||
using Image = OpenGL::Image;
|
using Image = OpenGL::Image;
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
#include "video_core/host_shaders/blit_color_float_frag_spv.h"
|
#include "video_core/host_shaders/blit_color_float_frag_spv.h"
|
||||||
#include "video_core/host_shaders/blit_color_msaa_frag_spv.h"
|
#include "video_core/host_shaders/blit_color_msaa_frag_spv.h"
|
||||||
#include "video_core/host_shaders/blit_depth_frag_spv.h"
|
|
||||||
#include "video_core/host_shaders/blit_depth_msaa_frag_spv.h"
|
#include "video_core/host_shaders/blit_depth_msaa_frag_spv.h"
|
||||||
#include "video_core/host_shaders/blit_depth_stencil_msaa_frag_spv.h"
|
#include "video_core/host_shaders/blit_depth_stencil_msaa_frag_spv.h"
|
||||||
#include "video_core/host_shaders/convert_abgr8_to_d24s8_frag_spv.h"
|
#include "video_core/host_shaders/convert_abgr8_to_d24s8_frag_spv.h"
|
||||||
@@ -22,15 +21,7 @@
|
|||||||
#include "video_core/host_shaders/convert_depth_to_float_frag_spv.h"
|
#include "video_core/host_shaders/convert_depth_to_float_frag_spv.h"
|
||||||
#include "video_core/host_shaders/convert_float_to_depth_frag_spv.h"
|
#include "video_core/host_shaders/convert_float_to_depth_frag_spv.h"
|
||||||
#include "video_core/host_shaders/convert_msaa_to_non_msaa_frag_spv.h"
|
#include "video_core/host_shaders/convert_msaa_to_non_msaa_frag_spv.h"
|
||||||
#include "video_core/host_shaders/convert_msaa_to_non_msaa_depth_frag_spv.h"
|
|
||||||
#include "video_core/host_shaders/convert_msaa_to_non_msaa_depth_stencil_frag_spv.h"
|
|
||||||
#include "video_core/host_shaders/convert_msaa_to_non_msaa_sint_frag_spv.h"
|
|
||||||
#include "video_core/host_shaders/convert_msaa_to_non_msaa_uint_frag_spv.h"
|
|
||||||
#include "video_core/host_shaders/convert_non_msaa_to_msaa_frag_spv.h"
|
#include "video_core/host_shaders/convert_non_msaa_to_msaa_frag_spv.h"
|
||||||
#include "video_core/host_shaders/convert_non_msaa_to_msaa_sint_frag_spv.h"
|
|
||||||
#include "video_core/host_shaders/convert_non_msaa_to_msaa_uint_frag_spv.h"
|
|
||||||
#include "video_core/host_shaders/convert_non_msaa_to_msaa_depth_frag_spv.h"
|
|
||||||
#include "video_core/host_shaders/convert_non_msaa_to_msaa_depth_stencil_frag_spv.h"
|
|
||||||
#include "video_core/host_shaders/convert_s8d24_to_abgr8_frag_spv.h"
|
#include "video_core/host_shaders/convert_s8d24_to_abgr8_frag_spv.h"
|
||||||
#include "video_core/host_shaders/full_screen_triangle_vert_spv.h"
|
#include "video_core/host_shaders/full_screen_triangle_vert_spv.h"
|
||||||
#include "video_core/host_shaders/vulkan_blit_depth_stencil_frag_spv.h"
|
#include "video_core/host_shaders/vulkan_blit_depth_stencil_frag_spv.h"
|
||||||
@@ -527,19 +518,8 @@ void RecordShaderReadBarrier(Scheduler& scheduler, const ImageView& image_view)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] MSAACopyFormatClass FormatClass(VideoCore::Surface::PixelFormat format) {
|
|
||||||
if (!VideoCore::Surface::IsPixelFormatInteger(format)) {
|
|
||||||
return MSAACopyFormatClass::Float;
|
|
||||||
}
|
|
||||||
if (VideoCore::Surface::IsPixelFormatSignedInteger(format)) {
|
|
||||||
return MSAACopyFormatClass::SignedInteger;
|
|
||||||
}
|
|
||||||
return MSAACopyFormatClass::UnsignedInteger;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] vk::ImageView MakeMSAACopyView(const vk::Device& device, VkImage image,
|
[[nodiscard]] vk::ImageView MakeMSAACopyView(const vk::Device& device, VkImage image,
|
||||||
VkFormat format, u32 base_level, u32 base_layer,
|
VkFormat format, u32 base_level) {
|
||||||
VkImageAspectFlags aspect_mask) {
|
|
||||||
return device.CreateImageView(VkImageViewCreateInfo{
|
return device.CreateImageView(VkImageViewCreateInfo{
|
||||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
@@ -554,10 +534,10 @@ void RecordShaderReadBarrier(Scheduler& scheduler, const ImageView& image_view)
|
|||||||
.a = VK_COMPONENT_SWIZZLE_IDENTITY,
|
.a = VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||||
},
|
},
|
||||||
.subresourceRange{
|
.subresourceRange{
|
||||||
.aspectMask = aspect_mask,
|
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||||
.baseMipLevel = base_level,
|
.baseMipLevel = base_level,
|
||||||
.levelCount = 1,
|
.levelCount = 1,
|
||||||
.baseArrayLayer = base_layer,
|
.baseArrayLayer = 0,
|
||||||
.layerCount = 1,
|
.layerCount = 1,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -606,17 +586,12 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_,
|
|||||||
msaa_copy_pipeline_layout(device.GetLogical().CreatePipelineLayout(PipelineLayoutCreateInfo(
|
msaa_copy_pipeline_layout(device.GetLogical().CreatePipelineLayout(PipelineLayoutCreateInfo(
|
||||||
one_texture_set_layout.address(),
|
one_texture_set_layout.address(),
|
||||||
PUSH_CONSTANT_RANGE<VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(MSAACopyPushConstants)>))),
|
PUSH_CONSTANT_RANGE<VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(MSAACopyPushConstants)>))),
|
||||||
msaa_copy_depth_stencil_pipeline_layout(
|
|
||||||
device.GetLogical().CreatePipelineLayout(PipelineLayoutCreateInfo(
|
|
||||||
two_textures_set_layout.address(),
|
|
||||||
PUSH_CONSTANT_RANGE<VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(MSAACopyPushConstants)>))),
|
|
||||||
full_screen_vert(BuildShader(device, FULL_SCREEN_TRIANGLE_VERT_SPV)),
|
full_screen_vert(BuildShader(device, FULL_SCREEN_TRIANGLE_VERT_SPV)),
|
||||||
blit_color_to_color_frag(BuildShader(device, BLIT_COLOR_FLOAT_FRAG_SPV)),
|
blit_color_to_color_frag(BuildShader(device, BLIT_COLOR_FLOAT_FRAG_SPV)),
|
||||||
blit_color_msaa_frag(BuildShader(device, BLIT_COLOR_MSAA_FRAG_SPV)),
|
blit_color_msaa_frag(BuildShader(device, BLIT_COLOR_MSAA_FRAG_SPV)),
|
||||||
blit_depth_stencil_frag(device.IsExtShaderStencilExportSupported()
|
blit_depth_stencil_frag(device.IsExtShaderStencilExportSupported()
|
||||||
? BuildShader(device, VULKAN_BLIT_DEPTH_STENCIL_FRAG_SPV)
|
? BuildShader(device, VULKAN_BLIT_DEPTH_STENCIL_FRAG_SPV)
|
||||||
: vk::ShaderModule{}),
|
: vk::ShaderModule{}),
|
||||||
blit_depth_frag(BuildShader(device, BLIT_DEPTH_FRAG_SPV)),
|
|
||||||
blit_depth_msaa_frag(BuildShader(device, BLIT_DEPTH_MSAA_FRAG_SPV)),
|
blit_depth_msaa_frag(BuildShader(device, BLIT_DEPTH_MSAA_FRAG_SPV)),
|
||||||
blit_depth_stencil_msaa_frag(device.IsExtShaderStencilExportSupported()
|
blit_depth_stencil_msaa_frag(device.IsExtShaderStencilExportSupported()
|
||||||
? BuildShader(device, BLIT_DEPTH_STENCIL_MSAA_FRAG_SPV)
|
? BuildShader(device, BLIT_DEPTH_STENCIL_MSAA_FRAG_SPV)
|
||||||
@@ -634,25 +609,7 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_,
|
|||||||
convert_d24s8_to_abgr8_frag(BuildShader(device, CONVERT_D24S8_TO_ABGR8_FRAG_SPV)),
|
convert_d24s8_to_abgr8_frag(BuildShader(device, CONVERT_D24S8_TO_ABGR8_FRAG_SPV)),
|
||||||
convert_s8d24_to_abgr8_frag(BuildShader(device, CONVERT_S8D24_TO_ABGR8_FRAG_SPV)),
|
convert_s8d24_to_abgr8_frag(BuildShader(device, CONVERT_S8D24_TO_ABGR8_FRAG_SPV)),
|
||||||
convert_msaa_to_non_msaa_frag(BuildShader(device, CONVERT_MSAA_TO_NON_MSAA_FRAG_SPV)),
|
convert_msaa_to_non_msaa_frag(BuildShader(device, CONVERT_MSAA_TO_NON_MSAA_FRAG_SPV)),
|
||||||
convert_msaa_to_non_msaa_sint_frag(
|
|
||||||
BuildShader(device, CONVERT_MSAA_TO_NON_MSAA_SINT_FRAG_SPV)),
|
|
||||||
convert_msaa_to_non_msaa_uint_frag(
|
|
||||||
BuildShader(device, CONVERT_MSAA_TO_NON_MSAA_UINT_FRAG_SPV)),
|
|
||||||
convert_msaa_to_non_msaa_depth_frag(
|
|
||||||
BuildShader(device, CONVERT_MSAA_TO_NON_MSAA_DEPTH_FRAG_SPV)),
|
|
||||||
convert_msaa_to_non_msaa_depth_stencil_frag(
|
|
||||||
BuildShader(device, CONVERT_MSAA_TO_NON_MSAA_DEPTH_STENCIL_FRAG_SPV)),
|
|
||||||
convert_non_msaa_to_msaa_frag(BuildShader(device, CONVERT_NON_MSAA_TO_MSAA_FRAG_SPV)),
|
convert_non_msaa_to_msaa_frag(BuildShader(device, CONVERT_NON_MSAA_TO_MSAA_FRAG_SPV)),
|
||||||
convert_non_msaa_to_msaa_sint_frag(
|
|
||||||
BuildShader(device, CONVERT_NON_MSAA_TO_MSAA_SINT_FRAG_SPV)),
|
|
||||||
convert_non_msaa_to_msaa_uint_frag(
|
|
||||||
BuildShader(device, CONVERT_NON_MSAA_TO_MSAA_UINT_FRAG_SPV)),
|
|
||||||
convert_non_msaa_to_msaa_depth_frag(
|
|
||||||
BuildShader(device, CONVERT_NON_MSAA_TO_MSAA_DEPTH_FRAG_SPV)),
|
|
||||||
convert_non_msaa_to_msaa_depth_stencil_frag(
|
|
||||||
device.IsExtShaderStencilExportSupported()
|
|
||||||
? BuildShader(device, CONVERT_NON_MSAA_TO_MSAA_DEPTH_STENCIL_FRAG_SPV)
|
|
||||||
: vk::ShaderModule{}),
|
|
||||||
linear_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_LINEAR>)),
|
linear_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_LINEAR>)),
|
||||||
nearest_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_NEAREST>)) {}
|
nearest_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_NEAREST>)) {}
|
||||||
|
|
||||||
@@ -667,13 +624,24 @@ void BlitImageHelper::BlitColor(const Framebuffer* dst_framebuffer, const ImageV
|
|||||||
.renderpass = dst_framebuffer->RenderPass(),
|
.renderpass = dst_framebuffer->RenderPass(),
|
||||||
.operation = operation,
|
.operation = operation,
|
||||||
};
|
};
|
||||||
VkSampler sampler = *nearest_sampler;
|
const VkPipelineLayout layout = *one_texture_pipeline_layout;
|
||||||
if (is_linear) {
|
const VkSampler sampler = is_linear ? *linear_sampler : *nearest_sampler;
|
||||||
sampler = *linear_sampler;
|
const VkPipeline pipeline = FindOrEmplaceColorPipeline(key);
|
||||||
}
|
const VkImageView src_view = src_image_view.Handle(Shader::TextureType::Color2D);
|
||||||
BlitImpl(dst_framebuffer, src_image_view, dst_region, src_region,
|
|
||||||
FindOrEmplaceColorPipeline(key), sampler,
|
RecordShaderReadBarrier(scheduler, src_image_view);
|
||||||
src_image_view.Handle(Shader::TextureType::Color2D), VK_NULL_HANDLE, false);
|
scheduler.RequestRenderpass(dst_framebuffer);
|
||||||
|
scheduler.Record([this, dst_region, src_region, pipeline, layout, sampler,
|
||||||
|
src_view](vk::CommandBuffer cmdbuf) {
|
||||||
|
const VkDescriptorSet descriptor_set = one_texture_descriptor_allocator.Commit();
|
||||||
|
UpdateOneTextureDescriptorSet(device, descriptor_set, sampler, src_view);
|
||||||
|
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||||
|
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set,
|
||||||
|
nullptr);
|
||||||
|
BindBlitState(cmdbuf, layout, dst_region, src_region);
|
||||||
|
cmdbuf.Draw(3, 1, 0, 0);
|
||||||
|
});
|
||||||
|
scheduler.InvalidateState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlitImageHelper::BlitColor(const Framebuffer* dst_framebuffer, VkImageView src_image_view,
|
void BlitImageHelper::BlitColor(const Framebuffer* dst_framebuffer, VkImageView src_image_view,
|
||||||
@@ -702,29 +670,24 @@ void BlitImageHelper::BlitColor(const Framebuffer* dst_framebuffer, VkImageView
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlitImageHelper::BlitImpl(const Framebuffer* dst_framebuffer,
|
void BlitImageHelper::BlitColorMSAA(const Framebuffer* dst_framebuffer,
|
||||||
const ImageView& src_image_view, const Region2D& dst_region,
|
const ImageView& src_image_view, const Region2D& dst_region,
|
||||||
const Region2D& src_region, VkPipeline pipeline, VkSampler sampler,
|
const Region2D& src_region) {
|
||||||
VkImageView src_view, VkImageView src_stencil_view,
|
const BlitMSAAPipelineKey key{
|
||||||
bool blit_stencil) {
|
.renderpass = dst_framebuffer->RenderPass(),
|
||||||
VkPipelineLayout layout = *one_texture_pipeline_layout;
|
.samples = dst_framebuffer->Samples(),
|
||||||
if (blit_stencil) {
|
};
|
||||||
layout = *two_textures_pipeline_layout;
|
const VkPipelineLayout layout = *one_texture_pipeline_layout;
|
||||||
}
|
const VkSampler sampler = *nearest_sampler;
|
||||||
|
const VkPipeline pipeline = FindOrEmplaceBlitColorMSAAPipeline(key);
|
||||||
|
const VkImageView src_view = src_image_view.Handle(Shader::TextureType::Color2D);
|
||||||
|
|
||||||
RecordShaderReadBarrier(scheduler, src_image_view);
|
RecordShaderReadBarrier(scheduler, src_image_view);
|
||||||
scheduler.RequestRenderpass(dst_framebuffer);
|
scheduler.RequestRenderpass(dst_framebuffer);
|
||||||
scheduler.Record([this, dst_region, src_region, pipeline, layout, sampler, src_view,
|
scheduler.Record([this, dst_region, src_region, pipeline, layout, sampler,
|
||||||
src_stencil_view, blit_stencil](vk::CommandBuffer cmdbuf) {
|
src_view](vk::CommandBuffer cmdbuf) {
|
||||||
VkDescriptorSet descriptor_set = VK_NULL_HANDLE;
|
const VkDescriptorSet descriptor_set = one_texture_descriptor_allocator.Commit();
|
||||||
if (blit_stencil) {
|
|
||||||
descriptor_set = two_textures_descriptor_allocator.Commit();
|
|
||||||
UpdateTwoTexturesDescriptorSet(device, descriptor_set, sampler, src_view,
|
|
||||||
src_stencil_view);
|
|
||||||
} else {
|
|
||||||
descriptor_set = one_texture_descriptor_allocator.Commit();
|
|
||||||
UpdateOneTextureDescriptorSet(device, descriptor_set, sampler, src_view);
|
UpdateOneTextureDescriptorSet(device, descriptor_set, sampler, src_view);
|
||||||
}
|
|
||||||
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||||
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set,
|
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set,
|
||||||
nullptr);
|
nullptr);
|
||||||
@@ -734,56 +697,41 @@ void BlitImageHelper::BlitImpl(const Framebuffer* dst_framebuffer,
|
|||||||
scheduler.InvalidateState();
|
scheduler.InvalidateState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlitImageHelper::BlitColorMSAA(const Framebuffer* dst_framebuffer,
|
|
||||||
const ImageView& src_image_view, const Region2D& dst_region,
|
|
||||||
const Region2D& src_region) {
|
|
||||||
const BlitMSAAPipelineKey key{
|
|
||||||
.renderpass = dst_framebuffer->RenderPass(),
|
|
||||||
.samples = dst_framebuffer->Samples(),
|
|
||||||
};
|
|
||||||
BlitImpl(dst_framebuffer, src_image_view, dst_region, src_region,
|
|
||||||
FindOrEmplaceBlitColorMSAAPipeline(key), *nearest_sampler,
|
|
||||||
src_image_view.Handle(Shader::TextureType::Color2D), VK_NULL_HANDLE, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BlitImageHelper::BlitDepthStencilMSAA(const Framebuffer* dst_framebuffer,
|
|
||||||
ImageView& src_image_view, const Region2D& dst_region,
|
|
||||||
const Region2D& src_region) {
|
|
||||||
const bool blit_stencil =
|
|
||||||
dst_framebuffer->HasAspectStencilBit() && device.IsExtShaderStencilExportSupported();
|
|
||||||
const BlitMSAAPipelineKey key{
|
|
||||||
.renderpass = dst_framebuffer->RenderPass(),
|
|
||||||
.samples = dst_framebuffer->Samples(),
|
|
||||||
};
|
|
||||||
VkImageView src_stencil_view = VK_NULL_HANDLE;
|
|
||||||
if (blit_stencil) {
|
|
||||||
src_stencil_view = src_image_view.StencilView();
|
|
||||||
}
|
|
||||||
BlitImpl(dst_framebuffer, src_image_view, dst_region, src_region,
|
|
||||||
FindOrEmplaceBlitDepthStencilMSAAPipeline(key, blit_stencil), *nearest_sampler,
|
|
||||||
src_image_view.DepthView(), src_stencil_view, blit_stencil);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BlitImageHelper::BlitDepth(const Framebuffer* dst_framebuffer, ImageView& src_image_view,
|
|
||||||
const Region2D& dst_region, const Region2D& src_region) {
|
|
||||||
BlitImpl(dst_framebuffer, src_image_view, dst_region, src_region,
|
|
||||||
FindOrEmplaceBlitDepthPipeline(dst_framebuffer->RenderPass()), *nearest_sampler,
|
|
||||||
src_image_view.DepthView(), VK_NULL_HANDLE, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BlitImageHelper::ResolveDepthStencil(const Framebuffer* dst_framebuffer,
|
void BlitImageHelper::ResolveDepthStencil(const Framebuffer* dst_framebuffer,
|
||||||
ImageView& src_image_view, const Region2D& dst_region,
|
ImageView& src_image_view, const Region2D& dst_region,
|
||||||
const Region2D& src_region) {
|
const Region2D& src_region) {
|
||||||
const bool resolve_stencil =
|
const bool resolve_stencil =
|
||||||
dst_framebuffer->HasAspectStencilBit() && device.IsExtShaderStencilExportSupported();
|
dst_framebuffer->HasAspectStencilBit() && device.IsExtShaderStencilExportSupported();
|
||||||
VkImageView src_stencil_view = VK_NULL_HANDLE;
|
const VkPipeline pipeline =
|
||||||
|
FindOrEmplaceResolveDepthStencilPipeline(dst_framebuffer->RenderPass(), resolve_stencil);
|
||||||
|
const VkPipelineLayout layout =
|
||||||
|
resolve_stencil ? *two_textures_pipeline_layout : *one_texture_pipeline_layout;
|
||||||
|
const VkSampler sampler = *nearest_sampler;
|
||||||
|
const VkImageView src_depth_view = src_image_view.DepthView();
|
||||||
|
const VkImageView src_stencil_view =
|
||||||
|
resolve_stencil ? src_image_view.StencilView() : VK_NULL_HANDLE;
|
||||||
|
|
||||||
|
RecordShaderReadBarrier(scheduler, src_image_view);
|
||||||
|
scheduler.RequestRenderpass(dst_framebuffer);
|
||||||
|
scheduler.Record([this, dst_region, src_region, pipeline, layout, sampler, src_depth_view,
|
||||||
|
src_stencil_view, resolve_stencil](vk::CommandBuffer cmdbuf) {
|
||||||
if (resolve_stencil) {
|
if (resolve_stencil) {
|
||||||
src_stencil_view = src_image_view.StencilView();
|
const VkDescriptorSet descriptor_set = two_textures_descriptor_allocator.Commit();
|
||||||
|
UpdateTwoTexturesDescriptorSet(device, descriptor_set, sampler, src_depth_view,
|
||||||
|
src_stencil_view);
|
||||||
|
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set,
|
||||||
|
nullptr);
|
||||||
|
} else {
|
||||||
|
const VkDescriptorSet descriptor_set = one_texture_descriptor_allocator.Commit();
|
||||||
|
UpdateOneTextureDescriptorSet(device, descriptor_set, sampler, src_depth_view);
|
||||||
|
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set,
|
||||||
|
nullptr);
|
||||||
}
|
}
|
||||||
BlitImpl(dst_framebuffer, src_image_view, dst_region, src_region,
|
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||||
FindOrEmplaceResolveDepthStencilPipeline(dst_framebuffer->RenderPass(),
|
BindBlitState(cmdbuf, layout, dst_region, src_region);
|
||||||
resolve_stencil),
|
cmdbuf.Draw(3, 1, 0, 0);
|
||||||
*nearest_sampler, src_image_view.DepthView(), src_stencil_view, resolve_stencil);
|
});
|
||||||
|
scheduler.InvalidateState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlitImageHelper::BlitDepthStencil(const Framebuffer* dst_framebuffer,
|
void BlitImageHelper::BlitDepthStencil(const Framebuffer* dst_framebuffer,
|
||||||
@@ -791,23 +739,35 @@ void BlitImageHelper::BlitDepthStencil(const Framebuffer* dst_framebuffer,
|
|||||||
const Region2D& dst_region, const Region2D& src_region,
|
const Region2D& dst_region, const Region2D& src_region,
|
||||||
Tegra::Engines::Fermi2D::Filter filter,
|
Tegra::Engines::Fermi2D::Filter filter,
|
||||||
Tegra::Engines::Fermi2D::Operation operation) {
|
Tegra::Engines::Fermi2D::Operation operation) {
|
||||||
|
if (!device.IsExtShaderStencilExportSupported()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
ASSERT(filter == Tegra::Engines::Fermi2D::Filter::Point);
|
ASSERT(filter == Tegra::Engines::Fermi2D::Filter::Point);
|
||||||
ASSERT(operation == Tegra::Engines::Fermi2D::Operation::SrcCopy);
|
ASSERT(operation == Tegra::Engines::Fermi2D::Operation::SrcCopy);
|
||||||
const bool blit_stencil = device.IsExtShaderStencilExportSupported();
|
|
||||||
const BlitImagePipelineKey key{
|
const BlitImagePipelineKey key{
|
||||||
.renderpass = dst_framebuffer->RenderPass(),
|
.renderpass = dst_framebuffer->RenderPass(),
|
||||||
.operation = operation,
|
.operation = operation,
|
||||||
};
|
};
|
||||||
VkPipeline pipeline{};
|
const VkPipelineLayout layout = *two_textures_pipeline_layout;
|
||||||
VkImageView src_stencil_view = VK_NULL_HANDLE;
|
const VkSampler sampler = *nearest_sampler;
|
||||||
if (blit_stencil) {
|
const VkPipeline pipeline = FindOrEmplaceDepthStencilPipeline(key);
|
||||||
pipeline = FindOrEmplaceDepthStencilPipeline(key);
|
const VkImageView src_depth_view = src_image_view.DepthView();
|
||||||
src_stencil_view = src_image_view.StencilView();
|
const VkImageView src_stencil_view = src_image_view.StencilView();
|
||||||
} else {
|
|
||||||
pipeline = FindOrEmplaceBlitDepthPipeline(key.renderpass);
|
RecordShaderReadBarrier(scheduler, src_image_view);
|
||||||
}
|
scheduler.RequestRenderpass(dst_framebuffer);
|
||||||
BlitImpl(dst_framebuffer, src_image_view, dst_region, src_region, pipeline, *nearest_sampler,
|
scheduler.Record([dst_region, src_region, pipeline, layout, sampler, src_depth_view,
|
||||||
src_image_view.DepthView(), src_stencil_view, blit_stencil);
|
src_stencil_view, this](vk::CommandBuffer cmdbuf) {
|
||||||
|
const VkDescriptorSet descriptor_set = two_textures_descriptor_allocator.Commit();
|
||||||
|
UpdateTwoTexturesDescriptorSet(device, descriptor_set, sampler, src_depth_view,
|
||||||
|
src_stencil_view);
|
||||||
|
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||||
|
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set,
|
||||||
|
nullptr);
|
||||||
|
BindBlitState(cmdbuf, layout, dst_region, src_region);
|
||||||
|
cmdbuf.Draw(3, 1, 0, 0);
|
||||||
|
});
|
||||||
|
scheduler.InvalidateState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlitImageHelper::ConvertD32ToR32(const Framebuffer* dst_framebuffer,
|
void BlitImageHelper::ConvertD32ToR32(const Framebuffer* dst_framebuffer,
|
||||||
@@ -922,36 +882,48 @@ void BlitImageHelper::ClearDepthStencil(const Framebuffer* dst_framebuffer, bool
|
|||||||
scheduler.InvalidateState();
|
scheduler.InvalidateState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlitImageHelper::CopyMSAAImpl(VkRenderPass renderpass, VkPipeline pipeline,
|
void BlitImageHelper::CopyMSAA(RenderPassCache& render_pass_cache, VkImage dst_image,
|
||||||
VkPipelineLayout layout, VkImage dst_image,
|
VideoCore::Surface::PixelFormat dst_format, VkImage src_image,
|
||||||
VkFormat dst_vk_format, VkImage src_image,
|
VideoCore::Surface::PixelFormat src_format, u32 num_samples,
|
||||||
VkFormat src_vk_format, s32 scale_x, s32 scale_y,
|
|
||||||
std::span<const VideoCommon::ImageCopy> copies,
|
std::span<const VideoCommon::ImageCopy> copies,
|
||||||
const MSAACopyAspectInfo& aspect_info, bool copy_stencil) {
|
bool msaa_to_non_msaa) {
|
||||||
while (!msaa_copy_resources.empty() && scheduler.IsFree(msaa_copy_resources.front().tick)) {
|
while (!msaa_copy_resources.empty() && scheduler.IsFree(msaa_copy_resources.front().tick)) {
|
||||||
msaa_copy_resources.pop_front();
|
msaa_copy_resources.pop_front();
|
||||||
}
|
}
|
||||||
|
const auto [samples_x, samples_y] = VideoCommon::SamplesLog2(static_cast<int>(num_samples));
|
||||||
|
const s32 scale_x = 1 << samples_x;
|
||||||
|
const s32 scale_y = 1 << samples_y;
|
||||||
|
const VkSampleCountFlagBits samples =
|
||||||
|
msaa_to_non_msaa ? VK_SAMPLE_COUNT_1_BIT : SampleCountFlag(num_samples);
|
||||||
|
RenderPassKey renderpass_key{};
|
||||||
|
renderpass_key.color_formats.fill(VideoCore::Surface::PixelFormat::Invalid);
|
||||||
|
renderpass_key.color_formats[0] = dst_format;
|
||||||
|
renderpass_key.depth_format = VideoCore::Surface::PixelFormat::Invalid;
|
||||||
|
renderpass_key.samples = samples;
|
||||||
|
const VkRenderPass renderpass = render_pass_cache.Get(renderpass_key);
|
||||||
|
const MSAACopyPipelineKey key{
|
||||||
|
.renderpass = renderpass,
|
||||||
|
.samples = samples,
|
||||||
|
.msaa_to_non_msaa = msaa_to_non_msaa,
|
||||||
|
};
|
||||||
|
const VkPipeline pipeline = FindOrEmplaceMSAACopyPipeline(key);
|
||||||
|
const VkPipelineLayout layout = *msaa_copy_pipeline_layout;
|
||||||
const VkSampler sampler = *nearest_sampler;
|
const VkSampler sampler = *nearest_sampler;
|
||||||
|
const VkFormat src_vk_format =
|
||||||
|
MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, true, src_format).format;
|
||||||
|
const VkFormat dst_vk_format =
|
||||||
|
MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, true, dst_format).format;
|
||||||
for (const VideoCommon::ImageCopy& copy : copies) {
|
for (const VideoCommon::ImageCopy& copy : copies) {
|
||||||
const s32 num_layers = (std::min)(copy.src_subresource.num_layers,
|
ASSERT(copy.src_subresource.base_layer == 0);
|
||||||
copy.dst_subresource.num_layers);
|
ASSERT(copy.src_subresource.num_layers == 1);
|
||||||
for (s32 layer = 0; layer < num_layers; ++layer) {
|
ASSERT(copy.dst_subresource.base_layer == 0);
|
||||||
const u32 src_level = static_cast<u32>(copy.src_subresource.base_level);
|
ASSERT(copy.dst_subresource.num_layers == 1);
|
||||||
const u32 src_layer = static_cast<u32>(copy.src_subresource.base_layer + layer);
|
|
||||||
vk::ImageView src_view =
|
vk::ImageView src_view =
|
||||||
MakeMSAACopyView(device.GetLogical(), src_image, src_vk_format, src_level,
|
MakeMSAACopyView(device.GetLogical(), src_image, src_vk_format,
|
||||||
src_layer, aspect_info.src_view_aspect);
|
static_cast<u32>(copy.src_subresource.base_level));
|
||||||
vk::ImageView src_stencil_view;
|
|
||||||
if (copy_stencil) {
|
|
||||||
src_stencil_view =
|
|
||||||
MakeMSAACopyView(device.GetLogical(), src_image, src_vk_format, src_level,
|
|
||||||
src_layer, VK_IMAGE_ASPECT_STENCIL_BIT);
|
|
||||||
}
|
|
||||||
vk::ImageView dst_view =
|
vk::ImageView dst_view =
|
||||||
MakeMSAACopyView(device.GetLogical(), dst_image, dst_vk_format,
|
MakeMSAACopyView(device.GetLogical(), dst_image, dst_vk_format,
|
||||||
static_cast<u32>(copy.dst_subresource.base_level),
|
static_cast<u32>(copy.dst_subresource.base_level));
|
||||||
static_cast<u32>(copy.dst_subresource.base_layer + layer),
|
|
||||||
aspect_info.attachment_aspect);
|
|
||||||
const VkOffset2D dst_offset{copy.dst_offset.x, copy.dst_offset.y};
|
const VkOffset2D dst_offset{copy.dst_offset.x, copy.dst_offset.y};
|
||||||
const VkExtent2D dst_extent{copy.extent.width, copy.extent.height};
|
const VkExtent2D dst_extent{copy.extent.width, copy.extent.height};
|
||||||
const VkRect2D render_area{
|
const VkRect2D render_area{
|
||||||
@@ -974,17 +946,13 @@ void BlitImageHelper::CopyMSAAImpl(VkRenderPass renderpass, VkPipeline pipeline,
|
|||||||
.src_offset = {copy.src_offset.x, copy.src_offset.y},
|
.src_offset = {copy.src_offset.x, copy.src_offset.y},
|
||||||
.scale = {scale_x, scale_y},
|
.scale = {scale_x, scale_y},
|
||||||
};
|
};
|
||||||
VkImageView src_stencil_handle = VK_NULL_HANDLE;
|
|
||||||
if (copy_stencil) {
|
|
||||||
src_stencil_handle = *src_stencil_view;
|
|
||||||
}
|
|
||||||
scheduler.RequestOutsideRenderPassOperationContext();
|
scheduler.RequestOutsideRenderPassOperationContext();
|
||||||
scheduler.Record([this, pipeline, layout, sampler, renderpass,
|
scheduler.Record([this, pipeline, layout, sampler, renderpass,
|
||||||
framebuffer_handle = *framebuffer, src_view_handle = *src_view,
|
framebuffer_handle = *framebuffer, src_view_handle = *src_view,
|
||||||
src_stencil_handle, src = src_image, dst = dst_image, render_area,
|
src = src_image, dst = dst_image, render_area,
|
||||||
aspect_info, push_constants](vk::CommandBuffer cmdbuf) {
|
push_constants](vk::CommandBuffer cmdbuf) {
|
||||||
const VkImageSubresourceRange barrier_range{
|
constexpr VkImageSubresourceRange color_range{
|
||||||
.aspectMask = aspect_info.barrier_aspect,
|
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||||
.baseMipLevel = 0,
|
.baseMipLevel = 0,
|
||||||
.levelCount = VK_REMAINING_MIP_LEVELS,
|
.levelCount = VK_REMAINING_MIP_LEVELS,
|
||||||
.baseArrayLayer = 0,
|
.baseArrayLayer = 0,
|
||||||
@@ -994,30 +962,38 @@ void BlitImageHelper::CopyMSAAImpl(VkRenderPass renderpass, VkPipeline pipeline,
|
|||||||
VkImageMemoryBarrier{
|
VkImageMemoryBarrier{
|
||||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
.srcAccessMask = aspect_info.pre_src_access,
|
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
|
||||||
.dstAccessMask = aspect_info.pre_src_dst_access,
|
VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||||
|
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
|
||||||
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
|
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||||
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
.image = src,
|
.image = src,
|
||||||
.subresourceRange = barrier_range,
|
.subresourceRange = color_range,
|
||||||
},
|
},
|
||||||
VkImageMemoryBarrier{
|
VkImageMemoryBarrier{
|
||||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
.srcAccessMask = aspect_info.pre_src_access,
|
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
|
||||||
.dstAccessMask = aspect_info.pre_dst_dst_access,
|
VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||||
|
.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
|
||||||
|
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
||||||
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
|
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||||
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
.image = dst,
|
.image = dst,
|
||||||
.subresourceRange = barrier_range,
|
.subresourceRange = color_range,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
cmdbuf.PipelineBarrier(aspect_info.pre_src_stages, aspect_info.pre_dst_stages, 0,
|
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
|
||||||
nullptr, nullptr, pre_barriers);
|
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
|
||||||
|
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
|
||||||
|
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||||
|
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
|
||||||
|
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||||
|
0, nullptr, nullptr, pre_barriers);
|
||||||
const VkRenderPassBeginInfo renderpass_bi{
|
const VkRenderPassBeginInfo renderpass_bi{
|
||||||
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
@@ -1028,15 +1004,8 @@ void BlitImageHelper::CopyMSAAImpl(VkRenderPass renderpass, VkPipeline pipeline,
|
|||||||
.pClearValues = nullptr,
|
.pClearValues = nullptr,
|
||||||
};
|
};
|
||||||
cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE);
|
cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE);
|
||||||
VkDescriptorSet descriptor_set = VK_NULL_HANDLE;
|
const VkDescriptorSet descriptor_set = one_texture_descriptor_allocator.Commit();
|
||||||
if (src_stencil_handle != VK_NULL_HANDLE) {
|
|
||||||
descriptor_set = two_textures_descriptor_allocator.Commit();
|
|
||||||
UpdateTwoTexturesDescriptorSet(device, descriptor_set, sampler, src_view_handle,
|
|
||||||
src_stencil_handle);
|
|
||||||
} else {
|
|
||||||
descriptor_set = one_texture_descriptor_allocator.Commit();
|
|
||||||
UpdateOneTextureDescriptorSet(device, descriptor_set, sampler, src_view_handle);
|
UpdateOneTextureDescriptorSet(device, descriptor_set, sampler, src_view_handle);
|
||||||
}
|
|
||||||
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||||
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set,
|
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set,
|
||||||
nullptr);
|
nullptr);
|
||||||
@@ -1056,17 +1025,20 @@ void BlitImageHelper::CopyMSAAImpl(VkRenderPass renderpass, VkPipeline pipeline,
|
|||||||
const VkImageMemoryBarrier post_barrier{
|
const VkImageMemoryBarrier post_barrier{
|
||||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
.srcAccessMask = aspect_info.post_src_access,
|
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
||||||
.dstAccessMask = aspect_info.post_dst_access,
|
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT,
|
||||||
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
|
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||||
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
.image = dst,
|
.image = dst,
|
||||||
.subresourceRange = barrier_range,
|
.subresourceRange = color_range,
|
||||||
};
|
};
|
||||||
cmdbuf.PipelineBarrier(aspect_info.post_src_stages, aspect_info.post_dst_stages, 0,
|
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||||
post_barrier);
|
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
|
||||||
|
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
|
||||||
|
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||||
|
0, post_barrier);
|
||||||
});
|
});
|
||||||
msaa_copy_resources.push_back(MSAACopyResources{
|
msaa_copy_resources.push_back(MSAACopyResources{
|
||||||
.tick = scheduler.CurrentTick(),
|
.tick = scheduler.CurrentTick(),
|
||||||
@@ -1074,72 +1046,10 @@ void BlitImageHelper::CopyMSAAImpl(VkRenderPass renderpass, VkPipeline pipeline,
|
|||||||
.dst_view = std::move(dst_view),
|
.dst_view = std::move(dst_view),
|
||||||
.framebuffer = std::move(framebuffer),
|
.framebuffer = std::move(framebuffer),
|
||||||
});
|
});
|
||||||
if (copy_stencil) {
|
|
||||||
msaa_copy_resources.push_back(MSAACopyResources{
|
|
||||||
.tick = scheduler.CurrentTick(),
|
|
||||||
.src_view = std::move(src_stencil_view),
|
|
||||||
.dst_view = vk::ImageView{},
|
|
||||||
.framebuffer = vk::Framebuffer{},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
scheduler.InvalidateState();
|
scheduler.InvalidateState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlitImageHelper::CopyMSAA(RenderPassCache& render_pass_cache, VkImage dst_image,
|
|
||||||
VideoCore::Surface::PixelFormat dst_format, VkImage src_image,
|
|
||||||
VideoCore::Surface::PixelFormat src_format, u32 num_samples,
|
|
||||||
std::span<const VideoCommon::ImageCopy> copies,
|
|
||||||
bool msaa_to_non_msaa) {
|
|
||||||
const auto [samples_x, samples_y] = VideoCommon::SamplesLog2(static_cast<int>(num_samples));
|
|
||||||
const s32 scale_x = 1 << samples_x;
|
|
||||||
const s32 scale_y = 1 << samples_y;
|
|
||||||
VkSampleCountFlagBits samples = SampleCountFlag(num_samples);
|
|
||||||
if (msaa_to_non_msaa) {
|
|
||||||
samples = VK_SAMPLE_COUNT_1_BIT;
|
|
||||||
}
|
|
||||||
RenderPassKey renderpass_key{};
|
|
||||||
renderpass_key.color_formats.fill(VideoCore::Surface::PixelFormat::Invalid);
|
|
||||||
renderpass_key.color_formats[0] = dst_format;
|
|
||||||
renderpass_key.depth_format = VideoCore::Surface::PixelFormat::Invalid;
|
|
||||||
renderpass_key.samples = samples;
|
|
||||||
const VkRenderPass renderpass = render_pass_cache.Get(renderpass_key);
|
|
||||||
const MSAACopyPipelineKey key{
|
|
||||||
.renderpass = renderpass,
|
|
||||||
.samples = samples,
|
|
||||||
.msaa_to_non_msaa = msaa_to_non_msaa,
|
|
||||||
.format_class = FormatClass(dst_format),
|
|
||||||
};
|
|
||||||
const MSAACopyAspectInfo aspect_info{
|
|
||||||
.src_view_aspect = VK_IMAGE_ASPECT_COLOR_BIT,
|
|
||||||
.attachment_aspect = VK_IMAGE_ASPECT_COLOR_BIT,
|
|
||||||
.barrier_aspect = VK_IMAGE_ASPECT_COLOR_BIT,
|
|
||||||
.pre_src_access = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_WRITE_BIT |
|
|
||||||
VK_ACCESS_TRANSFER_WRITE_BIT,
|
|
||||||
.pre_src_dst_access = VK_ACCESS_SHADER_READ_BIT,
|
|
||||||
.pre_dst_dst_access =
|
|
||||||
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
|
||||||
.pre_src_stages = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
|
|
||||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
|
|
||||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT,
|
|
||||||
.pre_dst_stages =
|
|
||||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
|
||||||
.post_src_access = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
|
||||||
.post_dst_access = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT,
|
|
||||||
.post_src_stages = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
|
||||||
.post_dst_stages = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
|
|
||||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT,
|
|
||||||
};
|
|
||||||
const VkFormat src_vk_format =
|
|
||||||
MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, true, src_format).format;
|
|
||||||
const VkFormat dst_vk_format =
|
|
||||||
MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, true, dst_format).format;
|
|
||||||
CopyMSAAImpl(renderpass, FindOrEmplaceMSAACopyPipeline(key), *msaa_copy_pipeline_layout,
|
|
||||||
dst_image, dst_vk_format, src_image, src_vk_format, scale_x, scale_y, copies,
|
|
||||||
aspect_info, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BlitImageHelper::Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer,
|
void BlitImageHelper::Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer,
|
||||||
const ImageView& src_image_view) {
|
const ImageView& src_image_view) {
|
||||||
const VkPipelineLayout layout = *one_texture_pipeline_layout;
|
const VkPipelineLayout layout = *one_texture_pipeline_layout;
|
||||||
@@ -1469,87 +1379,6 @@ VkPipeline BlitImageHelper::FindOrEmplaceBlitColorMSAAPipeline(const BlitMSAAPip
|
|||||||
return *blit_msaa_color_pipelines.back();
|
return *blit_msaa_color_pipelines.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
VkPipeline BlitImageHelper::FindOrEmplaceBlitDepthStencilMSAAPipeline(
|
|
||||||
const BlitMSAAPipelineKey& key, bool blit_stencil) {
|
|
||||||
auto& keys = blit_stencil ? blit_msaa_depth_stencil_keys : blit_msaa_depth_keys;
|
|
||||||
auto& pipelines = blit_stencil ? blit_msaa_depth_stencil_pipelines : blit_msaa_depth_pipelines;
|
|
||||||
const auto it = std::ranges::find(keys, key);
|
|
||||||
if (it != keys.end()) {
|
|
||||||
return *pipelines[std::distance(keys.begin(), it)];
|
|
||||||
}
|
|
||||||
keys.push_back(key);
|
|
||||||
const std::array stages =
|
|
||||||
MakeStages(*full_screen_vert,
|
|
||||||
blit_stencil ? *blit_depth_stencil_msaa_frag : *blit_depth_msaa_frag);
|
|
||||||
const VkPipelineMultisampleStateCreateInfo multisample_ci{
|
|
||||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
|
|
||||||
.pNext = nullptr,
|
|
||||||
.flags = 0,
|
|
||||||
.rasterizationSamples = key.samples,
|
|
||||||
.sampleShadingEnable = VK_TRUE,
|
|
||||||
.minSampleShading = 1.0f,
|
|
||||||
.pSampleMask = nullptr,
|
|
||||||
.alphaToCoverageEnable = VK_FALSE,
|
|
||||||
.alphaToOneEnable = VK_FALSE,
|
|
||||||
};
|
|
||||||
const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device);
|
|
||||||
pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({
|
|
||||||
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
|
||||||
.pNext = nullptr,
|
|
||||||
.flags = 0,
|
|
||||||
.stageCount = static_cast<u32>(stages.size()),
|
|
||||||
.pStages = stages.data(),
|
|
||||||
.pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
|
|
||||||
.pInputAssemblyState = &input_assembly_ci,
|
|
||||||
.pTessellationState = nullptr,
|
|
||||||
.pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO,
|
|
||||||
.pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
|
|
||||||
.pMultisampleState = &multisample_ci,
|
|
||||||
.pDepthStencilState = blit_stencil ? &PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO
|
|
||||||
: &PIPELINE_DEPTH_ONLY_STATE_CREATE_INFO,
|
|
||||||
.pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO,
|
|
||||||
.pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO,
|
|
||||||
.layout = blit_stencil ? *two_textures_pipeline_layout : *one_texture_pipeline_layout,
|
|
||||||
.renderPass = key.renderpass,
|
|
||||||
.subpass = 0,
|
|
||||||
.basePipelineHandle = VK_NULL_HANDLE,
|
|
||||||
.basePipelineIndex = 0,
|
|
||||||
}));
|
|
||||||
return *pipelines.back();
|
|
||||||
}
|
|
||||||
|
|
||||||
VkPipeline BlitImageHelper::FindOrEmplaceBlitDepthPipeline(VkRenderPass renderpass) {
|
|
||||||
const auto it = std::ranges::find(blit_depth_keys, renderpass);
|
|
||||||
if (it != blit_depth_keys.end()) {
|
|
||||||
return *blit_depth_pipelines[std::distance(blit_depth_keys.begin(), it)];
|
|
||||||
}
|
|
||||||
blit_depth_keys.push_back(renderpass);
|
|
||||||
const std::array stages = MakeStages(*full_screen_vert, *blit_depth_frag);
|
|
||||||
const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device);
|
|
||||||
blit_depth_pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({
|
|
||||||
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
|
||||||
.pNext = nullptr,
|
|
||||||
.flags = 0,
|
|
||||||
.stageCount = static_cast<u32>(stages.size()),
|
|
||||||
.pStages = stages.data(),
|
|
||||||
.pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
|
|
||||||
.pInputAssemblyState = &input_assembly_ci,
|
|
||||||
.pTessellationState = nullptr,
|
|
||||||
.pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO,
|
|
||||||
.pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
|
|
||||||
.pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
|
|
||||||
.pDepthStencilState = &PIPELINE_DEPTH_ONLY_STATE_CREATE_INFO,
|
|
||||||
.pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO,
|
|
||||||
.pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO,
|
|
||||||
.layout = *one_texture_pipeline_layout,
|
|
||||||
.renderPass = renderpass,
|
|
||||||
.subpass = 0,
|
|
||||||
.basePipelineHandle = VK_NULL_HANDLE,
|
|
||||||
.basePipelineIndex = 0,
|
|
||||||
}));
|
|
||||||
return *blit_depth_pipelines.back();
|
|
||||||
}
|
|
||||||
|
|
||||||
VkPipeline BlitImageHelper::FindOrEmplaceResolveDepthStencilPipeline(VkRenderPass renderpass,
|
VkPipeline BlitImageHelper::FindOrEmplaceResolveDepthStencilPipeline(VkRenderPass renderpass,
|
||||||
bool resolve_stencil) {
|
bool resolve_stencil) {
|
||||||
auto& keys = resolve_stencil ? resolve_depth_stencil_keys : resolve_depth_keys;
|
auto& keys = resolve_stencil ? resolve_depth_stencil_keys : resolve_depth_keys;
|
||||||
@@ -1588,83 +1417,15 @@ VkPipeline BlitImageHelper::FindOrEmplaceResolveDepthStencilPipeline(VkRenderPas
|
|||||||
return *pipelines.back();
|
return *pipelines.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlitImageHelper::CopyMSAADepth(RenderPassCache& render_pass_cache, VkImage dst_image,
|
|
||||||
VideoCore::Surface::PixelFormat dst_format, VkImage src_image,
|
|
||||||
VideoCore::Surface::PixelFormat src_format, u32 num_samples,
|
|
||||||
std::span<const VideoCommon::ImageCopy> copies,
|
|
||||||
bool copy_stencil, bool msaa_to_non_msaa) {
|
|
||||||
const auto [samples_x, samples_y] = VideoCommon::SamplesLog2(static_cast<int>(num_samples));
|
|
||||||
const s32 scale_x = 1 << samples_x;
|
|
||||||
const s32 scale_y = 1 << samples_y;
|
|
||||||
VkSampleCountFlagBits samples = SampleCountFlag(num_samples);
|
|
||||||
if (msaa_to_non_msaa) {
|
|
||||||
samples = VK_SAMPLE_COUNT_1_BIT;
|
|
||||||
}
|
|
||||||
RenderPassKey renderpass_key{};
|
|
||||||
renderpass_key.color_formats.fill(VideoCore::Surface::PixelFormat::Invalid);
|
|
||||||
renderpass_key.depth_format = dst_format;
|
|
||||||
renderpass_key.samples = samples;
|
|
||||||
const VkRenderPass renderpass = render_pass_cache.Get(renderpass_key);
|
|
||||||
const MSAACopyPipelineKey key{
|
|
||||||
.renderpass = renderpass,
|
|
||||||
.samples = samples,
|
|
||||||
.msaa_to_non_msaa = msaa_to_non_msaa,
|
|
||||||
.format_class = MSAACopyFormatClass::Float,
|
|
||||||
};
|
|
||||||
VkImageAspectFlags attachment_aspect = VK_IMAGE_ASPECT_DEPTH_BIT;
|
|
||||||
if (VideoCore::Surface::GetFormatType(dst_format) ==
|
|
||||||
VideoCore::Surface::SurfaceType::DepthStencil) {
|
|
||||||
attachment_aspect |= VK_IMAGE_ASPECT_STENCIL_BIT;
|
|
||||||
}
|
|
||||||
VkPipelineLayout layout = *msaa_copy_pipeline_layout;
|
|
||||||
if (copy_stencil) {
|
|
||||||
layout = *msaa_copy_depth_stencil_pipeline_layout;
|
|
||||||
}
|
|
||||||
const MSAACopyAspectInfo aspect_info{
|
|
||||||
.src_view_aspect = VK_IMAGE_ASPECT_DEPTH_BIT,
|
|
||||||
.attachment_aspect = attachment_aspect,
|
|
||||||
.barrier_aspect = attachment_aspect,
|
|
||||||
.pre_src_access =
|
|
||||||
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT,
|
|
||||||
.pre_src_dst_access = VK_ACCESS_SHADER_READ_BIT,
|
|
||||||
.pre_dst_dst_access = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
|
|
||||||
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
|
|
||||||
.pre_src_stages = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
|
|
||||||
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
|
|
||||||
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
|
||||||
.pre_dst_stages =
|
|
||||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT,
|
|
||||||
.post_src_access = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
|
|
||||||
.post_dst_access = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT |
|
|
||||||
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT,
|
|
||||||
.post_src_stages = VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
|
|
||||||
.post_dst_stages = vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER,
|
|
||||||
};
|
|
||||||
const VkFormat src_vk_format =
|
|
||||||
MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, true, src_format).format;
|
|
||||||
const VkFormat dst_vk_format =
|
|
||||||
MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, true, dst_format).format;
|
|
||||||
CopyMSAAImpl(renderpass, FindOrEmplaceMSAACopyDepthPipeline(key, copy_stencil), layout,
|
|
||||||
dst_image, dst_vk_format, src_image, src_vk_format, scale_x, scale_y, copies,
|
|
||||||
aspect_info, copy_stencil);
|
|
||||||
}
|
|
||||||
|
|
||||||
VkPipeline BlitImageHelper::FindOrEmplaceMSAACopyPipeline(const MSAACopyPipelineKey& key) {
|
VkPipeline BlitImageHelper::FindOrEmplaceMSAACopyPipeline(const MSAACopyPipelineKey& key) {
|
||||||
const auto it = std::ranges::find(msaa_copy_keys, key);
|
const auto it = std::ranges::find(msaa_copy_keys, key);
|
||||||
if (it != msaa_copy_keys.end()) {
|
if (it != msaa_copy_keys.end()) {
|
||||||
return *msaa_copy_pipelines[std::distance(msaa_copy_keys.begin(), it)];
|
return *msaa_copy_pipelines[std::distance(msaa_copy_keys.begin(), it)];
|
||||||
}
|
}
|
||||||
msaa_copy_keys.push_back(key);
|
msaa_copy_keys.push_back(key);
|
||||||
VkShaderModule frag_module = key.msaa_to_non_msaa ? *convert_msaa_to_non_msaa_frag
|
const std::array stages = MakeStages(*clear_color_vert, key.msaa_to_non_msaa
|
||||||
: *convert_non_msaa_to_msaa_frag;
|
? *convert_msaa_to_non_msaa_frag
|
||||||
if (key.format_class == MSAACopyFormatClass::SignedInteger) {
|
: *convert_non_msaa_to_msaa_frag);
|
||||||
frag_module = key.msaa_to_non_msaa ? *convert_msaa_to_non_msaa_sint_frag
|
|
||||||
: *convert_non_msaa_to_msaa_sint_frag;
|
|
||||||
} else if (key.format_class == MSAACopyFormatClass::UnsignedInteger) {
|
|
||||||
frag_module = key.msaa_to_non_msaa ? *convert_msaa_to_non_msaa_uint_frag
|
|
||||||
: *convert_non_msaa_to_msaa_uint_frag;
|
|
||||||
}
|
|
||||||
const std::array stages = MakeStages(*clear_color_vert, frag_module);
|
|
||||||
const VkPipelineMultisampleStateCreateInfo multisample_ci{
|
const VkPipelineMultisampleStateCreateInfo multisample_ci{
|
||||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
@@ -1701,85 +1462,6 @@ VkPipeline BlitImageHelper::FindOrEmplaceMSAACopyPipeline(const MSAACopyPipeline
|
|||||||
return *msaa_copy_pipelines.back();
|
return *msaa_copy_pipelines.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
VkPipeline BlitImageHelper::FindOrEmplaceMSAACopyDepthPipeline(const MSAACopyPipelineKey& key,
|
|
||||||
bool copy_stencil) {
|
|
||||||
auto& keys = copy_stencil ? msaa_copy_depth_stencil_keys : msaa_copy_depth_keys;
|
|
||||||
auto& pipelines = copy_stencil ? msaa_copy_depth_stencil_pipelines : msaa_copy_depth_pipelines;
|
|
||||||
const auto it = std::ranges::find(keys, key);
|
|
||||||
if (it != keys.end()) {
|
|
||||||
return *pipelines[std::distance(keys.begin(), it)];
|
|
||||||
}
|
|
||||||
keys.push_back(key);
|
|
||||||
VkShaderModule frag_module;
|
|
||||||
if (key.msaa_to_non_msaa) {
|
|
||||||
frag_module = copy_stencil ? *convert_msaa_to_non_msaa_depth_stencil_frag
|
|
||||||
: *convert_msaa_to_non_msaa_depth_frag;
|
|
||||||
} else {
|
|
||||||
frag_module = copy_stencil ? *convert_non_msaa_to_msaa_depth_stencil_frag
|
|
||||||
: *convert_non_msaa_to_msaa_depth_frag;
|
|
||||||
}
|
|
||||||
const std::array stages = MakeStages(*clear_color_vert, frag_module);
|
|
||||||
const VkPipelineMultisampleStateCreateInfo multisample_ci{
|
|
||||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
|
|
||||||
.pNext = nullptr,
|
|
||||||
.flags = 0,
|
|
||||||
.rasterizationSamples = key.samples,
|
|
||||||
.sampleShadingEnable = key.msaa_to_non_msaa ? VK_FALSE : VK_TRUE,
|
|
||||||
.minSampleShading = key.msaa_to_non_msaa ? 0.0f : 1.0f,
|
|
||||||
.pSampleMask = nullptr,
|
|
||||||
.alphaToCoverageEnable = VK_FALSE,
|
|
||||||
.alphaToOneEnable = VK_FALSE,
|
|
||||||
};
|
|
||||||
static constexpr VkStencilOpState REPLACE_STENCIL_OP{
|
|
||||||
.failOp = VK_STENCIL_OP_REPLACE,
|
|
||||||
.passOp = VK_STENCIL_OP_REPLACE,
|
|
||||||
.depthFailOp = VK_STENCIL_OP_REPLACE,
|
|
||||||
.compareOp = VK_COMPARE_OP_ALWAYS,
|
|
||||||
.compareMask = 0xFF,
|
|
||||||
.writeMask = 0xFF,
|
|
||||||
.reference = 0,
|
|
||||||
};
|
|
||||||
const VkPipelineDepthStencilStateCreateInfo depth_stencil_ci{
|
|
||||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
|
|
||||||
.pNext = nullptr,
|
|
||||||
.flags = 0,
|
|
||||||
.depthTestEnable = VK_TRUE,
|
|
||||||
.depthWriteEnable = VK_TRUE,
|
|
||||||
.depthCompareOp = VK_COMPARE_OP_ALWAYS,
|
|
||||||
.depthBoundsTestEnable = VK_FALSE,
|
|
||||||
.stencilTestEnable = copy_stencil ? VK_TRUE : VK_FALSE,
|
|
||||||
.front = copy_stencil ? REPLACE_STENCIL_OP : VkStencilOpState{},
|
|
||||||
.back = copy_stencil ? REPLACE_STENCIL_OP : VkStencilOpState{},
|
|
||||||
.minDepthBounds = 0.0f,
|
|
||||||
.maxDepthBounds = 0.0f,
|
|
||||||
};
|
|
||||||
const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci =
|
|
||||||
GetPipelineInputAssemblyStateCreateInfo(device);
|
|
||||||
pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({
|
|
||||||
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
|
||||||
.pNext = nullptr,
|
|
||||||
.flags = 0,
|
|
||||||
.stageCount = static_cast<u32>(stages.size()),
|
|
||||||
.pStages = stages.data(),
|
|
||||||
.pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
|
|
||||||
.pInputAssemblyState = &input_assembly_ci,
|
|
||||||
.pTessellationState = nullptr,
|
|
||||||
.pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO,
|
|
||||||
.pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
|
|
||||||
.pMultisampleState = &multisample_ci,
|
|
||||||
.pDepthStencilState = &depth_stencil_ci,
|
|
||||||
.pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO,
|
|
||||||
.pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO,
|
|
||||||
.layout = copy_stencil ? *msaa_copy_depth_stencil_pipeline_layout
|
|
||||||
: *msaa_copy_pipeline_layout,
|
|
||||||
.renderPass = key.renderpass,
|
|
||||||
.subpass = 0,
|
|
||||||
.basePipelineHandle = VK_NULL_HANDLE,
|
|
||||||
.basePipelineIndex = 0,
|
|
||||||
}));
|
|
||||||
return *pipelines.back();
|
|
||||||
}
|
|
||||||
|
|
||||||
void BlitImageHelper::ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass) {
|
void BlitImageHelper::ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass) {
|
||||||
ConvertPipeline(pipeline, renderpass, false);
|
ConvertPipeline(pipeline, renderpass, false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,19 +45,12 @@ struct BlitDepthStencilPipelineKey {
|
|||||||
u32 stencil_ref;
|
u32 stencil_ref;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class MSAACopyFormatClass : u32 {
|
|
||||||
Float,
|
|
||||||
SignedInteger,
|
|
||||||
UnsignedInteger,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct MSAACopyPipelineKey {
|
struct MSAACopyPipelineKey {
|
||||||
constexpr auto operator<=>(const MSAACopyPipelineKey&) const noexcept = default;
|
constexpr auto operator<=>(const MSAACopyPipelineKey&) const noexcept = default;
|
||||||
|
|
||||||
VkRenderPass renderpass;
|
VkRenderPass renderpass;
|
||||||
VkSampleCountFlagBits samples;
|
VkSampleCountFlagBits samples;
|
||||||
bool msaa_to_non_msaa;
|
bool msaa_to_non_msaa;
|
||||||
MSAACopyFormatClass format_class;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BlitMSAAPipelineKey {
|
struct BlitMSAAPipelineKey {
|
||||||
@@ -85,12 +78,6 @@ public:
|
|||||||
void BlitColorMSAA(const Framebuffer* dst_framebuffer, const ImageView& src_image_view,
|
void BlitColorMSAA(const Framebuffer* dst_framebuffer, const ImageView& src_image_view,
|
||||||
const Region2D& dst_region, const Region2D& src_region);
|
const Region2D& dst_region, const Region2D& src_region);
|
||||||
|
|
||||||
void BlitDepthStencilMSAA(const Framebuffer* dst_framebuffer, ImageView& src_image_view,
|
|
||||||
const Region2D& dst_region, const Region2D& src_region);
|
|
||||||
|
|
||||||
void BlitDepth(const Framebuffer* dst_framebuffer, ImageView& src_image_view,
|
|
||||||
const Region2D& dst_region, const Region2D& src_region);
|
|
||||||
|
|
||||||
void ResolveDepthStencil(const Framebuffer* dst_framebuffer, ImageView& src_image_view,
|
void ResolveDepthStencil(const Framebuffer* dst_framebuffer, ImageView& src_image_view,
|
||||||
const Region2D& dst_region, const Region2D& src_region);
|
const Region2D& dst_region, const Region2D& src_region);
|
||||||
|
|
||||||
@@ -129,39 +116,7 @@ public:
|
|||||||
VideoCore::Surface::PixelFormat src_format, u32 num_samples,
|
VideoCore::Surface::PixelFormat src_format, u32 num_samples,
|
||||||
std::span<const VideoCommon::ImageCopy> copies, bool msaa_to_non_msaa);
|
std::span<const VideoCommon::ImageCopy> copies, bool msaa_to_non_msaa);
|
||||||
|
|
||||||
void CopyMSAADepth(RenderPassCache& render_pass_cache, VkImage dst_image,
|
|
||||||
VideoCore::Surface::PixelFormat dst_format, VkImage src_image,
|
|
||||||
VideoCore::Surface::PixelFormat src_format, u32 num_samples,
|
|
||||||
std::span<const VideoCommon::ImageCopy> copies, bool copy_stencil,
|
|
||||||
bool msaa_to_non_msaa);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct MSAACopyAspectInfo {
|
|
||||||
VkImageAspectFlags src_view_aspect;
|
|
||||||
VkImageAspectFlags attachment_aspect;
|
|
||||||
VkImageAspectFlags barrier_aspect;
|
|
||||||
VkAccessFlags pre_src_access;
|
|
||||||
VkAccessFlags pre_src_dst_access;
|
|
||||||
VkAccessFlags pre_dst_dst_access;
|
|
||||||
VkPipelineStageFlags pre_src_stages;
|
|
||||||
VkPipelineStageFlags pre_dst_stages;
|
|
||||||
VkAccessFlags post_src_access;
|
|
||||||
VkAccessFlags post_dst_access;
|
|
||||||
VkPipelineStageFlags post_src_stages;
|
|
||||||
VkPipelineStageFlags post_dst_stages;
|
|
||||||
};
|
|
||||||
|
|
||||||
void BlitImpl(const Framebuffer* dst_framebuffer, const ImageView& src_image_view,
|
|
||||||
const Region2D& dst_region, const Region2D& src_region, VkPipeline pipeline,
|
|
||||||
VkSampler sampler, VkImageView src_view, VkImageView src_stencil_view,
|
|
||||||
bool blit_stencil);
|
|
||||||
|
|
||||||
void CopyMSAAImpl(VkRenderPass renderpass, VkPipeline pipeline, VkPipelineLayout layout,
|
|
||||||
VkImage dst_image, VkFormat dst_vk_format, VkImage src_image,
|
|
||||||
VkFormat src_vk_format, s32 scale_x, s32 scale_y,
|
|
||||||
std::span<const VideoCommon::ImageCopy> copies,
|
|
||||||
const MSAACopyAspectInfo& aspect_info, bool copy_stencil);
|
|
||||||
|
|
||||||
void Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer,
|
void Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer,
|
||||||
const ImageView& src_image_view);
|
const ImageView& src_image_view);
|
||||||
|
|
||||||
@@ -176,13 +131,7 @@ private:
|
|||||||
[[nodiscard]] VkPipeline FindOrEmplaceClearStencilPipeline(
|
[[nodiscard]] VkPipeline FindOrEmplaceClearStencilPipeline(
|
||||||
const BlitDepthStencilPipelineKey& key);
|
const BlitDepthStencilPipelineKey& key);
|
||||||
[[nodiscard]] VkPipeline FindOrEmplaceMSAACopyPipeline(const MSAACopyPipelineKey& key);
|
[[nodiscard]] VkPipeline FindOrEmplaceMSAACopyPipeline(const MSAACopyPipelineKey& key);
|
||||||
|
|
||||||
[[nodiscard]] VkPipeline FindOrEmplaceMSAACopyDepthPipeline(const MSAACopyPipelineKey& key,
|
|
||||||
bool copy_stencil);
|
|
||||||
[[nodiscard]] VkPipeline FindOrEmplaceBlitColorMSAAPipeline(const BlitMSAAPipelineKey& key);
|
[[nodiscard]] VkPipeline FindOrEmplaceBlitColorMSAAPipeline(const BlitMSAAPipelineKey& key);
|
||||||
[[nodiscard]] VkPipeline FindOrEmplaceBlitDepthStencilMSAAPipeline(
|
|
||||||
const BlitMSAAPipelineKey& key, bool blit_stencil);
|
|
||||||
[[nodiscard]] VkPipeline FindOrEmplaceBlitDepthPipeline(VkRenderPass renderpass);
|
|
||||||
[[nodiscard]] VkPipeline FindOrEmplaceResolveDepthStencilPipeline(VkRenderPass renderpass,
|
[[nodiscard]] VkPipeline FindOrEmplaceResolveDepthStencilPipeline(VkRenderPass renderpass,
|
||||||
bool resolve_stencil);
|
bool resolve_stencil);
|
||||||
|
|
||||||
@@ -213,12 +162,10 @@ private:
|
|||||||
vk::PipelineLayout two_textures_pipeline_layout;
|
vk::PipelineLayout two_textures_pipeline_layout;
|
||||||
vk::PipelineLayout clear_color_pipeline_layout;
|
vk::PipelineLayout clear_color_pipeline_layout;
|
||||||
vk::PipelineLayout msaa_copy_pipeline_layout;
|
vk::PipelineLayout msaa_copy_pipeline_layout;
|
||||||
vk::PipelineLayout msaa_copy_depth_stencil_pipeline_layout;
|
|
||||||
vk::ShaderModule full_screen_vert;
|
vk::ShaderModule full_screen_vert;
|
||||||
vk::ShaderModule blit_color_to_color_frag;
|
vk::ShaderModule blit_color_to_color_frag;
|
||||||
vk::ShaderModule blit_color_msaa_frag;
|
vk::ShaderModule blit_color_msaa_frag;
|
||||||
vk::ShaderModule blit_depth_stencil_frag;
|
vk::ShaderModule blit_depth_stencil_frag;
|
||||||
vk::ShaderModule blit_depth_frag;
|
|
||||||
vk::ShaderModule blit_depth_msaa_frag;
|
vk::ShaderModule blit_depth_msaa_frag;
|
||||||
vk::ShaderModule blit_depth_stencil_msaa_frag;
|
vk::ShaderModule blit_depth_stencil_msaa_frag;
|
||||||
vk::ShaderModule clear_color_vert;
|
vk::ShaderModule clear_color_vert;
|
||||||
@@ -232,15 +179,7 @@ private:
|
|||||||
vk::ShaderModule convert_d24s8_to_abgr8_frag;
|
vk::ShaderModule convert_d24s8_to_abgr8_frag;
|
||||||
vk::ShaderModule convert_s8d24_to_abgr8_frag;
|
vk::ShaderModule convert_s8d24_to_abgr8_frag;
|
||||||
vk::ShaderModule convert_msaa_to_non_msaa_frag;
|
vk::ShaderModule convert_msaa_to_non_msaa_frag;
|
||||||
vk::ShaderModule convert_msaa_to_non_msaa_sint_frag;
|
|
||||||
vk::ShaderModule convert_msaa_to_non_msaa_uint_frag;
|
|
||||||
vk::ShaderModule convert_msaa_to_non_msaa_depth_frag;
|
|
||||||
vk::ShaderModule convert_msaa_to_non_msaa_depth_stencil_frag;
|
|
||||||
vk::ShaderModule convert_non_msaa_to_msaa_frag;
|
vk::ShaderModule convert_non_msaa_to_msaa_frag;
|
||||||
vk::ShaderModule convert_non_msaa_to_msaa_sint_frag;
|
|
||||||
vk::ShaderModule convert_non_msaa_to_msaa_uint_frag;
|
|
||||||
vk::ShaderModule convert_non_msaa_to_msaa_depth_frag;
|
|
||||||
vk::ShaderModule convert_non_msaa_to_msaa_depth_stencil_frag;
|
|
||||||
vk::Sampler linear_sampler;
|
vk::Sampler linear_sampler;
|
||||||
vk::Sampler nearest_sampler;
|
vk::Sampler nearest_sampler;
|
||||||
|
|
||||||
@@ -254,18 +193,8 @@ private:
|
|||||||
std::vector<vk::Pipeline> clear_stencil_pipelines;
|
std::vector<vk::Pipeline> clear_stencil_pipelines;
|
||||||
std::vector<MSAACopyPipelineKey> msaa_copy_keys;
|
std::vector<MSAACopyPipelineKey> msaa_copy_keys;
|
||||||
std::vector<vk::Pipeline> msaa_copy_pipelines;
|
std::vector<vk::Pipeline> msaa_copy_pipelines;
|
||||||
std::vector<MSAACopyPipelineKey> msaa_copy_depth_keys;
|
|
||||||
std::vector<vk::Pipeline> msaa_copy_depth_pipelines;
|
|
||||||
std::vector<MSAACopyPipelineKey> msaa_copy_depth_stencil_keys;
|
|
||||||
std::vector<vk::Pipeline> msaa_copy_depth_stencil_pipelines;
|
|
||||||
std::vector<BlitMSAAPipelineKey> blit_msaa_color_keys;
|
std::vector<BlitMSAAPipelineKey> blit_msaa_color_keys;
|
||||||
std::vector<vk::Pipeline> blit_msaa_color_pipelines;
|
std::vector<vk::Pipeline> blit_msaa_color_pipelines;
|
||||||
std::vector<VkRenderPass> blit_depth_keys;
|
|
||||||
std::vector<vk::Pipeline> blit_depth_pipelines;
|
|
||||||
std::vector<BlitMSAAPipelineKey> blit_msaa_depth_keys;
|
|
||||||
std::vector<vk::Pipeline> blit_msaa_depth_pipelines;
|
|
||||||
std::vector<BlitMSAAPipelineKey> blit_msaa_depth_stencil_keys;
|
|
||||||
std::vector<vk::Pipeline> blit_msaa_depth_stencil_pipelines;
|
|
||||||
std::vector<VkRenderPass> resolve_depth_keys;
|
std::vector<VkRenderPass> resolve_depth_keys;
|
||||||
std::vector<vk::Pipeline> resolve_depth_pipelines;
|
std::vector<vk::Pipeline> resolve_depth_pipelines;
|
||||||
std::vector<VkRenderPass> resolve_depth_stencil_keys;
|
std::vector<VkRenderPass> resolve_depth_stencil_keys;
|
||||||
|
|||||||
@@ -370,9 +370,20 @@ inline void PushImageDescriptors(TextureCache& texture_cache,
|
|||||||
const VkImageView null_image_view{texture_cache.GetImageView(VideoCommon::NULL_IMAGE_VIEW_ID).Handle(desc.type)};
|
const VkImageView null_image_view{texture_cache.GetImageView(VideoCommon::NULL_IMAGE_VIEW_ID).Handle(desc.type)};
|
||||||
if (null_image_view != VK_NULL_HANDLE) vk_image_view = null_image_view;
|
if (null_image_view != VK_NULL_HANDLE) vk_image_view = null_image_view;
|
||||||
}
|
}
|
||||||
Sampler& sampler{texture_cache.GetSampler(sampler_id)};
|
const Sampler& sampler{texture_cache.GetSampler(sampler_id)};
|
||||||
guest_descriptor_queue.AddSampledImage(vk_image_view,
|
const bool use_fallback_sampler{sampler.HasAddedAnisotropy() &&
|
||||||
sampler.HandleFor(image_view, desc.is_depth));
|
!image_view.SupportsAnisotropy()};
|
||||||
|
VkSampler vk_sampler{use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy()
|
||||||
|
: sampler.Handle()};
|
||||||
|
if (sampler.HasLinearFiltering() &&
|
||||||
|
VideoCore::Surface::IsPixelFormatInteger(image_view.format)) {
|
||||||
|
vk_sampler = sampler.HandleWithNearestFilter();
|
||||||
|
}
|
||||||
|
if (desc.is_depth && sampler.HasDepthComparison() &&
|
||||||
|
!image_view.SupportsDepthComparison()) {
|
||||||
|
vk_sampler = sampler.HandleWithoutDepthComparison();
|
||||||
|
}
|
||||||
|
guest_descriptor_queue.AddSampledImage(vk_image_view, vk_sampler);
|
||||||
const bool element_rescaled{texture_cache.IsRescaling(image_view)};
|
const bool element_rescaled{texture_cache.IsRescaling(image_view)};
|
||||||
is_rescaled |= element_rescaled;
|
is_rescaled |= element_rescaled;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/div_ceil.h"
|
#include "common/div_ceil.h"
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
@@ -19,7 +17,7 @@
|
|||||||
|
|
||||||
namespace Vulkan {
|
namespace Vulkan {
|
||||||
|
|
||||||
using PushConstants = std::array<u32, 4 + 2 + 2 + 1>;
|
using PushConstants = std::array<u32, 4 + 2 + 1>;
|
||||||
|
|
||||||
SGSR::SGSR(const Device& device, MemoryAllocator& memory_allocator, size_t image_count, VkExtent2D extent, bool edge_dir)
|
SGSR::SGSR(const Device& device, MemoryAllocator& memory_allocator, size_t image_count, VkExtent2D extent, bool edge_dir)
|
||||||
: m_memory_allocator{memory_allocator}
|
: m_memory_allocator{memory_allocator}
|
||||||
@@ -102,28 +100,26 @@ VkImageView SGSR::Draw(const Device& device, Scheduler& scheduler, size_t image_
|
|||||||
|
|
||||||
const f32 input_image_width = f32(input_image_extent.width);
|
const f32 input_image_width = f32(input_image_extent.width);
|
||||||
const f32 input_image_height = f32(input_image_extent.height);
|
const f32 input_image_height = f32(input_image_extent.height);
|
||||||
const f32 crop_width = (crop_rect.right - crop_rect.left) * input_image_width;
|
const f32 viewport_width = (crop_rect.right - crop_rect.left) * input_image_width;
|
||||||
const f32 crop_height = (crop_rect.bottom - crop_rect.top) * input_image_height;
|
const f32 viewport_height = (crop_rect.bottom - crop_rect.top) * input_image_height;
|
||||||
static constexpr f32 EDGE_SHARPNESS_MAX = 2.0f;
|
// expected [0, 2]
|
||||||
const f32 edge_sharpness =
|
const f32 sharpening = f32(Settings::values.fsr_sharpening_slider.GetValue()) / 100.0f;
|
||||||
EDGE_SHARPNESS_MAX - f32(Settings::values.fsr_sharpening_slider.GetValue()) / 200.0f;
|
|
||||||
|
|
||||||
|
// p = (tex * viewport) / input = [0,n] (normalized texcoords)
|
||||||
|
// p * input = [0,1024], [0,768]
|
||||||
// layout( push_constant ) uniform constants {
|
// layout( push_constant ) uniform constants {
|
||||||
// highp vec4 ViewportInfo[1];
|
// highp vec4 ViewportInfo[1];
|
||||||
// highp vec2 ResizeFactor;
|
// highp vec2 ResizeFactor;
|
||||||
// highp vec2 CropOffset;
|
|
||||||
// highp float EdgeSharpness;
|
// highp float EdgeSharpness;
|
||||||
// };
|
// };
|
||||||
PushConstants viewport_con{};
|
PushConstants viewport_con{};
|
||||||
viewport_con[0] = std::bit_cast<u32>(1.f / input_image_width);
|
viewport_con[0] = std::bit_cast<u32>(std::abs(1.f / viewport_width));
|
||||||
viewport_con[1] = std::bit_cast<u32>(1.f / input_image_height);
|
viewport_con[1] = std::bit_cast<u32>(std::abs(1.f / viewport_height));
|
||||||
viewport_con[2] = std::bit_cast<u32>(input_image_width);
|
viewport_con[2] = std::bit_cast<u32>(std::abs(viewport_width));
|
||||||
viewport_con[3] = std::bit_cast<u32>(input_image_height);
|
viewport_con[3] = std::bit_cast<u32>(std::abs(viewport_height));
|
||||||
viewport_con[4] = std::bit_cast<u32>(crop_width / input_image_width);
|
viewport_con[4] = std::bit_cast<u32>(viewport_width / input_image_width);
|
||||||
viewport_con[5] = std::bit_cast<u32>(crop_height / input_image_height);
|
viewport_con[5] = std::bit_cast<u32>(viewport_height / input_image_height);
|
||||||
viewport_con[6] = std::bit_cast<u32>((std::min)(crop_rect.left, crop_rect.right));
|
viewport_con[6] = std::bit_cast<u32>(sharpening);
|
||||||
viewport_con[7] = std::bit_cast<u32>((std::min)(crop_rect.top, crop_rect.bottom));
|
|
||||||
viewport_con[8] = std::bit_cast<u32>(edge_sharpness);
|
|
||||||
|
|
||||||
UploadImages(device, scheduler);
|
UploadImages(device, scheduler);
|
||||||
UpdateDescriptorSets(device, source_image_view, image_index);
|
UpdateDescriptorSets(device, source_image_view, image_index);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user