mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-09-29 11:53:24 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7ba6a72566 | |||
| 5d3ad94e18 | |||
| 479df9809a | |||
| 5b4c29b123 | |||
| 5b4dc32c1a | |||
| a90ba7f6ac | |||
| 9b2a36791d | |||
| b85f289048 | |||
| 89004124a5 |
@@ -0,0 +1,5 @@
|
|||||||
|
- [ ] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
|
||||||
|
- [ ] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
|
||||||
|
- [ ] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.
|
||||||
|
|
||||||
|
-------------------
|
||||||
@@ -65,7 +65,7 @@ android {
|
|||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "dev.eden.eden_emulator"
|
applicationId = "dev.eden.eden_emulator"
|
||||||
minSdk = 33
|
minSdk = 24
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionName = getGitVersion()
|
versionName = getGitVersion()
|
||||||
versionCode = autoVersion
|
versionCode = autoVersion
|
||||||
|
|||||||
@@ -218,8 +218,6 @@ object NativeLibrary {
|
|||||||
|
|
||||||
external fun logSettings()
|
external fun logSettings()
|
||||||
|
|
||||||
external fun refreshThreadPolicies()
|
|
||||||
|
|
||||||
external fun getDebugKnobAt(index: Int): Boolean
|
external fun getDebugKnobAt(index: Int): Boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
-3
@@ -27,7 +27,6 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
|||||||
RENDERER_ASYNCHRONOUS_GPU_EMULATION("use_asynchronous_gpu_emulation"),
|
RENDERER_ASYNCHRONOUS_GPU_EMULATION("use_asynchronous_gpu_emulation"),
|
||||||
RENDERER_ASYNC_PRESENTATION("async_presentation"),
|
RENDERER_ASYNC_PRESENTATION("async_presentation"),
|
||||||
RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders"),
|
RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders"),
|
||||||
RENDERER_UNIFIED_MEMORY("use_unified_memory"),
|
|
||||||
RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"),
|
RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"),
|
||||||
ENABLE_BUFFER_HISTORY("enable_buffer_history"),
|
ENABLE_BUFFER_HISTORY("enable_buffer_history"),
|
||||||
USE_OPTIMIZED_VERTEX_BUFFERS("use_optimized_vertex_buffers"),
|
USE_OPTIMIZED_VERTEX_BUFFERS("use_optimized_vertex_buffers"),
|
||||||
@@ -37,8 +36,6 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
|||||||
RENDERER_DEBUG("debug"),
|
RENDERER_DEBUG("debug"),
|
||||||
RENDERER_PATCH_OLD_QCOM_DRIVERS("patch_old_qcom_drivers"),
|
RENDERER_PATCH_OLD_QCOM_DRIVERS("patch_old_qcom_drivers"),
|
||||||
RENDERER_VERTEX_INPUT_DYNAMIC_STATE("vertex_input_dynamic_state"),
|
RENDERER_VERTEX_INPUT_DYNAMIC_STATE("vertex_input_dynamic_state"),
|
||||||
RENDERER_DYNAMIC_RENDERING("dynamic_rendering"),
|
|
||||||
RENDERER_WORKGROUP_MEMORY_EXPLICIT_LAYOUT("workgroup_memory_explicit_layout"),
|
|
||||||
RENDERER_SAMPLE_SHADING("sample_shading"),
|
RENDERER_SAMPLE_SHADING("sample_shading"),
|
||||||
GPU_UNSWIZZLE_ENABLED("gpu_unswizzle_enabled"),
|
GPU_UNSWIZZLE_ENABLED("gpu_unswizzle_enabled"),
|
||||||
PICTURE_IN_PICTURE("picture_in_picture"),
|
PICTURE_IN_PICTURE("picture_in_picture"),
|
||||||
|
|||||||
+1
-22
@@ -155,20 +155,6 @@ abstract class SettingsItem(
|
|||||||
descriptionId = R.string.vertex_input_dynamic_state_description
|
descriptionId = R.string.vertex_input_dynamic_state_description
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
|
||||||
SwitchSetting(
|
|
||||||
BooleanSetting.RENDERER_DYNAMIC_RENDERING,
|
|
||||||
titleId = R.string.dynamic_rendering,
|
|
||||||
descriptionId = R.string.dynamic_rendering_description
|
|
||||||
)
|
|
||||||
)
|
|
||||||
put(
|
|
||||||
SwitchSetting(
|
|
||||||
BooleanSetting.RENDERER_WORKGROUP_MEMORY_EXPLICIT_LAYOUT,
|
|
||||||
titleId = R.string.workgroup_memory_explicit_layout,
|
|
||||||
descriptionId = R.string.workgroup_memory_explicit_layout_description
|
|
||||||
)
|
|
||||||
)
|
|
||||||
put(
|
put(
|
||||||
SliderSetting(
|
SliderSetting(
|
||||||
IntSetting.RENDERER_SAMPLE_SHADING,
|
IntSetting.RENDERER_SAMPLE_SHADING,
|
||||||
@@ -608,7 +594,7 @@ abstract class SettingsItem(
|
|||||||
IntSetting.ANDROID_PIPELINE_WORKERS,
|
IntSetting.ANDROID_PIPELINE_WORKERS,
|
||||||
titleId = R.string.pipeline_worker_cores,
|
titleId = R.string.pipeline_worker_cores,
|
||||||
descriptionId = R.string.pipeline_worker_cores_description,
|
descriptionId = R.string.pipeline_worker_cores_description,
|
||||||
min = 2,
|
min = 4,
|
||||||
max = 8,
|
max = 8,
|
||||||
units = "cores"
|
units = "cores"
|
||||||
)
|
)
|
||||||
@@ -699,13 +685,6 @@ abstract class SettingsItem(
|
|||||||
descriptionId = R.string.renderer_asynchronous_shaders_description
|
descriptionId = R.string.renderer_asynchronous_shaders_description
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
|
||||||
SwitchSetting(
|
|
||||||
BooleanSetting.RENDERER_UNIFIED_MEMORY,
|
|
||||||
titleId = R.string.renderer_unified_memory,
|
|
||||||
descriptionId = R.string.renderer_unified_memory_description
|
|
||||||
)
|
|
||||||
)
|
|
||||||
put(
|
put(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.FAST_GPU_TIME,
|
IntSetting.FAST_GPU_TIME,
|
||||||
|
|||||||
-3
@@ -304,7 +304,6 @@ class SettingsFragmentPresenter(
|
|||||||
add(BooleanSetting.EMULATE_BGR565.key)
|
add(BooleanSetting.EMULATE_BGR565.key)
|
||||||
add(BooleanSetting.RESCALE_HACK.key)
|
add(BooleanSetting.RESCALE_HACK.key)
|
||||||
add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key)
|
add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key)
|
||||||
add(BooleanSetting.RENDERER_UNIFIED_MEMORY.key)
|
|
||||||
add(IntSetting.ANDROID_PIPELINE_WORKERS.key)
|
add(IntSetting.ANDROID_PIPELINE_WORKERS.key)
|
||||||
add(BooleanSetting.RENDERER_ASYNCHRONOUS_GPU_EMULATION.key)
|
add(BooleanSetting.RENDERER_ASYNCHRONOUS_GPU_EMULATION.key)
|
||||||
add(BooleanSetting.RENDERER_ASYNC_PRESENTATION.key)
|
add(BooleanSetting.RENDERER_ASYNC_PRESENTATION.key)
|
||||||
@@ -314,8 +313,6 @@ class SettingsFragmentPresenter(
|
|||||||
|
|
||||||
add(IntSetting.RENDERER_DYNA_STATE.key)
|
add(IntSetting.RENDERER_DYNA_STATE.key)
|
||||||
add(BooleanSetting.RENDERER_VERTEX_INPUT_DYNAMIC_STATE.key)
|
add(BooleanSetting.RENDERER_VERTEX_INPUT_DYNAMIC_STATE.key)
|
||||||
add(BooleanSetting.RENDERER_DYNAMIC_RENDERING.key)
|
|
||||||
add(BooleanSetting.RENDERER_WORKGROUP_MEMORY_EXPLICIT_LAYOUT.key)
|
|
||||||
add(IntSetting.RENDERER_SAMPLE_SHADING.key)
|
add(IntSetting.RENDERER_SAMPLE_SHADING.key)
|
||||||
|
|
||||||
add(HeaderSetting(R.string.display))
|
add(HeaderSetting(R.string.display))
|
||||||
|
|||||||
@@ -1451,7 +1451,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
NativeLibrary.refreshThreadPolicies()
|
|
||||||
val b = _binding ?: return
|
val b = _binding ?: return
|
||||||
updateStatsPosition(IntSetting.PERF_OVERLAY_POSITION.getInt())
|
updateStatsPosition(IntSetting.PERF_OVERLAY_POSITION.getInt())
|
||||||
updateSocPosition(IntSetting.SOC_OVERLAY_POSITION.getInt())
|
updateSocPosition(IntSetting.SOC_OVERLAY_POSITION.getInt())
|
||||||
|
|||||||
@@ -147,6 +147,13 @@ namespace AndroidSettings {
|
|||||||
&show_performance_overlay};
|
&show_performance_overlay};
|
||||||
|
|
||||||
|
|
||||||
|
Settings::Setting<s32> pipeline_worker_count{linkage, 4, "pipeline_worker_count",
|
||||||
|
Settings::Category::Android,
|
||||||
|
Settings::Specialization::Default,
|
||||||
|
true,
|
||||||
|
true};
|
||||||
|
|
||||||
|
|
||||||
Settings::Setting<bool> show_input_overlay{linkage, true, "show_input_overlay",
|
Settings::Setting<bool> show_input_overlay{linkage, true, "show_input_overlay",
|
||||||
Settings::Category::Overlay};
|
Settings::Category::Overlay};
|
||||||
Settings::Setting<bool> overlay_snap_to_grid{linkage, false, "overlay_snap_to_grid",
|
Settings::Setting<bool> overlay_snap_to_grid{linkage, false, "overlay_snap_to_grid",
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ extern "C" {
|
|||||||
#include "common/scope_exit.h"
|
#include "common/scope_exit.h"
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
#include "common/thread.h"
|
|
||||||
#include "frontend_common/play_time_manager.h"
|
#include "frontend_common/play_time_manager.h"
|
||||||
#include "core/constants.h"
|
#include "core/constants.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
@@ -1183,10 +1182,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_logSettings(JNIEnv* env, jobject jobj
|
|||||||
Settings::LogSettings();
|
Settings::LogSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_refreshThreadPolicies(JNIEnv* env, jobject jobj) {
|
|
||||||
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)));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1000,7 +1000,6 @@
|
|||||||
|
|
||||||
<!-- Renderer Accuracy -->
|
<!-- Renderer Accuracy -->
|
||||||
<string name="renderer_accuracy_low">سريع</string>
|
<string name="renderer_accuracy_low">سريع</string>
|
||||||
<string name="renderer_accuracy_medium">متوازن</string>
|
|
||||||
<string name="renderer_accuracy_high">دقيق</string>
|
<string name="renderer_accuracy_high">دقيق</string>
|
||||||
|
|
||||||
<!-- DMA Accuracy -->
|
<!-- DMA Accuracy -->
|
||||||
|
|||||||
@@ -707,7 +707,6 @@
|
|||||||
|
|
||||||
<!-- Renderer Accuracy -->
|
<!-- Renderer Accuracy -->
|
||||||
<string name="renderer_accuracy_low">Rychlý</string>
|
<string name="renderer_accuracy_low">Rychlý</string>
|
||||||
<string name="renderer_accuracy_medium">Vyvážený</string>
|
|
||||||
<string name="renderer_accuracy_high">Přesný</string>
|
<string name="renderer_accuracy_high">Přesný</string>
|
||||||
|
|
||||||
<!-- DMA Accuracy -->
|
<!-- DMA Accuracy -->
|
||||||
|
|||||||
@@ -927,7 +927,6 @@ Wirklich fortfahren?</string>
|
|||||||
|
|
||||||
<!-- Renderer Accuracy -->
|
<!-- Renderer Accuracy -->
|
||||||
<string name="renderer_accuracy_low">Schnell</string>
|
<string name="renderer_accuracy_low">Schnell</string>
|
||||||
<string name="renderer_accuracy_medium">Ausgeglichen</string>
|
|
||||||
<string name="renderer_accuracy_high">Genau</string>
|
<string name="renderer_accuracy_high">Genau</string>
|
||||||
|
|
||||||
<!-- DMA Accuracy -->
|
<!-- DMA Accuracy -->
|
||||||
|
|||||||
@@ -993,7 +993,6 @@
|
|||||||
|
|
||||||
<!-- Renderer Accuracy -->
|
<!-- Renderer Accuracy -->
|
||||||
<string name="renderer_accuracy_low">Rápido</string>
|
<string name="renderer_accuracy_low">Rápido</string>
|
||||||
<string name="renderer_accuracy_medium">Equilibrado</string>
|
|
||||||
<string name="renderer_accuracy_high">Preciso</string>
|
<string name="renderer_accuracy_high">Preciso</string>
|
||||||
|
|
||||||
<!-- DMA Accuracy -->
|
<!-- DMA Accuracy -->
|
||||||
|
|||||||
@@ -939,7 +939,6 @@
|
|||||||
|
|
||||||
<!-- Renderer Accuracy -->
|
<!-- Renderer Accuracy -->
|
||||||
<string name="renderer_accuracy_low">Rapide</string>
|
<string name="renderer_accuracy_low">Rapide</string>
|
||||||
<string name="renderer_accuracy_medium">Moyen</string>
|
|
||||||
<string name="renderer_accuracy_high">Précis</string>
|
<string name="renderer_accuracy_high">Précis</string>
|
||||||
|
|
||||||
<!-- DMA Accuracy -->
|
<!-- DMA Accuracy -->
|
||||||
|
|||||||
@@ -893,7 +893,6 @@
|
|||||||
|
|
||||||
<!-- Renderer Accuracy -->
|
<!-- Renderer Accuracy -->
|
||||||
<string name="renderer_accuracy_low">Szybkie</string>
|
<string name="renderer_accuracy_low">Szybkie</string>
|
||||||
<string name="renderer_accuracy_medium">Zrównoważony</string>
|
|
||||||
<string name="renderer_accuracy_high">Dokładny</string>
|
<string name="renderer_accuracy_high">Dokładny</string>
|
||||||
|
|
||||||
<!-- DMA Accuracy -->
|
<!-- DMA Accuracy -->
|
||||||
|
|||||||
@@ -840,7 +840,6 @@
|
|||||||
|
|
||||||
<string name="renderer_none">Nenhum</string>
|
<string name="renderer_none">Nenhum</string>
|
||||||
|
|
||||||
<string name="renderer_accuracy_medium">Média</string>
|
|
||||||
<string name="renderer_accuracy_high">Alta</string>
|
<string name="renderer_accuracy_high">Alta</string>
|
||||||
|
|
||||||
<!-- DMA Accuracy -->
|
<!-- DMA Accuracy -->
|
||||||
|
|||||||
@@ -983,7 +983,6 @@
|
|||||||
|
|
||||||
<!-- Renderer Accuracy -->
|
<!-- Renderer Accuracy -->
|
||||||
<string name="renderer_accuracy_low">Быстрый</string>
|
<string name="renderer_accuracy_low">Быстрый</string>
|
||||||
<string name="renderer_accuracy_medium">Сбалансированный</string>
|
|
||||||
<string name="renderer_accuracy_high">Точный</string>
|
<string name="renderer_accuracy_high">Точный</string>
|
||||||
|
|
||||||
<!-- DMA Accuracy -->
|
<!-- DMA Accuracy -->
|
||||||
|
|||||||
@@ -986,7 +986,6 @@
|
|||||||
|
|
||||||
<!-- Renderer Accuracy -->
|
<!-- Renderer Accuracy -->
|
||||||
<string name="renderer_accuracy_low">Швидко</string>
|
<string name="renderer_accuracy_low">Швидко</string>
|
||||||
<string name="renderer_accuracy_medium">Збалансовано</string>
|
|
||||||
<string name="renderer_accuracy_high">Точно</string>
|
<string name="renderer_accuracy_high">Точно</string>
|
||||||
|
|
||||||
<!-- DMA Accuracy -->
|
<!-- DMA Accuracy -->
|
||||||
|
|||||||
@@ -990,7 +990,6 @@
|
|||||||
|
|
||||||
<!-- Renderer Accuracy -->
|
<!-- Renderer Accuracy -->
|
||||||
<string name="renderer_accuracy_low">快速</string>
|
<string name="renderer_accuracy_low">快速</string>
|
||||||
<string name="renderer_accuracy_medium">均衡</string>
|
|
||||||
<string name="renderer_accuracy_high">精确</string>
|
<string name="renderer_accuracy_high">精确</string>
|
||||||
|
|
||||||
<!-- DMA Accuracy -->
|
<!-- DMA Accuracy -->
|
||||||
|
|||||||
@@ -843,7 +843,6 @@
|
|||||||
|
|
||||||
<string name="renderer_none">無</string>
|
<string name="renderer_none">無</string>
|
||||||
|
|
||||||
<string name="renderer_accuracy_medium">平衡</string>
|
|
||||||
<string name="renderer_accuracy_high">準確</string>
|
<string name="renderer_accuracy_high">準確</string>
|
||||||
|
|
||||||
<!-- DMA Accuracy -->
|
<!-- DMA Accuracy -->
|
||||||
|
|||||||
@@ -103,14 +103,12 @@
|
|||||||
|
|
||||||
<string-array name="rendererAccuracyNames">
|
<string-array name="rendererAccuracyNames">
|
||||||
<item>@string/renderer_accuracy_low</item>
|
<item>@string/renderer_accuracy_low</item>
|
||||||
<item>@string/renderer_accuracy_medium</item>
|
|
||||||
<item>@string/renderer_accuracy_high</item>
|
<item>@string/renderer_accuracy_high</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<integer-array name="rendererAccuracyValues">
|
<integer-array name="rendererAccuracyValues">
|
||||||
<item>0</item>
|
<item>0</item>
|
||||||
<item>1</item>
|
<item>1</item>
|
||||||
<item>2</item>
|
|
||||||
</integer-array>
|
</integer-array>
|
||||||
|
|
||||||
<!-- VRAM USAGE MODE CHOICES -->
|
<!-- VRAM USAGE MODE CHOICES -->
|
||||||
|
|||||||
@@ -479,7 +479,7 @@
|
|||||||
<string name="advanced">Advanced</string>
|
<string name="advanced">Advanced</string>
|
||||||
|
|
||||||
<string name="renderer_accuracy">GPU Mode</string>
|
<string name="renderer_accuracy">GPU Mode</string>
|
||||||
<string name="renderer_accuracy_description">Controls the GPU emulation mode. Most games render fine with Fast or Balanced modes, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode.</string>
|
<string name="renderer_accuracy_description">Controls the GPU emulation mode. Most games render fine with Fast, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode.</string>
|
||||||
<string name="dma_accuracy">DMA Accuracy</string>
|
<string name="dma_accuracy">DMA Accuracy</string>
|
||||||
<string name="dma_accuracy_description">Controls the DMA precision accuracy. Safe precision can fix issues in some games, but it can also impact performance in some cases. If unsure, leave this on Default.</string>
|
<string name="dma_accuracy_description">Controls the DMA precision accuracy. Safe precision can fix issues in some games, but it can also impact performance in some cases. If unsure, leave this on Default.</string>
|
||||||
<string name="gpu_fence_behavior">GPU Fence Behavior</string>
|
<string name="gpu_fence_behavior">GPU Fence Behavior</string>
|
||||||
@@ -524,8 +524,6 @@
|
|||||||
<string name="rescale_hack_description">Enables a legacy handling for the rescale configuration pass for games by using a quick rescale path</string>
|
<string name="rescale_hack_description">Enables a legacy handling for the rescale configuration pass for games by using a quick rescale path</string>
|
||||||
<string name="renderer_asynchronous_shaders">Use asynchronous shaders</string>
|
<string name="renderer_asynchronous_shaders">Use asynchronous shaders</string>
|
||||||
<string name="renderer_asynchronous_shaders_description">Compiles shaders asynchronously. This may reduce stutters but may also introduce glitches.</string>
|
<string name="renderer_asynchronous_shaders_description">Compiles shaders asynchronously. This may reduce stutters but may also introduce glitches.</string>
|
||||||
<string name="renderer_unified_memory">Unified memory access (UMA)</string>
|
|
||||||
<string name="renderer_unified_memory_description">Allows GPU write buffer readbacks directly into guest memory, skipping the CPU staging copy.</string>
|
|
||||||
<string name="gpu_unswizzle_settings">GPU Unswizzle Settings</string>
|
<string name="gpu_unswizzle_settings">GPU Unswizzle Settings</string>
|
||||||
<string name="gpu_unswizzle_settings_description">Configure GPU-based texture unswizzling parameters or disable it entirely. Adjust these settings to balance performance and texture loading quality.</string>
|
<string name="gpu_unswizzle_settings_description">Configure GPU-based texture unswizzling parameters or disable it entirely. Adjust these settings to balance performance and texture loading quality.</string>
|
||||||
<string name="gpu_unswizzle_enable">Enable GPU Unswizzle</string>
|
<string name="gpu_unswizzle_enable">Enable GPU Unswizzle</string>
|
||||||
@@ -546,10 +544,6 @@
|
|||||||
<string name="disabled">Disabled</string>
|
<string name="disabled">Disabled</string>
|
||||||
<string name="vertex_input_dynamic_state">Vertex Input Dynamic State</string>
|
<string name="vertex_input_dynamic_state">Vertex Input Dynamic State</string>
|
||||||
<string name="vertex_input_dynamic_state_description">Enabling this feature allows for more flexible vertex input handling, potentially reducing pipeline compilation time in vertex/buffer.</string>
|
<string name="vertex_input_dynamic_state_description">Enabling this feature allows for more flexible vertex input handling, potentially reducing pipeline compilation time in vertex/buffer.</string>
|
||||||
<string name="dynamic_rendering">Dynamic Rendering</string>
|
|
||||||
<string name="dynamic_rendering_description">Render without render pass and framebuffer objects. Results vary by driver: some gain performance, others lose it.</string>
|
|
||||||
<string name="workgroup_memory_explicit_layout">Workgroup Memory Explicit Layout</string>
|
|
||||||
<string name="workgroup_memory_explicit_layout_description">Let shaders declare explicit layouts for workgroup memory. Disabled by default: some Qualcomm drivers are unstable with it.</string>
|
|
||||||
<string name="sample_shading_fraction">Sample Shading</string>
|
<string name="sample_shading_fraction">Sample Shading</string>
|
||||||
<string name="sample_shading_fraction_description">Allows the fragment shader to execute per sample in a multi-sampled fragment instead once per fragment. Improves graphics quality at the cost of some performance.</string>
|
<string name="sample_shading_fraction_description">Allows the fragment shader to execute per sample in a multi-sampled fragment instead once per fragment. Improves graphics quality at the cost of some performance.</string>
|
||||||
|
|
||||||
@@ -1045,7 +1039,6 @@
|
|||||||
|
|
||||||
<!-- Renderer Accuracy -->
|
<!-- Renderer Accuracy -->
|
||||||
<string name="renderer_accuracy_low">Fast</string>
|
<string name="renderer_accuracy_low">Fast</string>
|
||||||
<string name="renderer_accuracy_medium">Balanced</string>
|
|
||||||
<string name="renderer_accuracy_high">Accurate</string>
|
<string name="renderer_accuracy_high">Accurate</string>
|
||||||
|
|
||||||
<!-- DMA Accuracy -->
|
<!-- DMA Accuracy -->
|
||||||
|
|||||||
@@ -476,6 +476,29 @@ std::string SanitizePath(std::string_view path_, DirectorySeparator directory_se
|
|||||||
path.erase(std::unique(start, path.end(),
|
path.erase(std::unique(start, path.end(),
|
||||||
[type2](char c1, char c2) { return c1 == type2 && c2 == type2; }),
|
[type2](char c1, char c2) { return c1 == type2 && c2 == type2; }),
|
||||||
path.end());
|
path.end());
|
||||||
|
|
||||||
|
const bool absolute = !path.empty() && path[0] == type2;
|
||||||
|
std::vector<std::string_view> parts;
|
||||||
|
|
||||||
|
for (const auto part : SplitPathComponents(path))
|
||||||
|
{
|
||||||
|
if (part.empty() || part == ".")
|
||||||
|
continue;
|
||||||
|
if (part == ".." && !parts.empty() && parts.back() != "..")
|
||||||
|
parts.pop_back();
|
||||||
|
else if (part != "..") parts.push_back(part);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string resolved = absolute ? std::string(1, type2) : std::string{};
|
||||||
|
for (std::size_t i = 0; i < parts.size(); ++i)
|
||||||
|
{
|
||||||
|
if (i != 0)
|
||||||
|
resolved += type2;
|
||||||
|
resolved.append(parts[i].data(), parts[i].size());
|
||||||
|
}
|
||||||
|
|
||||||
|
path = std::move(resolved);
|
||||||
|
|
||||||
return std::string(RemoveTrailingSlash(path));
|
return std::string(RemoveTrailingSlash(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-366
@@ -51,45 +51,14 @@
|
|||||||
|
|
||||||
#endif // ^^^ POSIX ^^^
|
#endif // ^^^ POSIX ^^^
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "common/alignment.h"
|
#include "common/alignment.h"
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/free_region_manager.h"
|
#include "common/free_region_manager.h"
|
||||||
#include "common/host_memory.h"
|
#include "common/host_memory.h"
|
||||||
#include "common/logging.h"
|
#include "common/logging.h"
|
||||||
#include "common/memory_detect.h"
|
|
||||||
#include "common/settings.h"
|
|
||||||
|
|
||||||
#ifdef __ANDROID__
|
|
||||||
#include <dlfcn.h>
|
|
||||||
#include <android/hardware_buffer.h>
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
struct NativeHandle {
|
|
||||||
int version;
|
|
||||||
int numFds;
|
|
||||||
int numInts;
|
|
||||||
int data[1];
|
|
||||||
};
|
|
||||||
|
|
||||||
using PFN_AHardwareBuffer_getNativeHandle = const NativeHandle* (*)(const AHardwareBuffer*);
|
|
||||||
|
|
||||||
PFN_AHardwareBuffer_getNativeHandle ResolveGetNativeHandle() {
|
|
||||||
void* const lib = dlopen("libnativewindow.so", RTLD_NOW);
|
|
||||||
if (lib == nullptr) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
return reinterpret_cast<PFN_AHardwareBuffer_getNativeHandle>(
|
|
||||||
dlsym(lib, "AHardwareBuffer_getNativeHandle"));
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(__ANDROID__) && __ANDROID_API__ < 30
|
#if defined(__ANDROID__) && __ANDROID_API__ < 30
|
||||||
#include <sys/syscall.h>
|
#include <sys/syscall.h>
|
||||||
@@ -106,12 +75,6 @@ namespace Common {
|
|||||||
[[maybe_unused]] constexpr size_t PageAlignment = 0x1000;
|
[[maybe_unused]] constexpr size_t PageAlignment = 0x1000;
|
||||||
[[maybe_unused]] constexpr size_t HugePageSize = 0x200000;
|
[[maybe_unused]] constexpr size_t HugePageSize = 0x200000;
|
||||||
|
|
||||||
static std::atomic<u64> committed_backing_size{};
|
|
||||||
|
|
||||||
u64 GetCommittedBackingSize() noexcept {
|
|
||||||
return committed_backing_size.load(std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
|
||||||
// Manually imported for MinGW compatibility
|
// Manually imported for MinGW compatibility
|
||||||
@@ -160,7 +123,7 @@ static void GetFuncAddress(Common::DynamicLibrary& dll, const char* name, T& pfn
|
|||||||
|
|
||||||
class HostMemory::Impl {
|
class HostMemory::Impl {
|
||||||
public:
|
public:
|
||||||
explicit Impl(size_t backing_size_, size_t virtual_size_, size_t)
|
explicit Impl(size_t backing_size_, size_t virtual_size_)
|
||||||
: backing_size{backing_size_}
|
: backing_size{backing_size_}
|
||||||
, virtual_size{virtual_size_}
|
, virtual_size{virtual_size_}
|
||||||
, process{GetCurrentProcess()}
|
, process{GetCurrentProcess()}
|
||||||
@@ -266,10 +229,6 @@ public:
|
|||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsBackingShared() const noexcept {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const size_t backing_size; ///< Size of the backing memory in bytes
|
const size_t backing_size; ///< Size of the backing memory in bytes
|
||||||
const size_t virtual_size; ///< Size of the virtual address placeholder in bytes
|
const size_t virtual_size; ///< Size of the virtual address placeholder in bytes
|
||||||
|
|
||||||
@@ -542,10 +501,9 @@ static int shm_open_anon(int flags, mode_t mode) {
|
|||||||
|
|
||||||
class HostMemory::Impl {
|
class HostMemory::Impl {
|
||||||
public:
|
public:
|
||||||
explicit Impl(size_t backing_size_, size_t virtual_size_, size_t preferred_offset_)
|
explicit Impl(size_t backing_size_, size_t virtual_size_)
|
||||||
: backing_size{backing_size_}
|
: backing_size{backing_size_}
|
||||||
, virtual_size{virtual_size_}
|
, virtual_size{virtual_size_}
|
||||||
, preferred_offset{preferred_offset_}
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
bool Init() {
|
bool Init() {
|
||||||
@@ -585,15 +543,10 @@ public:
|
|||||||
LOG_WARNING(Common_Memory, "Using private mappings instead of shared ones");
|
LOG_WARNING(Common_Memory, "Using private mappings instead of shared ones");
|
||||||
backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
|
backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
|
||||||
if (fd > 0) {
|
if (fd > 0) {
|
||||||
|
fd = -1;
|
||||||
close(fd);
|
close(fd);
|
||||||
}
|
}
|
||||||
fd = -1;
|
|
||||||
} else {
|
} else {
|
||||||
#ifdef __ANDROID__
|
|
||||||
if (InitAhbBacking()) {
|
|
||||||
return InitVirtual();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
|
backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
|
||||||
}
|
}
|
||||||
if (backing_base == MAP_FAILED) {
|
if (backing_base == MAP_FAILED) {
|
||||||
@@ -601,10 +554,7 @@ public:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return InitVirtual();
|
// Virtual memory initialization
|
||||||
}
|
|
||||||
|
|
||||||
bool InitVirtual() {
|
|
||||||
virtual_base = virtual_map_base = static_cast<u8*>(ChooseVirtualBase(virtual_size));
|
virtual_base = virtual_map_base = static_cast<u8*>(ChooseVirtualBase(virtual_size));
|
||||||
if (virtual_base == MAP_FAILED) {
|
if (virtual_base == MAP_FAILED) {
|
||||||
LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno));
|
LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno));
|
||||||
@@ -617,248 +567,6 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef __ANDROID__
|
|
||||||
static AHardwareBuffer_Desc MakeBlobDesc(size_t len) {
|
|
||||||
return AHardwareBuffer_Desc{
|
|
||||||
.width = static_cast<u32>(len),
|
|
||||||
.height = 1,
|
|
||||||
.layers = 1,
|
|
||||||
.format = AHARDWAREBUFFER_FORMAT_BLOB,
|
|
||||||
.usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN |
|
|
||||||
AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN |
|
|
||||||
AHARDWAREBUFFER_USAGE_GPU_DATA_BUFFER,
|
|
||||||
.stride = 0,
|
|
||||||
.rfu0 = 0,
|
|
||||||
.rfu1 = 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool ProbeAhbBacking(PFN_AHardwareBuffer_getNativeHandle get_native_handle) {
|
|
||||||
const AHardwareBuffer_Desc desc = MakeBlobDesc(PageAlignment * 2);
|
|
||||||
AHardwareBuffer* buffer{};
|
|
||||||
if (AHardwareBuffer_allocate(&desc, &buffer) != 0 || buffer == nullptr) {
|
|
||||||
LOG_WARNING(HW_Memory, "Hardware buffer probe allocation failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const NativeHandle* const handle = get_native_handle(buffer);
|
|
||||||
if (handle == nullptr || handle->numFds < 1) {
|
|
||||||
LOG_WARNING(HW_Memory, "Hardware buffer has no mappable file descriptor");
|
|
||||||
AHardwareBuffer_release(buffer);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const int probe_fd = handle->data[0];
|
|
||||||
bool ok = true;
|
|
||||||
const auto try_map = [&](int prot, off_t offset, const char* what) {
|
|
||||||
if (!ok) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
void* const ptr = mmap(nullptr, PageAlignment, prot, MAP_SHARED, probe_fd, offset);
|
|
||||||
if (ptr == MAP_FAILED) {
|
|
||||||
LOG_WARNING(HW_Memory, "Hardware buffer backing rejects {}: {}", what,
|
|
||||||
strerror(errno));
|
|
||||||
ok = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
munmap(ptr, PageAlignment);
|
|
||||||
};
|
|
||||||
try_map(PROT_READ | PROT_WRITE, 0, "shared mappings");
|
|
||||||
try_map(PROT_READ | PROT_WRITE, static_cast<off_t>(PageAlignment), "mappings at an offset");
|
|
||||||
#ifdef ARCHITECTURE_arm64
|
|
||||||
try_map(PROT_READ | PROT_EXEC, 0, "executable mappings");
|
|
||||||
#endif
|
|
||||||
AHardwareBuffer_release(buffer);
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t ComputeAhbBudget(size_t window_size) const {
|
|
||||||
const u64 total_physical = Common::GetMemInfo().TotalPhysicalMemory;
|
|
||||||
if (total_physical == 0) {
|
|
||||||
LOG_WARNING(HW_Memory, "Host memory size is unknown, not committing hardware buffers");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
constexpr u64 MinimumTotalPhysical = 7ULL << 30;
|
|
||||||
if (total_physical < MinimumTotalPhysical) {
|
|
||||||
LOG_INFO(HW_Memory,
|
|
||||||
"Skipping hardware buffer backing, {} MiB of RAM is below the {} MiB minimum",
|
|
||||||
total_physical >> 20, MinimumTotalPhysical >> 20);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
const u64 max_map_count = Common::GetMaxMapCount();
|
|
||||||
constexpr u64 ReservedMaps = 24576;
|
|
||||||
if (max_map_count == 0 || max_map_count <= ReservedMaps) {
|
|
||||||
LOG_WARNING(HW_Memory,
|
|
||||||
"Skipping hardware buffer backing, vm.max_map_count is unknown or too low");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
u64 budget = total_physical / 6;
|
|
||||||
budget = (std::min)(budget, (max_map_count - ReservedMaps) * PageAlignment);
|
|
||||||
const u64 available = Common::GetAvailablePhysicalMemory();
|
|
||||||
if (available != 0) {
|
|
||||||
constexpr u64 Headroom = 2ULL << 30;
|
|
||||||
budget = (std::min)(budget, available > Headroom ? available - Headroom : 0);
|
|
||||||
}
|
|
||||||
budget = (std::min)(budget, static_cast<u64>(backing_size));
|
|
||||||
budget = Common::AlignDown(budget, window_size);
|
|
||||||
constexpr u64 MinimumBudget = 256ULL << 20;
|
|
||||||
if (budget < MinimumBudget) {
|
|
||||||
LOG_INFO(HW_Memory,
|
|
||||||
"Skipping hardware buffer backing, only {} MiB could be committed on a {} MiB "
|
|
||||||
"system with {} MiB available and vm.max_map_count {}",
|
|
||||||
budget >> 20, total_physical >> 20, available >> 20, max_map_count);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return static_cast<size_t>(budget);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool InitAhbBacking() {
|
|
||||||
if (!Settings::values.use_unified_memory.GetValue()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
static const PFN_AHardwareBuffer_getNativeHandle get_native_handle =
|
|
||||||
ResolveGetNativeHandle();
|
|
||||||
if (get_native_handle == nullptr) {
|
|
||||||
LOG_WARNING(HW_Memory, "AHardwareBuffer_getNativeHandle is not available");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
constexpr size_t window_size = 64ULL << 20;
|
|
||||||
const AHardwareBuffer_Desc window_desc = MakeBlobDesc(window_size);
|
|
||||||
if (AHardwareBuffer_isSupported(&window_desc) == 0) {
|
|
||||||
LOG_WARNING(HW_Memory, "Allocator rejects {} MiB hardware buffer windows",
|
|
||||||
window_size >> 20);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const size_t budget = ComputeAhbBudget(window_size);
|
|
||||||
if (budget == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!ProbeAhbBacking(get_native_handle)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const size_t aligned_backing = Common::AlignDown(backing_size, window_size);
|
|
||||||
const size_t region_size = (std::min)(budget, aligned_backing);
|
|
||||||
const size_t region_base = Common::AlignDown(
|
|
||||||
(std::min)(preferred_offset, aligned_backing - region_size), window_size);
|
|
||||||
const size_t num_windows = region_size / window_size;
|
|
||||||
|
|
||||||
std::vector<AHardwareBuffer*> buffers;
|
|
||||||
std::vector<int> buffer_fds;
|
|
||||||
const auto cleanup = [&] {
|
|
||||||
for (AHardwareBuffer* buffer : buffers) {
|
|
||||||
AHardwareBuffer_release(buffer);
|
|
||||||
}
|
|
||||||
buffers.clear();
|
|
||||||
buffer_fds.clear();
|
|
||||||
};
|
|
||||||
for (size_t i = 0; i < num_windows; ++i) {
|
|
||||||
const AHardwareBuffer_Desc desc = MakeBlobDesc(window_size);
|
|
||||||
AHardwareBuffer* buffer{};
|
|
||||||
if (AHardwareBuffer_allocate(&desc, &buffer) != 0 || buffer == nullptr) {
|
|
||||||
LOG_WARNING(HW_Memory, "Hardware buffer allocation failed for window {} of {}", i,
|
|
||||||
num_windows);
|
|
||||||
cleanup();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
buffers.push_back(buffer);
|
|
||||||
const NativeHandle* const handle = get_native_handle(buffer);
|
|
||||||
if (handle == nullptr || handle->numFds < 1) {
|
|
||||||
LOG_WARNING(HW_Memory, "Hardware buffer has no mappable file descriptor");
|
|
||||||
cleanup();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const int buffer_fd = handle->data[0];
|
|
||||||
const off_t buffer_len = lseek(buffer_fd, 0, SEEK_END);
|
|
||||||
if (buffer_len < static_cast<off_t>(window_size)) {
|
|
||||||
LOG_WARNING(HW_Memory, "Hardware buffer descriptor smaller than requested");
|
|
||||||
cleanup();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
buffer_fds.push_back(buffer_fd);
|
|
||||||
}
|
|
||||||
u8* const base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_NONE,
|
|
||||||
MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0));
|
|
||||||
if (base == MAP_FAILED) {
|
|
||||||
LOG_WARNING(HW_Memory, "Failed to reserve backing address space: {}", strerror(errno));
|
|
||||||
cleanup();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const auto map_over_reservation = [&](size_t offset, size_t len, int map_fd,
|
|
||||||
off_t map_offset) {
|
|
||||||
if (len == 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (mmap(base + offset, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, map_fd,
|
|
||||||
map_offset) == MAP_FAILED) {
|
|
||||||
LOG_WARNING(HW_Memory, "Backing mmap failed: {}", strerror(errno));
|
|
||||||
munmap(base, backing_size);
|
|
||||||
cleanup();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
if (!map_over_reservation(0, region_base, fd, 0)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (size_t i = 0; i < num_windows; ++i) {
|
|
||||||
if (!map_over_reservation(region_base + i * window_size, window_size, buffer_fds[i],
|
|
||||||
0)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const size_t tail_offset = region_base + region_size;
|
|
||||||
if (!map_over_reservation(tail_offset, backing_size - tail_offset, fd,
|
|
||||||
static_cast<off_t>(tail_offset))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
backing_base = base;
|
|
||||||
ahb_windows = std::move(buffers);
|
|
||||||
ahb_fds = std::move(buffer_fds);
|
|
||||||
ahb_window_size = window_size;
|
|
||||||
ahb_base = region_base;
|
|
||||||
ahb_bytes = region_size;
|
|
||||||
committed_backing_size.store(region_size, std::memory_order_relaxed);
|
|
||||||
LOG_INFO(HW_Memory,
|
|
||||||
"Guest memory {:#x}-{:#x} backed by {} hardware buffer windows, {} MiB committed",
|
|
||||||
region_base, region_base + region_size, ahb_windows.size(), region_size >> 20);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MapBackingRange(size_t virtual_offset, size_t host_offset, size_t length, int prot_flags) {
|
|
||||||
while (length > 0) {
|
|
||||||
int map_fd = fd;
|
|
||||||
off_t map_offset = static_cast<off_t>(host_offset);
|
|
||||||
size_t chunk = length;
|
|
||||||
if (host_offset < ahb_base) {
|
|
||||||
chunk = (std::min)(chunk, ahb_base - host_offset);
|
|
||||||
} else if (host_offset < ahb_base + ahb_bytes) {
|
|
||||||
const size_t relative = host_offset - ahb_base;
|
|
||||||
const size_t window = relative / ahb_window_size;
|
|
||||||
const size_t local = relative % ahb_window_size;
|
|
||||||
map_fd = ahb_fds[window];
|
|
||||||
map_offset = static_cast<off_t>(local);
|
|
||||||
chunk = (std::min)(chunk, ahb_window_size - local);
|
|
||||||
}
|
|
||||||
void* const ret = mmap(virtual_base + virtual_offset, chunk, prot_flags,
|
|
||||||
MAP_SHARED | MAP_FIXED, map_fd, map_offset);
|
|
||||||
ASSERT_MSG(ret != MAP_FAILED, "mmap: {}", strerror(errno));
|
|
||||||
virtual_offset += chunk;
|
|
||||||
host_offset += chunk;
|
|
||||||
length -= chunk;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::span<AHardwareBuffer* const> AhbWindows() const noexcept {
|
|
||||||
return ahb_windows;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t AhbWindowSize() const noexcept {
|
|
||||||
return ahb_bytes != 0 ? ahb_window_size : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t AhbBase() const noexcept {
|
|
||||||
return ahb_base;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
~Impl() {
|
~Impl() {
|
||||||
Release();
|
Release();
|
||||||
}
|
}
|
||||||
@@ -879,12 +587,6 @@ public:
|
|||||||
#ifdef ARCHITECTURE_arm64
|
#ifdef ARCHITECTURE_arm64
|
||||||
if (True(perms & MemoryPermission::Execute))
|
if (True(perms & MemoryPermission::Execute))
|
||||||
prot_flags |= PROT_EXEC;
|
prot_flags |= PROT_EXEC;
|
||||||
#endif
|
|
||||||
#ifdef __ANDROID__
|
|
||||||
if (ahb_bytes != 0) {
|
|
||||||
MapBackingRange(virtual_offset, host_offset, length, prot_flags);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
int flags = (fd >= 0 ? MAP_SHARED : MAP_PRIVATE) | MAP_FIXED;
|
int flags = (fd >= 0 ? MAP_SHARED : MAP_PRIVATE) | MAP_FIXED;
|
||||||
void* ret = mmap(virtual_base + virtual_offset, length, prot_flags, flags, fd, host_offset);
|
void* ret = mmap(virtual_base + virtual_offset, length, prot_flags, flags, fd, host_offset);
|
||||||
@@ -930,18 +632,8 @@ public:
|
|||||||
virtual_base = nullptr;
|
virtual_base = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsBackingShared() const noexcept {
|
|
||||||
#ifdef __ANDROID__
|
|
||||||
if (ahb_bytes != 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return fd >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const size_t backing_size; ///< Size of the backing memory in bytes
|
const size_t backing_size; ///< Size of the backing memory in bytes
|
||||||
const size_t virtual_size; ///< Size of the virtual address placeholder in bytes
|
const size_t virtual_size; ///< Size of the virtual address placeholder in bytes
|
||||||
const size_t preferred_offset;
|
|
||||||
|
|
||||||
u8* backing_base{reinterpret_cast<u8*>(MAP_FAILED)};
|
u8* backing_base{reinterpret_cast<u8*>(MAP_FAILED)};
|
||||||
u8* virtual_base{reinterpret_cast<u8*>(MAP_FAILED)};
|
u8* virtual_base{reinterpret_cast<u8*>(MAP_FAILED)};
|
||||||
@@ -964,18 +656,6 @@ private:
|
|||||||
int ret = close(fd);
|
int ret = close(fd);
|
||||||
ASSERT_MSG(ret == 0, "close failed: {}", strerror(errno));
|
ASSERT_MSG(ret == 0, "close failed: {}", strerror(errno));
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef __ANDROID__
|
|
||||||
for (AHardwareBuffer* buffer : ahb_windows) {
|
|
||||||
AHardwareBuffer_release(buffer);
|
|
||||||
}
|
|
||||||
ahb_windows.clear();
|
|
||||||
ahb_fds.clear();
|
|
||||||
if (ahb_bytes != 0) {
|
|
||||||
committed_backing_size.store(0, std::memory_order_relaxed);
|
|
||||||
ahb_bytes = 0;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AdjustMap(size_t* virtual_offset, size_t* length) {
|
void AdjustMap(size_t* virtual_offset, size_t* length) {
|
||||||
@@ -1001,19 +681,11 @@ private:
|
|||||||
|
|
||||||
int fd{-1}; // memfd file descriptor, -1 is the error value of memfd_create
|
int fd{-1}; // memfd file descriptor, -1 is the error value of memfd_create
|
||||||
FreeRegionManager free_manager{};
|
FreeRegionManager free_manager{};
|
||||||
|
|
||||||
#ifdef __ANDROID__
|
|
||||||
std::vector<AHardwareBuffer*> ahb_windows;
|
|
||||||
std::vector<int> ahb_fds;
|
|
||||||
size_t ahb_window_size{};
|
|
||||||
size_t ahb_base{};
|
|
||||||
size_t ahb_bytes{};
|
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ^^^ POSIX ^^^
|
#endif // ^^^ POSIX ^^^
|
||||||
|
|
||||||
HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_, size_t preferred_offset_)
|
HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_)
|
||||||
: backing_size(backing_size_)
|
: backing_size(backing_size_)
|
||||||
, virtual_size(virtual_size_)
|
, virtual_size(virtual_size_)
|
||||||
{
|
{
|
||||||
@@ -1025,7 +697,7 @@ HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_, size_t prefer
|
|||||||
#else
|
#else
|
||||||
// Try to allocate a fastmem arena.
|
// Try to allocate a fastmem arena.
|
||||||
// The implementation will fail with std::bad_alloc on errors.
|
// The implementation will fail with std::bad_alloc on errors.
|
||||||
impl = std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment), AlignUp(virtual_size, PageAlignment) + HugePageSize, preferred_offset_);
|
impl = std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment), AlignUp(virtual_size, PageAlignment) + HugePageSize);
|
||||||
if (impl->Init()) {
|
if (impl->Init()) {
|
||||||
backing_base = impl->backing_base;
|
backing_base = impl->backing_base;
|
||||||
virtual_base = impl->virtual_base;
|
virtual_base = impl->virtual_base;
|
||||||
@@ -1095,38 +767,6 @@ void HostMemory::ClearBackingRegion(size_t physical_offset, size_t length, u32 f
|
|||||||
std::memset(backing_base + physical_offset, fill_value, length);
|
std::memset(backing_base + physical_offset, fill_value, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::span<AHardwareBuffer* const> HostMemory::BackingHardwareBuffers() const noexcept {
|
|
||||||
#ifdef __ANDROID__
|
|
||||||
return impl ? impl->AhbWindows() : std::span<AHardwareBuffer* const>{};
|
|
||||||
#else
|
|
||||||
return {};
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t HostMemory::BackingHardwareBufferWindowSize() const noexcept {
|
|
||||||
#ifdef __ANDROID__
|
|
||||||
return impl ? impl->AhbWindowSize() : 0;
|
|
||||||
#else
|
|
||||||
return 0;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HostMemory::IsBackingShared() const noexcept {
|
|
||||||
#if defined(__OPENORBIS__) || defined(__managarm__)
|
|
||||||
return false;
|
|
||||||
#else
|
|
||||||
return impl && impl->IsBackingShared();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t HostMemory::BackingHardwareBufferBase() const noexcept {
|
|
||||||
#ifdef __ANDROID__
|
|
||||||
return impl ? impl->AhbBase() : 0;
|
|
||||||
#else
|
|
||||||
return 0;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void HostMemory::EnableDirectMappedAddress() {
|
void HostMemory::EnableDirectMappedAddress() {
|
||||||
#if !(defined(__OPENORBIS__) || defined(__managarm__))
|
#if !(defined(__OPENORBIS__) || defined(__managarm__))
|
||||||
if (impl) {
|
if (impl) {
|
||||||
|
|||||||
@@ -8,17 +8,12 @@
|
|||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <span>
|
|
||||||
#include "common/common_funcs.h"
|
#include "common/common_funcs.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/virtual_buffer.h"
|
#include "common/virtual_buffer.h"
|
||||||
|
|
||||||
struct AHardwareBuffer;
|
|
||||||
|
|
||||||
namespace Common {
|
namespace Common {
|
||||||
|
|
||||||
[[nodiscard]] u64 GetCommittedBackingSize() noexcept;
|
|
||||||
|
|
||||||
enum class MemoryPermission : u32 {
|
enum class MemoryPermission : u32 {
|
||||||
Read = 1 << 0,
|
Read = 1 << 0,
|
||||||
Write = 1 << 1,
|
Write = 1 << 1,
|
||||||
@@ -33,7 +28,7 @@ DECLARE_ENUM_FLAG_OPERATORS(MemoryPermission)
|
|||||||
*/
|
*/
|
||||||
class HostMemory {
|
class HostMemory {
|
||||||
public:
|
public:
|
||||||
explicit HostMemory(size_t backing_size_, size_t virtual_size_, size_t preferred_offset_ = 0);
|
explicit HostMemory(size_t backing_size_, size_t virtual_size_);
|
||||||
~HostMemory();
|
~HostMemory();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -67,18 +62,6 @@ public:
|
|||||||
return backing_base;
|
return backing_base;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] size_t BackingSize() const noexcept {
|
|
||||||
return backing_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] std::span<AHardwareBuffer* const> BackingHardwareBuffers() const noexcept;
|
|
||||||
|
|
||||||
[[nodiscard]] size_t BackingHardwareBufferWindowSize() const noexcept;
|
|
||||||
|
|
||||||
[[nodiscard]] size_t BackingHardwareBufferBase() const noexcept;
|
|
||||||
|
|
||||||
[[nodiscard]] bool IsBackingShared() const noexcept;
|
|
||||||
|
|
||||||
[[nodiscard]] u8* VirtualBasePointer() noexcept {
|
[[nodiscard]] u8* VirtualBasePointer() noexcept {
|
||||||
return virtual_base;
|
return virtual_base;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,10 +17,6 @@
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <cstdio>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
#include "common/memory_detect.h"
|
#include "common/memory_detect.h"
|
||||||
|
|
||||||
namespace Common {
|
namespace Common {
|
||||||
@@ -73,55 +69,4 @@ const MemoryInfo& GetMemInfo() {
|
|||||||
return mem_info;
|
return mem_info;
|
||||||
}
|
}
|
||||||
|
|
||||||
u64 GetAvailablePhysicalMemory() {
|
|
||||||
#ifdef _WIN32
|
|
||||||
MEMORYSTATUSEX memorystatus;
|
|
||||||
memorystatus.dwLength = sizeof(memorystatus);
|
|
||||||
if (GlobalMemoryStatusEx(&memorystatus)) {
|
|
||||||
return memorystatus.ullAvailPhys;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
#elif defined(__linux__)
|
|
||||||
if (std::FILE* const file = std::fopen("/proc/meminfo", "re")) {
|
|
||||||
char line[256];
|
|
||||||
u64 available = 0;
|
|
||||||
while (std::fgets(line, sizeof(line), file) != nullptr) {
|
|
||||||
if (std::strncmp(line, "MemAvailable:", 13) == 0) {
|
|
||||||
available = std::strtoull(line + 13, nullptr, 10) * 1024ULL;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::fclose(file);
|
|
||||||
if (available != 0) {
|
|
||||||
return available;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
struct sysinfo info;
|
|
||||||
if (sysinfo(&info) == 0) {
|
|
||||||
const u64 unit = info.mem_unit != 0 ? info.mem_unit : 1ULL;
|
|
||||||
return (static_cast<u64>(info.freeram) + static_cast<u64>(info.bufferram)) * unit;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
#else
|
|
||||||
return 0;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
u64 GetMaxMapCount() {
|
|
||||||
#ifdef __linux__
|
|
||||||
if (std::FILE* const file = std::fopen("/proc/sys/vm/max_map_count", "re")) {
|
|
||||||
char line[32];
|
|
||||||
u64 count = 0;
|
|
||||||
if (std::fgets(line, sizeof(line), file) != nullptr) {
|
|
||||||
count = std::strtoull(line, nullptr, 10);
|
|
||||||
}
|
|
||||||
std::fclose(file);
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
#else
|
|
||||||
return 0;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Common
|
} // namespace Common
|
||||||
|
|||||||
@@ -18,8 +18,4 @@ struct MemoryInfo {
|
|||||||
*/
|
*/
|
||||||
[[nodiscard]] const MemoryInfo& GetMemInfo();
|
[[nodiscard]] const MemoryInfo& GetMemInfo();
|
||||||
|
|
||||||
[[nodiscard]] u64 GetAvailablePhysicalMemory();
|
|
||||||
|
|
||||||
[[nodiscard]] u64 GetMaxMapCount();
|
|
||||||
|
|
||||||
} // namespace Common
|
} // namespace Common
|
||||||
|
|||||||
@@ -587,9 +587,6 @@ struct Values {
|
|||||||
SwitchableSetting<bool> use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders",
|
SwitchableSetting<bool> use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders",
|
||||||
Category::RendererHacks};
|
Category::RendererHacks};
|
||||||
|
|
||||||
SwitchableSetting<bool> use_unified_memory{linkage, false, "use_unified_memory",
|
|
||||||
Category::RendererHacks};
|
|
||||||
|
|
||||||
SwitchableSetting<GpuUnswizzleSize> gpu_unswizzle_texture_size{linkage,
|
SwitchableSetting<GpuUnswizzleSize> gpu_unswizzle_texture_size{linkage,
|
||||||
GpuUnswizzleSize::Large,
|
GpuUnswizzleSize::Large,
|
||||||
"gpu_unswizzle_texture_size",
|
"gpu_unswizzle_texture_size",
|
||||||
@@ -638,16 +635,6 @@ struct Values {
|
|||||||
#endif
|
#endif
|
||||||
"vertex_input_dynamic_state", Category::RendererExtensions};
|
"vertex_input_dynamic_state", Category::RendererExtensions};
|
||||||
|
|
||||||
SwitchableSetting<bool> dynamic_rendering{linkage, true, "dynamic_rendering",
|
|
||||||
Category::RendererExtensions};
|
|
||||||
|
|
||||||
SwitchableSetting<bool> workgroup_memory_explicit_layout{
|
|
||||||
linkage, false, "workgroup_memory_explicit_layout", Category::RendererExtensions};
|
|
||||||
|
|
||||||
SwitchableSetting<s32, true> pipeline_worker_count{
|
|
||||||
linkage, 2, 2, 8, "pipeline_worker_count", Category::RendererAdvanced,
|
|
||||||
Specialization::Scalar};
|
|
||||||
|
|
||||||
Setting<bool> renderer_debug{linkage, false, "debug", Category::RendererDebug};
|
Setting<bool> renderer_debug{linkage, false, "debug", Category::RendererDebug};
|
||||||
Setting<bool> renderer_shader_feedback{linkage, false, "shader_feedback",
|
Setting<bool> renderer_shader_feedback{linkage, false, "shader_feedback",
|
||||||
Category::RendererDebug};
|
Category::RendererDebug};
|
||||||
|
|||||||
+21
-293
@@ -1,6 +1,5 @@
|
|||||||
// 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
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
|
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
|
||||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
@@ -40,258 +39,6 @@
|
|||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef __ANDROID__
|
|
||||||
#include <sys/resource.h>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstring>
|
|
||||||
#include <fstream>
|
|
||||||
#include <mutex>
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
constexpr int ANDROID_THREAD_PRIORITY_AUDIO = -16;
|
|
||||||
constexpr int ANDROID_THREAD_PRIORITY_URGENT_DISPLAY = -8;
|
|
||||||
constexpr int ANDROID_THREAD_PRIORITY_DISPLAY = -4;
|
|
||||||
constexpr int ANDROID_THREAD_PRIORITY_DEFAULT = 0;
|
|
||||||
constexpr int ANDROID_THREAD_PRIORITY_BACKGROUND = 10;
|
|
||||||
|
|
||||||
constexpr size_t ANDROID_MINIMUM_PERFORMANCE_CORES = 4;
|
|
||||||
|
|
||||||
enum class CoreGroup {
|
|
||||||
Unrestricted,
|
|
||||||
Performance,
|
|
||||||
Efficiency,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CoreTopology {
|
|
||||||
cpu_set_t allowed;
|
|
||||||
cpu_set_t performance;
|
|
||||||
cpu_set_t efficiency;
|
|
||||||
bool separated;
|
|
||||||
bool initialized;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ThreadPolicy {
|
|
||||||
pid_t tid;
|
|
||||||
CoreGroup group;
|
|
||||||
int nice_value;
|
|
||||||
bool has_nice;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::mutex g_topology_mutex;
|
|
||||||
CoreTopology g_topology{};
|
|
||||||
|
|
||||||
std::mutex g_policy_mutex;
|
|
||||||
|
|
||||||
std::vector<ThreadPolicy>& Policies() {
|
|
||||||
static auto* const policies = new std::vector<ThreadPolicy>();
|
|
||||||
return *policies;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PolicyRegistration {
|
|
||||||
~PolicyRegistration() {
|
|
||||||
const pid_t tid = gettid();
|
|
||||||
std::scoped_lock lock{g_policy_mutex};
|
|
||||||
std::erase_if(Policies(), [tid](const ThreadPolicy& policy) { return policy.tid == tid; });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
thread_local PolicyRegistration t_policy_registration;
|
|
||||||
|
|
||||||
int PossibleCpuCount() {
|
|
||||||
std::ifstream file("/sys/devices/system/cpu/possible");
|
|
||||||
std::string list;
|
|
||||||
if (file && std::getline(file, list) && !list.empty()) {
|
|
||||||
int highest = -1;
|
|
||||||
const char* cursor = list.c_str();
|
|
||||||
while (*cursor != '\0') {
|
|
||||||
char* end = nullptr;
|
|
||||||
const long value = std::strtol(cursor, &end, 10);
|
|
||||||
if (end == cursor) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
highest = (std::max)(highest, static_cast<int>(value));
|
|
||||||
cursor = end;
|
|
||||||
while (*cursor == '-' || *cursor == ',') {
|
|
||||||
++cursor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (highest >= 0) {
|
|
||||||
return (std::min)(highest + 1, CPU_SETSIZE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const long configured = sysconf(_SC_NPROCESSORS_CONF);
|
|
||||||
if (configured > 0) {
|
|
||||||
return static_cast<int>((std::min<long>)(configured, CPU_SETSIZE));
|
|
||||||
}
|
|
||||||
return static_cast<int>((std::min<unsigned>)(std::thread::hardware_concurrency(), CPU_SETSIZE));
|
|
||||||
}
|
|
||||||
|
|
||||||
long ReadCpuScalar(int cpu, const char* node) {
|
|
||||||
long value = 0;
|
|
||||||
std::ifstream file("/sys/devices/system/cpu/cpu" + std::to_string(cpu) + "/" + node);
|
|
||||||
if (!file || !(file >> value) || value <= 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::pair<long, int>> CollectCoreWeights(const cpu_set_t& allowed, int total,
|
|
||||||
const char* node, bool require_all) {
|
|
||||||
std::vector<std::pair<long, int>> cores;
|
|
||||||
for (int cpu = 0; cpu < total; ++cpu) {
|
|
||||||
if (!CPU_ISSET(cpu, &allowed)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const long weight = ReadCpuScalar(cpu, node);
|
|
||||||
if (weight <= 0) {
|
|
||||||
if (require_all) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
LOG_WARNING(Common, "Could not read {} for CPU {}, treating it as an efficiency core",
|
|
||||||
node, cpu);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
cores.emplace_back(weight, cpu);
|
|
||||||
}
|
|
||||||
return cores;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ComputeTopologyLocked() {
|
|
||||||
g_topology.initialized = true;
|
|
||||||
g_topology.separated = false;
|
|
||||||
CPU_ZERO(&g_topology.allowed);
|
|
||||||
CPU_ZERO(&g_topology.performance);
|
|
||||||
CPU_ZERO(&g_topology.efficiency);
|
|
||||||
|
|
||||||
if (sched_getaffinity(getpid(), sizeof(g_topology.allowed), &g_topology.allowed) != 0) {
|
|
||||||
LOG_WARNING(Common, "Could not query process CPU affinity: {}",
|
|
||||||
::Common::GetLastErrorMsg());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int total = PossibleCpuCount();
|
|
||||||
auto cores = CollectCoreWeights(g_topology.allowed, total, "cpu_capacity", true);
|
|
||||||
if (cores.empty()) {
|
|
||||||
cores = CollectCoreWeights(g_topology.allowed, total, "cpufreq/cpuinfo_max_freq", false);
|
|
||||||
}
|
|
||||||
if (cores.empty()) {
|
|
||||||
LOG_WARNING(Common, "Could not determine CPU topology, thread placement is disabled");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::sort(cores.begin(), cores.end(),
|
|
||||||
[](const auto& lhs, const auto& rhs) { return lhs.first > rhs.first; });
|
|
||||||
|
|
||||||
const size_t allowed_count = static_cast<size_t>(CPU_COUNT(&g_topology.allowed));
|
|
||||||
const size_t maximum =
|
|
||||||
allowed_count > 2 * ANDROID_MINIMUM_PERFORMANCE_CORES
|
|
||||||
? allowed_count - ANDROID_MINIMUM_PERFORMANCE_CORES
|
|
||||||
: ANDROID_MINIMUM_PERFORMANCE_CORES;
|
|
||||||
|
|
||||||
size_t taken = 0;
|
|
||||||
long cluster_weight = cores.front().first;
|
|
||||||
for (const auto& [weight, cpu] : cores) {
|
|
||||||
if (weight != cluster_weight) {
|
|
||||||
if (taken >= ANDROID_MINIMUM_PERFORMANCE_CORES) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
cluster_weight = weight;
|
|
||||||
}
|
|
||||||
if (taken >= maximum) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
CPU_SET(cpu, &g_topology.performance);
|
|
||||||
++taken;
|
|
||||||
}
|
|
||||||
if (taken == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int cpu = 0; cpu < total; ++cpu) {
|
|
||||||
if (CPU_ISSET(cpu, &g_topology.allowed) && !CPU_ISSET(cpu, &g_topology.performance)) {
|
|
||||||
CPU_SET(cpu, &g_topology.efficiency);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
g_topology.separated = CPU_COUNT(&g_topology.efficiency) > 0;
|
|
||||||
LOG_INFO(Common, "CPU topology: {} performance cores, {} efficiency cores, separation {}",
|
|
||||||
CPU_COUNT(&g_topology.performance), CPU_COUNT(&g_topology.efficiency),
|
|
||||||
g_topology.separated ? "enabled" : "unavailable");
|
|
||||||
}
|
|
||||||
|
|
||||||
void EnsureTopologyLocked() {
|
|
||||||
if (!g_topology.initialized) {
|
|
||||||
ComputeTopologyLocked();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RefreshTopologyLocked() {
|
|
||||||
if (!g_topology.initialized) {
|
|
||||||
ComputeTopologyLocked();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cpu_set_t current;
|
|
||||||
CPU_ZERO(¤t);
|
|
||||||
if (sched_getaffinity(getpid(), sizeof(current), ¤t) != 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (std::memcmp(¤t, &g_topology.allowed, sizeof(current)) != 0) {
|
|
||||||
ComputeTopologyLocked();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ApplyCoreGroupLocked(pid_t tid, CoreGroup group) {
|
|
||||||
if (!g_topology.separated || group == CoreGroup::Unrestricted) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const cpu_set_t& mask =
|
|
||||||
group == CoreGroup::Performance ? g_topology.performance : g_topology.efficiency;
|
|
||||||
if (CPU_COUNT(&mask) == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (sched_setaffinity(tid, sizeof(mask), &mask) != 0) {
|
|
||||||
LOG_WARNING(Common, "Could not restrict thread {} to its core group: {}", tid,
|
|
||||||
::Common::GetLastErrorMsg());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ThreadPolicy& AcquirePolicyLocked(pid_t tid) {
|
|
||||||
auto& policies = Policies();
|
|
||||||
for (auto& policy : policies) {
|
|
||||||
if (policy.tid == tid) {
|
|
||||||
return policy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return policies.emplace_back(ThreadPolicy{tid, CoreGroup::Unrestricted, 0, false});
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetCurrentThreadCoreGroup(CoreGroup group) {
|
|
||||||
const pid_t tid = gettid();
|
|
||||||
{
|
|
||||||
std::scoped_lock lock{g_topology_mutex};
|
|
||||||
EnsureTopologyLocked();
|
|
||||||
ApplyCoreGroupLocked(tid, group);
|
|
||||||
}
|
|
||||||
(void)&t_policy_registration;
|
|
||||||
std::scoped_lock lock{g_policy_mutex};
|
|
||||||
AcquirePolicyLocked(tid).group = group;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RememberCurrentThreadNice(pid_t tid, int nice_value) {
|
|
||||||
(void)&t_policy_registration;
|
|
||||||
std::scoped_lock lock{g_policy_mutex};
|
|
||||||
ThreadPolicy& policy = AcquirePolicyLocked(tid);
|
|
||||||
policy.nice_value = nice_value;
|
|
||||||
policy.has_nice = true;
|
|
||||||
}
|
|
||||||
} // Anonymous namespace
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "common/cpu_features.h"
|
#include "common/cpu_features.h"
|
||||||
#ifdef ARCHITECTURE_x86_64
|
#ifdef ARCHITECTURE_x86_64
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
@@ -301,6 +48,7 @@ void RememberCurrentThreadNice(pid_t tid, int nice_value) {
|
|||||||
#endif
|
#endif
|
||||||
#include "common/x64/rdtsc.h"
|
#include "common/x64/rdtsc.h"
|
||||||
#endif
|
#endif
|
||||||
|
#include "core/core_timing.h"
|
||||||
|
|
||||||
namespace Common {
|
namespace Common {
|
||||||
|
|
||||||
@@ -330,24 +78,6 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) {
|
|||||||
}
|
}
|
||||||
}();
|
}();
|
||||||
set_thread_priority(find_thread(NULL), priority);
|
set_thread_priority(find_thread(NULL), priority);
|
||||||
#elif defined(__ANDROID__)
|
|
||||||
const int nice_value = [&]() {
|
|
||||||
switch (new_priority) {
|
|
||||||
case ThreadPriority::Low: return ANDROID_THREAD_PRIORITY_BACKGROUND;
|
|
||||||
case ThreadPriority::Normal: return ANDROID_THREAD_PRIORITY_DEFAULT;
|
|
||||||
case ThreadPriority::High: return ANDROID_THREAD_PRIORITY_DISPLAY;
|
|
||||||
case ThreadPriority::VeryHigh: return ANDROID_THREAD_PRIORITY_URGENT_DISPLAY;
|
|
||||||
case ThreadPriority::Critical: return ANDROID_THREAD_PRIORITY_AUDIO;
|
|
||||||
default: return ANDROID_THREAD_PRIORITY_DEFAULT;
|
|
||||||
}
|
|
||||||
}();
|
|
||||||
const pid_t tid = gettid();
|
|
||||||
if (setpriority(PRIO_PROCESS, static_cast<id_t>(tid), nice_value) != 0) {
|
|
||||||
LOG_WARNING(Common, "Could not set thread nice value to {}: {}", nice_value,
|
|
||||||
GetLastErrorMsg());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
RememberCurrentThreadNice(tid, nice_value);
|
|
||||||
#else
|
#else
|
||||||
pthread_t this_thread = pthread_self();
|
pthread_t this_thread = pthread_self();
|
||||||
const auto scheduling_type = SCHED_OTHER;
|
const auto scheduling_type = SCHED_OTHER;
|
||||||
@@ -402,31 +132,29 @@ void SetCurrentThreadName(const char* name) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetCurrentThreadToPerformanceCores() {
|
void PinCurrentThreadToPerformanceCore(size_t core_id) {
|
||||||
|
ASSERT(core_id < 4);
|
||||||
|
// If we set a flag for a CPU that doesn't exist, the thread may not be allowed to
|
||||||
|
// run in ANY processor!
|
||||||
|
auto const total_cores = std::thread::hardware_concurrency();
|
||||||
|
if (core_id < total_cores) {
|
||||||
#if defined(__ANDROID__)
|
#if defined(__ANDROID__)
|
||||||
SetCurrentThreadCoreGroup(CoreGroup::Performance);
|
cpu_set_t set;
|
||||||
|
CPU_ZERO(&set);
|
||||||
|
CPU_SET(core_id, &set);
|
||||||
|
sched_setaffinity(pthread_self(), sizeof(set), &set);
|
||||||
|
#elif defined(__linux__) || defined(__FreeBSD__)
|
||||||
|
cpu_set_t set;
|
||||||
|
CPU_ZERO(&set);
|
||||||
|
CPU_SET(core_id, &set);
|
||||||
|
pthread_setaffinity_np(pthread_self(), sizeof(set), &set);
|
||||||
|
#elif defined(_WIN32)
|
||||||
|
DWORD set = 1UL << core_id;
|
||||||
|
SetThreadAffinityMask(GetCurrentThread(), set);
|
||||||
|
#else
|
||||||
|
// No pin functionality implemented
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetCurrentThreadToEfficiencyCores() {
|
|
||||||
#if defined(__ANDROID__)
|
|
||||||
SetCurrentThreadCoreGroup(CoreGroup::Efficiency);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void RefreshThreadPolicies() {
|
|
||||||
#if defined(__ANDROID__)
|
|
||||||
std::scoped_lock topology_lock{g_topology_mutex};
|
|
||||||
RefreshTopologyLocked();
|
|
||||||
|
|
||||||
std::scoped_lock policy_lock{g_policy_mutex};
|
|
||||||
for (const auto& policy : Policies()) {
|
|
||||||
if (policy.has_nice) {
|
|
||||||
setpriority(PRIO_PROCESS, static_cast<id_t>(policy.tid), policy.nice_value);
|
|
||||||
}
|
|
||||||
ApplyCoreGroupLocked(policy.tid, policy.group);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ARCHITECTURE_x86_64
|
#ifdef ARCHITECTURE_x86_64
|
||||||
|
|||||||
+1
-9
@@ -99,16 +99,8 @@ enum class ThreadPriority : u32 {
|
|||||||
Critical = 4,
|
Critical = 4,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class ThreadPlacement : u32 {
|
|
||||||
Default = 0,
|
|
||||||
Background = 1,
|
|
||||||
Efficiency = 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
void SetCurrentThreadPriority(ThreadPriority new_priority);
|
void SetCurrentThreadPriority(ThreadPriority new_priority);
|
||||||
void SetCurrentThreadName(const char* name);
|
void SetCurrentThreadName(const char* name);
|
||||||
void SetCurrentThreadToPerformanceCores();
|
void PinCurrentThreadToPerformanceCore(size_t core_id);
|
||||||
void SetCurrentThreadToEfficiencyCores();
|
|
||||||
void RefreshThreadPolicies();
|
|
||||||
|
|
||||||
} // namespace Common
|
} // namespace Common
|
||||||
|
|||||||
@@ -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 2020 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||||
@@ -37,17 +37,10 @@ class StatefulThreadWorker {
|
|||||||
using StateMaker = std::conditional_t<with_state, std::function<StateType()>, DummyCallable>;
|
using StateMaker = std::conditional_t<with_state, std::function<StateType()>, DummyCallable>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit StatefulThreadWorker(size_t num_workers, std::string name, StateMaker func = {},
|
explicit StatefulThreadWorker(size_t num_workers, std::string name, StateMaker func = {})
|
||||||
ThreadPlacement placement = ThreadPlacement::Default)
|
|
||||||
: workers_queued{num_workers}, thread_name{std::move(name)} {
|
: workers_queued{num_workers}, thread_name{std::move(name)} {
|
||||||
const auto lambda = [this, func, placement](std::stop_token stop_token) {
|
const auto lambda = [this, func](std::stop_token stop_token) {
|
||||||
Common::SetCurrentThreadName(thread_name.c_str());
|
Common::SetCurrentThreadName(thread_name.c_str());
|
||||||
if (placement != ThreadPlacement::Default) {
|
|
||||||
Common::SetCurrentThreadPriority(ThreadPriority::Low);
|
|
||||||
}
|
|
||||||
if (placement == ThreadPlacement::Efficiency) {
|
|
||||||
Common::SetCurrentThreadToEfficiencyCores();
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
[[maybe_unused]] std::conditional_t<with_state, StateType, int> state{func()};
|
[[maybe_unused]] std::conditional_t<with_state, StateType, int> state{func()};
|
||||||
while (!stop_token.stop_requested()) {
|
while (!stop_token.stop_requested()) {
|
||||||
|
|||||||
@@ -157,8 +157,6 @@ bool ArmNce::HandleGuestAlignmentFault(GuestContext* guest_ctx, void* raw_info,
|
|||||||
return HandleFailedGuestFault(guest_ctx, raw_info, raw_context);
|
return HandleFailedGuestFault(guest_ctx, raw_info, raw_context);
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr size_t NCE_WRITE_FAULT_CLUSTER_PAGES = 4;
|
|
||||||
|
|
||||||
bool ArmNce::HandleGuestAccessFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) {
|
bool ArmNce::HandleGuestAccessFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) {
|
||||||
auto* info = static_cast<siginfo_t*>(raw_info);
|
auto* info = static_cast<siginfo_t*>(raw_info);
|
||||||
|
|
||||||
@@ -167,7 +165,7 @@ bool ArmNce::HandleGuestAccessFault(GuestContext* guest_ctx, void* raw_info, voi
|
|||||||
const Common::ProcessAddress addr =
|
const Common::ProcessAddress addr =
|
||||||
(reinterpret_cast<u64>(info->si_addr) & ~Memory::YUZU_PAGEMASK);
|
(reinterpret_cast<u64>(info->si_addr) & ~Memory::YUZU_PAGEMASK);
|
||||||
auto& memory = guest_ctx->parent->m_running_thread->GetOwnerProcess()->GetMemory();
|
auto& memory = guest_ctx->parent->m_running_thread->GetOwnerProcess()->GetMemory();
|
||||||
if (memory.InvalidateNCE(addr, Memory::YUZU_PAGESIZE * NCE_WRITE_FAULT_CLUSTER_PAGES)) {
|
if (memory.InvalidateNCE(addr, Memory::YUZU_PAGESIZE)) {
|
||||||
// We handled the access successfully and are returning to guest code.
|
// We handled the access successfully and are returning to guest code.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,7 +145,14 @@ bool Patcher::PatchText(std::span<const u8> program_image, const Kernel::CodeSet
|
|||||||
|
|
||||||
// MRS Xn, CNTFRQ_EL0
|
// MRS Xn, CNTFRQ_EL0
|
||||||
if (auto mrs = MRS{inst}; mrs.Verify() && mrs.GetSystemReg() == CntfrqEl0) {
|
if (auto mrs = MRS{inst}; mrs.Verify() && mrs.GetSystemReg() == CntfrqEl0) {
|
||||||
UNREACHABLE();
|
bool pre_buffer = false;
|
||||||
|
auto ret = AddRelocations(pre_buffer);
|
||||||
|
if (pre_buffer) {
|
||||||
|
WriteCntfrqHandler(ret, oaknut::XReg{static_cast<int>(mrs.GetRt())}, c_pre);
|
||||||
|
} else {
|
||||||
|
WriteCntfrqHandler(ret, oaknut::XReg{static_cast<int>(mrs.GetRt())}, c);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// MSR TPIDR_EL0, Xn
|
// MSR TPIDR_EL0, Xn
|
||||||
@@ -577,6 +584,16 @@ void Patcher::WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg,
|
|||||||
this->BranchToModule(module_dest);
|
this->BranchToModule(module_dest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Patcher::WriteCntfrqHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::VectorCodeGenerator& cg) {
|
||||||
|
cg.MOV(dest_reg, Common::WallClock::CNTFRQ);
|
||||||
|
|
||||||
|
// Jump back to the instruction after the emulated MRS.
|
||||||
|
if (&cg == &c_pre)
|
||||||
|
this->BranchToModulePre(module_dest);
|
||||||
|
else
|
||||||
|
this->BranchToModule(module_dest);
|
||||||
|
}
|
||||||
|
|
||||||
void Patcher::WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::VectorCodeGenerator& cg) {
|
void Patcher::WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::VectorCodeGenerator& cg) {
|
||||||
#if defined(HAS_NCE)
|
#if defined(HAS_NCE)
|
||||||
static Common::WallClock clock(false, 1);
|
static Common::WallClock clock(false, 1);
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ private:
|
|||||||
void WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id, oaknut::VectorCodeGenerator& code, oaknut::Label& save_ctx, oaknut::Label& load_ctx);
|
void WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id, oaknut::VectorCodeGenerator& code, oaknut::Label& save_ctx, oaknut::Label& load_ctx);
|
||||||
void WriteMrsHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::SystemReg src_reg, oaknut::VectorCodeGenerator& code);
|
void WriteMrsHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::SystemReg src_reg, oaknut::VectorCodeGenerator& code);
|
||||||
void WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg, oaknut::VectorCodeGenerator& code);
|
void WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg, oaknut::VectorCodeGenerator& code);
|
||||||
|
void WriteCntfrqHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::VectorCodeGenerator& code);
|
||||||
void WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::VectorCodeGenerator& code);
|
void WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::VectorCodeGenerator& code);
|
||||||
|
|
||||||
// Convenience wrappers using default code generator
|
// Convenience wrappers using default code generator
|
||||||
@@ -90,6 +91,7 @@ private:
|
|||||||
void WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id) { WriteSvcTrampoline(module_dest, svc_id, c, m_save_context, m_load_context); }
|
void WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id) { WriteSvcTrampoline(module_dest, svc_id, c, m_save_context, m_load_context); }
|
||||||
void WriteMrsHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::SystemReg src_reg) { WriteMrsHandler(module_dest, dest_reg, src_reg, c); }
|
void WriteMrsHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::SystemReg src_reg) { WriteMrsHandler(module_dest, dest_reg, src_reg, c); }
|
||||||
void WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg) { WriteMsrHandler(module_dest, src_reg, c); }
|
void WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg) { WriteMsrHandler(module_dest, src_reg, c); }
|
||||||
|
void WriteCntfrqHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg) { WriteCntfrqHandler(module_dest, dest_reg, c); }
|
||||||
void WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg) { WriteCntpctHandler(module_dest, dest_reg, c); }
|
void WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg) { WriteCntpctHandler(module_dest, dest_reg, c); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
+1
-5
@@ -118,7 +118,6 @@ struct System::Impl {
|
|||||||
|
|
||||||
is_multicore = Settings::values.use_multi_core.GetValue();
|
is_multicore = Settings::values.use_multi_core.GetValue();
|
||||||
extended_memory_layout = Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb;
|
extended_memory_layout = Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb;
|
||||||
unified_memory = Settings::values.use_unified_memory.GetValue();
|
|
||||||
|
|
||||||
core_timing.SetMulticore(is_multicore);
|
core_timing.SetMulticore(is_multicore);
|
||||||
core_timing.Initialize([&system]() { system.RegisterHostThread(); });
|
core_timing.Initialize([&system]() { system.RegisterHostThread(); });
|
||||||
@@ -146,8 +145,7 @@ struct System::Impl {
|
|||||||
!device_memory.has_value() ||
|
!device_memory.has_value() ||
|
||||||
is_multicore != Settings::values.use_multi_core.GetValue() ||
|
is_multicore != Settings::values.use_multi_core.GetValue() ||
|
||||||
extended_memory_layout != (Settings::values.memory_layout_mode.GetValue() !=
|
extended_memory_layout != (Settings::values.memory_layout_mode.GetValue() !=
|
||||||
Settings::MemoryLayout::Memory_4Gb) ||
|
Settings::MemoryLayout::Memory_4Gb);
|
||||||
unified_memory != Settings::values.use_unified_memory.GetValue();
|
|
||||||
|
|
||||||
if (!must_reinitialize) {
|
if (!must_reinitialize) {
|
||||||
return;
|
return;
|
||||||
@@ -158,7 +156,6 @@ struct System::Impl {
|
|||||||
is_multicore = Settings::values.use_multi_core.GetValue();
|
is_multicore = Settings::values.use_multi_core.GetValue();
|
||||||
extended_memory_layout =
|
extended_memory_layout =
|
||||||
Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb;
|
Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb;
|
||||||
unified_memory = Settings::values.use_unified_memory.GetValue();
|
|
||||||
|
|
||||||
Initialize(system);
|
Initialize(system);
|
||||||
}
|
}
|
||||||
@@ -506,7 +503,6 @@ struct System::Impl {
|
|||||||
std::atomic_bool is_powered_on{};
|
std::atomic_bool is_powered_on{};
|
||||||
bool is_multicore : 1 = false;
|
bool is_multicore : 1 = false;
|
||||||
bool extended_memory_layout : 1 = false;
|
bool extended_memory_layout : 1 = false;
|
||||||
bool unified_memory : 1 = false;
|
|
||||||
bool exit_locked : 1 = false;
|
bool exit_locked : 1 = false;
|
||||||
bool exit_requested : 1 = false;
|
bool exit_requested : 1 = false;
|
||||||
bool nvdec_active : 1 = false;
|
bool nvdec_active : 1 = false;
|
||||||
|
|||||||
@@ -58,8 +58,7 @@ void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) {
|
|||||||
if (is_multicore) {
|
if (is_multicore) {
|
||||||
timer_thread = std::jthread([this](std::stop_token stop_token) {
|
timer_thread = std::jthread([this](std::stop_token stop_token) {
|
||||||
Common::SetCurrentThreadName("HostTiming");
|
Common::SetCurrentThreadName("HostTiming");
|
||||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::VeryHigh);
|
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
|
||||||
Common::SetCurrentThreadToPerformanceCores();
|
|
||||||
on_thread_init();
|
on_thread_init();
|
||||||
has_started = true;
|
has_started = true;
|
||||||
|
|
||||||
|
|||||||
@@ -174,7 +174,12 @@ void CpuManager::RunThread(std::stop_token token, std::size_t core) {
|
|||||||
std::string name = is_multicore ? ("CPUCore_" + std::to_string(core)) : std::string{"CPUThread"};
|
std::string name = is_multicore ? ("CPUCore_" + std::to_string(core)) : std::string{"CPUThread"};
|
||||||
Common::SetCurrentThreadName(name.c_str());
|
Common::SetCurrentThreadName(name.c_str());
|
||||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
|
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
|
||||||
Common::SetCurrentThreadToPerformanceCores();
|
#ifdef __ANDROID__
|
||||||
|
// Aimed specifically for Snapdragon 8 Elite devices
|
||||||
|
// This kills performance on desktop, but boosts perf for UMA devices
|
||||||
|
// like the S8E. Mediatek and Mali likely won't suffer.
|
||||||
|
Common::PinCurrentThreadToPerformanceCore(core);
|
||||||
|
#endif
|
||||||
auto& data = core_data[core];
|
auto& data = core_data[core];
|
||||||
data.host_context = Common::Fiber::ThreadToFiber();
|
data.host_context = Common::Fiber::ThreadToFiber();
|
||||||
|
|
||||||
|
|||||||
@@ -12,18 +12,9 @@ constexpr size_t VirtualReserveSize = 1ULL << 38;
|
|||||||
constexpr size_t VirtualReserveSize = 1ULL << 39;
|
constexpr size_t VirtualReserveSize = 1ULL << 39;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace {
|
|
||||||
size_t ApplicationPoolOffset() {
|
|
||||||
using Init = Kernel::Board::Nintendo::Nx::KSystemControl::Init;
|
|
||||||
const size_t dram_size = Init::GetIntendedMemorySize();
|
|
||||||
const size_t application_pool_size = Init::GetApplicationPoolSize();
|
|
||||||
return dram_size > application_pool_size ? dram_size - application_pool_size : 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DeviceMemory::DeviceMemory()
|
DeviceMemory::DeviceMemory()
|
||||||
: buffer{Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetIntendedMemorySize(),
|
: buffer{Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetIntendedMemorySize(),
|
||||||
VirtualReserveSize, ApplicationPoolOffset()} {}
|
VirtualReserveSize} {}
|
||||||
|
|
||||||
DeviceMemory::~DeviceMemory() = default;
|
DeviceMemory::~DeviceMemory() = default;
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,6 @@
|
|||||||
#include "common/scratch_buffer.h"
|
#include "common/scratch_buffer.h"
|
||||||
#include "common/virtual_buffer.h"
|
#include "common/virtual_buffer.h"
|
||||||
|
|
||||||
struct AHardwareBuffer;
|
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
constexpr size_t DEVICE_PAGEBITS = 12ULL;
|
constexpr size_t DEVICE_PAGEBITS = 12ULL;
|
||||||
@@ -97,34 +95,6 @@ public:
|
|||||||
ApplyOpOnPAddr(address, buffer, operation);
|
ApplyOpOnPAddr(address, buffer, operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
u8* GetPhysicalBase() noexcept {
|
|
||||||
return reinterpret_cast<u8*>(physical_base);
|
|
||||||
}
|
|
||||||
|
|
||||||
const u8* GetPhysicalBase() const noexcept {
|
|
||||||
return reinterpret_cast<const u8*>(physical_base);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t GetPhysicalSize() const noexcept {
|
|
||||||
return physical_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::span<AHardwareBuffer* const> GetBackingHardwareBuffers() const noexcept {
|
|
||||||
return ahb_windows;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t GetBackingHardwareBufferWindowSize() const noexcept {
|
|
||||||
return ahb_window_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t GetBackingHardwareBufferBase() const noexcept {
|
|
||||||
return ahb_base;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsBackingShared() const noexcept {
|
|
||||||
return backing_is_shared;
|
|
||||||
}
|
|
||||||
|
|
||||||
PAddr GetPhysicalRawAddressFromDAddr(DAddr address) const {
|
PAddr GetPhysicalRawAddressFromDAddr(DAddr address) const {
|
||||||
PAddr subbits = PAddr(address & page_mask);
|
PAddr subbits = PAddr(address & page_mask);
|
||||||
auto paddr = tracked_entries[(address >> page_bits)].compressed_physical_ptr;
|
auto paddr = tracked_entries[(address >> page_bits)].compressed_physical_ptr;
|
||||||
@@ -156,10 +126,6 @@ public:
|
|||||||
// New batch API to update multiple ranges with a single lock acquisition.
|
// New batch API to update multiple ranges with a single lock acquisition.
|
||||||
void UpdatePagesCachedBatch(std::span<const std::pair<DAddr, size_t>> ranges, s32 delta);
|
void UpdatePagesCachedBatch(std::span<const std::pair<DAddr, size_t>> ranges, s32 delta);
|
||||||
|
|
||||||
void UpdateTexturePagesCount(DAddr addr, size_t size, s32 delta);
|
|
||||||
|
|
||||||
[[nodiscard]] bool IsRegionTextureCached(DAddr addr, size_t size) const noexcept;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct TranslationEntry {
|
struct TranslationEntry {
|
||||||
DAddr guest_page{};
|
DAddr guest_page{};
|
||||||
@@ -205,11 +171,6 @@ private:
|
|||||||
std::unique_ptr<DeviceMemoryManagerAllocator<Traits>> impl;
|
std::unique_ptr<DeviceMemoryManagerAllocator<Traits>> impl;
|
||||||
|
|
||||||
const uintptr_t physical_base;
|
const uintptr_t physical_base;
|
||||||
const size_t physical_size;
|
|
||||||
const std::span<AHardwareBuffer* const> ahb_windows;
|
|
||||||
const size_t ahb_window_size;
|
|
||||||
const size_t ahb_base;
|
|
||||||
const bool backing_is_shared;
|
|
||||||
DeviceInterface* device_inter;
|
DeviceInterface* device_inter;
|
||||||
|
|
||||||
struct TrackedEntry {
|
struct TrackedEntry {
|
||||||
@@ -273,7 +234,6 @@ private:
|
|||||||
(1ULL << (device_virtual_bits - page_bits)) / subentries;
|
(1ULL << (device_virtual_bits - page_bits)) / subentries;
|
||||||
using CachedPages = std::array<CounterEntry, num_counter_entries>;
|
using CachedPages = std::array<CounterEntry, num_counter_entries>;
|
||||||
std::unique_ptr<CachedPages> cached_pages;
|
std::unique_ptr<CachedPages> cached_pages;
|
||||||
std::unique_ptr<CachedPages> texture_cached_pages;
|
|
||||||
Common::RangeMutex counter_guard;
|
Common::RangeMutex counter_guard;
|
||||||
std::mutex mapping_guard;
|
std::mutex mapping_guard;
|
||||||
|
|
||||||
|
|||||||
@@ -171,18 +171,12 @@ struct DeviceMemoryManagerAllocator {
|
|||||||
template <typename Traits>
|
template <typename Traits>
|
||||||
DeviceMemoryManager<Traits>::DeviceMemoryManager(const DeviceMemory& device_memory_)
|
DeviceMemoryManager<Traits>::DeviceMemoryManager(const DeviceMemory& device_memory_)
|
||||||
: physical_base{uintptr_t(device_memory_.buffer.BackingBasePointer())}
|
: physical_base{uintptr_t(device_memory_.buffer.BackingBasePointer())}
|
||||||
, physical_size{device_memory_.buffer.BackingSize()}
|
|
||||||
, ahb_windows{device_memory_.buffer.BackingHardwareBuffers()}
|
|
||||||
, ahb_window_size{device_memory_.buffer.BackingHardwareBufferWindowSize()}
|
|
||||||
, ahb_base{device_memory_.buffer.BackingHardwareBufferBase()}
|
|
||||||
, backing_is_shared{device_memory_.buffer.IsBackingShared()}
|
|
||||||
, device_inter{nullptr}
|
, device_inter{nullptr}
|
||||||
, compressed_device_addr(1ULL << ((Settings::values.memory_layout_mode.GetValue() == Settings::MemoryLayout::Memory_4Gb ? physical_min_bits : physical_max_bits) - Memory::YUZU_PAGEBITS))
|
, compressed_device_addr(1ULL << ((Settings::values.memory_layout_mode.GetValue() == Settings::MemoryLayout::Memory_4Gb ? physical_min_bits : physical_max_bits) - Memory::YUZU_PAGEBITS))
|
||||||
, tracked_entries(device_as_size >> Memory::YUZU_PAGEBITS)
|
, tracked_entries(device_as_size >> Memory::YUZU_PAGEBITS)
|
||||||
{
|
{
|
||||||
impl = std::make_unique<DeviceMemoryManagerAllocator<Traits>>();
|
impl = std::make_unique<DeviceMemoryManagerAllocator<Traits>>();
|
||||||
cached_pages = std::make_unique<CachedPages>();
|
cached_pages = std::make_unique<CachedPages>();
|
||||||
texture_cached_pages = std::make_unique<CachedPages>();
|
|
||||||
|
|
||||||
const size_t total_virtual = device_as_size >> Memory::YUZU_PAGEBITS;
|
const size_t total_virtual = device_as_size >> Memory::YUZU_PAGEBITS;
|
||||||
for (size_t i = 0; i < total_virtual; i++) {
|
for (size_t i = 0; i < total_virtual; i++) {
|
||||||
@@ -631,28 +625,6 @@ void DeviceMemoryManager<Traits>::UpdatePagesCachedCount(DAddr addr, size_t size
|
|||||||
UpdatePagesCachedCountNoLock(addr, size, delta);
|
UpdatePagesCachedCountNoLock(addr, size, delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Traits>
|
|
||||||
void DeviceMemoryManager<Traits>::UpdateTexturePagesCount(DAddr addr, size_t size, s32 delta) {
|
|
||||||
Common::ScopedRangeLock lk(counter_guard, addr, size);
|
|
||||||
const size_t page_end = Common::DivCeil(addr + size, Memory::YUZU_PAGESIZE);
|
|
||||||
for (size_t page = addr >> Memory::YUZU_PAGEBITS; page != page_end; ++page) {
|
|
||||||
CounterAtomicType& count = texture_cached_pages->at(page >> subentries_shift).Count(page);
|
|
||||||
count.fetch_add(static_cast<CounterType>(delta), std::memory_order_release);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Traits>
|
|
||||||
bool DeviceMemoryManager<Traits>::IsRegionTextureCached(DAddr addr, size_t size) const noexcept {
|
|
||||||
const size_t page_end = Common::DivCeil(addr + size, Memory::YUZU_PAGESIZE);
|
|
||||||
for (size_t page = addr >> Memory::YUZU_PAGEBITS; page != page_end; ++page) {
|
|
||||||
if (texture_cached_pages->at(page >> subentries_shift).Count(page).load(
|
|
||||||
std::memory_order_acquire) != 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Traits>
|
template <typename Traits>
|
||||||
void DeviceMemoryManager<Traits>::UpdatePagesCachedBatch(std::span<const std::pair<DAddr, size_t>> ranges, s32 delta) {
|
void DeviceMemoryManager<Traits>::UpdatePagesCachedBatch(std::span<const std::pair<DAddr, size_t>> ranges, s32 delta) {
|
||||||
if (ranges.empty()) {
|
if (ranges.empty()) {
|
||||||
|
|||||||
@@ -984,6 +984,22 @@ bool RegisteredCache::RemoveExistingEntry(u64 title_id) const {
|
|||||||
return removed_data;
|
return removed_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool RegisteredCache::Delete(const NcaID& id) const {
|
||||||
|
const auto path = GetRelativePathFromNcaID(id, false, true, false);
|
||||||
|
|
||||||
|
const bool is_file = dir->GetFileRelative(path) != nullptr;
|
||||||
|
const bool is_dir = dir->GetDirectoryRelative(path) != nullptr;
|
||||||
|
|
||||||
|
if (is_file) {
|
||||||
|
return dir->DeleteFile(path);
|
||||||
|
}
|
||||||
|
if (is_dir) {
|
||||||
|
return dir->DeleteSubdirectoryRecursive(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
InstallResult RegisteredCache::RawInstallNCA(const NCA& nca, const VfsCopyFunction& copy,
|
InstallResult RegisteredCache::RawInstallNCA(const NCA& nca, const VfsCopyFunction& copy,
|
||||||
bool overwrite_if_exists,
|
bool overwrite_if_exists,
|
||||||
std::optional<NcaID> override_id) {
|
std::optional<NcaID> override_id) {
|
||||||
|
|||||||
@@ -188,6 +188,7 @@ public:
|
|||||||
|
|
||||||
// Removes an existing entry based on title id
|
// Removes an existing entry based on title id
|
||||||
bool RemoveExistingEntry(u64 title_id) const;
|
bool RemoveExistingEntry(u64 title_id) const;
|
||||||
|
bool Delete(const NcaID& id) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
|||||||
@@ -35,6 +35,16 @@ namespace {
|
|||||||
|
|
||||||
constexpr size_t MaxOpenFiles = 8192;
|
constexpr size_t MaxOpenFiles = 8192;
|
||||||
|
|
||||||
|
bool IsWithinRoot(std::string_view root, std::string_view full_path) {
|
||||||
|
if (root.empty())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (full_path.size() < root.size() || full_path.substr(0, root.size()) != root)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return full_path.size() == root.size() || full_path[root.size()] == '/' || full_path[root.size()] == '\\';
|
||||||
|
}
|
||||||
|
|
||||||
constexpr FS::FileAccessMode ModeFlagsToFileAccessMode(OpenMode mode) {
|
constexpr FS::FileAccessMode ModeFlagsToFileAccessMode(OpenMode mode) {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case OpenMode::Read:
|
case OpenMode::Read:
|
||||||
@@ -403,7 +413,8 @@ RealVfsDirectory::~RealVfsDirectory() = default;
|
|||||||
|
|
||||||
VirtualFile RealVfsDirectory::GetFileRelative(std::string_view relative_path) const {
|
VirtualFile RealVfsDirectory::GetFileRelative(std::string_view relative_path) const {
|
||||||
const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path));
|
const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path));
|
||||||
if (!FS::Exists(full_path) || FS::IsDir(full_path)) {
|
if (!FS::Exists(full_path) || FS::IsDir(full_path)
|
||||||
|
|| !IsWithinRoot(FS::SanitizePath(path), full_path)) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
return base.OpenFile(full_path, perms);
|
return base.OpenFile(full_path, perms);
|
||||||
@@ -411,7 +422,8 @@ VirtualFile RealVfsDirectory::GetFileRelative(std::string_view relative_path) co
|
|||||||
|
|
||||||
VirtualDir RealVfsDirectory::GetDirectoryRelative(std::string_view relative_path) const {
|
VirtualDir RealVfsDirectory::GetDirectoryRelative(std::string_view relative_path) const {
|
||||||
const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path));
|
const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path));
|
||||||
if (!FS::Exists(full_path) || !FS::IsDir(full_path)) {
|
if (!FS::Exists(full_path) || !FS::IsDir(full_path)
|
||||||
|
|| !IsWithinRoot(FS::SanitizePath(path), full_path)) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
return base.OpenDirectory(full_path, perms);
|
return base.OpenDirectory(full_path, perms);
|
||||||
@@ -427,7 +439,7 @@ VirtualDir RealVfsDirectory::GetSubdirectory(std::string_view name) const {
|
|||||||
|
|
||||||
VirtualFile RealVfsDirectory::CreateFileRelative(std::string_view relative_path) {
|
VirtualFile RealVfsDirectory::CreateFileRelative(std::string_view relative_path) {
|
||||||
const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path));
|
const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path));
|
||||||
if (!FS::CreateParentDirs(full_path)) {
|
if (!FS::CreateParentDirs(full_path) || !IsWithinRoot(FS::SanitizePath(path), full_path)) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
return base.CreateFile(full_path, perms);
|
return base.CreateFile(full_path, perms);
|
||||||
@@ -440,7 +452,7 @@ VirtualDir RealVfsDirectory::CreateDirectoryRelative(std::string_view relative_p
|
|||||||
|
|
||||||
bool RealVfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) {
|
bool RealVfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) {
|
||||||
const auto full_path = FS::SanitizePath(this->path + '/' + std::string(name));
|
const auto full_path = FS::SanitizePath(this->path + '/' + std::string(name));
|
||||||
return base.DeleteDirectory(full_path);
|
return FS::RemoveDirRecursively(full_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<VirtualFile> RealVfsDirectory::GetFiles() const {
|
std::vector<VirtualFile> RealVfsDirectory::GetFiles() const {
|
||||||
@@ -506,7 +518,7 @@ VirtualFile RealVfsDirectory::CreateFile(std::string_view name) {
|
|||||||
|
|
||||||
bool RealVfsDirectory::DeleteSubdirectory(std::string_view name) {
|
bool RealVfsDirectory::DeleteSubdirectory(std::string_view name) {
|
||||||
const std::string subdir_path = (path + '/').append(name);
|
const std::string subdir_path = (path + '/').append(name);
|
||||||
return base.DeleteDirectory(subdir_path);
|
return FS::RemoveDir(subdir_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RealVfsDirectory::DeleteFile(std::string_view name) {
|
bool RealVfsDirectory::DeleteFile(std::string_view name) {
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ ISelfController::ISelfController(Core::System& system_, std::shared_ptr<Applet>
|
|||||||
{64, nullptr, "SetInputDetectionSourceSet"},
|
{64, nullptr, "SetInputDetectionSourceSet"},
|
||||||
{65, D<&ISelfController::ReportUserIsActive>, "ReportUserIsActive"},
|
{65, D<&ISelfController::ReportUserIsActive>, "ReportUserIsActive"},
|
||||||
{66, nullptr, "GetCurrentIlluminance"},
|
{66, nullptr, "GetCurrentIlluminance"},
|
||||||
{67, nullptr, "IsIlluminanceAvailable"},
|
{67, D<&ISelfController::IsIlluminanceAvailable>, "IsIlluminanceAvailable"},
|
||||||
{68, D<&ISelfController::SetAutoSleepDisabled>, "SetAutoSleepDisabled"},
|
{68, D<&ISelfController::SetAutoSleepDisabled>, "SetAutoSleepDisabled"},
|
||||||
{69, D<&ISelfController::IsAutoSleepDisabled>, "IsAutoSleepDisabled"},
|
{69, D<&ISelfController::IsAutoSleepDisabled>, "IsAutoSleepDisabled"},
|
||||||
{70, nullptr, "ReportMultimediaError"},
|
{70, nullptr, "ReportMultimediaError"},
|
||||||
@@ -347,6 +347,12 @@ Result ISelfController::IsAutoSleepDisabled(Out<bool> out_is_auto_sleep_disabled
|
|||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result ISelfController::IsIlluminanceAvailable(Out<bool> out_is_illuminance_available) {
|
||||||
|
LOG_WARNING(Service_AM, "(stubbed)");
|
||||||
|
*out_is_illuminance_available = false;
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
Result ISelfController::SetInputDetectionPolicy(InputDetectionPolicy input_detection_policy) {
|
Result ISelfController::SetInputDetectionPolicy(InputDetectionPolicy input_detection_policy) {
|
||||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 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
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||||
@@ -60,6 +60,7 @@ private:
|
|||||||
Result ReportUserIsActive();
|
Result ReportUserIsActive();
|
||||||
Result SetAutoSleepDisabled(bool is_auto_sleep_disabled);
|
Result SetAutoSleepDisabled(bool is_auto_sleep_disabled);
|
||||||
Result IsAutoSleepDisabled(Out<bool> out_is_auto_sleep_disabled);
|
Result IsAutoSleepDisabled(Out<bool> out_is_auto_sleep_disabled);
|
||||||
|
Result IsIlluminanceAvailable(Out<bool> out_is_illuminance_available);
|
||||||
Result SetInputDetectionPolicy(InputDetectionPolicy input_detection_policy);
|
Result SetInputDetectionPolicy(InputDetectionPolicy input_detection_policy);
|
||||||
Result GetAccumulatedSuspendedTickValue(Out<u64> out_accumulated_suspended_tick_value);
|
Result GetAccumulatedSuspendedTickValue(Out<u64> out_accumulated_suspended_tick_value);
|
||||||
Result GetAccumulatedSuspendedTickChangedEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
|
Result GetAccumulatedSuspendedTickChangedEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
|
||||||
|
|||||||
@@ -43,6 +43,16 @@ static FileSys::VirtualDir GetDirectoryRelativeWrapped(FileSys::VirtualDir base,
|
|||||||
return base->GetDirectoryRelative(dir_name);
|
return base->GetDirectoryRelative(dir_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::string_view GetGuestParentPath(std::string_view path) {
|
||||||
|
const auto name_index = path.find_last_of("\\/");
|
||||||
|
return name_index == std::string_view::npos ? std::string_view{} : path.substr(0, name_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string_view GetGuestFilename(std::string_view path) {
|
||||||
|
const auto name_index = path.find_last_of("\\/");
|
||||||
|
return name_index == std::string_view::npos ? path : path.substr(name_index + 1);
|
||||||
|
}
|
||||||
|
|
||||||
VfsDirectoryServiceWrapper::VfsDirectoryServiceWrapper(FileSys::VirtualDir backing_)
|
VfsDirectoryServiceWrapper::VfsDirectoryServiceWrapper(FileSys::VirtualDir backing_)
|
||||||
: backing(std::move(backing_)) {}
|
: backing(std::move(backing_)) {}
|
||||||
|
|
||||||
@@ -83,11 +93,12 @@ Result VfsDirectoryServiceWrapper::DeleteFile(const std::string& path_) const {
|
|||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
|
const auto filename = GetGuestFilename(path);
|
||||||
if (dir == nullptr || dir->GetFile(Common::FS::GetFilename(path)) == nullptr) {
|
auto dir = GetDirectoryRelativeWrapped(backing, GetGuestParentPath(path));
|
||||||
|
if (filename.empty() || dir == nullptr || dir->GetFile(filename) == nullptr) {
|
||||||
return FileSys::ResultPathNotFound;
|
return FileSys::ResultPathNotFound;
|
||||||
}
|
}
|
||||||
if (!dir->DeleteFile(Common::FS::GetFilename(path))) {
|
if (!dir->DeleteFile(filename)) {
|
||||||
// TODO(DarkLordZach): Find a better error code for this
|
// TODO(DarkLordZach): Find a better error code for this
|
||||||
return ResultUnknown;
|
return ResultUnknown;
|
||||||
}
|
}
|
||||||
@@ -97,7 +108,9 @@ Result VfsDirectoryServiceWrapper::DeleteFile(const std::string& path_) const {
|
|||||||
|
|
||||||
Result VfsDirectoryServiceWrapper::CreateDirectory(const std::string& path_) const {
|
Result VfsDirectoryServiceWrapper::CreateDirectory(const std::string& path_) const {
|
||||||
std::string path(Common::FS::SanitizePath(path_));
|
std::string path(Common::FS::SanitizePath(path_));
|
||||||
|
if (GetDirectoryRelativeWrapped(backing, path) != nullptr) {
|
||||||
|
return FileSys::ResultPathAlreadyExists;
|
||||||
|
}
|
||||||
// NOTE: This is inaccurate behavior. CreateDirectory is not recursive.
|
// NOTE: This is inaccurate behavior. CreateDirectory is not recursive.
|
||||||
// CreateDirectory should return PathNotFound if the parent directory does not exist.
|
// CreateDirectory should return PathNotFound if the parent directory does not exist.
|
||||||
// This is here temporarily in order to have UMM "work" in the meantime.
|
// This is here temporarily in order to have UMM "work" in the meantime.
|
||||||
@@ -117,8 +130,19 @@ Result VfsDirectoryServiceWrapper::CreateDirectory(const std::string& path_) con
|
|||||||
|
|
||||||
Result VfsDirectoryServiceWrapper::DeleteDirectory(const std::string& path_) const {
|
Result VfsDirectoryServiceWrapper::DeleteDirectory(const std::string& path_) const {
|
||||||
std::string path(Common::FS::SanitizePath(path_));
|
std::string path(Common::FS::SanitizePath(path_));
|
||||||
auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
|
const auto dirname = GetGuestFilename(path);
|
||||||
if (!dir->DeleteSubdirectory(Common::FS::GetFilename(path))) {
|
auto dir = GetDirectoryRelativeWrapped(backing, GetGuestParentPath(path));
|
||||||
|
FileSys::VirtualDir target{};
|
||||||
|
if (!dirname.empty() && dir != nullptr) {
|
||||||
|
target = dir->GetSubdirectory(dirname);
|
||||||
|
}
|
||||||
|
if (target == nullptr) {
|
||||||
|
return FileSys::ResultPathNotFound;
|
||||||
|
}
|
||||||
|
if (!target->GetFiles().empty() || !target->GetSubdirectories().empty()) {
|
||||||
|
return ResultUnknown;
|
||||||
|
}
|
||||||
|
if (!dir->DeleteSubdirectory(dirname)) {
|
||||||
// TODO(DarkLordZach): Find a better error code for this
|
// TODO(DarkLordZach): Find a better error code for this
|
||||||
return ResultUnknown;
|
return ResultUnknown;
|
||||||
}
|
}
|
||||||
@@ -127,8 +151,12 @@ Result VfsDirectoryServiceWrapper::DeleteDirectory(const std::string& path_) con
|
|||||||
|
|
||||||
Result VfsDirectoryServiceWrapper::DeleteDirectoryRecursively(const std::string& path_) const {
|
Result VfsDirectoryServiceWrapper::DeleteDirectoryRecursively(const std::string& path_) const {
|
||||||
std::string path(Common::FS::SanitizePath(path_));
|
std::string path(Common::FS::SanitizePath(path_));
|
||||||
auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
|
const auto dirname = GetGuestFilename(path);
|
||||||
if (!dir->DeleteSubdirectoryRecursive(Common::FS::GetFilename(path))) {
|
auto dir = GetDirectoryRelativeWrapped(backing, GetGuestParentPath(path));
|
||||||
|
if (dirname.empty() || dir == nullptr || dir->GetSubdirectory(dirname) == nullptr) {
|
||||||
|
return FileSys::ResultPathNotFound;
|
||||||
|
}
|
||||||
|
if (!dir->DeleteSubdirectoryRecursive(dirname)) {
|
||||||
// TODO(DarkLordZach): Find a better error code for this
|
// TODO(DarkLordZach): Find a better error code for this
|
||||||
return ResultUnknown;
|
return ResultUnknown;
|
||||||
}
|
}
|
||||||
@@ -137,9 +165,13 @@ Result VfsDirectoryServiceWrapper::DeleteDirectoryRecursively(const std::string&
|
|||||||
|
|
||||||
Result VfsDirectoryServiceWrapper::CleanDirectoryRecursively(const std::string& path) const {
|
Result VfsDirectoryServiceWrapper::CleanDirectoryRecursively(const std::string& path) const {
|
||||||
const std::string sanitized_path(Common::FS::SanitizePath(path));
|
const std::string sanitized_path(Common::FS::SanitizePath(path));
|
||||||
auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(sanitized_path));
|
const auto dirname = GetGuestFilename(sanitized_path);
|
||||||
|
auto dir = GetDirectoryRelativeWrapped(backing, GetGuestParentPath(sanitized_path));
|
||||||
|
|
||||||
if (!dir->CleanSubdirectoryRecursive(Common::FS::GetFilename(sanitized_path))) {
|
if (dirname.empty() || dir == nullptr || dir->GetSubdirectory(dirname) == nullptr) {
|
||||||
|
return FileSys::ResultPathNotFound;
|
||||||
|
}
|
||||||
|
if (!dir->CleanSubdirectoryRecursive(dirname)) {
|
||||||
// TODO(DarkLordZach): Find a better error code for this
|
// TODO(DarkLordZach): Find a better error code for this
|
||||||
return ResultUnknown;
|
return ResultUnknown;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ IHidServer::IHidServer(Core::System& system_, std::shared_ptr<ResourceManager> r
|
|||||||
{3013, nullptr, "SetDebugPadGenericPadMap"}, //21.0.0+
|
{3013, nullptr, "SetDebugPadGenericPadMap"}, //21.0.0+
|
||||||
{3014, nullptr, "GetDebugPadKeyboardMap"}, //21.0.0+
|
{3014, nullptr, "GetDebugPadKeyboardMap"}, //21.0.0+
|
||||||
{3015, nullptr, "SetDebugPadKeyboardMap"}, //21.0.0+
|
{3015, nullptr, "SetDebugPadKeyboardMap"}, //21.0.0+
|
||||||
{3150, nullptr, "SetMouseLibraryVersion"}, //21.0.0+
|
{3150, C<&IHidServer::SetMouseLibraryVersion>, "SetMouseLibraryVersion"}, //21.0.0+
|
||||||
// What? -- {12010, nullptr, "SetButtonConfigLeft"},
|
// What? -- {12010, nullptr, "SetButtonConfigLeft"},
|
||||||
};
|
};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
@@ -1471,6 +1471,12 @@ Result IHidServer::SetTouchScreenResolution(u32 width, u32 height,
|
|||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result IHidServer::SetMouseLibraryVersion(ClientAppletResourceUserId aruid) {
|
||||||
|
LOG_INFO(Service_HID, "(STUBBED) called, applet_resource_user_id={}", aruid.pid);
|
||||||
|
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
std::shared_ptr<ResourceManager> IHidServer::GetResourceManager() {
|
std::shared_ptr<ResourceManager> IHidServer::GetResourceManager() {
|
||||||
resource_manager->Initialize();
|
resource_manager->Initialize();
|
||||||
return resource_manager;
|
return resource_manager;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 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
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
@@ -263,6 +263,7 @@ private:
|
|||||||
Result IsFirmwareUpdateNeededForNotification(Out<bool> out_is_firmware_update_needed,
|
Result IsFirmwareUpdateNeededForNotification(Out<bool> out_is_firmware_update_needed,
|
||||||
s32 unknown, ClientAppletResourceUserId aruid);
|
s32 unknown, ClientAppletResourceUserId aruid);
|
||||||
Result SetTouchScreenResolution(u32 width, u32 height, ClientAppletResourceUserId aruid);
|
Result SetTouchScreenResolution(u32 width, u32 height, ClientAppletResourceUserId aruid);
|
||||||
|
Result SetMouseLibraryVersion(ClientAppletResourceUserId aruid);
|
||||||
|
|
||||||
std::shared_ptr<ResourceManager> resource_manager;
|
std::shared_ptr<ResourceManager> resource_manager;
|
||||||
std::shared_ptr<HidFirmwareSettings> firmware_settings;
|
std::shared_ptr<HidFirmwareSettings> firmware_settings;
|
||||||
|
|||||||
@@ -1,9 +1,19 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// 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 <algorithm>
|
||||||
|
#include <array>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "core/core.h"
|
||||||
|
#include "core/file_sys/registered_cache.h"
|
||||||
#include "core/file_sys/romfs_factory.h"
|
#include "core/file_sys/romfs_factory.h"
|
||||||
|
#include "core/hle/api_version.h"
|
||||||
|
#include "core/hle/service/filesystem/filesystem.h"
|
||||||
#include "core/hle/service/ipc_helpers.h"
|
#include "core/hle/service/ipc_helpers.h"
|
||||||
#include "core/hle/service/ncm/ncm.h"
|
#include "core/hle/service/ncm/ncm.h"
|
||||||
#include "core/hle/service/server_manager.h"
|
#include "core/hle/service/server_manager.h"
|
||||||
@@ -88,6 +98,268 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class IContentStorage final : public ServiceFramework<IContentStorage> {
|
||||||
|
public:
|
||||||
|
explicit IContentStorage(Core::System& system_, FileSys::StorageId id)
|
||||||
|
: ServiceFramework{system_, "IContentStorage"}, storage{id} {
|
||||||
|
// clang-format off
|
||||||
|
static const FunctionInfo functions[] = {
|
||||||
|
{0, &IContentStorage::GeneratePlaceHolderId, "GeneratePlaceHolderId"},
|
||||||
|
{1, &IContentStorage::CreatePlaceHolder, "CreatePlaceHolder"},
|
||||||
|
{2, &IContentStorage::DeletePlaceHolder, "DeletePlaceHolder"},
|
||||||
|
{4, &IContentStorage::WritePlaceHolder, "WritePlaceHolder"},
|
||||||
|
{5, &IContentStorage::Register, "Register"},
|
||||||
|
{6, &IContentStorage::Delete, "Delete"},
|
||||||
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
RegisterHandlers(functions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void GeneratePlaceHolderId(HLERequestContext& ctx) {
|
||||||
|
LOG_DEBUG(Service_NCM, "called");
|
||||||
|
|
||||||
|
IPC::ResponseBuilder rb{ctx, 6};
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
rb.PushRaw(FileSys::PlaceholderCache::Generate());
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreatePlaceHolder(HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp{ctx};
|
||||||
|
[[maybe_unused]] FileSys::NcaID content_id{};
|
||||||
|
FileSys::NcaID placeholder_id{};
|
||||||
|
if constexpr (HLE::ApiVersion::HOS_VERSION_MAJOR >= 16) {
|
||||||
|
placeholder_id = rp.PopRaw<FileSys::NcaID>();
|
||||||
|
content_id = rp.PopRaw<FileSys::NcaID>();
|
||||||
|
} else {
|
||||||
|
content_id = rp.PopRaw<FileSys::NcaID>();
|
||||||
|
placeholder_id = rp.PopRaw<FileSys::NcaID>();
|
||||||
|
}
|
||||||
|
const auto size = rp.Pop<s64>();
|
||||||
|
|
||||||
|
auto* const placeholder_cache =
|
||||||
|
system.GetFileSystemController().GetPlaceholderCacheForStorage(storage);
|
||||||
|
const bool succeeded =
|
||||||
|
placeholder_cache != nullptr && size >= 0 &&
|
||||||
|
(placeholder_cache->Exists(placeholder_id) ||
|
||||||
|
placeholder_cache->Create(placeholder_id, static_cast<u64>(size)));
|
||||||
|
|
||||||
|
if (succeeded) {
|
||||||
|
LOG_DEBUG(Service_NCM, "called, storage_id={}, size={}", static_cast<u32>(storage),
|
||||||
|
size);
|
||||||
|
} else {
|
||||||
|
LOG_WARNING(Service_NCM, "failed, storage_id={}, size={}", static_cast<u32>(storage),
|
||||||
|
size);
|
||||||
|
}
|
||||||
|
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(succeeded ? ResultSuccess : ResultUnknown);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeletePlaceHolder(HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp{ctx};
|
||||||
|
const auto placeholder_id = rp.PopRaw<FileSys::NcaID>();
|
||||||
|
|
||||||
|
auto* const placeholder_cache =
|
||||||
|
system.GetFileSystemController().GetPlaceholderCacheForStorage(storage);
|
||||||
|
const bool succeeded =
|
||||||
|
placeholder_cache != nullptr &&
|
||||||
|
(!placeholder_cache->Exists(placeholder_id) || placeholder_cache->Delete(placeholder_id));
|
||||||
|
|
||||||
|
if (succeeded) {
|
||||||
|
LOG_DEBUG(Service_NCM, "called, storage_id={}", static_cast<u32>(storage));
|
||||||
|
} else {
|
||||||
|
LOG_WARNING(Service_NCM, "failed, storage_id={}", static_cast<u32>(storage));
|
||||||
|
}
|
||||||
|
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(succeeded ? ResultSuccess : ResultUnknown);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WritePlaceHolder(HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp{ctx};
|
||||||
|
const auto placeholder_id = rp.PopRaw<FileSys::NcaID>();
|
||||||
|
const auto offset = rp.Pop<u64>();
|
||||||
|
const auto data = ctx.ReadBuffer();
|
||||||
|
|
||||||
|
auto* const placeholder_cache =
|
||||||
|
system.GetFileSystemController().GetPlaceholderCacheForStorage(storage);
|
||||||
|
const std::vector<u8> write_data{data.begin(), data.end()};
|
||||||
|
const bool succeeded =
|
||||||
|
placeholder_cache != nullptr &&
|
||||||
|
placeholder_cache->Write(placeholder_id, offset, write_data);
|
||||||
|
|
||||||
|
if (succeeded) {
|
||||||
|
LOG_DEBUG(Service_NCM, "called, storage_id={}, offset={}, size={}",
|
||||||
|
static_cast<u32>(storage), offset, data.size());
|
||||||
|
} else {
|
||||||
|
LOG_WARNING(Service_NCM, "failed, storage_id={}, offset={}, size={}",
|
||||||
|
static_cast<u32>(storage), offset, data.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(succeeded ? ResultSuccess : ResultUnknown);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Register(HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp{ctx};
|
||||||
|
FileSys::NcaID content_id{};
|
||||||
|
FileSys::NcaID placeholder_id{};
|
||||||
|
if constexpr (HLE::ApiVersion::HOS_VERSION_MAJOR >= 16) {
|
||||||
|
placeholder_id = rp.PopRaw<FileSys::NcaID>();
|
||||||
|
content_id = rp.PopRaw<FileSys::NcaID>();
|
||||||
|
} else {
|
||||||
|
content_id = rp.PopRaw<FileSys::NcaID>();
|
||||||
|
placeholder_id = rp.PopRaw<FileSys::NcaID>();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& fsc = system.GetFileSystemController();
|
||||||
|
auto* const placeholder_cache = fsc.GetPlaceholderCacheForStorage(storage);
|
||||||
|
auto* const registered_cache = fsc.GetRegisteredCacheForStorage(storage);
|
||||||
|
const bool succeeded =
|
||||||
|
placeholder_cache != nullptr && registered_cache != nullptr &&
|
||||||
|
placeholder_cache->Register(registered_cache, placeholder_id, content_id);
|
||||||
|
|
||||||
|
if (succeeded) {
|
||||||
|
LOG_DEBUG(Service_NCM, "called, storage_id={}", static_cast<u32>(storage));
|
||||||
|
} else {
|
||||||
|
LOG_WARNING(Service_NCM, "failed, storage_id={}", static_cast<u32>(storage));
|
||||||
|
}
|
||||||
|
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(succeeded ? ResultSuccess : ResultUnknown);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Delete(HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp{ctx};
|
||||||
|
const auto content_id = rp.PopRaw<FileSys::NcaID>();
|
||||||
|
|
||||||
|
auto* const registered_cache =
|
||||||
|
system.GetFileSystemController().GetRegisteredCacheForStorage(storage);
|
||||||
|
const bool succeeded = registered_cache != nullptr && registered_cache->Delete(content_id);
|
||||||
|
if (succeeded) {
|
||||||
|
registered_cache->Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (succeeded) {
|
||||||
|
LOG_DEBUG(Service_NCM, "called, storage_id={}", static_cast<u32>(storage));
|
||||||
|
} else {
|
||||||
|
LOG_WARNING(Service_NCM, "failed, storage_id={}", static_cast<u32>(storage));
|
||||||
|
}
|
||||||
|
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(succeeded ? ResultSuccess : ResultUnknown);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileSys::StorageId storage;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IContentMetaDatabase final : public ServiceFramework<IContentMetaDatabase> {
|
||||||
|
public:
|
||||||
|
explicit IContentMetaDatabase(Core::System& system_, FileSys::StorageId id)
|
||||||
|
: ServiceFramework{system_, "IContentMetaDatabase"}, storage{id} {
|
||||||
|
// clang-format off
|
||||||
|
static const FunctionInfo functions[] = {
|
||||||
|
{0, &IContentMetaDatabase::Set, "Set"},
|
||||||
|
{2, &IContentMetaDatabase::Remove, "Remove"},
|
||||||
|
{8, &IContentMetaDatabase::Has, "Has"},
|
||||||
|
{15, &IContentMetaDatabase::Commit, "Commit"},
|
||||||
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
RegisterHandlers(functions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct ContentMetaKey {
|
||||||
|
u64 id;
|
||||||
|
u32 version;
|
||||||
|
FileSys::TitleType type;
|
||||||
|
u8 install_type;
|
||||||
|
std::array<u8, 2> padding;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(ContentMetaKey) == 0x10);
|
||||||
|
|
||||||
|
void Set(HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp{ctx};
|
||||||
|
const auto key = rp.PopRaw<ContentMetaKey>();
|
||||||
|
|
||||||
|
const auto entry_matches = [&key](const ContentMetaKey& entry) {
|
||||||
|
return entry.id == key.id && entry.version == key.version && entry.type == key.type &&
|
||||||
|
entry.install_type == key.install_type;
|
||||||
|
};
|
||||||
|
if (std::find_if(entries.begin(), entries.end(), entry_matches) == entries.end()) {
|
||||||
|
entries.push_back(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DEBUG(Service_NCM,
|
||||||
|
"called, storage_id={}, title_id={:016X}, version={}, type={}, size={}",
|
||||||
|
static_cast<u32>(storage), key.id, key.version, static_cast<u8>(key.type),
|
||||||
|
ctx.GetReadBufferSize());
|
||||||
|
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Remove(HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp{ctx};
|
||||||
|
const auto key = rp.PopRaw<ContentMetaKey>();
|
||||||
|
|
||||||
|
std::erase_if(entries, [&key](const ContentMetaKey& entry) {
|
||||||
|
return entry.id == key.id && entry.version == key.version && entry.type == key.type &&
|
||||||
|
entry.install_type == key.install_type;
|
||||||
|
});
|
||||||
|
|
||||||
|
LOG_DEBUG(Service_NCM, "called, storage_id={}, title_id={:016X}, version={}, type={}",
|
||||||
|
static_cast<u32>(storage), key.id, key.version, static_cast<u8>(key.type));
|
||||||
|
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Has(HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp{ctx};
|
||||||
|
const auto key = rp.PopRaw<ContentMetaKey>();
|
||||||
|
|
||||||
|
const bool has_pending =
|
||||||
|
std::find_if(entries.begin(), entries.end(), [&key](const ContentMetaKey& entry) {
|
||||||
|
return entry.id == key.id && entry.version == key.version &&
|
||||||
|
entry.type == key.type && entry.install_type == key.install_type;
|
||||||
|
}) != entries.end();
|
||||||
|
|
||||||
|
auto* const registered_cache =
|
||||||
|
system.GetFileSystemController().GetRegisteredCacheForStorage(storage);
|
||||||
|
const bool has_registered =
|
||||||
|
registered_cache != nullptr &&
|
||||||
|
registered_cache->HasEntry(key.id, FileSys::ContentRecordType::Meta);
|
||||||
|
|
||||||
|
LOG_DEBUG(Service_NCM, "called, storage_id={}, title_id={:016X}, version={}, type={}, has={}",
|
||||||
|
static_cast<u32>(storage), key.id, key.version, static_cast<u8>(key.type),
|
||||||
|
has_pending || has_registered);
|
||||||
|
|
||||||
|
IPC::ResponseBuilder rb{ctx, 3};
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
rb.Push(has_pending || has_registered);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Commit(HLERequestContext& ctx) {
|
||||||
|
auto* const registered_cache =
|
||||||
|
system.GetFileSystemController().GetRegisteredCacheForStorage(storage);
|
||||||
|
if (registered_cache != nullptr) {
|
||||||
|
registered_cache->Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DEBUG(Service_NCM, "called, storage_id={}", static_cast<u32>(storage));
|
||||||
|
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileSys::StorageId storage;
|
||||||
|
std::vector<ContentMetaKey> entries;
|
||||||
|
};
|
||||||
|
|
||||||
class LR final : public ServiceFramework<LR> {
|
class LR final : public ServiceFramework<LR> {
|
||||||
public:
|
public:
|
||||||
explicit LR(Core::System& system_) : ServiceFramework{system_, "lr"} {
|
explicit LR(Core::System& system_) : ServiceFramework{system_, "lr"} {
|
||||||
@@ -113,8 +385,8 @@ public:
|
|||||||
{1, nullptr, "CreateContentMetaDatabase"},
|
{1, nullptr, "CreateContentMetaDatabase"},
|
||||||
{2, nullptr, "VerifyContentStorage"},
|
{2, nullptr, "VerifyContentStorage"},
|
||||||
{3, nullptr, "VerifyContentMetaDatabase"},
|
{3, nullptr, "VerifyContentMetaDatabase"},
|
||||||
{4, nullptr, "OpenContentStorage"},
|
{4, &NCM::OpenContentStorage, "OpenContentStorage"},
|
||||||
{5, nullptr, "OpenContentMetaDatabase"},
|
{5, &NCM::OpenContentMetaDatabase, "OpenContentMetaDatabase"},
|
||||||
{6, nullptr, "CloseContentStorageForcibly"},
|
{6, nullptr, "CloseContentStorageForcibly"},
|
||||||
{7, nullptr, "CloseContentMetaDatabaseForcibly"},
|
{7, nullptr, "CloseContentMetaDatabaseForcibly"},
|
||||||
{8, nullptr, "CleanupContentMetaDatabase"},
|
{8, nullptr, "CleanupContentMetaDatabase"},
|
||||||
@@ -130,6 +402,29 @@ public:
|
|||||||
|
|
||||||
RegisterHandlers(functions);
|
RegisterHandlers(functions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void OpenContentStorage(HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp{ctx};
|
||||||
|
const auto storage_id = rp.PopEnum<FileSys::StorageId>();
|
||||||
|
|
||||||
|
LOG_DEBUG(Service_NCM, "called, storage_id={}", static_cast<u32>(storage_id));
|
||||||
|
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
rb.PushIpcInterface<IContentStorage>(ctx, system, storage_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenContentMetaDatabase(HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp{ctx};
|
||||||
|
const auto storage_id = rp.PopEnum<FileSys::StorageId>();
|
||||||
|
|
||||||
|
LOG_DEBUG(Service_NCM, "called, storage_id={}", static_cast<u32>(storage_id));
|
||||||
|
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
rb.PushIpcInterface<IContentMetaDatabase>(ctx, system, storage_id);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void LoopProcess(Core::System& system) {
|
void LoopProcess(Core::System& system) {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "core/file_sys/registered_cache.h"
|
#include "core/file_sys/registered_cache.h"
|
||||||
#include "core/hle/service/cmif_serialization.h"
|
#include "core/hle/service/cmif_serialization.h"
|
||||||
#include "core/hle/service/filesystem/filesystem.h"
|
#include "core/hle/service/filesystem/filesystem.h"
|
||||||
|
#include "core/hle/service/ipc_helpers.h"
|
||||||
#include "core/hle/service/ns/application_manager_interface.h"
|
#include "core/hle/service/ns/application_manager_interface.h"
|
||||||
|
|
||||||
#include "core/file_sys/content_archive.h"
|
#include "core/file_sys/content_archive.h"
|
||||||
@@ -19,6 +20,7 @@
|
|||||||
#include "core/launch_timestamp_cache.h"
|
#include "core/launch_timestamp_cache.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace Service::NS {
|
namespace Service::NS {
|
||||||
@@ -36,14 +38,14 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_
|
|||||||
{1, nullptr, "GenerateApplicationRecordCount"},
|
{1, nullptr, "GenerateApplicationRecordCount"},
|
||||||
{2, D<&IApplicationManagerInterface::GetApplicationRecordUpdateSystemEvent>, "GetApplicationRecordUpdateSystemEvent"},
|
{2, D<&IApplicationManagerInterface::GetApplicationRecordUpdateSystemEvent>, "GetApplicationRecordUpdateSystemEvent"},
|
||||||
{3, nullptr, "GetApplicationViewDeprecated"},
|
{3, nullptr, "GetApplicationViewDeprecated"},
|
||||||
{4, nullptr, "DeleteApplicationEntity"},
|
{4, D<&IApplicationManagerInterface::DeleteApplicationEntity>, "DeleteApplicationEntity"},
|
||||||
{5, nullptr, "DeleteApplicationCompletely"},
|
{5, D<&IApplicationManagerInterface::DeleteApplicationCompletely>, "DeleteApplicationCompletely"},
|
||||||
{6, nullptr, "IsAnyApplicationEntityRedundant"},
|
{6, nullptr, "IsAnyApplicationEntityRedundant"},
|
||||||
{7, nullptr, "DeleteRedundantApplicationEntity"},
|
{7, nullptr, "DeleteRedundantApplicationEntity"},
|
||||||
{8, nullptr, "IsApplicationEntityMovable"},
|
{8, nullptr, "IsApplicationEntityMovable"},
|
||||||
{9, nullptr, "MoveApplicationEntity"},
|
{9, nullptr, "MoveApplicationEntity"},
|
||||||
{11, nullptr, "CalculateApplicationOccupiedSize"},
|
{11, nullptr, "CalculateApplicationOccupiedSize"},
|
||||||
{16, nullptr, "PushApplicationRecord"},
|
{16, &IApplicationManagerInterface::PushApplicationRecord, "PushApplicationRecord"},
|
||||||
{17, nullptr, "ListApplicationRecordContentMeta"},
|
{17, nullptr, "ListApplicationRecordContentMeta"},
|
||||||
{19, nullptr, "LaunchApplicationOld"},
|
{19, nullptr, "LaunchApplicationOld"},
|
||||||
{21, nullptr, "GetApplicationContentPath"},
|
{21, nullptr, "GetApplicationContentPath"},
|
||||||
@@ -643,6 +645,27 @@ Result IApplicationManagerInterface::IsAnyApplicationEntityInstalled(
|
|||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result IApplicationManagerInterface::DeleteApplicationEntity(u64 application_id) {
|
||||||
|
LOG_DEBUG(Service_NS, "called, application_id={:016X}", application_id);
|
||||||
|
|
||||||
|
auto& fsc = system.GetFileSystemController();
|
||||||
|
if (auto* const user_cache = fsc.GetUserNANDContents(); user_cache != nullptr) {
|
||||||
|
user_cache->RemoveExistingEntry(application_id);
|
||||||
|
user_cache->Refresh();
|
||||||
|
}
|
||||||
|
if (auto* const sdmc_cache = fsc.GetSDMCContents(); sdmc_cache != nullptr) {
|
||||||
|
sdmc_cache->RemoveExistingEntry(application_id);
|
||||||
|
sdmc_cache->Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
record_update_system_event.Signal(system.Kernel());
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result IApplicationManagerInterface::DeleteApplicationCompletely(u64 application_id) {
|
||||||
|
R_RETURN(DeleteApplicationEntity(application_id));
|
||||||
|
}
|
||||||
|
|
||||||
Result IApplicationManagerInterface::GetApplicationViewDeprecated(
|
Result IApplicationManagerInterface::GetApplicationViewDeprecated(
|
||||||
OutArray<ApplicationViewV19, BufferAttr_HipcMapAlias> out_application_views,
|
OutArray<ApplicationViewV19, BufferAttr_HipcMapAlias> out_application_views,
|
||||||
InArray<u64, BufferAttr_HipcMapAlias> application_ids) {
|
InArray<u64, BufferAttr_HipcMapAlias> application_ids) {
|
||||||
@@ -843,6 +866,29 @@ Result IApplicationManagerInterface::Unknown4053() {
|
|||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IApplicationManagerInterface::PushApplicationRecord(HLERequestContext& ctx) {
|
||||||
|
const auto record = ctx.ReadBuffer();
|
||||||
|
u64 application_id{};
|
||||||
|
if (record.size() >= sizeof(application_id)) {
|
||||||
|
std::memcpy(&application_id, record.data(), sizeof(application_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DEBUG(Service_NS, "called, application_id={:016X}, size={}", application_id, record.size());
|
||||||
|
|
||||||
|
auto& fsc = system.GetFileSystemController();
|
||||||
|
if (auto* const user_cache = fsc.GetUserNANDContents(); user_cache != nullptr) {
|
||||||
|
user_cache->Refresh();
|
||||||
|
}
|
||||||
|
if (auto* const sdmc_cache = fsc.GetSDMCContents(); sdmc_cache != nullptr) {
|
||||||
|
sdmc_cache->Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
record_update_system_event.Signal(system.Kernel());
|
||||||
|
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
void IApplicationManagerInterface::ListApplicationTitle(HLERequestContext& ctx) {
|
void IApplicationManagerInterface::ListApplicationTitle(HLERequestContext& ctx) {
|
||||||
LOG_DEBUG(Service_NS, "called");
|
LOG_DEBUG(Service_NS, "called");
|
||||||
IReadOnlyApplicationControlDataInterface(system).ListApplicationTitle(ctx);
|
IReadOnlyApplicationControlDataInterface(system).ListApplicationTitle(ctx);
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ public:
|
|||||||
Result ResumeAll();
|
Result ResumeAll();
|
||||||
Result IsQualificationTransitionSupportedByProcessId(Out<bool> out_is_supported,
|
Result IsQualificationTransitionSupportedByProcessId(Out<bool> out_is_supported,
|
||||||
u64 process_id);
|
u64 process_id);
|
||||||
|
Result DeleteApplicationEntity(u64 application_id);
|
||||||
|
Result DeleteApplicationCompletely(u64 application_id);
|
||||||
Result GetStorageSize(Out<s64> out_total_space_size, Out<s64> out_free_space_size,
|
Result GetStorageSize(Out<s64> out_total_space_size, Out<s64> out_free_space_size,
|
||||||
FileSys::StorageId storage_id);
|
FileSys::StorageId storage_id);
|
||||||
Result TouchApplication(u64 application_id);
|
Result TouchApplication(u64 application_id);
|
||||||
@@ -74,6 +76,7 @@ public:
|
|||||||
Result RequestDownloadApplicationControlDataInBackground(u64 control_source,
|
Result RequestDownloadApplicationControlDataInBackground(u64 control_source,
|
||||||
u64 application_id);
|
u64 application_id);
|
||||||
|
|
||||||
|
void PushApplicationRecord(HLERequestContext& ctx);
|
||||||
void ListApplicationTitle(HLERequestContext& ctx);
|
void ListApplicationTitle(HLERequestContext& ctx);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -375,24 +375,20 @@ NvResult nvhost_as_gpu::MapBufferEx(IoctlMapBufferEx& params) {
|
|||||||
mapping_map.insert_or_assign(params.offset, Mapping(params.handle, device_address, params.offset, size, false, big_page, false));
|
mapping_map.insert_or_assign(params.offset, Mapping(params.handle, device_address, params.offset, size, false, big_page, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
map_buffer_offsets.insert(params.offset);
|
||||||
|
|
||||||
return NvResult::Success;
|
return NvResult::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
NvResult nvhost_as_gpu::UnmapBuffer(IoctlUnmapBuffer& params) {
|
NvResult nvhost_as_gpu::UnmapBuffer(IoctlUnmapBuffer& params) {
|
||||||
LOG_DEBUG(Service_NVDRV, "called, offset={:#X}", params.offset);
|
|
||||||
|
|
||||||
std::scoped_lock lock(mutex);
|
std::scoped_lock lock(mutex);
|
||||||
|
if (auto const offset_it = map_buffer_offsets.find(params.offset); offset_it != map_buffer_offsets.end()) {
|
||||||
|
LOG_DEBUG(Service_NVDRV, "called, offset={:#X}", params.offset);
|
||||||
if (!vm.initialised) {
|
if (!vm.initialised) {
|
||||||
return NvResult::BadValue;
|
return NvResult::BadValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto const it = mapping_map.find(params.offset);
|
auto const it = mapping_map.find(params.offset);
|
||||||
if (it == mapping_map.end()) {
|
|
||||||
LOG_WARNING(Service_NVDRV, "Couldn't find region to unmap at {:#X}", params.offset);
|
|
||||||
return NvResult::Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto const mapping = it->second;
|
auto const mapping = it->second;
|
||||||
if (!mapping.fixed) {
|
if (!mapping.fixed) {
|
||||||
auto& allocator{mapping.big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
|
auto& allocator{mapping.big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
|
||||||
@@ -409,8 +405,9 @@ NvResult nvhost_as_gpu::UnmapBuffer(IoctlUnmapBuffer& params) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
nvmap.UnpinHandle(mapping.handle);
|
nvmap.UnpinHandle(mapping.handle);
|
||||||
mapping_map.erase(it);
|
mapping_map.erase(params.offset);
|
||||||
|
map_buffer_offsets.erase(params.offset);
|
||||||
|
}
|
||||||
return NvResult::Success;
|
return NvResult::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <ankerl/unordered_dense.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "common/address_space.h"
|
#include "common/address_space.h"
|
||||||
@@ -112,6 +113,8 @@ private:
|
|||||||
};
|
};
|
||||||
static_assert(sizeof(IoctlRemapEntry) == 20, "IoctlRemapEntry is incorrect size");
|
static_assert(sizeof(IoctlRemapEntry) == 20, "IoctlRemapEntry is incorrect size");
|
||||||
|
|
||||||
|
ankerl::unordered_dense::set<s64_le> map_buffer_offsets{};
|
||||||
|
|
||||||
struct IoctlMapBufferEx {
|
struct IoctlMapBufferEx {
|
||||||
MappingFlags flags{}; // bit0: fixed_offset, bit2: cacheable
|
MappingFlags flags{}; // bit0: fixed_offset, bit2: cacheable
|
||||||
u32_le kind{}; // -1 is default
|
u32_le kind{}; // -1 is default
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
// 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 <algorithm>
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/logging.h"
|
#include "common/logging.h"
|
||||||
@@ -265,7 +264,7 @@ NvResult nvhost_ctrl_gpu::ZCullGetInfo(IoctlNvgpuGpuZcullGetInfoArgs& params) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NvResult nvhost_ctrl_gpu::ZBCSetTable(IoctlZbcSetTable& params) {
|
NvResult nvhost_ctrl_gpu::ZBCSetTable(IoctlZbcSetTable& params) {
|
||||||
if (params.type == 0 || params.type > supported_types) {
|
if (params.type > supported_types) {
|
||||||
LOG_ERROR(Service_NVDRV, "ZBCSetTable: invalid type {:#X}", params.type);
|
LOG_ERROR(Service_NVDRV, "ZBCSetTable: invalid type {:#X}", params.type);
|
||||||
return NvResult::BadParameter;
|
return NvResult::BadParameter;
|
||||||
}
|
}
|
||||||
@@ -280,61 +279,42 @@ NvResult nvhost_ctrl_gpu::ZBCSetTable(IoctlZbcSetTable& params) {
|
|||||||
color_entry.format = params.format;
|
color_entry.format = params.format;
|
||||||
color_entry.ref_cnt = 1u;
|
color_entry.ref_cnt = 1u;
|
||||||
|
|
||||||
const auto color_end = zbc_colors.begin() + zbc_used_color_entries;
|
auto color_it = std::ranges::find_if(zbc_colors,
|
||||||
auto color_it = std::find_if(zbc_colors.begin(), color_end,
|
|
||||||
[&](const ZbcColorEntry& color_in_question) {
|
[&](const ZbcColorEntry& color_in_question) {
|
||||||
return color_entry.format == color_in_question.format &&
|
return color_entry.format == color_in_question.format &&
|
||||||
color_entry.color_ds == color_in_question.color_ds &&
|
color_entry.color_ds == color_in_question.color_ds &&
|
||||||
color_entry.color_l2 == color_in_question.color_l2;
|
color_entry.color_l2 == color_in_question.color_l2;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (color_it != color_end) {
|
if (color_it != zbc_colors.end()) {
|
||||||
++color_it->ref_cnt;
|
++color_it->ref_cnt;
|
||||||
LOG_DEBUG(Service_NVDRV, "ZBCSetTable: reused color entry fmt={:#X}, ref_cnt={:#X}",
|
LOG_DEBUG(Service_NVDRV, "ZBCSetTable: reused color entry fmt={:#X}, ref_cnt={:#X}",
|
||||||
params.format, color_it->ref_cnt);
|
params.format, color_it->ref_cnt);
|
||||||
break;
|
} else {
|
||||||
}
|
zbc_colors.push_back(color_entry);
|
||||||
|
|
||||||
if (zbc_used_color_entries >= zbc_table_size) {
|
|
||||||
LOG_WARNING(Service_NVDRV, "ZBCSetTable: color table is full, fmt={:#X}",
|
|
||||||
params.format);
|
|
||||||
return NvResult::InsufficientMemory;
|
|
||||||
}
|
|
||||||
|
|
||||||
zbc_colors[zbc_used_color_entries] = color_entry;
|
|
||||||
LOG_DEBUG(Service_NVDRV, "ZBCSetTable: added color entry fmt={:#X}, index={:#X}",
|
LOG_DEBUG(Service_NVDRV, "ZBCSetTable: added color entry fmt={:#X}, index={:#X}",
|
||||||
params.format, zbc_used_color_entries);
|
params.format, zbc_colors.size() - 1);
|
||||||
++zbc_used_color_entries;
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ZBCTypes::depth: {
|
case ZBCTypes::depth: {
|
||||||
ZbcDepthEntry depth_entry{params.depth, params.format, 1u};
|
ZbcDepthEntry depth_entry{params.depth, params.format, 1u};
|
||||||
|
|
||||||
const auto depth_end = zbc_depths.begin() + zbc_used_depth_entries;
|
auto depth_it = std::ranges::find_if(zbc_depths,
|
||||||
auto depth_it = std::find_if(zbc_depths.begin(), depth_end,
|
|
||||||
[&](const ZbcDepthEntry& depth_entry_in_question) {
|
[&](const ZbcDepthEntry& depth_entry_in_question) {
|
||||||
return depth_entry.format == depth_entry_in_question.format &&
|
return depth_entry.format == depth_entry_in_question.format &&
|
||||||
depth_entry.depth == depth_entry_in_question.depth;
|
depth_entry.depth == depth_entry_in_question.depth;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (depth_it != depth_end) {
|
if (depth_it != zbc_depths.end()) {
|
||||||
++depth_it->ref_cnt;
|
++depth_it->ref_cnt;
|
||||||
LOG_DEBUG(Service_NVDRV, "ZBCSetTable: reused depth entry fmt={:#X}, ref_cnt={:#X}",
|
LOG_DEBUG(Service_NVDRV, "ZBCSetTable: reused depth entry fmt={:#X}, ref_cnt={:#X}",
|
||||||
depth_entry.format, depth_it->ref_cnt);
|
depth_entry.format, depth_it->ref_cnt);
|
||||||
break;
|
} else {
|
||||||
}
|
zbc_depths.push_back(depth_entry);
|
||||||
|
|
||||||
if (zbc_used_depth_entries >= zbc_table_size) {
|
|
||||||
LOG_WARNING(Service_NVDRV, "ZBCSetTable: depth table is full, fmt={:#X}",
|
|
||||||
depth_entry.format);
|
|
||||||
return NvResult::InsufficientMemory;
|
|
||||||
}
|
|
||||||
|
|
||||||
zbc_depths[zbc_used_depth_entries] = depth_entry;
|
|
||||||
LOG_DEBUG(Service_NVDRV, "ZBCSetTable: added depth entry fmt={:#X}, index={:#X}",
|
LOG_DEBUG(Service_NVDRV, "ZBCSetTable: added depth entry fmt={:#X}, index={:#X}",
|
||||||
depth_entry.format, zbc_used_depth_entries);
|
depth_entry.format, zbc_depths.size() - 1);
|
||||||
++zbc_used_depth_entries;
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,34 +329,35 @@ NvResult nvhost_ctrl_gpu::ZBCQueryTable(IoctlZbcQueryTable& params) {
|
|||||||
|
|
||||||
std::scoped_lock lk(zbc_mutex);
|
std::scoped_lock lk(zbc_mutex);
|
||||||
|
|
||||||
if (params.type == 0) {
|
switch (static_cast<ZBCTypes>(params.type)) {
|
||||||
params.index_size = zbc_table_size;
|
case ZBCTypes::color: {
|
||||||
return NvResult::Success;
|
if (params.index_size >= zbc_colors.size()) {
|
||||||
}
|
LOG_ERROR(Service_NVDRV, "ZBCQueryTable: invalid color index {:#X}", params.index_size);
|
||||||
|
|
||||||
if (params.index_size >= zbc_table_size) {
|
|
||||||
LOG_ERROR(Service_NVDRV, "ZBCQueryTable: invalid index {:#X}", params.index_size);
|
|
||||||
return NvResult::BadParameter;
|
return NvResult::BadParameter;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (static_cast<ZBCTypes>(params.type)) {
|
|
||||||
case ZBCTypes::color: {
|
|
||||||
const auto& colors = zbc_colors[params.index_size];
|
const auto& colors = zbc_colors[params.index_size];
|
||||||
std::copy_n(colors.color_ds.begin(), colors.color_ds.size(), std::begin(params.color_ds));
|
std::copy_n(colors.color_ds.begin(), colors.color_ds.size(), std::begin(params.color_ds));
|
||||||
std::copy_n(colors.color_l2.begin(), colors.color_l2.size(), std::begin(params.color_l2));
|
std::copy_n(colors.color_l2.begin(), colors.color_l2.size(), std::begin(params.color_l2));
|
||||||
params.depth = 0;
|
params.depth = 0;
|
||||||
params.ref_cnt = colors.ref_cnt;
|
params.ref_cnt = colors.ref_cnt;
|
||||||
params.format = colors.format;
|
params.format = colors.format;
|
||||||
|
params.index_size = static_cast<u32>(zbc_colors.size());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ZBCTypes::depth: {
|
case ZBCTypes::depth: {
|
||||||
|
if (params.index_size >= zbc_depths.size()) {
|
||||||
|
LOG_ERROR(Service_NVDRV, "ZBCQueryTable: invalid depth index {:#X}", params.index_size);
|
||||||
|
return NvResult::BadParameter;
|
||||||
|
}
|
||||||
|
|
||||||
const auto& depth_entry = zbc_depths[params.index_size];
|
const auto& depth_entry = zbc_depths[params.index_size];
|
||||||
std::fill(std::begin(params.color_ds), std::end(params.color_ds), 0);
|
std::fill(std::begin(params.color_ds), std::end(params.color_ds), 0);
|
||||||
std::fill(std::begin(params.color_l2), std::end(params.color_l2), 0);
|
std::fill(std::begin(params.color_l2), std::end(params.color_l2), 0);
|
||||||
params.depth = depth_entry.depth;
|
params.depth = depth_entry.depth;
|
||||||
params.ref_cnt = depth_entry.ref_cnt;
|
params.ref_cnt = depth_entry.ref_cnt;
|
||||||
params.format = depth_entry.format;
|
params.format = depth_entry.format;
|
||||||
break;
|
params.index_size = static_cast<u32>(zbc_depths.size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <array>
|
#include <vector>
|
||||||
|
|
||||||
#include "common/common_funcs.h"
|
#include "common/common_funcs.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
@@ -212,13 +212,9 @@ private:
|
|||||||
Kernel::KEvent* unknown_event;
|
Kernel::KEvent* unknown_event;
|
||||||
|
|
||||||
// ZBC Tables
|
// ZBC Tables
|
||||||
static constexpr u32 zbc_table_size = 15u;
|
|
||||||
|
|
||||||
std::mutex zbc_mutex{};
|
std::mutex zbc_mutex{};
|
||||||
std::array<ZbcColorEntry, zbc_table_size> zbc_colors{};
|
std::vector<ZbcColorEntry> zbc_colors{};
|
||||||
std::array<ZbcDepthEntry, zbc_table_size> zbc_depths{};
|
std::vector<ZbcDepthEntry> zbc_depths{};
|
||||||
u32 zbc_used_color_entries{};
|
|
||||||
u32 zbc_used_depth_entries{};
|
|
||||||
const u32 supported_types = 2u;
|
const u32 supported_types = 2u;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -174,9 +174,7 @@ NvResult nvhost_gpu::SetChannelPriority(IoctlChannelSetPriority& params) {
|
|||||||
case ChannelPriority::Low: channel_timeslice = 1300; break;
|
case ChannelPriority::Low: channel_timeslice = 1300; break;
|
||||||
case ChannelPriority::Medium: channel_timeslice = 2600; break;
|
case ChannelPriority::Medium: channel_timeslice = 2600; break;
|
||||||
case ChannelPriority::High: channel_timeslice = 5200; break;
|
case ChannelPriority::High: channel_timeslice = 5200; break;
|
||||||
default:
|
default : return NvResult::BadParameter;
|
||||||
LOG_WARNING(Service_NVDRV, "unknown channel priority {:#X}", channel_priority);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return NvResult::Success;
|
return NvResult::Success;
|
||||||
@@ -280,20 +278,18 @@ NvResult nvhost_gpu::AllocateObjectContext(IoctlAllocObjCtx& params) {
|
|||||||
params.flags = allowed_mask;
|
params.flags = allowed_mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
params.obj_id = 0;
|
|
||||||
|
|
||||||
s32_le ctx_class_number_index =
|
s32_le ctx_class_number_index =
|
||||||
GetObjectContextClassNumberIndex(static_cast<CtxClasses>(params.class_num));
|
GetObjectContextClassNumberIndex(static_cast<CtxClasses>(params.class_num));
|
||||||
if (ctx_class_number_index < 0) {
|
if (ctx_class_number_index < 0) {
|
||||||
LOG_WARNING(Service_NVDRV, "Untracked class number for object context: {:#X}",
|
LOG_ERROR(Service_NVDRV, "Invalid class number for object context: {:#X}",
|
||||||
params.class_num);
|
params.class_num);
|
||||||
return NvResult::Success;
|
return NvResult::BadParameter;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctxObjs[ctx_class_number_index].has_value()) {
|
if (ctxObjs[ctx_class_number_index].has_value()) {
|
||||||
LOG_DEBUG(Service_NVDRV, "Object context for class {:#X} already allocated on this channel",
|
LOG_WARNING(Service_NVDRV, "Object context for class {:#X} already allocated on this channel",
|
||||||
params.class_num);
|
params.class_num);
|
||||||
return NvResult::Success;
|
return NvResult::AlreadyAllocated;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defer actual hardware context binding until channel is initialized.
|
// Defer actual hardware context binding until channel is initialized.
|
||||||
@@ -439,6 +435,10 @@ NvResult nvhost_gpu::ChannelSetTimeout(IoctlChannelSetTimeout& params) {
|
|||||||
NvResult nvhost_gpu::ChannelSetTimeslice(IoctlSetTimeslice& params) {
|
NvResult nvhost_gpu::ChannelSetTimeslice(IoctlSetTimeslice& params) {
|
||||||
LOG_INFO(Service_NVDRV, "called, timeslice={:#X}", params.timeslice);
|
LOG_INFO(Service_NVDRV, "called, timeslice={:#X}", params.timeslice);
|
||||||
|
|
||||||
|
if (params.timeslice < 1000 || params.timeslice > 5000) {
|
||||||
|
return NvResult::BadParameter;
|
||||||
|
}
|
||||||
|
|
||||||
channel_timeslice = params.timeslice;
|
channel_timeslice = params.timeslice;
|
||||||
|
|
||||||
return NvResult::Success;
|
return NvResult::Success;
|
||||||
|
|||||||
@@ -20,23 +20,33 @@ BufferQueueCore::~BufferQueueCore() = default;
|
|||||||
void BufferQueueCore::PushHistory(u64 frame_number, s64 queue_time, s64 presentation_time, BufferState state) {
|
void BufferQueueCore::PushHistory(u64 frame_number, s64 queue_time, s64 presentation_time, BufferState state) {
|
||||||
std::lock_guard lk(buffer_history_mutex);
|
std::lock_guard lk(buffer_history_mutex);
|
||||||
|
|
||||||
buffer_history_pos = (buffer_history_pos + 1) % BUFFER_HISTORY_SIZE;
|
auto it = buffer_history_map.find(frame_number);
|
||||||
buffer_history[buffer_history_pos] = BufferHistoryInfo{
|
if (it != buffer_history_map.end()) {
|
||||||
|
it->second.state = state;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer_history_map.emplace(frame_number, BufferHistoryInfo{
|
||||||
frame_number,
|
frame_number,
|
||||||
queue_time,
|
queue_time,
|
||||||
presentation_time,
|
presentation_time,
|
||||||
state
|
state
|
||||||
};
|
});
|
||||||
|
buffer_history_order.push_back(frame_number);
|
||||||
|
|
||||||
|
if (buffer_history_order.size() > BUFFER_HISTORY_SIZE) {
|
||||||
|
u64 oldest_frame = buffer_history_order.front();
|
||||||
|
buffer_history_order.pop_front();
|
||||||
|
buffer_history_map.erase(oldest_frame);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BufferQueueCore::UpdateHistory(u64 frame_number, BufferState state) {
|
void BufferQueueCore::UpdateHistory(u64 frame_number, BufferState state) {
|
||||||
std::lock_guard lk(buffer_history_mutex);
|
std::lock_guard lk(buffer_history_mutex);
|
||||||
|
|
||||||
for (auto& entry : buffer_history) {
|
auto it = buffer_history_map.find(frame_number);
|
||||||
if (entry.frame_number == frame_number) {
|
if (it != buffer_history_map.end()) {
|
||||||
entry.state = state;
|
it->second.state = state;
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,13 +9,14 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
|
#include <deque>
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <unordered_map>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
#include "core/hle/service/nvnflinger/buffer_item.h"
|
#include "core/hle/service/nvnflinger/buffer_item.h"
|
||||||
@@ -27,15 +28,12 @@
|
|||||||
|
|
||||||
namespace Service::android {
|
namespace Service::android {
|
||||||
|
|
||||||
#pragma pack(push, 1)
|
|
||||||
struct BufferHistoryInfo {
|
struct BufferHistoryInfo {
|
||||||
u64 frame_number;
|
u64 frame_number{};
|
||||||
s64 queue_time;
|
s64 queue_time{};
|
||||||
s64 presentation_time;
|
s64 presentation_time{};
|
||||||
BufferState state;
|
BufferState state{};
|
||||||
};
|
};
|
||||||
#pragma pack(pop)
|
|
||||||
static_assert(sizeof(BufferHistoryInfo) == 0x1C, "BufferHistoryInfo must be 28 bytes");
|
|
||||||
|
|
||||||
class IConsumerListener;
|
class IConsumerListener;
|
||||||
class IProducerListener;
|
class IProducerListener;
|
||||||
@@ -90,9 +88,9 @@ private:
|
|||||||
bool buffer_has_been_queued{};
|
bool buffer_has_been_queued{};
|
||||||
u64 frame_counter{};
|
u64 frame_counter{};
|
||||||
|
|
||||||
std::array<BufferHistoryInfo, BUFFER_HISTORY_SIZE> buffer_history{};
|
std::unordered_map<u64, BufferHistoryInfo> buffer_history_map{};
|
||||||
u32 buffer_history_pos{BUFFER_HISTORY_SIZE - 1};
|
|
||||||
mutable std::mutex buffer_history_mutex{};
|
mutable std::mutex buffer_history_mutex{};
|
||||||
|
std::deque<u64> buffer_history_order;
|
||||||
|
|
||||||
u32 transform_hint{};
|
u32 transform_hint{};
|
||||||
bool is_allocating{};
|
bool is_allocating{};
|
||||||
|
|||||||
@@ -507,8 +507,6 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
|
|||||||
|
|
||||||
sticky_transform = sticky_transform_;
|
sticky_transform = sticky_transform_;
|
||||||
|
|
||||||
const bool track_history = Settings::values.enable_buffer_history.GetValue();
|
|
||||||
|
|
||||||
if (core->queue.empty()) {
|
if (core->queue.empty()) {
|
||||||
core->queue.push_back(item);
|
core->queue.push_back(item);
|
||||||
listener_available = core->consumer_listener;
|
listener_available = core->consumer_listener;
|
||||||
@@ -516,7 +514,7 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
|
|||||||
auto front = core->queue.begin();
|
auto front = core->queue.begin();
|
||||||
if (front->is_droppable && core->StillTracking(*front)) {
|
if (front->is_droppable && core->StillTracking(*front)) {
|
||||||
slots[front->slot].buffer_state = BufferState::Free;
|
slots[front->slot].buffer_state = BufferState::Free;
|
||||||
if (track_history) {
|
if (Settings::values.enable_buffer_history.GetValue()) {
|
||||||
core->UpdateHistory(front->frame_number, BufferState::Free);
|
core->UpdateHistory(front->frame_number, BufferState::Free);
|
||||||
}
|
}
|
||||||
slots[front->slot].frame_number = 0;
|
slots[front->slot].frame_number = 0;
|
||||||
@@ -531,7 +529,7 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (track_history) {
|
if (Settings::values.enable_buffer_history.GetValue()) {
|
||||||
core->PushHistory(core->frame_counter, slots[slot].queue_time, slots[slot].presentation_time, BufferState::Queued);
|
core->PushHistory(core->frame_counter, slots[slot].queue_time, slots[slot].presentation_time, BufferState::Queued);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -904,31 +902,26 @@ void BufferQueueProducer::Transact(u32 code, std::span<const u8> parcel_data,
|
|||||||
|
|
||||||
const s32 request = parcel_in.Read<s32>();
|
const s32 request = parcel_in.Read<s32>();
|
||||||
if (request <= 0) {
|
if (request <= 0) {
|
||||||
status = Status::BadValue;
|
parcel_out.Write(Status::BadValue);
|
||||||
parcel_out.Write<s32>(0);
|
parcel_out.Write<s32>(0);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr u32 history_size = BufferQueueCore::BUFFER_HISTORY_SIZE;
|
std::vector<BufferHistoryInfo> snapshot;
|
||||||
std::array<BufferHistoryInfo, history_size> snapshot{};
|
|
||||||
s32 count{};
|
|
||||||
|
|
||||||
{
|
{
|
||||||
std::scoped_lock lk(core->buffer_history_mutex);
|
std::scoped_lock lk(core->buffer_history_mutex);
|
||||||
|
for (auto& [frame, info] : core->buffer_history_map) {
|
||||||
const u32 newest = core->buffer_history_pos;
|
snapshot.push_back(info);
|
||||||
for (u32 i = 0; i < history_size; ++i) {
|
|
||||||
const auto& entry = core->buffer_history[(newest + history_size - i) % history_size];
|
|
||||||
if (entry.frame_number == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
snapshot[count] = entry;
|
|
||||||
++count;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const s32 limit = (std::min)(request, count);
|
std::sort(snapshot.begin(), snapshot.end(), [](auto& a, auto& b){
|
||||||
|
return a.frame_number > b.frame_number;
|
||||||
|
});
|
||||||
|
|
||||||
|
const s32 limit = std::min(request, (s32)snapshot.size());
|
||||||
|
parcel_out.Write(Status::NoError);
|
||||||
parcel_out.Write<s32>(limit);
|
parcel_out.Write<s32>(limit);
|
||||||
for (s32 i = 0; i < limit; ++i) {
|
for (s32 i = 0; i < limit; ++i) {
|
||||||
parcel_out.Write(snapshot[i]);
|
parcel_out.Write(snapshot[i]);
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// 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
|
||||||
|
|
||||||
@@ -28,9 +31,9 @@ SPL_MIG::SPL_MIG(Core::System& system_, std::shared_ptr<Module> module_)
|
|||||||
static const FunctionInfo functions[] = {
|
static const FunctionInfo functions[] = {
|
||||||
{0, &SPL::GetConfig, "GetConfig"},
|
{0, &SPL::GetConfig, "GetConfig"},
|
||||||
{1, &SPL::ModularExponentiate, "ModularExponentiate"},
|
{1, &SPL::ModularExponentiate, "ModularExponentiate"},
|
||||||
{2, nullptr, "GenerateAesKek"},
|
{2, &SPL::GenerateAesKek, "GenerateAesKek"},
|
||||||
{3, nullptr, "LoadAesKey"},
|
{3, nullptr, "LoadAesKey"},
|
||||||
{4, nullptr, "GenerateAesKey"},
|
{4, &SPL::GenerateAesKey, "GenerateAesKey"},
|
||||||
{5, &SPL::SetConfig, "SetConfig"},
|
{5, &SPL::SetConfig, "SetConfig"},
|
||||||
{7, &SPL::GenerateRandomBytes, "GenerateRandomBytes"},
|
{7, &SPL::GenerateRandomBytes, "GenerateRandomBytes"},
|
||||||
{11, &SPL::IsDevelopment, "IsDevelopment"},
|
{11, &SPL::IsDevelopment, "IsDevelopment"},
|
||||||
|
|||||||
@@ -59,6 +59,36 @@ void Module::Interface::ModularExponentiate(HLERequestContext& ctx) {
|
|||||||
rb.Push(ResultSecureMonitorNotImplemented);
|
rb.Push(ResultSecureMonitorNotImplemented);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Module::Interface::GenerateAesKek(HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp{ctx};
|
||||||
|
[[maybe_unused]] const auto key_source = rp.PopRaw<KeySource>();
|
||||||
|
const auto generation = rp.Pop<u32>();
|
||||||
|
const auto option = rp.Pop<u32>();
|
||||||
|
|
||||||
|
LOG_WARNING(Service_SPL, "(STUBBED) called, generation={:#x}, option={:#x}", generation,
|
||||||
|
option);
|
||||||
|
|
||||||
|
AccessKey access_key{};
|
||||||
|
|
||||||
|
IPC::ResponseBuilder rb{ctx, 6};
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
rb.PushRaw(access_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Module::Interface::GenerateAesKey(HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp{ctx};
|
||||||
|
[[maybe_unused]] const auto access_key = rp.PopRaw<AccessKey>();
|
||||||
|
[[maybe_unused]] const auto key_source = rp.PopRaw<KeySource>();
|
||||||
|
|
||||||
|
LOG_WARNING(Service_SPL, "(STUBBED) called");
|
||||||
|
|
||||||
|
AesKey aes_key{};
|
||||||
|
|
||||||
|
IPC::ResponseBuilder rb{ctx, 6};
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
rb.PushRaw(aes_key);
|
||||||
|
}
|
||||||
|
|
||||||
void Module::Interface::SetConfig(HLERequestContext& ctx) {
|
void Module::Interface::SetConfig(HLERequestContext& ctx) {
|
||||||
UNIMPLEMENTED_MSG("SetConfig is not implemented!");
|
UNIMPLEMENTED_MSG("SetConfig is not implemented!");
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// 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
|
||||||
|
|
||||||
@@ -25,6 +28,8 @@ public:
|
|||||||
// General
|
// General
|
||||||
void GetConfig(HLERequestContext& ctx);
|
void GetConfig(HLERequestContext& ctx);
|
||||||
void ModularExponentiate(HLERequestContext& ctx);
|
void ModularExponentiate(HLERequestContext& ctx);
|
||||||
|
void GenerateAesKek(HLERequestContext& ctx);
|
||||||
|
void GenerateAesKey(HLERequestContext& ctx);
|
||||||
void SetConfig(HLERequestContext& ctx);
|
void SetConfig(HLERequestContext& ctx);
|
||||||
void GenerateRandomBytes(HLERequestContext& ctx);
|
void GenerateRandomBytes(HLERequestContext& ctx);
|
||||||
void IsDevelopment(HLERequestContext& ctx);
|
void IsDevelopment(HLERequestContext& ctx);
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
#include "common/thread.h"
|
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/core_timing.h"
|
#include "core/core_timing.h"
|
||||||
#include "core/hle/service/vi/conductor.h"
|
#include "core/hle/service/vi/conductor.h"
|
||||||
@@ -77,7 +76,6 @@ void Conductor::ProcessVsync() {
|
|||||||
|
|
||||||
void Conductor::VsyncThread(std::stop_token token) {
|
void Conductor::VsyncThread(std::stop_token token) {
|
||||||
Common::SetCurrentThreadName("VSyncThread");
|
Common::SetCurrentThreadName("VSyncThread");
|
||||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
|
|
||||||
|
|
||||||
while (!token.stop_requested()) {
|
while (!token.stop_requested()) {
|
||||||
m_signal.Wait();
|
m_signal.Wait();
|
||||||
|
|||||||
@@ -230,11 +230,6 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
|
|||||||
tr("Preserves GPU-modified data by reading it back before uploading.\nSome games require this to render certain effects properly."));
|
tr("Preserves GPU-modified data by reading it back before uploading.\nSome games require this to render certain effects properly."));
|
||||||
INSERT(Settings, use_asynchronous_shaders, tr("Enable asynchronous shader compilation"),
|
INSERT(Settings, use_asynchronous_shaders, tr("Enable asynchronous shader compilation"),
|
||||||
tr("May reduce shader stutter."));
|
tr("May reduce shader stutter."));
|
||||||
INSERT(Settings, use_unified_memory, tr("Enable unified memory access (UMA)"),
|
|
||||||
tr("Lets the GPU write buffer readbacks directly into guest memory."));
|
|
||||||
INSERT(Settings, pipeline_worker_count, tr("Pipeline Worker Threads"),
|
|
||||||
tr("Number of threads used to build Vulkan pipelines.\n"
|
|
||||||
"Higher values speed up compilation at the cost of heat and power."));
|
|
||||||
INSERT(Settings, fast_gpu_time, tr("Fast GPU Time"),
|
INSERT(Settings, fast_gpu_time, tr("Fast GPU Time"),
|
||||||
tr("Overclocks the emulated GPU to increase dynamic resolution and render "
|
tr("Overclocks the emulated GPU to increase dynamic resolution and render "
|
||||||
"distance.\nUse 256 for maximal performance and 512 for maximal graphics fidelity."));
|
"distance.\nUse 256 for maximal performance and 512 for maximal graphics fidelity."));
|
||||||
@@ -292,12 +287,6 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
|
|||||||
INSERT(Settings, vertex_input_dynamic_state, tr("Vertex Input Dynamic State"),
|
INSERT(Settings, vertex_input_dynamic_state, tr("Vertex Input Dynamic State"),
|
||||||
tr("Enables vertex input dynamic state feature for better quality and performance."));
|
tr("Enables vertex input dynamic state feature for better quality and performance."));
|
||||||
|
|
||||||
INSERT(Settings, dynamic_rendering, tr("Dynamic Rendering"),
|
|
||||||
tr("Renders without render pass and framebuffer objects.\n"
|
|
||||||
"Results vary by driver: some gain performance, others lose it."));
|
|
||||||
|
|
||||||
INSERT(Settings, workgroup_memory_explicit_layout, QString(), QString());
|
|
||||||
|
|
||||||
INSERT(
|
INSERT(
|
||||||
Settings, sample_shading, tr("Sample Shading"),
|
Settings, sample_shading, tr("Sample Shading"),
|
||||||
tr("Allows the fragment shader to execute per sample in a multi-sampled fragment "
|
tr("Allows the fragment shader to execute per sample in a multi-sampled fragment "
|
||||||
|
|||||||
@@ -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);
|
||||||
@@ -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[] = {
|
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[] = {
|
|||||||
#undef OPCODE
|
#undef OPCODE
|
||||||
};
|
};
|
||||||
|
|
||||||
u8 NUM_ARGS[] = {
|
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[];
|
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[];
|
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, )
|
||||||
|
|||||||
+81
-17
@@ -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
|
||||||
@@ -17,12 +17,39 @@ enum class Mode : u64 {
|
|||||||
Attr,
|
Attr,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class SZ : u64 {
|
||||||
|
U8,
|
||||||
|
U16,
|
||||||
|
U32,
|
||||||
|
F32
|
||||||
|
};
|
||||||
|
|
||||||
enum class Shift : u64 {
|
enum class Shift : u64 {
|
||||||
Default,
|
Default,
|
||||||
U16,
|
U16,
|
||||||
B32,
|
B32,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
IR::U32 scaleIndex(IR::IREmitter& ir, IR::U32 index, Shift shift) {
|
||||||
|
switch (shift) {
|
||||||
|
case Shift::Default: return index;
|
||||||
|
case Shift::U16: return ir.ShiftLeftLogical(index, ir.Imm32(1));
|
||||||
|
case Shift::B32: return ir.ShiftLeftLogical(index, ir.Imm32(2));
|
||||||
|
default: UNREACHABLE();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IR::U32 skewBytes(IR::IREmitter& ir, SZ sizeRead) {
|
||||||
|
const IR::U32 lane = ir.LaneId();
|
||||||
|
switch (sizeRead) {
|
||||||
|
case SZ::U8: return lane;
|
||||||
|
case SZ::U16: return ir.ShiftLeftLogical(lane, ir.Imm32(1));
|
||||||
|
case SZ::U32:
|
||||||
|
case SZ::F32: return ir.ShiftLeftLogical(lane, ir.Imm32(2));
|
||||||
|
default: UNREACHABLE();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // Anonymous namespace
|
} // Anonymous namespace
|
||||||
|
|
||||||
void TranslatorVisitor::ISBERD(u64 insn) {
|
void TranslatorVisitor::ISBERD(u64 insn) {
|
||||||
@@ -37,28 +64,65 @@ void TranslatorVisitor::ISBERD(u64 insn) {
|
|||||||
BitField<31, 1, u64> skew;
|
BitField<31, 1, u64> skew;
|
||||||
BitField<32, 1, u64> o;
|
BitField<32, 1, u64> o;
|
||||||
BitField<33, 2, Mode> mode;
|
BitField<33, 2, Mode> mode;
|
||||||
|
BitField<36, 4, SZ> sz;
|
||||||
BitField<47, 2, Shift> shift;
|
BitField<47, 2, Shift> shift;
|
||||||
} const isberd{insn};
|
} const isberd{insn};
|
||||||
|
|
||||||
if (isberd.skew != 0) {
|
IR::U32 index{};
|
||||||
throw NotImplementedException("ISBERD SKEW");
|
if (isberd.src_reg_num.Value() == 0xFF) {
|
||||||
}
|
index = ir.Imm32(isberd.imm.Value());
|
||||||
if (isberd.o != 0) {
|
} else {
|
||||||
throw NotImplementedException("ISBERD O");
|
const IR::U32 scaledIndex = scaleIndex(ir, X(isberd.src_reg.Value()), isberd.shift.Value());
|
||||||
|
index = ir.IAdd(scaledIndex, ir.Imm32(isberd.imm.Value()));
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (isberd.mode.Value()) {
|
if (isberd.o.Value()) {
|
||||||
case Mode::Default:
|
if (isberd.skew.Value()) {
|
||||||
X(isberd.dest_reg.Value(), X(isberd.src_reg.Value()));
|
index = ir.IAdd(index, skewBytes(ir, isberd.sz.Value()));
|
||||||
return;
|
|
||||||
case Mode::Attr:
|
|
||||||
LOG_DEBUG(Shader, "(STUBBED) ISBERD Mode Attr");
|
|
||||||
X(isberd.dest_reg.Value(), X(isberd.src_reg.Value()));
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
throw NotImplementedException("ISBERD Mode {}",
|
|
||||||
static_cast<u64>(isberd.mode.Value()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const IR::U64 index64 = ir.UConvert(64, index);
|
||||||
|
IR::U32 globalLoaded{};
|
||||||
|
switch (isberd.sz.Value()) {
|
||||||
|
case SZ::U8: globalLoaded = ir.LoadGlobalU8 (index64); break;
|
||||||
|
case SZ::U16: globalLoaded = ir.LoadGlobalU16(index64); break;
|
||||||
|
case SZ::U32:
|
||||||
|
case SZ::F32: globalLoaded = ir.LoadGlobal32(index64); break;
|
||||||
|
default: UNREACHABLE();
|
||||||
|
}
|
||||||
|
X(isberd.dest_reg.Value(), globalLoaded);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isberd.mode.Value() != Mode::Default) {
|
||||||
|
if (isberd.skew.Value()) {
|
||||||
|
index = ir.IAdd(index, skewBytes(ir, SZ::U32));
|
||||||
|
}
|
||||||
|
|
||||||
|
IR::F32 float_index{};
|
||||||
|
switch (isberd.mode.Value()) {
|
||||||
|
case Mode::Patch: float_index = ir.GetPatch(index.Patch());
|
||||||
|
break;
|
||||||
|
case Mode::Prim: float_index = ir.GetAttribute(index.Attribute());
|
||||||
|
break;
|
||||||
|
case Mode::Attr: float_index = ir.GetAttributeIndexed(index);
|
||||||
|
break;
|
||||||
|
default: UNREACHABLE();
|
||||||
|
}
|
||||||
|
X(isberd.dest_reg.Value(), ir.BitCast<IR::U32>(float_index));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isberd.skew.Value()) {
|
||||||
|
X(isberd.dest_reg.Value(), ir.IAdd(X(isberd.src_reg.Value()), ir.LaneId()));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback copy
|
||||||
|
X(isberd.dest_reg.Value(), X(isberd.src_reg.Value()));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Shader::Maxwell
|
} // namespace Shader::Maxwell
|
||||||
|
|||||||
@@ -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{};
|
||||||
|
|||||||
@@ -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{};
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ add_library(video_core STATIC
|
|||||||
control/channel_state_cache.h
|
control/channel_state_cache.h
|
||||||
control/scheduler.cpp
|
control/scheduler.cpp
|
||||||
control/scheduler.h
|
control/scheduler.h
|
||||||
deferred_destruction_queue.h
|
delayed_destruction_ring.h
|
||||||
dirty_flags.cpp
|
dirty_flags.cpp
|
||||||
dirty_flags.h
|
dirty_flags.h
|
||||||
dma_pusher.cpp
|
dma_pusher.cpp
|
||||||
@@ -158,8 +158,6 @@ add_library(video_core STATIC
|
|||||||
renderer_vulkan/vk_compute_pass.h
|
renderer_vulkan/vk_compute_pass.h
|
||||||
renderer_vulkan/vk_compute_pipeline.cpp
|
renderer_vulkan/vk_compute_pipeline.cpp
|
||||||
renderer_vulkan/vk_compute_pipeline.h
|
renderer_vulkan/vk_compute_pipeline.h
|
||||||
renderer_vulkan/vk_descriptor_buffer.cpp
|
|
||||||
renderer_vulkan/vk_descriptor_buffer.h
|
|
||||||
renderer_vulkan/vk_descriptor_pool.cpp
|
renderer_vulkan/vk_descriptor_pool.cpp
|
||||||
renderer_vulkan/vk_descriptor_pool.h
|
renderer_vulkan/vk_descriptor_pool.h
|
||||||
renderer_vulkan/vk_fence_manager.cpp
|
renderer_vulkan/vk_fence_manager.cpp
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <bit>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
|
|
||||||
@@ -32,89 +31,44 @@ BufferCache<P>::BufferCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, R
|
|||||||
immediately_free = (Settings::values.vram_usage_mode.GetValue() == Settings::VramUsageMode::Aggressive);
|
immediately_free = (Settings::values.vram_usage_mode.GetValue() == Settings::VramUsageMode::Aggressive);
|
||||||
#endif
|
#endif
|
||||||
if (!runtime.CanReportMemoryUsage()) {
|
if (!runtime.CanReportMemoryUsage()) {
|
||||||
memory_budget = FALLBACK_MEMORY_BUDGET;
|
minimum_memory = DEFAULT_EXPECTED_MEMORY;
|
||||||
|
critical_memory = DEFAULT_CRITICAL_MEMORY;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
memory_budget = runtime.GetDeviceLocalMemory();
|
const s64 device_local_memory = static_cast<s64>(runtime.GetDeviceLocalMemory());
|
||||||
|
const s64 min_spacing_expected = device_local_memory - 1_GiB;
|
||||||
|
const s64 min_spacing_critical = device_local_memory - 512_MiB;
|
||||||
|
const s64 mem_threshold = (std::min)(device_local_memory, TARGET_THRESHOLD);
|
||||||
|
const s64 min_vacancy_expected = (6 * mem_threshold) / 10;
|
||||||
|
const s64 min_vacancy_critical = (2 * mem_threshold) / 10;
|
||||||
|
minimum_memory = static_cast<u64>(
|
||||||
|
(std::max)((std::min)(device_local_memory - min_vacancy_expected, min_spacing_expected),
|
||||||
|
DEFAULT_EXPECTED_MEMORY));
|
||||||
|
critical_memory = static_cast<u64>(
|
||||||
|
(std::max)((std::min)(device_local_memory - min_vacancy_critical, min_spacing_critical),
|
||||||
|
DEFAULT_CRITICAL_MEMORY));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
BufferCache<P>::~BufferCache() = default;
|
BufferCache<P>::~BufferCache() = default;
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
u64 BufferCache<P>::DeviceUsage(bool force_refresh) {
|
void BufferCache<P>::RunGarbageCollector() {
|
||||||
if (!runtime.CanReportMemoryUsage()) {
|
const bool aggressive_gc = total_used_memory >= critical_memory;
|
||||||
return total_used_memory;
|
const u64 ticks_to_destroy = aggressive_gc ? 60 : 120;
|
||||||
}
|
int num_iterations = aggressive_gc ? 64 : 32;
|
||||||
if (force_refresh || usage_refresh_countdown == 0) {
|
const auto clean_up = [this, &num_iterations](BufferId buffer_id) {
|
||||||
cached_device_usage = runtime.GetDeviceAllocationUsage();
|
if (num_iterations == 0) {
|
||||||
usage_refresh_countdown = USAGE_REFRESH_INTERVAL;
|
|
||||||
} else {
|
|
||||||
--usage_refresh_countdown;
|
|
||||||
}
|
|
||||||
return cached_device_usage;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class P>
|
|
||||||
u64 BufferCache<P>::ReclaimMemory(u64 target_bytes, bool allow_download) {
|
|
||||||
if (target_bytes == 0 || in_reclaim) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
in_reclaim = true;
|
|
||||||
u64 freed = 0;
|
|
||||||
const auto clean_up = [&](BufferId buffer_id) {
|
|
||||||
if (freed >= target_bytes) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
--num_iterations;
|
||||||
auto& buffer = slot_buffers[buffer_id];
|
auto& buffer = slot_buffers[buffer_id];
|
||||||
if (!allow_download && IsRegionGpuModified(buffer.CpuAddr(), buffer.SizeBytes())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const u64 buffer_bytes = Common::AlignUp(buffer.SizeBytes(), 1024);
|
|
||||||
DownloadBufferMemory(buffer);
|
DownloadBufferMemory(buffer);
|
||||||
DeleteBuffer(buffer_id);
|
DeleteBuffer(buffer_id);
|
||||||
freed += buffer_bytes;
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
const u64 cold_tick =
|
lru_cache.ForEachItemBelow(frame_tick - ticks_to_destroy, clean_up);
|
||||||
frame_tick > RECLAIM_GUARD_FRAMES ? frame_tick - RECLAIM_GUARD_FRAMES : 0;
|
|
||||||
lru_cache.ForEachItemBelow(cold_tick, clean_up);
|
|
||||||
if (freed == 0) {
|
|
||||||
lru_cache.ForEachItemBelow(frame_tick > 0 ? frame_tick - 1 : 0, clean_up);
|
|
||||||
}
|
|
||||||
in_reclaim = false;
|
|
||||||
usage_refresh_countdown = 0;
|
|
||||||
reclaim_stalled = freed == 0;
|
|
||||||
if (freed > 0) {
|
|
||||||
reclaim_wait_sync_point = runtime.CurrentSyncPoint();
|
|
||||||
}
|
|
||||||
return freed;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class P>
|
|
||||||
void BufferCache<P>::ReclaimDeferredResources(u64 completed_sync_point) {
|
|
||||||
sentenced_buffers.Reclaim(completed_sync_point);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class P>
|
|
||||||
void BufferCache<P>::EnsureHeadroom(bool allow_download) {
|
|
||||||
if (reclaim_stalled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (runtime.CompletedSyncPoint() < reclaim_wait_sync_point) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const u64 limit = memory_budget > RECLAIM_HEADROOM ? memory_budget - RECLAIM_HEADROOM : 0;
|
|
||||||
const u64 usage = DeviceUsage(false);
|
|
||||||
if (usage <= limit) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const u64 target = (limit / 100) * RECLAIM_TARGET_PERCENT;
|
|
||||||
const u64 excess = usage - target;
|
|
||||||
const u64 usage_mib = (std::max)(usage >> 20, u64{1});
|
|
||||||
const u64 share = (((excess >> 20) * (total_used_memory >> 20)) / usage_mib) << 20;
|
|
||||||
ReclaimMemory((std::min)(share, total_used_memory), allow_download);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
@@ -142,11 +96,15 @@ void BufferCache<P>::TickFrame() {
|
|||||||
const bool skip_preferred = hits * 256 < shots * 251;
|
const bool skip_preferred = hits * 256 < shots * 251;
|
||||||
channel_state->uniform_buffer_skip_cache_size = skip_preferred ? DEFAULT_SKIP_CACHE_SIZE : 0;
|
channel_state->uniform_buffer_skip_cache_size = skip_preferred ? DEFAULT_SKIP_CACHE_SIZE : 0;
|
||||||
|
|
||||||
usage_refresh_countdown = 0;
|
// If we can obtain the memory info, use it instead of the estimate.
|
||||||
reclaim_stalled = false;
|
if (runtime.CanReportMemoryUsage()) {
|
||||||
ReclaimDeferredResources(runtime.CompletedSyncPoint());
|
total_used_memory = runtime.GetDeviceMemoryUsage();
|
||||||
EnsureHeadroom(true);
|
}
|
||||||
|
if (total_used_memory >= minimum_memory) {
|
||||||
|
RunGarbageCollector();
|
||||||
|
}
|
||||||
++frame_tick;
|
++frame_tick;
|
||||||
|
delayed_destruction_ring.Tick();
|
||||||
|
|
||||||
for (auto& buffer : async_buffers_death_ring) {
|
for (auto& buffer : async_buffers_death_ring) {
|
||||||
runtime.FreeDeferredStagingBuffer(buffer);
|
runtime.FreeDeferredStagingBuffer(buffer);
|
||||||
@@ -217,71 +175,9 @@ std::optional<VideoCore::RasterizerDownloadArea> BufferCache<P>::GetFlushArea(DA
|
|||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
void BufferCache<P>::DownloadMemory(DAddr device_addr, u64 size) {
|
void BufferCache<P>::DownloadMemory(DAddr device_addr, u64 size) {
|
||||||
if constexpr (!USE_MEMORY_MAPS) {
|
|
||||||
std::scoped_lock lock{mutex};
|
|
||||||
ForEachBufferInRange(device_addr, size, [&](BufferId, Buffer& buffer) {
|
ForEachBufferInRange(device_addr, size, [&](BufferId, Buffer& buffer) {
|
||||||
DownloadBufferMemory(buffer, device_addr, size);
|
DownloadBufferMemory(buffer, device_addr, size);
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
boost::container::small_vector<std::pair<BufferCopy, BufferId>, 8> downloads;
|
|
||||||
u64 total_size_bytes = 0;
|
|
||||||
u64 largest_copy = 0;
|
|
||||||
|
|
||||||
std::unique_lock lock{mutex};
|
|
||||||
ForEachBufferInRange(device_addr, size, [&](BufferId buffer_id, Buffer& buffer) {
|
|
||||||
memory_tracker.ForEachDownloadRangeAndClear(
|
|
||||||
device_addr, size, [&](u64 device_addr_out, u64 range_size) {
|
|
||||||
const DAddr buffer_addr = buffer.CpuAddr();
|
|
||||||
const auto add_download = [&](DAddr start, DAddr end) {
|
|
||||||
const u64 new_offset = start - buffer_addr;
|
|
||||||
const u64 new_size = end - start;
|
|
||||||
downloads.push_back({
|
|
||||||
BufferCopy{
|
|
||||||
.src_offset = new_offset,
|
|
||||||
.dst_offset = total_size_bytes,
|
|
||||||
.size = new_size,
|
|
||||||
},
|
|
||||||
buffer_id,
|
|
||||||
});
|
|
||||||
constexpr u64 align = 64ULL;
|
|
||||||
constexpr u64 mask = ~(align - 1ULL);
|
|
||||||
total_size_bytes += (new_size + align - 1) & mask;
|
|
||||||
largest_copy = (std::max)(largest_copy, new_size);
|
|
||||||
};
|
|
||||||
gpu_modified_ranges.ForEachInRange(device_addr_out, range_size, add_download);
|
|
||||||
ClearDownload(device_addr_out, range_size);
|
|
||||||
gpu_modified_ranges.Subtract(device_addr_out, range_size);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
if (total_size_bytes == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes);
|
|
||||||
boost::container::small_vector<BufferCopy, 8> writebacks;
|
|
||||||
runtime.PreCopyBarrier();
|
|
||||||
for (auto& [copy, buffer_id] : downloads) {
|
|
||||||
copy.dst_offset += download_staging.offset;
|
|
||||||
Buffer& buffer = slot_buffers[buffer_id];
|
|
||||||
buffer.MarkUsage(copy.src_offset, copy.size);
|
|
||||||
const std::array copies{copy};
|
|
||||||
runtime.CopyBuffer(download_staging.buffer, buffer, copies, false);
|
|
||||||
BufferCopy writeback{copy};
|
|
||||||
writeback.src_offset = static_cast<u64>(buffer.CpuAddr()) + copy.src_offset;
|
|
||||||
writebacks.push_back(writeback);
|
|
||||||
}
|
|
||||||
runtime.PostCopyBarrier();
|
|
||||||
lock.unlock();
|
|
||||||
|
|
||||||
runtime.Finish();
|
|
||||||
const u8* const base = download_staging.mapped_span.data();
|
|
||||||
for (const BufferCopy& writeback : writebacks) {
|
|
||||||
const u64 staging_offset = writeback.dst_offset - download_staging.offset;
|
|
||||||
device_memory.WriteBlockUnsafe(static_cast<DAddr>(writeback.src_offset),
|
|
||||||
base + staging_offset, writeback.size);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
@@ -318,7 +214,7 @@ bool BufferCache<P>::DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 am
|
|||||||
auto& src_buffer = slot_buffers[buffer_a];
|
auto& src_buffer = slot_buffers[buffer_a];
|
||||||
auto& dest_buffer = slot_buffers[buffer_b];
|
auto& dest_buffer = slot_buffers[buffer_b];
|
||||||
SynchronizeBuffer(src_buffer, *cpu_src_address, static_cast<u32>(amount));
|
SynchronizeBuffer(src_buffer, *cpu_src_address, static_cast<u32>(amount));
|
||||||
memory_tracker.UnmarkRegionAsCpuModified(*cpu_dest_address, static_cast<u32>(amount));
|
SynchronizeBuffer(dest_buffer, *cpu_dest_address, static_cast<u32>(amount));
|
||||||
std::array copies{BufferCopy{
|
std::array copies{BufferCopy{
|
||||||
.src_offset = src_buffer.Offset(*cpu_src_address),
|
.src_offset = src_buffer.Offset(*cpu_src_address),
|
||||||
.dst_offset = dest_buffer.Offset(*cpu_dest_address),
|
.dst_offset = dest_buffer.Offset(*cpu_dest_address),
|
||||||
@@ -675,11 +571,7 @@ void BufferCache<P>::AccumulateFlushes() {
|
|||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
bool BufferCache<P>::ShouldWaitAsyncFlushes() const noexcept {
|
bool BufferCache<P>::ShouldWaitAsyncFlushes() const noexcept {
|
||||||
if (async_buffers.empty()) {
|
return (!async_buffers.empty() && async_buffers.front().has_value());
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return async_buffers.front().has_value() ||
|
|
||||||
!pending_downloads.front().unified_copies.empty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
@@ -687,7 +579,6 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
|
|||||||
AccumulateFlushes();
|
AccumulateFlushes();
|
||||||
|
|
||||||
if (committed_gpu_modified_ranges.empty()) {
|
if (committed_gpu_modified_ranges.empty()) {
|
||||||
pending_downloads.emplace_back();
|
|
||||||
async_buffers.emplace_back(std::optional<Async_Buffer>{});
|
async_buffers.emplace_back(std::optional<Async_Buffer>{});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -747,83 +638,27 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
|
|||||||
}
|
}
|
||||||
committed_gpu_modified_ranges.clear();
|
committed_gpu_modified_ranges.clear();
|
||||||
if (downloads.empty()) {
|
if (downloads.empty()) {
|
||||||
pending_downloads.emplace_back();
|
|
||||||
async_buffers.emplace_back(std::optional<Async_Buffer>{});
|
async_buffers.emplace_back(std::optional<Async_Buffer>{});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes, true);
|
||||||
struct QueuedUnifiedCopy {
|
boost::container::small_vector<BufferCopy, 4> normalized_copies;
|
||||||
u64 window;
|
|
||||||
BufferId buffer_id;
|
|
||||||
boost::container::small_vector<BufferCopy, 16> copies;
|
|
||||||
};
|
|
||||||
|
|
||||||
AsyncDownloadBatch batch;
|
|
||||||
boost::container::small_vector<std::pair<BufferCopy, BufferId>, 16> staging_downloads;
|
|
||||||
boost::container::small_vector<QueuedUnifiedCopy, 4> unified_copy_queue;
|
|
||||||
boost::container::small_vector<u64, 4> window_ids;
|
|
||||||
UnifiedWindowGroups groups;
|
|
||||||
u64 staging_size_bytes = 0;
|
|
||||||
for (auto& [copy, buffer_id] : downloads) {
|
|
||||||
Buffer& buffer = slot_buffers[buffer_id];
|
|
||||||
const DAddr orig_device_addr = buffer.CpuAddr() + copy.src_offset;
|
|
||||||
bool unified = false;
|
|
||||||
if constexpr (USE_UNIFIED_MEMORY) {
|
|
||||||
if (runtime.HasUnifiedMemory()) {
|
|
||||||
window_ids.clear();
|
|
||||||
groups.clear();
|
|
||||||
unified = ResolveUnifiedWindows(orig_device_addr, copy.src_offset, copy.size,
|
|
||||||
window_ids, groups);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BufferCopy record{copy};
|
|
||||||
record.src_offset = static_cast<size_t>(orig_device_addr);
|
|
||||||
if (unified) {
|
|
||||||
async_downloads.Add(orig_device_addr, copy.size);
|
|
||||||
buffer.MarkUsage(copy.src_offset, copy.size);
|
|
||||||
for (size_t i = 0; i < window_ids.size(); ++i) {
|
|
||||||
unified_copy_queue.push_back(
|
|
||||||
QueuedUnifiedCopy{window_ids[i], buffer_id, std::move(groups[i])});
|
|
||||||
}
|
|
||||||
batch.unified_copies.push_back(record);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
copy.dst_offset = staging_size_bytes;
|
|
||||||
constexpr u64 align = 64ULL;
|
|
||||||
staging_size_bytes += (copy.size + align - 1) & ~(align - 1ULL);
|
|
||||||
staging_downloads.push_back({copy, buffer_id});
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<Async_Buffer> download_staging;
|
|
||||||
if (!staging_downloads.empty()) {
|
|
||||||
download_staging = runtime.DownloadStagingBuffer(staging_size_bytes, true);
|
|
||||||
}
|
|
||||||
runtime.PreCopyBarrier();
|
runtime.PreCopyBarrier();
|
||||||
for (auto& [copy, buffer_id] : staging_downloads) {
|
for (auto& [copy, buffer_id] : downloads) {
|
||||||
copy.dst_offset += download_staging->offset;
|
copy.dst_offset += download_staging.offset;
|
||||||
const std::array copies{copy};
|
const std::array copies{copy};
|
||||||
|
BufferCopy second_copy{copy};
|
||||||
Buffer& buffer = slot_buffers[buffer_id];
|
Buffer& buffer = slot_buffers[buffer_id];
|
||||||
BufferCopy record{copy};
|
second_copy.src_offset = static_cast<size_t>(buffer.CpuAddr()) + copy.src_offset;
|
||||||
record.src_offset = static_cast<size_t>(buffer.CpuAddr()) + copy.src_offset;
|
const DAddr orig_device_addr = static_cast<DAddr>(second_copy.src_offset);
|
||||||
const DAddr orig_device_addr = static_cast<DAddr>(record.src_offset);
|
|
||||||
async_downloads.Add(orig_device_addr, copy.size);
|
async_downloads.Add(orig_device_addr, copy.size);
|
||||||
buffer.MarkUsage(copy.src_offset, copy.size);
|
buffer.MarkUsage(copy.src_offset, copy.size);
|
||||||
runtime.CopyBuffer(download_staging->buffer, buffer, copies, false);
|
runtime.CopyBuffer(download_staging.buffer, buffer, copies, false);
|
||||||
batch.staging_copies.push_back(record);
|
normalized_copies.push_back(second_copy);
|
||||||
}
|
|
||||||
if constexpr (USE_UNIFIED_MEMORY) {
|
|
||||||
for (const auto& queued : unified_copy_queue) {
|
|
||||||
const std::span<const BufferCopy> group_span(queued.copies.data(),
|
|
||||||
queued.copies.size());
|
|
||||||
runtime.CopyToUnifiedMemory(queued.window, slot_buffers[queued.buffer_id], group_span);
|
|
||||||
}
|
|
||||||
if (!unified_copy_queue.empty()) {
|
|
||||||
runtime.UnifiedMemoryHostBarrier();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
runtime.PostCopyBarrier();
|
runtime.PostCopyBarrier();
|
||||||
pending_downloads.emplace_back(std::move(batch));
|
pending_downloads.emplace_back(std::move(normalized_copies));
|
||||||
async_buffers.emplace_back(std::move(download_staging));
|
async_buffers.emplace_back(download_staging);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
@@ -838,50 +673,33 @@ void BufferCache<P>::PopAsyncFlushes() {
|
|||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
void BufferCache<P>::PopAsyncBuffers() {
|
void BufferCache<P>::PopAsyncBuffers() {
|
||||||
struct Writeback {
|
|
||||||
DAddr addr;
|
|
||||||
const u8* src;
|
|
||||||
u64 size;
|
|
||||||
};
|
|
||||||
boost::container::small_vector<Writeback, 8> writebacks;
|
|
||||||
{
|
|
||||||
std::scoped_lock lock{mutex};
|
|
||||||
if (async_buffers.empty()) {
|
if (async_buffers.empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto& batch = pending_downloads.front();
|
if (!async_buffers.front().has_value()) {
|
||||||
|
async_buffers.pop_front();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto& downloads = pending_downloads.front();
|
||||||
auto& async_buffer = async_buffers.front();
|
auto& async_buffer = async_buffers.front();
|
||||||
if (async_buffer.has_value()) {
|
u8* base = async_buffer->mapped_span.data();
|
||||||
const u8* base = async_buffer->mapped_span.data();
|
|
||||||
const size_t base_offset = async_buffer->offset;
|
const size_t base_offset = async_buffer->offset;
|
||||||
for (const auto& copy : batch.staging_copies) {
|
for (const auto& copy : downloads) {
|
||||||
const DAddr device_addr = static_cast<DAddr>(copy.src_offset);
|
const DAddr device_addr = static_cast<DAddr>(copy.src_offset);
|
||||||
const u64 dst_offset = copy.dst_offset - base_offset;
|
const u64 dst_offset = copy.dst_offset - base_offset;
|
||||||
const u8* read_mapped_memory = base + dst_offset;
|
const u8* read_mapped_memory = base + dst_offset;
|
||||||
async_downloads.ForEachInRange(
|
async_downloads.ForEachInRange(device_addr, copy.size, [&](DAddr start, DAddr end, s32) {
|
||||||
device_addr, copy.size, [&](DAddr start, DAddr end, s32) {
|
device_memory.WriteBlockUnsafe(start, &read_mapped_memory[start - device_addr],
|
||||||
writebacks.push_back(
|
end - start);
|
||||||
{start, &read_mapped_memory[start - device_addr], end - start});
|
|
||||||
});
|
});
|
||||||
async_downloads.Subtract(device_addr, copy.size, [&](DAddr start, DAddr end) {
|
async_downloads.Subtract(device_addr, copy.size, [&](DAddr start, DAddr end) {
|
||||||
gpu_modified_ranges.Subtract(start, end - start);
|
gpu_modified_ranges.Subtract(start, end - start);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async_buffers_death_ring.emplace_back(*async_buffer);
|
async_buffers_death_ring.emplace_back(*async_buffer);
|
||||||
}
|
|
||||||
for (const auto& copy : batch.unified_copies) {
|
|
||||||
const DAddr device_addr = static_cast<DAddr>(copy.src_offset);
|
|
||||||
async_downloads.Subtract(device_addr, copy.size, [&](DAddr start, DAddr end) {
|
|
||||||
gpu_modified_ranges.Subtract(start, end - start);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
async_buffers.pop_front();
|
async_buffers.pop_front();
|
||||||
pending_downloads.pop_front();
|
pending_downloads.pop_front();
|
||||||
}
|
}
|
||||||
for (const auto& wb : writebacks) {
|
|
||||||
device_memory.WriteBlockUnsafe(wb.addr, wb.src, wb.size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
bool BufferCache<P>::IsRegionGpuModified(DAddr addr, size_t size) {
|
bool BufferCache<P>::IsRegionGpuModified(DAddr addr, size_t size) {
|
||||||
@@ -991,46 +809,46 @@ void BufferCache<P>::BindHostVertexBuffers() {
|
|||||||
|
|
||||||
if (use_optimized_vertex_buffers) {
|
if (use_optimized_vertex_buffers) {
|
||||||
auto& flags = maxwell3d->dirty.flags;
|
auto& flags = maxwell3d->dirty.flags;
|
||||||
const u32 enabled_mask = enabled_vertex_buffers_mask;
|
u32 enabled_mask = enabled_vertex_buffers_mask;
|
||||||
bool any_dirty = false;
|
HostBindings<Buffer> bindings{};
|
||||||
u32 pending_mask = enabled_mask;
|
u32 last_index = (std::numeric_limits<u32>::max)();
|
||||||
while (pending_mask != 0) {
|
const auto flush_bindings = [&]() {
|
||||||
const u32 index = std::countr_zero(pending_mask);
|
if (bindings.buffers.empty()) {
|
||||||
pending_mask &= (pending_mask - 1);
|
return;
|
||||||
|
}
|
||||||
|
bindings.max_index = bindings.min_index + static_cast<u32>(bindings.buffers.size());
|
||||||
|
runtime.BindVertexBuffers(bindings);
|
||||||
|
bindings = HostBindings<Buffer>{};
|
||||||
|
last_index = (std::numeric_limits<u32>::max)();
|
||||||
|
};
|
||||||
|
while (enabled_mask != 0) {
|
||||||
|
const u32 index = std::countr_zero(enabled_mask);
|
||||||
|
enabled_mask &= (enabled_mask - 1);
|
||||||
const Binding& binding = VertexBufferSlot(index);
|
const Binding& binding = VertexBufferSlot(index);
|
||||||
Buffer& buffer = slot_buffers[binding.buffer_id];
|
Buffer& buffer = slot_buffers[binding.buffer_id];
|
||||||
TouchBuffer(buffer, binding.buffer_id);
|
TouchBuffer(buffer, binding.buffer_id);
|
||||||
SynchronizeBuffer(buffer, binding.device_addr, binding.size);
|
SynchronizeBuffer(buffer, binding.device_addr, binding.size);
|
||||||
any_dirty |= flags[Dirty::VertexBuffer0 + index];
|
if (!flags[Dirty::VertexBuffer0 + index]) {
|
||||||
}
|
flush_bindings();
|
||||||
if (enabled_mask == 0 || !any_dirty) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const u32 min_index = static_cast<u32>(std::countr_zero(enabled_mask));
|
|
||||||
const u32 max_index = 32u - static_cast<u32>(std::countl_zero(enabled_mask));
|
|
||||||
HostBindings<Buffer> bindings{};
|
|
||||||
bindings.min_index = min_index;
|
|
||||||
bindings.max_index = max_index;
|
|
||||||
for (u32 index = min_index; index < max_index; ++index) {
|
|
||||||
flags[Dirty::VertexBuffer0 + index] = false;
|
|
||||||
const u32 stride = maxwell3d->regs.vertex_streams[index].stride;
|
|
||||||
if ((enabled_mask & (1u << index)) == 0) {
|
|
||||||
bindings.buffers.push_back(&slot_buffers[NULL_BUFFER_ID]);
|
|
||||||
bindings.offsets.push_back(0);
|
|
||||||
bindings.sizes.push_back(0);
|
|
||||||
bindings.strides.push_back(stride);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const Binding& binding = VertexBufferSlot(index);
|
flags[Dirty::VertexBuffer0 + index] = false;
|
||||||
Buffer& buffer = slot_buffers[binding.buffer_id];
|
const u32 stride = maxwell3d->regs.vertex_streams[index].stride;
|
||||||
const u32 offset = buffer.Offset(binding.device_addr);
|
const u32 offset = buffer.Offset(binding.device_addr);
|
||||||
buffer.MarkUsage(offset, binding.size);
|
buffer.MarkUsage(offset, binding.size);
|
||||||
|
if (!bindings.buffers.empty() && index != last_index + 1) {
|
||||||
|
flush_bindings();
|
||||||
|
}
|
||||||
|
if (bindings.buffers.empty()) {
|
||||||
|
bindings.min_index = index;
|
||||||
|
}
|
||||||
bindings.buffers.push_back(&buffer);
|
bindings.buffers.push_back(&buffer);
|
||||||
bindings.offsets.push_back(offset);
|
bindings.offsets.push_back(offset);
|
||||||
bindings.sizes.push_back(binding.size);
|
bindings.sizes.push_back(binding.size);
|
||||||
bindings.strides.push_back(stride);
|
bindings.strides.push_back(stride);
|
||||||
|
last_index = index;
|
||||||
}
|
}
|
||||||
runtime.BindVertexBuffers(bindings);
|
flush_bindings();
|
||||||
} else {
|
} else {
|
||||||
HostBindings<typename P::Buffer> host_bindings;
|
HostBindings<typename P::Buffer> host_bindings;
|
||||||
bool any_valid{false};
|
bool any_valid{false};
|
||||||
@@ -1103,6 +921,7 @@ void BufferCache<P>::BindHostGraphicsUniformBuffers(size_t stage) {
|
|||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 binding_index, bool needs_bind) {
|
void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 binding_index, bool needs_bind) {
|
||||||
|
++channel_state->uniform_cache_shots[0];
|
||||||
const Binding& binding = channel_state->uniform_buffers[stage][index];
|
const Binding& binding = channel_state->uniform_buffers[stage][index];
|
||||||
const DAddr device_addr = binding.device_addr;
|
const DAddr device_addr = binding.device_addr;
|
||||||
const u32 size = (std::min)(binding.size, (*channel_state->uniform_buffer_sizes)[stage][index]);
|
const u32 size = (std::min)(binding.size, (*channel_state->uniform_buffer_sizes)[stage][index]);
|
||||||
@@ -1121,12 +940,8 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
|
|||||||
return alignment > 1 && (offset % alignment) != 0;
|
return alignment > 1 && (offset % alignment) != 0;
|
||||||
}
|
}
|
||||||
}();
|
}();
|
||||||
|
|
||||||
const bool cached_buffer_is_current =
|
|
||||||
has_host_buffer && !memory_tracker.IsRegionCpuModified(device_addr, size);
|
|
||||||
const bool use_fast_buffer = needs_alignment_stream
|
const bool use_fast_buffer = needs_alignment_stream
|
||||||
|| (has_host_buffer && !cached_buffer_is_current
|
|| (has_host_buffer && size <= channel_state->uniform_buffer_skip_cache_size
|
||||||
&& size <= channel_state->uniform_buffer_skip_cache_size
|
|
||||||
&& !memory_tracker.IsRegionGpuModified(device_addr, size));
|
&& !memory_tracker.IsRegionGpuModified(device_addr, size));
|
||||||
if (use_fast_buffer) {
|
if (use_fast_buffer) {
|
||||||
if constexpr (IS_OPENGL) {
|
if constexpr (IS_OPENGL) {
|
||||||
@@ -1153,7 +968,7 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
|
|||||||
device_memory.ReadBlockUnsafe(device_addr, span.data(), size);
|
device_memory.ReadBlockUnsafe(device_addr, span.data(), size);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
++channel_state->uniform_cache_shots[0];
|
// Classic cached path
|
||||||
if (SynchronizeBuffer(buffer, device_addr, size)) {
|
if (SynchronizeBuffer(buffer, device_addr, size)) {
|
||||||
++channel_state->uniform_cache_hits[0];
|
++channel_state->uniform_cache_hits[0];
|
||||||
}
|
}
|
||||||
@@ -1761,7 +1576,6 @@ void BufferCache<P>::JoinOverlap(BufferId new_buffer_id, BufferId overlap_id,
|
|||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
BufferId BufferCache<P>::CreateBuffer(DAddr device_addr, u32 wanted_size) {
|
BufferId BufferCache<P>::CreateBuffer(DAddr device_addr, u32 wanted_size) {
|
||||||
EnsureHeadroom(false);
|
|
||||||
DAddr device_addr_end = Common::AlignUp(device_addr + wanted_size, CACHING_PAGESIZE);
|
DAddr device_addr_end = Common::AlignUp(device_addr + wanted_size, CACHING_PAGESIZE);
|
||||||
device_addr = Common::AlignDown(device_addr, CACHING_PAGESIZE);
|
device_addr = Common::AlignDown(device_addr, CACHING_PAGESIZE);
|
||||||
wanted_size = static_cast<u32>(device_addr_end - device_addr);
|
wanted_size = static_cast<u32>(device_addr_end - device_addr);
|
||||||
@@ -1799,7 +1613,7 @@ void BufferCache<P>::ChangeRegister(BufferId buffer_id) {
|
|||||||
total_used_memory += Common::AlignUp(size, 1024);
|
total_used_memory += Common::AlignUp(size, 1024);
|
||||||
buffer.setLRUID(lru_cache.Insert(buffer_id, frame_tick));
|
buffer.setLRUID(lru_cache.Insert(buffer_id, frame_tick));
|
||||||
} else {
|
} else {
|
||||||
total_used_memory -= std::min<u64>(total_used_memory, Common::AlignUp(size, 1024));
|
total_used_memory -= Common::AlignUp(size, 1024);
|
||||||
lru_cache.Free(buffer.getLRUID());
|
lru_cache.Free(buffer.getLRUID());
|
||||||
}
|
}
|
||||||
const DAddr device_addr_begin = buffer.CpuAddr();
|
const DAddr device_addr_begin = buffer.CpuAddr();
|
||||||
@@ -1885,98 +1699,6 @@ void BufferCache<P>::ImmediateUploadMemory([[maybe_unused]] Buffer& buffer,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class P>
|
|
||||||
bool BufferCache<P>::ResolveUnifiedWindows(
|
|
||||||
[[maybe_unused]] DAddr device_addr, [[maybe_unused]] u64 buffer_offset,
|
|
||||||
[[maybe_unused]] u64 size, [[maybe_unused]] boost::container::small_vector<u64, 4>& window_ids,
|
|
||||||
[[maybe_unused]] UnifiedWindowGroups& groups) {
|
|
||||||
if constexpr (USE_UNIFIED_MEMORY) {
|
|
||||||
const u8* const physical_base = device_memory.GetPhysicalBase();
|
|
||||||
const u64 unified_base = runtime.UnifiedMemoryBase();
|
|
||||||
const u64 unified_size = runtime.UnifiedMemorySize();
|
|
||||||
const u64 window_size = runtime.UnifiedMemoryWindowSize();
|
|
||||||
if (window_size == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const auto group_for = [&](u64 window) -> boost::container::small_vector<BufferCopy, 16>& {
|
|
||||||
for (size_t i = 0; i < window_ids.size(); ++i) {
|
|
||||||
if (window_ids[i] == window) {
|
|
||||||
return groups[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
window_ids.push_back(window);
|
|
||||||
groups.emplace_back();
|
|
||||||
return groups.back();
|
|
||||||
};
|
|
||||||
u64 downloaded = 0;
|
|
||||||
while (downloaded < size) {
|
|
||||||
const DAddr page_addr = device_addr + downloaded;
|
|
||||||
const u8* const ptr = device_memory.GetPointer<u8>(page_addr);
|
|
||||||
if (ptr == nullptr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const u64 page_offset = page_addr & Core::DEVICE_PAGEMASK;
|
|
||||||
u64 chunk = (std::min)(size - downloaded,
|
|
||||||
static_cast<u64>(Core::DEVICE_PAGESIZE) - page_offset);
|
|
||||||
const u64 phys_offset = static_cast<u64>(ptr - physical_base);
|
|
||||||
if (phys_offset < unified_base || phys_offset - unified_base + chunk > unified_size) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const u64 relative = phys_offset - unified_base;
|
|
||||||
const u64 window = relative / window_size;
|
|
||||||
const u64 local_offset = relative % window_size;
|
|
||||||
chunk = (std::min)(chunk, window_size - local_offset);
|
|
||||||
auto& group = group_for(window);
|
|
||||||
if (!group.empty()) {
|
|
||||||
BufferCopy& last = group.back();
|
|
||||||
if (last.src_offset + last.size == buffer_offset + downloaded &&
|
|
||||||
last.dst_offset + last.size == local_offset) {
|
|
||||||
last.size += chunk;
|
|
||||||
downloaded += chunk;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
group.push_back(BufferCopy{
|
|
||||||
.src_offset = buffer_offset + downloaded,
|
|
||||||
.dst_offset = local_offset,
|
|
||||||
.size = chunk,
|
|
||||||
});
|
|
||||||
downloaded += chunk;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class P>
|
|
||||||
bool BufferCache<P>::TryUnifiedDownloadMemory([[maybe_unused]] Buffer& buffer,
|
|
||||||
[[maybe_unused]] std::span<BufferCopy> copies) {
|
|
||||||
if constexpr (USE_UNIFIED_MEMORY) {
|
|
||||||
boost::container::small_vector<u64, 4> window_ids;
|
|
||||||
UnifiedWindowGroups groups;
|
|
||||||
for (const BufferCopy& copy : copies) {
|
|
||||||
if (!ResolveUnifiedWindows(buffer.CpuAddr() + copy.src_offset, copy.src_offset,
|
|
||||||
copy.size, window_ids, groups)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const BufferCopy& copy : copies) {
|
|
||||||
buffer.MarkUsage(copy.src_offset, copy.size);
|
|
||||||
}
|
|
||||||
runtime.PreCopyBarrier();
|
|
||||||
for (size_t i = 0; i < window_ids.size(); ++i) {
|
|
||||||
const std::span<const BufferCopy> group_span(groups[i].data(), groups[i].size());
|
|
||||||
runtime.CopyToUnifiedMemory(window_ids[i], buffer, group_span);
|
|
||||||
}
|
|
||||||
runtime.UnifiedMemoryHostBarrier();
|
|
||||||
runtime.Finish();
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
void BufferCache<P>::MappedUploadMemory([[maybe_unused]] Buffer& buffer,
|
void BufferCache<P>::MappedUploadMemory([[maybe_unused]] Buffer& buffer,
|
||||||
[[maybe_unused]] u64 total_size_bytes,
|
[[maybe_unused]] u64 total_size_bytes,
|
||||||
@@ -2080,12 +1802,6 @@ void BufferCache<P>::DownloadBufferMemory(Buffer& buffer, DAddr device_addr, u64
|
|||||||
}
|
}
|
||||||
|
|
||||||
if constexpr (USE_MEMORY_MAPS) {
|
if constexpr (USE_MEMORY_MAPS) {
|
||||||
if constexpr (USE_UNIFIED_MEMORY) {
|
|
||||||
if (runtime.HasUnifiedMemory() &&
|
|
||||||
TryUnifiedDownloadMemory(buffer, std::span(copies.data(), copies.size()))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes);
|
auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes);
|
||||||
const u8* const mapped_memory = download_staging.mapped_span.data();
|
const u8* const mapped_memory = download_staging.mapped_span.data();
|
||||||
const std::span<BufferCopy> copies_span(copies.data(), copies.data() + copies.size());
|
const std::span<BufferCopy> copies_span(copies.data(), copies.data() + copies.size());
|
||||||
@@ -2156,7 +1872,7 @@ void BufferCache<P>::DeleteBuffer(BufferId buffer_id, bool do_not_mark) {
|
|||||||
#ifdef YUZU_LEGACY
|
#ifdef YUZU_LEGACY
|
||||||
if (!do_not_mark || !immediately_free)
|
if (!do_not_mark || !immediately_free)
|
||||||
#endif
|
#endif
|
||||||
sentenced_buffers.Push(std::move(slot_buffers[buffer_id]), runtime.CurrentSyncPoint());
|
delayed_destruction_ring.Push(std::move(slot_buffers[buffer_id]));
|
||||||
|
|
||||||
slot_buffers.erase(buffer_id);
|
slot_buffers.erase(buffer_id);
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <bit>
|
#include <bit>
|
||||||
#include <deque>
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
@@ -31,7 +30,7 @@
|
|||||||
#include "common/slot_vector.h"
|
#include "common/slot_vector.h"
|
||||||
#include "video_core/buffer_cache/buffer_base.h"
|
#include "video_core/buffer_cache/buffer_base.h"
|
||||||
#include "video_core/control/channel_state_cache.h"
|
#include "video_core/control/channel_state_cache.h"
|
||||||
#include "video_core/deferred_destruction_queue.h"
|
#include "video_core/delayed_destruction_ring.h"
|
||||||
#include "video_core/dirty_flags.h"
|
#include "video_core/dirty_flags.h"
|
||||||
#include "video_core/engines/maxwell_3d.h"
|
#include "video_core/engines/maxwell_3d.h"
|
||||||
#include "video_core/engines/kepler_compute.h"
|
#include "video_core/engines/kepler_compute.h"
|
||||||
@@ -181,18 +180,15 @@ class BufferCache : public VideoCommon::ChannelSetupCaches<BufferCacheChannelInf
|
|||||||
static constexpr bool USE_MEMORY_MAPS = P::USE_MEMORY_MAPS;
|
static constexpr bool USE_MEMORY_MAPS = P::USE_MEMORY_MAPS;
|
||||||
static constexpr bool SEPARATE_IMAGE_BUFFERS_BINDINGS = P::SEPARATE_IMAGE_BUFFER_BINDINGS;
|
static constexpr bool SEPARATE_IMAGE_BUFFERS_BINDINGS = P::SEPARATE_IMAGE_BUFFER_BINDINGS;
|
||||||
static constexpr bool USE_MEMORY_MAPS_FOR_UPLOADS = P::USE_MEMORY_MAPS_FOR_UPLOADS;
|
static constexpr bool USE_MEMORY_MAPS_FOR_UPLOADS = P::USE_MEMORY_MAPS_FOR_UPLOADS;
|
||||||
static constexpr bool USE_UNIFIED_MEMORY = P::USE_UNIFIED_MEMORY;
|
|
||||||
|
|
||||||
#ifdef YUZU_LEGACY
|
#ifdef YUZU_LEGACY
|
||||||
static constexpr u64 RECLAIM_HEADROOM = 384_MiB;
|
static constexpr s64 TARGET_THRESHOLD = 3_GiB;
|
||||||
#else
|
#else
|
||||||
static constexpr u64 RECLAIM_HEADROOM = 512_MiB;
|
static constexpr s64 TARGET_THRESHOLD = 4_GiB;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static constexpr u64 FALLBACK_MEMORY_BUDGET = 2_GiB;
|
static constexpr s64 DEFAULT_EXPECTED_MEMORY = 512_MiB;
|
||||||
static constexpr u32 USAGE_REFRESH_INTERVAL = 16;
|
static constexpr s64 DEFAULT_CRITICAL_MEMORY = 1_GiB;
|
||||||
static constexpr u64 RECLAIM_GUARD_FRAMES = 8;
|
|
||||||
static constexpr u64 RECLAIM_TARGET_PERCENT = 95;
|
|
||||||
|
|
||||||
// Debug Flags.
|
// Debug Flags.
|
||||||
|
|
||||||
@@ -219,10 +215,6 @@ public:
|
|||||||
|
|
||||||
void TickFrame();
|
void TickFrame();
|
||||||
|
|
||||||
u64 ReclaimMemory(u64 target_bytes, bool allow_download);
|
|
||||||
|
|
||||||
void ReclaimDeferredResources(u64 completed_sync_point);
|
|
||||||
|
|
||||||
void WriteMemory(DAddr device_addr, u64 size);
|
void WriteMemory(DAddr device_addr, u64 size);
|
||||||
|
|
||||||
void CachedWriteMemory(DAddr device_addr, u64 size);
|
void CachedWriteMemory(DAddr device_addr, u64 size);
|
||||||
@@ -366,9 +358,7 @@ private:
|
|||||||
((device_addr + size) & ~Core::DEVICE_PAGEMASK);
|
((device_addr + size) & ~Core::DEVICE_PAGEMASK);
|
||||||
}
|
}
|
||||||
|
|
||||||
u64 DeviceUsage(bool force_refresh);
|
void RunGarbageCollector();
|
||||||
|
|
||||||
void EnsureHeadroom(bool allow_download);
|
|
||||||
|
|
||||||
void BindHostIndexBuffer();
|
void BindHostIndexBuffer();
|
||||||
|
|
||||||
@@ -453,15 +443,6 @@ private:
|
|||||||
|
|
||||||
void MappedUploadMemory(Buffer& buffer, u64 total_size_bytes, std::span<BufferCopy> copies);
|
void MappedUploadMemory(Buffer& buffer, u64 total_size_bytes, std::span<BufferCopy> copies);
|
||||||
|
|
||||||
bool TryUnifiedDownloadMemory(Buffer& buffer, std::span<BufferCopy> copies);
|
|
||||||
|
|
||||||
using UnifiedWindowGroups =
|
|
||||||
boost::container::small_vector<boost::container::small_vector<BufferCopy, 16>, 4>;
|
|
||||||
|
|
||||||
bool ResolveUnifiedWindows(DAddr device_addr, u64 buffer_offset, u64 size,
|
|
||||||
boost::container::small_vector<u64, 4>& window_ids,
|
|
||||||
UnifiedWindowGroups& groups);
|
|
||||||
|
|
||||||
void DownloadBufferMemory(Buffer& buffer_id);
|
void DownloadBufferMemory(Buffer& buffer_id);
|
||||||
|
|
||||||
void DownloadBufferMemory(Buffer& buffer_id, DAddr device_addr, u64 size);
|
void DownloadBufferMemory(Buffer& buffer_id, DAddr device_addr, u64 size);
|
||||||
@@ -494,7 +475,12 @@ private:
|
|||||||
Tegra::MaxwellDeviceMemoryManager& device_memory;
|
Tegra::MaxwellDeviceMemoryManager& device_memory;
|
||||||
|
|
||||||
Common::SlotVector<Buffer> slot_buffers;
|
Common::SlotVector<Buffer> slot_buffers;
|
||||||
DeferredDestructionQueue<Buffer> sentenced_buffers;
|
#ifdef YUZU_LEGACY
|
||||||
|
static constexpr size_t TICKS_TO_DESTROY = 6;
|
||||||
|
#else
|
||||||
|
static constexpr size_t TICKS_TO_DESTROY = 8;
|
||||||
|
#endif
|
||||||
|
DelayedDestructionRing<Buffer, TICKS_TO_DESTROY> delayed_destruction_ring;
|
||||||
|
|
||||||
const Tegra::Engines::Maxwell3D::DrawManager::IndirectParams* current_draw_indirect{};
|
const Tegra::Engines::Maxwell3D::DrawManager::IndirectParams* current_draw_indirect{};
|
||||||
|
|
||||||
@@ -512,14 +498,9 @@ private:
|
|||||||
std::deque<Common::RangeSet<DAddr>> committed_gpu_modified_ranges;
|
std::deque<Common::RangeSet<DAddr>> committed_gpu_modified_ranges;
|
||||||
|
|
||||||
// Async Buffers
|
// Async Buffers
|
||||||
struct AsyncDownloadBatch {
|
|
||||||
boost::container::small_vector<BufferCopy, 4> staging_copies;
|
|
||||||
boost::container::small_vector<BufferCopy, 4> unified_copies;
|
|
||||||
};
|
|
||||||
|
|
||||||
Common::OverlapRangeSet<DAddr> async_downloads;
|
Common::OverlapRangeSet<DAddr> async_downloads;
|
||||||
std::deque<std::optional<Async_Buffer>> async_buffers;
|
std::deque<std::optional<Async_Buffer>> async_buffers;
|
||||||
std::deque<AsyncDownloadBatch> pending_downloads;
|
std::deque<boost::container::small_vector<BufferCopy, 4>> pending_downloads;
|
||||||
std::optional<Async_Buffer> current_buffer;
|
std::optional<Async_Buffer> current_buffer;
|
||||||
|
|
||||||
std::deque<Async_Buffer> async_buffers_death_ring;
|
std::deque<Async_Buffer> async_buffers_death_ring;
|
||||||
@@ -534,14 +515,8 @@ private:
|
|||||||
Common::LeastRecentlyUsedCache<LRUItemParams> lru_cache;
|
Common::LeastRecentlyUsedCache<LRUItemParams> lru_cache;
|
||||||
u64 frame_tick = 0;
|
u64 frame_tick = 0;
|
||||||
u64 total_used_memory = 0;
|
u64 total_used_memory = 0;
|
||||||
u64 memory_budget = 0;
|
u64 minimum_memory = 0;
|
||||||
u64 cached_device_usage = 0;
|
u64 critical_memory = 0;
|
||||||
/// Sync point the last reclaim's evictions were queued at. Their memory is not back with the
|
|
||||||
/// device until this completes, so reclaiming again before then measures stale usage.
|
|
||||||
u64 reclaim_wait_sync_point = 0;
|
|
||||||
u32 usage_refresh_countdown = 0;
|
|
||||||
bool in_reclaim = false;
|
|
||||||
bool reclaim_stalled = false;
|
|
||||||
BufferId inline_buffer_id;
|
BufferId inline_buffer_id;
|
||||||
#ifdef YUZU_LEGACY
|
#ifdef YUZU_LEGACY
|
||||||
bool immediately_free = false;
|
bool immediately_free = false;
|
||||||
|
|||||||
@@ -16,6 +16,26 @@
|
|||||||
#include "video_core/memory_manager.h"
|
#include "video_core/memory_manager.h"
|
||||||
|
|
||||||
namespace Tegra::Control {
|
namespace Tegra::Control {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Match NVK/Nouveau's initial pushbuffer subchannel layout.
|
||||||
|
constexpr u32 Nvk3DSubchannel = 0;
|
||||||
|
constexpr u32 NvkComputeSubchannel = 1;
|
||||||
|
constexpr u32 Nvk2DSubchannel = 3;
|
||||||
|
constexpr u32 NvkCopySubchannel = 4;
|
||||||
|
|
||||||
|
void BindNvkDefaultSubchannels(ChannelState::Payload& payload) {
|
||||||
|
auto& dma_pusher = payload.dma_pusher;
|
||||||
|
dma_pusher.BindSubchannel(&payload.maxwell_3d, Nvk3DSubchannel, Engines::EngineTypes::Maxwell3D);
|
||||||
|
dma_pusher.BindSubchannel(&payload.kepler_compute, NvkComputeSubchannel,
|
||||||
|
Engines::EngineTypes::KeplerCompute);
|
||||||
|
// Subchannel 2 is M2MF there; Eden does not expose a 0x9039 engine yet.
|
||||||
|
dma_pusher.BindSubchannel(&payload.fermi_2d, Nvk2DSubchannel, Engines::EngineTypes::Fermi2D);
|
||||||
|
dma_pusher.BindSubchannel(&payload.maxwell_dma, NvkCopySubchannel,
|
||||||
|
Engines::EngineTypes::MaxwellDMA);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // Anonymous namespace
|
||||||
|
|
||||||
ChannelState::Payload::Payload(Core::System& system, MemoryManager& memory_manager, ChannelState& channel_state)
|
ChannelState::Payload::Payload(Core::System& system, MemoryManager& memory_manager, ChannelState& channel_state)
|
||||||
: maxwell_3d(memory_manager)
|
: maxwell_3d(memory_manager)
|
||||||
@@ -35,6 +55,7 @@ void ChannelState::Init(Core::System& system, u64 program_id_) {
|
|||||||
ASSERT(memory_manager);
|
ASSERT(memory_manager);
|
||||||
program_id = program_id_;
|
program_id = program_id_;
|
||||||
payload.emplace(system, *memory_manager, *this);
|
payload.emplace(system, *memory_manager, *this);
|
||||||
|
BindNvkDefaultSubchannels(*payload);
|
||||||
initialized = true;
|
initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
#include <boost/container/deque.hpp>
|
|
||||||
#include <boost/container/options.hpp>
|
|
||||||
|
|
||||||
#include "common/common_types.h"
|
|
||||||
|
|
||||||
namespace VideoCommon {
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
class DeferredDestructionQueue {
|
|
||||||
public:
|
|
||||||
void Push(T&& object, u64 sync_point) {
|
|
||||||
entries.emplace_back(std::move(object), sync_point);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Reclaim(u64 completed_sync_point) {
|
|
||||||
while (!entries.empty() && entries.front().sync_point <= completed_sync_point) {
|
|
||||||
entries.pop_front();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Clear() {
|
|
||||||
entries.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] size_t Size() const noexcept {
|
|
||||||
return entries.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] bool Empty() const noexcept {
|
|
||||||
return entries.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct Entry {
|
|
||||||
Entry(T&& object_, u64 sync_point_) noexcept
|
|
||||||
: object{std::move(object_)}, sync_point{sync_point_} {}
|
|
||||||
|
|
||||||
T object;
|
|
||||||
u64 sync_point;
|
|
||||||
};
|
|
||||||
|
|
||||||
using EntryDequeOptions =
|
|
||||||
boost::container::deque_options<boost::container::block_size<8u>>::type;
|
|
||||||
|
|
||||||
boost::container::deque<Entry, void, EntryDequeOptions> entries;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace VideoCommon
|
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace VideoCommon {
|
||||||
|
|
||||||
|
/// Container to push objects to be destroyed a few ticks in the future
|
||||||
|
template <typename T, size_t TICKS_TO_DESTROY>
|
||||||
|
class DelayedDestructionRing {
|
||||||
|
public:
|
||||||
|
void Tick() {
|
||||||
|
index = (index + 1) % TICKS_TO_DESTROY;
|
||||||
|
elements[index].clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Push(T&& object) {
|
||||||
|
elements[index].push_back(std::move(object));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
size_t index = 0;
|
||||||
|
std::array<std::vector<T>, TICKS_TO_DESTROY> elements;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace VideoCommon
|
||||||
@@ -71,8 +71,7 @@ void Fermi2D::Blit() {
|
|||||||
constexpr s64 null_derivative = 1ULL << 32;
|
constexpr s64 null_derivative = 1ULL << 32;
|
||||||
Surface src = regs.src;
|
Surface src = regs.src;
|
||||||
const auto bytes_per_pixel = BytesPerBlock(PixelFormatFromRenderTargetFormat(src.format));
|
const auto bytes_per_pixel = BytesPerBlock(PixelFormatFromRenderTargetFormat(src.format));
|
||||||
const u64 src_area = static_cast<u64>(src.width) * static_cast<u64>(src.height);
|
const bool delegate_to_gpu = src.width > 512 && src.height > 512 && bytes_per_pixel <= 8 &&
|
||||||
const bool delegate_to_gpu = src_area > 512ULL * 512ULL && bytes_per_pixel <= 8 &&
|
|
||||||
src.format != regs.dst.format;
|
src.format != regs.dst.format;
|
||||||
|
|
||||||
auto srcX = args.src_x0;
|
auto srcX = args.src_x0;
|
||||||
|
|||||||
@@ -22,10 +22,21 @@
|
|||||||
|
|
||||||
namespace Tegra::Engines {
|
namespace Tegra::Engines {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr u32 Gf100BindClassMask = 0xffff;
|
||||||
|
constexpr u32 Gf100BindValidMask = 0x1f0000 | Gf100BindClassMask;
|
||||||
|
|
||||||
|
} // Anonymous namespace
|
||||||
|
|
||||||
void Puller::ProcessBindMethod(DmaPusher& dma_pusher, const MethodCall& method_call) {
|
void Puller::ProcessBindMethod(DmaPusher& dma_pusher, const MethodCall& method_call) {
|
||||||
// Bind the current subchannel to the desired engine id.
|
LOG_DEBUG(HW_GPU, "Binding subchannel {} to engine {:#x}", method_call.subchannel, method_call.argument);
|
||||||
LOG_DEBUG(HW_GPU, "Binding subchannel {} to engine {}", method_call.subchannel, method_call.argument);
|
u32 engine = method_call.argument;
|
||||||
const auto engine_id = static_cast<EngineID>(method_call.argument);
|
if ((engine & ~Gf100BindClassMask) != 0 && (engine & ~Gf100BindValidMask) == 0) {
|
||||||
|
engine &= Gf100BindClassMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto engine_id = static_cast<EngineID>(engine);
|
||||||
bound_engines[method_call.subchannel] = engine_id;
|
bound_engines[method_call.subchannel] = engine_id;
|
||||||
switch (engine_id) {
|
switch (engine_id) {
|
||||||
case EngineID::FERMI_TWOD_A:
|
case EngineID::FERMI_TWOD_A:
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
#include "common/thread.h"
|
#include "common/thread.h"
|
||||||
#include "video_core/deferred_destruction_queue.h"
|
#include "video_core/delayed_destruction_ring.h"
|
||||||
#include "video_core/gpu.h"
|
#include "video_core/gpu.h"
|
||||||
#include "video_core/host1x/host1x.h"
|
#include "video_core/host1x/host1x.h"
|
||||||
#include "video_core/host1x/syncpoint_manager.h"
|
#include "video_core/host1x/syncpoint_manager.h"
|
||||||
@@ -50,8 +50,7 @@ public:
|
|||||||
/// Notify the fence manager about a new frame
|
/// Notify the fence manager about a new frame
|
||||||
void TickFrame() {
|
void TickFrame() {
|
||||||
std::unique_lock lock(ring_guard);
|
std::unique_lock lock(ring_guard);
|
||||||
++retire_tick;
|
delayed_destruction_ring.Tick();
|
||||||
sentenced_fences.Reclaim(retire_tick > RETIRE_DELAY ? retire_tick - RETIRE_DELAY : 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unlike other fences, this one doesn't
|
// Unlike other fences, this one doesn't
|
||||||
@@ -92,6 +91,9 @@ public:
|
|||||||
func();
|
func();
|
||||||
}
|
}
|
||||||
fences.push(std::move(new_fence));
|
fences.push(std::move(new_fence));
|
||||||
|
if (should_flush) {
|
||||||
|
rasterizer.FlushCommands();
|
||||||
|
}
|
||||||
if constexpr (can_async_check) {
|
if constexpr (can_async_check) {
|
||||||
guard.unlock();
|
guard.unlock();
|
||||||
cv.notify_all();
|
cv.notify_all();
|
||||||
@@ -184,7 +186,7 @@ private:
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
std::unique_lock lock(ring_guard);
|
std::unique_lock lock(ring_guard);
|
||||||
sentenced_fences.Push(std::move(current_fence), retire_tick);
|
delayed_destruction_ring.Push(std::move(current_fence));
|
||||||
}
|
}
|
||||||
fences.pop();
|
fences.pop();
|
||||||
}
|
}
|
||||||
@@ -217,7 +219,7 @@ private:
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
std::unique_lock lock(ring_guard);
|
std::unique_lock lock(ring_guard);
|
||||||
sentenced_fences.Push(std::move(current_fence), retire_tick);
|
delayed_destruction_ring.Push(std::move(current_fence));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -236,10 +238,10 @@ private:
|
|||||||
|
|
||||||
void PopAsyncFlushes() {
|
void PopAsyncFlushes() {
|
||||||
{
|
{
|
||||||
std::scoped_lock lock{texture_cache.mutex};
|
std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
|
||||||
texture_cache.PopAsyncFlushes();
|
texture_cache.PopAsyncFlushes();
|
||||||
}
|
|
||||||
buffer_cache.PopAsyncFlushes();
|
buffer_cache.PopAsyncFlushes();
|
||||||
|
}
|
||||||
query_cache.PopAsyncFlushes();
|
query_cache.PopAsyncFlushes();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,9 +264,7 @@ private:
|
|||||||
|
|
||||||
std::jthread fence_thread;
|
std::jthread fence_thread;
|
||||||
|
|
||||||
static constexpr u64 RETIRE_DELAY = 8;
|
DelayedDestructionRing<TFence, 8> delayed_destruction_ring;
|
||||||
u64 retire_tick = 1;
|
|
||||||
DeferredDestructionQueue<TFence> sentenced_fences;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace VideoCommon
|
} // namespace VideoCommon
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ void ThreadManager::StartThread(VideoCore::RendererBase& renderer, Core::Fronten
|
|||||||
thread = std::jthread([&](std::stop_token stop_token) {
|
thread = std::jthread([&](std::stop_token stop_token) {
|
||||||
Common::SetCurrentThreadName("GPU");
|
Common::SetCurrentThreadName("GPU");
|
||||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
|
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
|
||||||
Common::SetCurrentThreadToPerformanceCores();
|
|
||||||
system.RegisterHostThread();
|
system.RegisterHostThread();
|
||||||
|
|
||||||
auto current_context = context.Acquire();
|
auto current_context = context.Acquire();
|
||||||
|
|||||||
@@ -17,13 +17,11 @@ set(SHADER_FILES
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/astc_decoder.comp
|
${CMAKE_CURRENT_SOURCE_DIR}/astc_decoder.comp
|
||||||
${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}/block_linear_unswizzle_2d_buffer.comp
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/blit_color_msaa.frag
|
${CMAKE_CURRENT_SOURCE_DIR}/blit_color_msaa.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
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_3d_bcn.comp
|
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_3d_bcn.comp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_3d_buffer.comp
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/convert_abgr8_to_d24s8.frag
|
${CMAKE_CURRENT_SOURCE_DIR}/convert_abgr8_to_d24s8.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/convert_abgr8_to_d32f.frag
|
${CMAKE_CURRENT_SOURCE_DIR}/convert_abgr8_to_d32f.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/convert_d32f_to_abgr8.frag
|
${CMAKE_CURRENT_SOURCE_DIR}/convert_d32f_to_abgr8.frag
|
||||||
@@ -34,7 +32,6 @@ set(SHADER_FILES
|
|||||||
${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_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_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
|
||||||
|
|||||||
@@ -77,8 +77,14 @@ uvec4 local_buff;
|
|||||||
uvec4 color_endpoint_data;
|
uvec4 color_endpoint_data;
|
||||||
int color_bitsread = 0;
|
int color_bitsread = 0;
|
||||||
|
|
||||||
#define MAX_WEIGHT_VALUES 64
|
// Global "vector" to be pushed into when decoding
|
||||||
uint result_vector[MAX_WEIGHT_VALUES];
|
// At most will require BLOCK_WIDTH x BLOCK_HEIGHT in single plane mode
|
||||||
|
// At most will require BLOCK_WIDTH x BLOCK_HEIGHT x 2 in dual plane mode
|
||||||
|
// So the maximum would be 144 (12 x 12) elements, x 2 for two planes
|
||||||
|
#define DIVCEIL(number, divisor) (number + divisor - 1) / divisor
|
||||||
|
#define ARRAY_NUM_ELEMENTS 144
|
||||||
|
#define VECTOR_ARRAY_SIZE DIVCEIL(ARRAY_NUM_ELEMENTS * 2, 4)
|
||||||
|
uint result_vector[ARRAY_NUM_ELEMENTS * 2];
|
||||||
|
|
||||||
int result_index = 0;
|
int result_index = 0;
|
||||||
uint result_vector_max_index;
|
uint result_vector_max_index;
|
||||||
@@ -486,7 +492,7 @@ void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits, o
|
|||||||
A = ReplicateBitTo9((bitval & 1));
|
A = ReplicateBitTo9((bitval & 1));
|
||||||
switch (encoding) {
|
switch (encoding) {
|
||||||
case JUST_BITS:
|
case JUST_BITS:
|
||||||
color_values[out_index++] = FastReplicateTo8(bitval, bitlen);
|
color_values[++out_index] = FastReplicateTo8(bitval, bitlen);
|
||||||
break;
|
break;
|
||||||
case TRIT: {
|
case TRIT: {
|
||||||
D = QuintTritValue(val);
|
D = QuintTritValue(val);
|
||||||
@@ -565,7 +571,7 @@ void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits, o
|
|||||||
uint T = (D * C) + B;
|
uint T = (D * C) + B;
|
||||||
T ^= A;
|
T ^= A;
|
||||||
T = (A & 0x80) | (T >> 2);
|
T = (A & 0x80) | (T >> 2);
|
||||||
color_values[out_index++] = T;
|
color_values[++out_index] = T;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -747,12 +753,12 @@ void ComputeEndpoints(out uvec4 ep1, out uvec4 ep2, uint color_endpoint_mode, ui
|
|||||||
#define READ_UINT_VALUES(N) \
|
#define READ_UINT_VALUES(N) \
|
||||||
uvec4 V[2]; \
|
uvec4 V[2]; \
|
||||||
for (uint i = 0; i < N; i++) { \
|
for (uint i = 0; i < N; i++) { \
|
||||||
V[i / 4][i % 4] = color_values[colvals_index++]; \
|
V[i / 4][i % 4] = color_values[++colvals_index]; \
|
||||||
}
|
}
|
||||||
#define READ_INT_VALUES(N) \
|
#define READ_INT_VALUES(N) \
|
||||||
ivec4 V[2]; \
|
ivec4 V[2]; \
|
||||||
for (uint i = 0; i < N; i++) { \
|
for (uint i = 0; i < N; i++) { \
|
||||||
V[i / 4][i % 4] = int(color_values[colvals_index++]); \
|
V[i / 4][i % 4] = int(color_values[++colvals_index]); \
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (color_endpoint_mode) {
|
switch (color_endpoint_mode) {
|
||||||
@@ -1219,10 +1225,6 @@ void DecompressBlock(ivec3 coord) {
|
|||||||
FillError(coord);
|
FillError(coord);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (GetNumWeightValues(size_params, dual_plane) > MAX_WEIGHT_VALUES) {
|
|
||||||
FillError(coord);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uint partition_index = 1;
|
uint partition_index = 1;
|
||||||
uvec4 color_endpoint_mode = uvec4(0);
|
uvec4 color_endpoint_mode = uvec4(0);
|
||||||
uint ced_pointer = 0;
|
uint ced_pointer = 0;
|
||||||
@@ -1382,7 +1384,11 @@ void DecompressBlock(ivec3 coord) {
|
|||||||
p = Cf / 65535.0f;
|
p = Cf / 65535.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef VULKAN
|
||||||
|
imageStore(dest_image, coord + ivec3(i, j, 0), p.gbar);
|
||||||
|
#else
|
||||||
imageStore(dest_image, coord + ivec3(i, j, 0), clamp(p, 0.0f, 1.0f).gbar);
|
imageStore(dest_image, coord + ivec3(i, j, 0), clamp(p, 0.0f, 1.0f).gbar);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,104 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
#version 430
|
|
||||||
|
|
||||||
#extension GL_EXT_shader_16bit_storage : require
|
|
||||||
#extension GL_EXT_shader_8bit_storage : require
|
|
||||||
|
|
||||||
#define BINDING_INPUT_BUFFER 0
|
|
||||||
#define BINDING_OUTPUT_BUFFER 1
|
|
||||||
|
|
||||||
layout(push_constant) uniform PushConstants {
|
|
||||||
uvec3 dim;
|
|
||||||
uint bytes_per_block_log2;
|
|
||||||
|
|
||||||
uvec3 origin;
|
|
||||||
uint layer_stride;
|
|
||||||
|
|
||||||
uint block_size;
|
|
||||||
uint x_shift;
|
|
||||||
uint block_height;
|
|
||||||
uint block_height_mask;
|
|
||||||
} pc;
|
|
||||||
|
|
||||||
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU32 { uint u32data[]; };
|
|
||||||
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU64 { uvec2 u64data[]; };
|
|
||||||
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU128 { uvec4 u128data[]; };
|
|
||||||
|
|
||||||
layout(binding = BINDING_OUTPUT_BUFFER, std430) writeonly buffer OutputBuffer {
|
|
||||||
uint out_u32[];
|
|
||||||
};
|
|
||||||
|
|
||||||
layout(local_size_x = 16, local_size_y = 8, local_size_z = 1) in;
|
|
||||||
|
|
||||||
const uint GOB_SIZE_X = 64;
|
|
||||||
const uint GOB_SIZE_Y = 8;
|
|
||||||
|
|
||||||
const uint GOB_SIZE_X_SHIFT = 6;
|
|
||||||
const uint GOB_SIZE_Y_SHIFT = 3;
|
|
||||||
const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT;
|
|
||||||
|
|
||||||
const uvec2 SWIZZLE_MASK = uvec2(GOB_SIZE_X - 1u, GOB_SIZE_Y - 1u);
|
|
||||||
|
|
||||||
uint SwizzleTable(uint pos) {
|
|
||||||
const uint t[8] = uint[](
|
|
||||||
0x12100200, 0x13110301, 0x16140604, 0x17150705,
|
|
||||||
0x1a180a08, 0x1b190b09, 0x1e1c0e0c, 0x1f1d0f0d
|
|
||||||
);
|
|
||||||
const uint i = pos >> 4;
|
|
||||||
const uint h = (t[i / 4] >> ((i % 4) * 8)) & 0xff;
|
|
||||||
return (h << 4) | (pos & 0xf);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint SwizzleOffset(uvec2 pos) {
|
|
||||||
pos = pos & SWIZZLE_MASK;
|
|
||||||
return SwizzleTable(pos.y * 64u + pos.x);
|
|
||||||
}
|
|
||||||
|
|
||||||
uvec4 ReadTexel(uint offset) {
|
|
||||||
switch (pc.bytes_per_block_log2) {
|
|
||||||
case 2u:
|
|
||||||
return uvec4(u32data[offset / 4u], 0u, 0u, 0u);
|
|
||||||
case 3u:
|
|
||||||
return uvec4(u64data[offset / 8u], 0u, 0u);
|
|
||||||
case 4u:
|
|
||||||
return u128data[offset / 16u];
|
|
||||||
}
|
|
||||||
return uvec4(0u);
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
uvec3 coord = gl_GlobalInvocationID;
|
|
||||||
if (coord.x >= pc.dim.x || coord.y >= pc.dim.y || coord.z >= pc.dim.z) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uvec3 pos = coord + pc.origin;
|
|
||||||
pos.x <<= pc.bytes_per_block_log2;
|
|
||||||
|
|
||||||
uint swizzle = SwizzleOffset(pos.xy);
|
|
||||||
uint block_y = pos.y >> GOB_SIZE_Y_SHIFT;
|
|
||||||
|
|
||||||
uint offset = 0u;
|
|
||||||
offset += pos.z * pc.layer_stride;
|
|
||||||
offset += (block_y >> pc.block_height) * pc.block_size;
|
|
||||||
offset += (block_y & pc.block_height_mask) << GOB_SIZE_SHIFT;
|
|
||||||
offset += (pos.x >> GOB_SIZE_X_SHIFT) << pc.x_shift;
|
|
||||||
offset += swizzle;
|
|
||||||
|
|
||||||
uvec4 texel = ReadTexel(offset);
|
|
||||||
|
|
||||||
uint words = 1u << (pc.bytes_per_block_log2 - 2u);
|
|
||||||
uint linear_index = coord.x + coord.y * pc.dim.x + coord.z * pc.dim.x * pc.dim.y;
|
|
||||||
uint out_idx = linear_index * words;
|
|
||||||
|
|
||||||
out_u32[out_idx] = texel.x;
|
|
||||||
if (words > 1u) {
|
|
||||||
out_u32[out_idx + 1u] = texel.y;
|
|
||||||
}
|
|
||||||
if (words > 2u) {
|
|
||||||
out_u32[out_idx + 2u] = texel.z;
|
|
||||||
out_u32[out_idx + 3u] = texel.w;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
#version 430
|
|
||||||
|
|
||||||
#define BINDING_INPUT_BUFFER 0
|
|
||||||
#define BINDING_OUTPUT_BUFFER 1
|
|
||||||
|
|
||||||
layout(push_constant) uniform PushConstants {
|
|
||||||
uvec3 dim;
|
|
||||||
uint bytes_per_block_log2;
|
|
||||||
|
|
||||||
uvec3 origin;
|
|
||||||
uint slice_size;
|
|
||||||
|
|
||||||
uint block_size;
|
|
||||||
uint x_shift;
|
|
||||||
uint block_height;
|
|
||||||
uint block_height_mask;
|
|
||||||
|
|
||||||
uint block_depth;
|
|
||||||
uint block_depth_mask;
|
|
||||||
} pc;
|
|
||||||
|
|
||||||
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU32 { uint u32data[]; };
|
|
||||||
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU64 { uvec2 u64data[]; };
|
|
||||||
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU128 { uvec4 u128data[]; };
|
|
||||||
|
|
||||||
layout(binding = BINDING_OUTPUT_BUFFER, std430) writeonly buffer OutputBuffer {
|
|
||||||
uint out_u32[];
|
|
||||||
};
|
|
||||||
|
|
||||||
layout(local_size_x = 8, local_size_y = 8, local_size_z = 4) in;
|
|
||||||
|
|
||||||
const uint GOB_SIZE_X = 64;
|
|
||||||
const uint GOB_SIZE_Y = 8;
|
|
||||||
|
|
||||||
const uint GOB_SIZE_X_SHIFT = 6;
|
|
||||||
const uint GOB_SIZE_Y_SHIFT = 3;
|
|
||||||
const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT;
|
|
||||||
|
|
||||||
const uvec2 SWIZZLE_MASK = uvec2(GOB_SIZE_X - 1u, GOB_SIZE_Y - 1u);
|
|
||||||
|
|
||||||
uint SwizzleTable(uint pos) {
|
|
||||||
const uint t[8] = uint[](
|
|
||||||
0x12100200, 0x13110301, 0x16140604, 0x17150705,
|
|
||||||
0x1a180a08, 0x1b190b09, 0x1e1c0e0c, 0x1f1d0f0d
|
|
||||||
);
|
|
||||||
const uint i = pos >> 4;
|
|
||||||
const uint h = (t[i / 4] >> ((i % 4) * 8)) & 0xff;
|
|
||||||
return (h << 4) | (pos & 0xf);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint SwizzleOffset(uvec2 pos) {
|
|
||||||
pos = pos & SWIZZLE_MASK;
|
|
||||||
return SwizzleTable(pos.y * 64u + pos.x);
|
|
||||||
}
|
|
||||||
|
|
||||||
uvec4 ReadTexel(uint offset) {
|
|
||||||
switch (pc.bytes_per_block_log2) {
|
|
||||||
case 2u:
|
|
||||||
return uvec4(u32data[offset / 4u], 0u, 0u, 0u);
|
|
||||||
case 3u:
|
|
||||||
return uvec4(u64data[offset / 8u], 0u, 0u);
|
|
||||||
case 4u:
|
|
||||||
return u128data[offset / 16u];
|
|
||||||
}
|
|
||||||
return uvec4(0u);
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
uvec3 coord = gl_GlobalInvocationID;
|
|
||||||
if (coord.x >= pc.dim.x || coord.y >= pc.dim.y || coord.z >= pc.dim.z) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uvec3 pos = coord + pc.origin;
|
|
||||||
pos.x <<= pc.bytes_per_block_log2;
|
|
||||||
|
|
||||||
uint swizzle = SwizzleOffset(pos.xy);
|
|
||||||
uint block_y = pos.y >> GOB_SIZE_Y_SHIFT;
|
|
||||||
|
|
||||||
uint offset = 0u;
|
|
||||||
offset += (pos.z >> pc.block_depth) * pc.slice_size;
|
|
||||||
offset += (pos.z & pc.block_depth_mask) << (GOB_SIZE_SHIFT + pc.block_height);
|
|
||||||
offset += (block_y >> pc.block_height) * pc.block_size;
|
|
||||||
offset += (block_y & pc.block_height_mask) << GOB_SIZE_SHIFT;
|
|
||||||
offset += (pos.x >> GOB_SIZE_X_SHIFT) << pc.x_shift;
|
|
||||||
offset += swizzle;
|
|
||||||
|
|
||||||
uvec4 texel = ReadTexel(offset);
|
|
||||||
|
|
||||||
uint words = 1u << (pc.bytes_per_block_log2 - 2u);
|
|
||||||
uint linear_index = coord.x + coord.y * pc.dim.x + coord.z * pc.dim.x * pc.dim.y;
|
|
||||||
uint out_idx = linear_index * words;
|
|
||||||
|
|
||||||
out_u32[out_idx] = texel.x;
|
|
||||||
if (words > 1u) {
|
|
||||||
out_u32[out_idx + 1u] = texel.y;
|
|
||||||
}
|
|
||||||
if (words > 2u) {
|
|
||||||
out_u32[out_idx + 2u] = texel.z;
|
|
||||||
out_u32[out_idx + 3u] = texel.w;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user