mirror of
https://git.eden-emu.dev/archive/citron
synced 2026-04-14 00:30:51 -04:00
feat(video_core): implement comprehensive VRAM management system
Add automatic VRAM leak prevention with configurable garbage collection to prevent device loss crashes during extended play sessions. New Settings: - vram_limit_mb: Configurable VRAM limit (0 = auto-detect 80%) - gc_aggressiveness: Off/Light/Moderate/Heavy/Extreme levels - texture_eviction_frames: Frames before texture eviction (default 2) - buffer_eviction_frames: Frames before buffer eviction (default 5) - sparse_texture_priority_eviction: Prioritize large sparse textures - log_vram_usage: Enable VRAM statistics logging Core Changes: - Enhanced texture cache with LRU eviction and sparse texture priority - Enhanced buffer cache with configurable eviction thresholds - Added VRAM pressure monitoring using VK_EXT_memory_budget - Emergency GC triggers at 90%/95% VRAM usage thresholds Platform Support: - Desktop: Settings in Graphics > Advanced tab - Android: Settings in Zep Zone category Fixes VRAM climbing steadily during gameplay until device loss. Target: Stable VRAM usage below configured limit for 2+ hours. Signed-off-by: Zephyron <zephyron@citron-emu.org>
This commit is contained in:
@@ -32,7 +32,11 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
|||||||
SHOW_SHADER_BUILDING_OVERLAY("show_shader_building_overlay"),
|
SHOW_SHADER_BUILDING_OVERLAY("show_shader_building_overlay"),
|
||||||
SHOW_PERFORMANCE_GRAPH("show_performance_graph"),
|
SHOW_PERFORMANCE_GRAPH("show_performance_graph"),
|
||||||
USE_CONDITIONAL_RENDERING("use_conditional_rendering"),
|
USE_CONDITIONAL_RENDERING("use_conditional_rendering"),
|
||||||
AIRPLANE_MODE("airplane_mode");
|
AIRPLANE_MODE("airplane_mode"),
|
||||||
|
|
||||||
|
// VRAM Management settings (FIXED: VRAM leak prevention)
|
||||||
|
SPARSE_TEXTURE_PRIORITY_EVICTION("sparse_texture_priority_eviction"),
|
||||||
|
LOG_VRAM_USAGE("log_vram_usage");
|
||||||
|
|
||||||
override fun getBoolean(needsGlobal: Boolean): Boolean =
|
override fun getBoolean(needsGlobal: Boolean): Boolean =
|
||||||
NativeConfig.getBoolean(key, needsGlobal)
|
NativeConfig.getBoolean(key, needsGlobal)
|
||||||
|
|||||||
@@ -40,6 +40,12 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
|
|||||||
VRAM_USAGE_MODE("vram_usage_mode"),
|
VRAM_USAGE_MODE("vram_usage_mode"),
|
||||||
EXTENDED_DYNAMIC_STATE("extended_dynamic_state"),
|
EXTENDED_DYNAMIC_STATE("extended_dynamic_state"),
|
||||||
|
|
||||||
|
// VRAM Management settings (FIXED: VRAM leak prevention)
|
||||||
|
VRAM_LIMIT_MB("vram_limit_mb"),
|
||||||
|
GC_AGGRESSIVENESS("gc_aggressiveness"),
|
||||||
|
TEXTURE_EVICTION_FRAMES("texture_eviction_frames"),
|
||||||
|
BUFFER_EVICTION_FRAMES("buffer_eviction_frames"),
|
||||||
|
|
||||||
// Applet Mode settings
|
// Applet Mode settings
|
||||||
CABINET_APPLET_MODE("cabinet_applet_mode"),
|
CABINET_APPLET_MODE("cabinet_applet_mode"),
|
||||||
CONTROLLER_APPLET_MODE("controller_applet_mode"),
|
CONTROLLER_APPLET_MODE("controller_applet_mode"),
|
||||||
|
|||||||
@@ -479,6 +479,61 @@ abstract class SettingsItem(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// VRAM Management Settings (FIXED: VRAM leak prevention)
|
||||||
|
put(
|
||||||
|
SliderSetting(
|
||||||
|
IntSetting.VRAM_LIMIT_MB,
|
||||||
|
titleId = R.string.vram_limit_mb,
|
||||||
|
descriptionId = R.string.vram_limit_mb_description,
|
||||||
|
min = 0,
|
||||||
|
max = 16384,
|
||||||
|
units = " MB"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SingleChoiceSetting(
|
||||||
|
IntSetting.GC_AGGRESSIVENESS,
|
||||||
|
titleId = R.string.gc_aggressiveness,
|
||||||
|
descriptionId = R.string.gc_aggressiveness_description,
|
||||||
|
choicesId = R.array.gcAggressivenessNames,
|
||||||
|
valuesId = R.array.gcAggressivenessValues
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SliderSetting(
|
||||||
|
IntSetting.TEXTURE_EVICTION_FRAMES,
|
||||||
|
titleId = R.string.texture_eviction_frames,
|
||||||
|
descriptionId = R.string.texture_eviction_frames_description,
|
||||||
|
min = 1,
|
||||||
|
max = 60,
|
||||||
|
units = " frames"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SliderSetting(
|
||||||
|
IntSetting.BUFFER_EVICTION_FRAMES,
|
||||||
|
titleId = R.string.buffer_eviction_frames,
|
||||||
|
descriptionId = R.string.buffer_eviction_frames_description,
|
||||||
|
min = 1,
|
||||||
|
max = 120,
|
||||||
|
units = " frames"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SwitchSetting(
|
||||||
|
BooleanSetting.SPARSE_TEXTURE_PRIORITY_EVICTION,
|
||||||
|
titleId = R.string.sparse_texture_priority_eviction,
|
||||||
|
descriptionId = R.string.sparse_texture_priority_eviction_description
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SwitchSetting(
|
||||||
|
BooleanSetting.LOG_VRAM_USAGE,
|
||||||
|
titleId = R.string.log_vram_usage,
|
||||||
|
descriptionId = R.string.log_vram_usage_description
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
// Applet Mode Settings
|
// Applet Mode Settings
|
||||||
put(
|
put(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
|
|||||||
@@ -1036,6 +1036,15 @@ class SettingsFragmentPresenter(
|
|||||||
add(HeaderSetting(R.string.frame_skipping_header))
|
add(HeaderSetting(R.string.frame_skipping_header))
|
||||||
add(IntSetting.FRAME_SKIPPING.key)
|
add(IntSetting.FRAME_SKIPPING.key)
|
||||||
add(IntSetting.FRAME_SKIPPING_MODE.key)
|
add(IntSetting.FRAME_SKIPPING_MODE.key)
|
||||||
|
|
||||||
|
// VRAM Management settings (FIXED: VRAM leak prevention)
|
||||||
|
add(HeaderSetting(R.string.vram_management_header))
|
||||||
|
add(IntSetting.VRAM_LIMIT_MB.key)
|
||||||
|
add(IntSetting.GC_AGGRESSIVENESS.key)
|
||||||
|
add(IntSetting.TEXTURE_EVICTION_FRAMES.key)
|
||||||
|
add(IntSetting.BUFFER_EVICTION_FRAMES.key)
|
||||||
|
add(BooleanSetting.SPARSE_TEXTURE_PRIORITY_EVICTION.key)
|
||||||
|
add(BooleanSetting.LOG_VRAM_USAGE.key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -440,6 +440,23 @@
|
|||||||
<item>3</item>
|
<item>3</item>
|
||||||
</integer-array>
|
</integer-array>
|
||||||
|
|
||||||
|
<!-- VRAM Management setting arrays (FIXED: VRAM leak prevention) -->
|
||||||
|
<string-array name="gcAggressivenessNames">
|
||||||
|
<item>@string/gc_aggressiveness_off</item>
|
||||||
|
<item>@string/gc_aggressiveness_light</item>
|
||||||
|
<item>@string/gc_aggressiveness_moderate</item>
|
||||||
|
<item>@string/gc_aggressiveness_heavy</item>
|
||||||
|
<item>@string/gc_aggressiveness_extreme</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<integer-array name="gcAggressivenessValues">
|
||||||
|
<item>0</item>
|
||||||
|
<item>1</item>
|
||||||
|
<item>2</item>
|
||||||
|
<item>3</item>
|
||||||
|
<item>4</item>
|
||||||
|
</integer-array>
|
||||||
|
|
||||||
<!-- Applet Mode setting arrays -->
|
<!-- Applet Mode setting arrays -->
|
||||||
<string-array name="appletModeNames">
|
<string-array name="appletModeNames">
|
||||||
<item>HLE (High-Level Emulation)</item>
|
<item>HLE (High-Level Emulation)</item>
|
||||||
|
|||||||
@@ -448,6 +448,7 @@
|
|||||||
<string name="memory_layout_header">Memory Layout</string>
|
<string name="memory_layout_header">Memory Layout</string>
|
||||||
<string name="astc_settings_header">ASTC Settings</string>
|
<string name="astc_settings_header">ASTC Settings</string>
|
||||||
<string name="advanced_graphics_header">Advanced Graphics</string>
|
<string name="advanced_graphics_header">Advanced Graphics</string>
|
||||||
|
<string name="vram_management_header">VRAM Management</string>
|
||||||
<string name="applet_settings_header">Applet Settings</string>
|
<string name="applet_settings_header">Applet Settings</string>
|
||||||
|
|
||||||
<!-- Applet Mode Settings -->
|
<!-- Applet Mode Settings -->
|
||||||
@@ -1273,6 +1274,25 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||||||
<string name="frame_skipping_enabled">Enabled</string>
|
<string name="frame_skipping_enabled">Enabled</string>
|
||||||
<string name="frame_skipping_mode_adaptive">Adaptive</string>
|
<string name="frame_skipping_mode_adaptive">Adaptive</string>
|
||||||
<string name="frame_skipping_mode_fixed">Fixed</string>
|
<string name="frame_skipping_mode_fixed">Fixed</string>
|
||||||
|
|
||||||
|
<!-- VRAM Management settings (FIXED: VRAM leak prevention) -->
|
||||||
|
<string name="vram_limit_mb">VRAM Limit (MB)</string>
|
||||||
|
<string name="vram_limit_mb_description">Maximum VRAM usage limit in megabytes. Set to 0 for auto-detection (80%% of available VRAM). Recommended: 6144 for 8GB GPUs, 4096 for 6GB GPUs.</string>
|
||||||
|
<string name="gc_aggressiveness">GC Aggressiveness</string>
|
||||||
|
<string name="gc_aggressiveness_description">Controls how aggressively the emulator evicts unused textures and buffers from VRAM. Higher levels free memory faster but may cause more texture reloading.</string>
|
||||||
|
<string name="texture_eviction_frames">Texture Eviction Frames</string>
|
||||||
|
<string name="texture_eviction_frames_description">Number of frames a texture must be unused before it can be evicted. Lower values free VRAM faster but may cause more texture reloading.</string>
|
||||||
|
<string name="buffer_eviction_frames">Buffer Eviction Frames</string>
|
||||||
|
<string name="buffer_eviction_frames_description">Number of frames a buffer must be unused before it can be evicted. Lower values free VRAM faster but may cause more buffer reloading.</string>
|
||||||
|
<string name="sparse_texture_priority_eviction">Sparse Texture Priority Eviction</string>
|
||||||
|
<string name="sparse_texture_priority_eviction_description">Prioritize evicting large sparse textures when VRAM pressure is high. Helps prevent VRAM exhaustion in games with large texture atlases.</string>
|
||||||
|
<string name="log_vram_usage">Log VRAM Usage</string>
|
||||||
|
<string name="log_vram_usage_description">Enable logging of VRAM usage statistics for debugging purposes.</string>
|
||||||
|
<string name="gc_aggressiveness_off">Off (Not Recommended)</string>
|
||||||
|
<string name="gc_aggressiveness_light">Light</string>
|
||||||
|
<string name="gc_aggressiveness_moderate">Moderate (Recommended)</string>
|
||||||
|
<string name="gc_aggressiveness_heavy">Heavy (Low VRAM)</string>
|
||||||
|
<string name="gc_aggressiveness_extreme">Extreme (4GB VRAM)</string>
|
||||||
<string name="frame_skipping_header">Frame Skipping</string>
|
<string name="frame_skipping_header">Frame Skipping</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -192,6 +192,31 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
|
|||||||
"of available video memory for performance. Has no effect on integrated graphics. "
|
"of available video memory for performance. Has no effect on integrated graphics. "
|
||||||
"Aggressive mode may severely impact the performance of other applications such as "
|
"Aggressive mode may severely impact the performance of other applications such as "
|
||||||
"recording software."));
|
"recording software."));
|
||||||
|
|
||||||
|
// FIXED: VRAM leak prevention - New VRAM management settings
|
||||||
|
INSERT(Settings, vram_limit_mb, tr("VRAM Limit (MB):"),
|
||||||
|
tr("Sets the maximum VRAM usage limit in megabytes. Set to 0 for auto-detection "
|
||||||
|
"(80% of available VRAM). Recommended: 6144 for 8GB GPUs, 4096 for 6GB GPUs."));
|
||||||
|
INSERT(Settings, gc_aggressiveness, tr("GC Aggressiveness:"),
|
||||||
|
tr("Controls how aggressively the emulator evicts unused textures and buffers from VRAM.\n"
|
||||||
|
"Off: Disable automatic cleanup (not recommended, may cause crashes).\n"
|
||||||
|
"Light: Gentle cleanup, keeps more textures cached.\n"
|
||||||
|
"Moderate: Balanced cleanup (recommended for most users).\n"
|
||||||
|
"Heavy: Aggressive cleanup for low VRAM systems (6GB or less).\n"
|
||||||
|
"Extreme: Maximum cleanup for very low VRAM systems (4GB)."));
|
||||||
|
INSERT(Settings, texture_eviction_frames, tr("Texture Eviction Frames:"),
|
||||||
|
tr("Number of frames a texture must be unused before it can be evicted. "
|
||||||
|
"Lower values free VRAM faster but may cause more texture reloading."));
|
||||||
|
INSERT(Settings, buffer_eviction_frames, tr("Buffer Eviction Frames:"),
|
||||||
|
tr("Number of frames a buffer must be unused before it can be evicted. "
|
||||||
|
"Lower values free VRAM faster but may cause more buffer reloading."));
|
||||||
|
INSERT(Settings, sparse_texture_priority_eviction, tr("Sparse Texture Priority Eviction"),
|
||||||
|
tr("Prioritize evicting large sparse textures when VRAM pressure is high. "
|
||||||
|
"This helps prevent VRAM exhaustion in games with large texture atlases."));
|
||||||
|
INSERT(Settings, log_vram_usage, tr("Log VRAM Usage"),
|
||||||
|
tr("Enable logging of VRAM usage statistics for debugging purposes. "
|
||||||
|
"Check the log for 'VRAM GC' and 'VRAM Status' messages."));
|
||||||
|
|
||||||
INSERT(
|
INSERT(
|
||||||
Settings, vsync_mode, tr("VSync Mode:"),
|
Settings, vsync_mode, tr("VSync Mode:"),
|
||||||
tr("FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen "
|
tr("FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen "
|
||||||
@@ -367,6 +392,17 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QWidget* parent) {
|
|||||||
PAIR(ExtendedDynamicState, EDS2, tr("EDS2")),
|
PAIR(ExtendedDynamicState, EDS2, tr("EDS2")),
|
||||||
PAIR(ExtendedDynamicState, EDS3, tr("EDS3")),
|
PAIR(ExtendedDynamicState, EDS3, tr("EDS3")),
|
||||||
}});
|
}});
|
||||||
|
|
||||||
|
// FIXED: VRAM leak prevention - GC Aggressiveness dropdown options
|
||||||
|
translations->insert({Settings::EnumMetadata<Settings::GCAggressiveness>::Index(),
|
||||||
|
{
|
||||||
|
PAIR(GCAggressiveness, Off, tr("Off (Not Recommended)")),
|
||||||
|
PAIR(GCAggressiveness, Light, tr("Light")),
|
||||||
|
PAIR(GCAggressiveness, Moderate, tr("Moderate (Recommended)")),
|
||||||
|
PAIR(GCAggressiveness, Heavy, tr("Heavy (Low VRAM)")),
|
||||||
|
PAIR(GCAggressiveness, Extreme, tr("Extreme (4GB VRAM)")),
|
||||||
|
}});
|
||||||
|
|
||||||
translations->insert({Settings::EnumMetadata<Settings::RendererBackend>::Index(),
|
translations->insert({Settings::EnumMetadata<Settings::RendererBackend>::Index(),
|
||||||
{
|
{
|
||||||
#ifdef HAS_OPENGL
|
#ifdef HAS_OPENGL
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ SWITCHABLE(AstcDecodeMode, true);
|
|||||||
SWITCHABLE(AstcRecompression, true);
|
SWITCHABLE(AstcRecompression, true);
|
||||||
SWITCHABLE(AudioMode, true);
|
SWITCHABLE(AudioMode, true);
|
||||||
SWITCHABLE(ExtendedDynamicState, true);
|
SWITCHABLE(ExtendedDynamicState, true);
|
||||||
|
SWITCHABLE(GCAggressiveness, true);
|
||||||
SWITCHABLE(CpuBackend, true);
|
SWITCHABLE(CpuBackend, true);
|
||||||
SWITCHABLE(CpuAccuracy, true);
|
SWITCHABLE(CpuAccuracy, true);
|
||||||
SWITCHABLE(FullscreenMode, true);
|
SWITCHABLE(FullscreenMode, true);
|
||||||
@@ -501,6 +502,61 @@ struct Values {
|
|||||||
VramUsageMode::Insane,
|
VramUsageMode::Insane,
|
||||||
"vram_usage_mode",
|
"vram_usage_mode",
|
||||||
Category::RendererAdvanced};
|
Category::RendererAdvanced};
|
||||||
|
|
||||||
|
// FIXED: VRAM leak prevention - New memory management settings
|
||||||
|
// VRAM limit in MB (0 = auto-detect based on GPU, default 6144 for 6GB limit)
|
||||||
|
SwitchableSetting<u32, true> vram_limit_mb{linkage,
|
||||||
|
0, // 0 = auto-detect (80% of available VRAM)
|
||||||
|
0, // min: 0 (auto)
|
||||||
|
32768, // max: 32GB
|
||||||
|
"vram_limit_mb",
|
||||||
|
Category::RendererAdvanced,
|
||||||
|
Specialization::Default,
|
||||||
|
true,
|
||||||
|
true};
|
||||||
|
|
||||||
|
// GC aggressiveness level for texture/buffer cache eviction
|
||||||
|
SwitchableSetting<GCAggressiveness, true> gc_aggressiveness{linkage,
|
||||||
|
GCAggressiveness::Moderate,
|
||||||
|
GCAggressiveness::Off,
|
||||||
|
GCAggressiveness::Extreme,
|
||||||
|
"gc_aggressiveness",
|
||||||
|
Category::RendererAdvanced,
|
||||||
|
Specialization::Default,
|
||||||
|
true,
|
||||||
|
true};
|
||||||
|
|
||||||
|
// Number of frames before unused textures are evicted (default 2)
|
||||||
|
SwitchableSetting<u32, true> texture_eviction_frames{linkage,
|
||||||
|
2, // default: 2 frames
|
||||||
|
1, // min: 1 frame
|
||||||
|
60, // max: 60 frames (1 second at 60fps)
|
||||||
|
"texture_eviction_frames",
|
||||||
|
Category::RendererAdvanced,
|
||||||
|
Specialization::Default,
|
||||||
|
true,
|
||||||
|
true};
|
||||||
|
|
||||||
|
// Number of frames before unused buffers are evicted (default 5)
|
||||||
|
SwitchableSetting<u32, true> buffer_eviction_frames{linkage,
|
||||||
|
5, // default: 5 frames
|
||||||
|
1, // min: 1 frame
|
||||||
|
120, // max: 120 frames (2 seconds at 60fps)
|
||||||
|
"buffer_eviction_frames",
|
||||||
|
Category::RendererAdvanced,
|
||||||
|
Specialization::Default,
|
||||||
|
true,
|
||||||
|
true};
|
||||||
|
|
||||||
|
// Enable sparse texture priority eviction (evict large unmapped pages first)
|
||||||
|
SwitchableSetting<bool> sparse_texture_priority_eviction{linkage, true,
|
||||||
|
"sparse_texture_priority_eviction",
|
||||||
|
Category::RendererAdvanced};
|
||||||
|
|
||||||
|
// Enable VRAM usage logging for debugging
|
||||||
|
SwitchableSetting<bool> log_vram_usage{linkage, false, "log_vram_usage",
|
||||||
|
Category::RendererAdvanced};
|
||||||
|
|
||||||
SwitchableSetting<bool> async_presentation{linkage,
|
SwitchableSetting<bool> async_presentation{linkage,
|
||||||
#ifdef ANDROID
|
#ifdef ANDROID
|
||||||
true,
|
true,
|
||||||
|
|||||||
@@ -880,6 +880,32 @@ inline u32 EnumMetadata<ExtendedDynamicState>::Index() {
|
|||||||
return 26;
|
return 26;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXED: VRAM leak prevention - GC aggressiveness levels
|
||||||
|
enum class GCAggressiveness : u32 {
|
||||||
|
Off = 0, // Disable automatic GC (not recommended)
|
||||||
|
Light = 1, // Light GC - only evict very old textures
|
||||||
|
Moderate = 2, // Moderate GC - balanced eviction (default)
|
||||||
|
Heavy = 3, // Heavy GC - aggressive eviction for low VRAM systems
|
||||||
|
Extreme = 4, // Extreme GC - maximum eviction for 4GB VRAM systems
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline std::vector<std::pair<std::string, GCAggressiveness>>
|
||||||
|
EnumMetadata<GCAggressiveness>::Canonicalizations() {
|
||||||
|
return {
|
||||||
|
{"Off", GCAggressiveness::Off},
|
||||||
|
{"Light", GCAggressiveness::Light},
|
||||||
|
{"Moderate", GCAggressiveness::Moderate},
|
||||||
|
{"Heavy", GCAggressiveness::Heavy},
|
||||||
|
{"Extreme", GCAggressiveness::Extreme},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline u32 EnumMetadata<GCAggressiveness>::Index() {
|
||||||
|
return 27;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
template <typename Type>
|
template <typename Type>
|
||||||
inline std::string CanonicalizeEnum(Type id) {
|
inline std::string CanonicalizeEnum(Type id) {
|
||||||
|
|||||||
@@ -26,24 +26,60 @@ BufferCache<P>::BufferCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, R
|
|||||||
gpu_modified_ranges.Clear();
|
gpu_modified_ranges.Clear();
|
||||||
inline_buffer_id = NULL_BUFFER_ID;
|
inline_buffer_id = NULL_BUFFER_ID;
|
||||||
|
|
||||||
|
// FIXED: VRAM leak prevention - Initialize buffer VRAM management from settings
|
||||||
|
const u32 configured_limit_mb = Settings::values.vram_limit_mb.GetValue();
|
||||||
|
|
||||||
if (!runtime.CanReportMemoryUsage()) {
|
if (!runtime.CanReportMemoryUsage()) {
|
||||||
minimum_memory = DEFAULT_EXPECTED_MEMORY;
|
minimum_memory = DEFAULT_EXPECTED_MEMORY;
|
||||||
critical_memory = DEFAULT_CRITICAL_MEMORY;
|
critical_memory = DEFAULT_CRITICAL_MEMORY;
|
||||||
|
vram_limit_bytes = configured_limit_mb > 0 ? static_cast<u64>(configured_limit_mb) * 1_MiB
|
||||||
|
: 6_GiB;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const s64 device_local_memory = static_cast<s64>(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;
|
// FIXED: VRAM leak prevention - Use configured limit or auto-detect
|
||||||
const s64 mem_threshold = std::min(device_local_memory, TARGET_THRESHOLD);
|
if (configured_limit_mb > 0) {
|
||||||
const s64 min_vacancy_expected = (6 * mem_threshold) / 10;
|
vram_limit_bytes = static_cast<u64>(configured_limit_mb) * 1_MiB;
|
||||||
const s64 min_vacancy_critical = (2 * mem_threshold) / 10;
|
} else {
|
||||||
minimum_memory = static_cast<u64>(
|
vram_limit_bytes = static_cast<u64>(device_local_memory * 0.80);
|
||||||
std::max(std::min(device_local_memory - min_vacancy_expected, min_spacing_expected),
|
}
|
||||||
DEFAULT_EXPECTED_MEMORY));
|
|
||||||
critical_memory = static_cast<u64>(
|
// Adjust thresholds based on GC aggressiveness setting
|
||||||
std::max(std::min(device_local_memory - min_vacancy_critical, min_spacing_critical),
|
const auto gc_level = Settings::values.gc_aggressiveness.GetValue();
|
||||||
DEFAULT_CRITICAL_MEMORY));
|
f32 expected_ratio = 0.5f;
|
||||||
|
f32 critical_ratio = 0.7f;
|
||||||
|
|
||||||
|
switch (gc_level) {
|
||||||
|
case Settings::GCAggressiveness::Off:
|
||||||
|
expected_ratio = 0.90f;
|
||||||
|
critical_ratio = 0.95f;
|
||||||
|
break;
|
||||||
|
case Settings::GCAggressiveness::Light:
|
||||||
|
expected_ratio = 0.70f;
|
||||||
|
critical_ratio = 0.85f;
|
||||||
|
break;
|
||||||
|
case Settings::GCAggressiveness::Moderate:
|
||||||
|
expected_ratio = 0.50f;
|
||||||
|
critical_ratio = 0.70f;
|
||||||
|
break;
|
||||||
|
case Settings::GCAggressiveness::Heavy:
|
||||||
|
expected_ratio = 0.40f;
|
||||||
|
critical_ratio = 0.60f;
|
||||||
|
break;
|
||||||
|
case Settings::GCAggressiveness::Extreme:
|
||||||
|
expected_ratio = 0.30f;
|
||||||
|
critical_ratio = 0.50f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
minimum_memory = static_cast<u64>(vram_limit_bytes * expected_ratio);
|
||||||
|
critical_memory = static_cast<u64>(vram_limit_bytes * critical_ratio);
|
||||||
|
|
||||||
|
LOG_INFO(Render_Vulkan,
|
||||||
|
"Buffer cache VRAM initialized: limit={}MB, minimum={}MB, critical={}MB",
|
||||||
|
vram_limit_bytes / 1_MiB, minimum_memory / 1_MiB, critical_memory / 1_MiB);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
@@ -51,20 +87,90 @@ BufferCache<P>::~BufferCache() = default;
|
|||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
void BufferCache<P>::RunGarbageCollector() {
|
void BufferCache<P>::RunGarbageCollector() {
|
||||||
|
// FIXED: VRAM leak prevention - Enhanced buffer GC with settings integration
|
||||||
|
|
||||||
|
const auto gc_level = Settings::values.gc_aggressiveness.GetValue();
|
||||||
|
if (gc_level == Settings::GCAggressiveness::Off) {
|
||||||
|
return; // GC disabled by user
|
||||||
|
}
|
||||||
|
|
||||||
const bool aggressive_gc = total_used_memory >= critical_memory;
|
const bool aggressive_gc = total_used_memory >= critical_memory;
|
||||||
const u64 ticks_to_destroy = aggressive_gc ? 60 : 120;
|
const bool emergency_gc = total_used_memory >= static_cast<u64>(vram_limit_bytes * BUFFER_VRAM_CRITICAL_THRESHOLD);
|
||||||
int num_iterations = aggressive_gc ? 64 : 32;
|
|
||||||
const auto clean_up = [this, &num_iterations](BufferId buffer_id) {
|
// FIXED: VRAM leak prevention - Get eviction frames from settings
|
||||||
|
const u64 eviction_frames = Settings::values.buffer_eviction_frames.GetValue();
|
||||||
|
|
||||||
|
// Adjust based on GC level
|
||||||
|
u64 base_ticks = eviction_frames;
|
||||||
|
int base_iterations = 32;
|
||||||
|
|
||||||
|
switch (gc_level) {
|
||||||
|
case Settings::GCAggressiveness::Light:
|
||||||
|
base_ticks = eviction_frames * 2;
|
||||||
|
base_iterations = 16;
|
||||||
|
break;
|
||||||
|
case Settings::GCAggressiveness::Moderate:
|
||||||
|
base_ticks = eviction_frames;
|
||||||
|
base_iterations = 32;
|
||||||
|
break;
|
||||||
|
case Settings::GCAggressiveness::Heavy:
|
||||||
|
base_ticks = std::max(1ULL, eviction_frames / 2);
|
||||||
|
base_iterations = 64;
|
||||||
|
break;
|
||||||
|
case Settings::GCAggressiveness::Extreme:
|
||||||
|
base_ticks = 1;
|
||||||
|
base_iterations = 128;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 ticks_to_destroy;
|
||||||
|
int num_iterations;
|
||||||
|
|
||||||
|
if (emergency_gc) {
|
||||||
|
ticks_to_destroy = 1;
|
||||||
|
num_iterations = base_iterations * 4;
|
||||||
|
LOG_WARNING(Render_Vulkan, "Buffer cache emergency GC: usage={}MB, limit={}MB",
|
||||||
|
total_used_memory / 1_MiB, vram_limit_bytes / 1_MiB);
|
||||||
|
} else if (aggressive_gc) {
|
||||||
|
ticks_to_destroy = std::max(1ULL, base_ticks / 2);
|
||||||
|
num_iterations = base_iterations * 2;
|
||||||
|
} else {
|
||||||
|
ticks_to_destroy = base_ticks;
|
||||||
|
num_iterations = base_iterations;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 bytes_freed = 0;
|
||||||
|
const auto clean_up = [this, &num_iterations, &bytes_freed](BufferId buffer_id) {
|
||||||
if (num_iterations == 0) {
|
if (num_iterations == 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
--num_iterations;
|
--num_iterations;
|
||||||
auto& buffer = slot_buffers[buffer_id];
|
auto& buffer = slot_buffers[buffer_id];
|
||||||
|
const u64 buffer_size = buffer.SizeBytes();
|
||||||
|
|
||||||
DownloadBufferMemory(buffer);
|
DownloadBufferMemory(buffer);
|
||||||
DeleteBuffer(buffer_id);
|
DeleteBuffer(buffer_id);
|
||||||
|
|
||||||
|
bytes_freed += buffer_size;
|
||||||
|
--buffer_count;
|
||||||
|
if (buffer_size >= LARGE_BUFFER_THRESHOLD) {
|
||||||
|
large_buffer_memory -= buffer_size;
|
||||||
|
--large_buffer_count;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
lru_cache.ForEachItemBelow(frame_tick - ticks_to_destroy, clean_up);
|
lru_cache.ForEachItemBelow(frame_tick - ticks_to_destroy, clean_up);
|
||||||
|
|
||||||
|
evicted_buffer_bytes += bytes_freed;
|
||||||
|
|
||||||
|
// FIXED: VRAM leak prevention - Log buffer eviction if enabled
|
||||||
|
if (Settings::values.log_vram_usage.GetValue() && bytes_freed > 0) {
|
||||||
|
LOG_INFO(Render_Vulkan, "Buffer GC: evicted {}MB, total={}MB, usage={}MB/{}MB",
|
||||||
|
bytes_freed / 1_MiB, evicted_buffer_bytes / 1_MiB, total_used_memory / 1_MiB,
|
||||||
|
vram_limit_bytes / 1_MiB);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
@@ -96,9 +202,22 @@ void BufferCache<P>::TickFrame() {
|
|||||||
if (runtime.CanReportMemoryUsage()) {
|
if (runtime.CanReportMemoryUsage()) {
|
||||||
total_used_memory = runtime.GetDeviceMemoryUsage();
|
total_used_memory = runtime.GetDeviceMemoryUsage();
|
||||||
}
|
}
|
||||||
if (total_used_memory >= minimum_memory) {
|
|
||||||
|
// FIXED: VRAM leak prevention - Enhanced buffer GC triggering
|
||||||
|
const auto gc_level = Settings::values.gc_aggressiveness.GetValue();
|
||||||
|
const bool should_gc = gc_level != Settings::GCAggressiveness::Off &&
|
||||||
|
(total_used_memory >= minimum_memory ||
|
||||||
|
total_used_memory >= static_cast<u64>(vram_limit_bytes * BUFFER_VRAM_WARNING_THRESHOLD));
|
||||||
|
|
||||||
|
if (should_gc) {
|
||||||
RunGarbageCollector();
|
RunGarbageCollector();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXED: VRAM leak prevention - Force additional GC if still above critical
|
||||||
|
if (total_used_memory >= critical_memory && gc_level != Settings::GCAggressiveness::Off) {
|
||||||
|
RunGarbageCollector();
|
||||||
|
}
|
||||||
|
|
||||||
++frame_tick;
|
++frame_tick;
|
||||||
delayed_destruction_ring.Tick();
|
delayed_destruction_ring.Tick();
|
||||||
|
|
||||||
@@ -1420,12 +1539,31 @@ template <bool insert>
|
|||||||
void BufferCache<P>::ChangeRegister(BufferId buffer_id) {
|
void BufferCache<P>::ChangeRegister(BufferId buffer_id) {
|
||||||
Buffer& buffer = slot_buffers[buffer_id];
|
Buffer& buffer = slot_buffers[buffer_id];
|
||||||
const auto size = buffer.SizeBytes();
|
const auto size = buffer.SizeBytes();
|
||||||
|
const u64 aligned_size = Common::AlignUp(size, 1024);
|
||||||
|
const bool is_large = aligned_size >= LARGE_BUFFER_THRESHOLD;
|
||||||
|
|
||||||
if (insert) {
|
if (insert) {
|
||||||
total_used_memory += Common::AlignUp(size, 1024);
|
total_used_memory += aligned_size;
|
||||||
buffer.setLRUID(lru_cache.Insert(buffer_id, frame_tick));
|
buffer.setLRUID(lru_cache.Insert(buffer_id, frame_tick));
|
||||||
|
|
||||||
|
// FIXED: VRAM leak prevention - Track buffer statistics
|
||||||
|
++buffer_count;
|
||||||
|
if (is_large) {
|
||||||
|
large_buffer_memory += aligned_size;
|
||||||
|
++large_buffer_count;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
total_used_memory -= Common::AlignUp(size, 1024);
|
total_used_memory -= aligned_size;
|
||||||
lru_cache.Free(buffer.getLRUID());
|
lru_cache.Free(buffer.getLRUID());
|
||||||
|
|
||||||
|
// FIXED: VRAM leak prevention - Update buffer statistics on removal
|
||||||
|
if (buffer_count > 0) {
|
||||||
|
--buffer_count;
|
||||||
|
}
|
||||||
|
if (is_large && large_buffer_count > 0) {
|
||||||
|
large_buffer_memory -= aligned_size;
|
||||||
|
--large_buffer_count;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const DAddr device_addr_begin = buffer.CpuAddr();
|
const DAddr device_addr_begin = buffer.CpuAddr();
|
||||||
const DAddr device_addr_end = device_addr_begin + size;
|
const DAddr device_addr_end = device_addr_begin + size;
|
||||||
|
|||||||
@@ -175,6 +175,12 @@ class BufferCache : public VideoCommon::ChannelSetupCaches<BufferCacheChannelInf
|
|||||||
static constexpr s64 DEFAULT_CRITICAL_MEMORY = 1_GiB;
|
static constexpr s64 DEFAULT_CRITICAL_MEMORY = 1_GiB;
|
||||||
static constexpr s64 TARGET_THRESHOLD = 4_GiB;
|
static constexpr s64 TARGET_THRESHOLD = 4_GiB;
|
||||||
|
|
||||||
|
// FIXED: VRAM leak prevention - Enhanced buffer eviction constants
|
||||||
|
static constexpr u64 DEFAULT_BUFFER_EVICTION_FRAMES = 5;
|
||||||
|
static constexpr size_t LARGE_BUFFER_THRESHOLD = 8_MiB;
|
||||||
|
static constexpr f32 BUFFER_VRAM_WARNING_THRESHOLD = 0.70f;
|
||||||
|
static constexpr f32 BUFFER_VRAM_CRITICAL_THRESHOLD = 0.85f;
|
||||||
|
|
||||||
// Debug Flags.
|
// Debug Flags.
|
||||||
|
|
||||||
static constexpr bool DISABLE_DOWNLOADS = true;
|
static constexpr bool DISABLE_DOWNLOADS = true;
|
||||||
@@ -350,6 +356,31 @@ public:
|
|||||||
RunGarbageCollector();
|
RunGarbageCollector();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXED: VRAM leak prevention - Enhanced public interface for buffer VRAM management
|
||||||
|
|
||||||
|
/// Get buffer VRAM usage statistics
|
||||||
|
struct BufferVRAMStats {
|
||||||
|
u64 total_used_bytes;
|
||||||
|
u64 large_buffer_bytes;
|
||||||
|
u64 evicted_total;
|
||||||
|
u32 buffer_count;
|
||||||
|
u32 large_buffer_count;
|
||||||
|
};
|
||||||
|
[[nodiscard]] BufferVRAMStats GetBufferVRAMStats() const noexcept {
|
||||||
|
return BufferVRAMStats{
|
||||||
|
.total_used_bytes = total_used_memory,
|
||||||
|
.large_buffer_bytes = large_buffer_memory,
|
||||||
|
.evicted_total = evicted_buffer_bytes,
|
||||||
|
.buffer_count = buffer_count,
|
||||||
|
.large_buffer_count = large_buffer_count,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if buffer VRAM pressure is high
|
||||||
|
[[nodiscard]] bool IsBufferVRAMPressureHigh() const noexcept {
|
||||||
|
return total_used_memory >= minimum_memory;
|
||||||
|
}
|
||||||
|
|
||||||
void BindHostIndexBuffer();
|
void BindHostIndexBuffer();
|
||||||
|
|
||||||
void BindHostVertexBuffers();
|
void BindHostVertexBuffers();
|
||||||
@@ -488,6 +519,13 @@ public:
|
|||||||
u64 critical_memory = 0;
|
u64 critical_memory = 0;
|
||||||
BufferId inline_buffer_id;
|
BufferId inline_buffer_id;
|
||||||
|
|
||||||
|
// FIXED: VRAM leak prevention - Enhanced buffer memory tracking
|
||||||
|
u64 vram_limit_bytes = 0; // Configured VRAM limit for buffers
|
||||||
|
u64 large_buffer_memory = 0; // Memory used by large buffers (>8MB)
|
||||||
|
u64 evicted_buffer_bytes = 0; // Total bytes evicted since start
|
||||||
|
u32 buffer_count = 0; // Total buffer count
|
||||||
|
u32 large_buffer_count = 0; // Large buffer count
|
||||||
|
|
||||||
std::array<BufferId, ((1ULL << 34) >> CACHING_PAGEBITS)> page_table;
|
std::array<BufferId, ((1ULL << 34) >> CACHING_PAGEBITS)> page_table;
|
||||||
Common::ScratchBuffer<u8> tmp_buffer;
|
Common::ScratchBuffer<u8> tmp_buffer;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -159,6 +159,25 @@ void RendererVulkan::Composite(std::span<const Tegra::FramebufferConfig> framebu
|
|||||||
render_window.OnFrameDisplayed();
|
render_window.OnFrameDisplayed();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// FIXED: VRAM leak prevention - Check VRAM pressure before rendering
|
||||||
|
if (device.CanReportMemoryUsage()) {
|
||||||
|
const u64 current_usage = device.GetDeviceMemoryUsage();
|
||||||
|
const u64 total_vram = device.GetDeviceLocalMemory();
|
||||||
|
const u32 configured_limit = Settings::values.vram_limit_mb.GetValue();
|
||||||
|
const u64 vram_limit = configured_limit > 0
|
||||||
|
? static_cast<u64>(configured_limit) * 1024ULL * 1024ULL
|
||||||
|
: static_cast<u64>(total_vram * 0.80);
|
||||||
|
|
||||||
|
// If VRAM usage is above 90% of limit, trigger emergency GC on texture/buffer caches
|
||||||
|
if (current_usage >= static_cast<u64>(vram_limit * 0.90)) {
|
||||||
|
LOG_WARNING(Render_Vulkan,
|
||||||
|
"VRAM pressure critical: {}MB/{}MB ({:.1f}%), triggering emergency GC",
|
||||||
|
current_usage / (1024ULL * 1024ULL), vram_limit / (1024ULL * 1024ULL),
|
||||||
|
(static_cast<f32>(current_usage) / vram_limit) * 100.0f);
|
||||||
|
rasterizer.TriggerMemoryGC();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RenderAppletCaptureLayer(framebuffers);
|
RenderAppletCaptureLayer(framebuffers);
|
||||||
|
|
||||||
if (!render_window.IsShown()) {
|
if (!render_window.IsShown()) {
|
||||||
@@ -201,6 +220,30 @@ void RendererVulkan::Report() const {
|
|||||||
LOG_INFO(Render_Vulkan, "Vulkan: {}", api_version);
|
LOG_INFO(Render_Vulkan, "Vulkan: {}", api_version);
|
||||||
LOG_INFO(Render_Vulkan, "Available VRAM: {:.2f} GiB", available_vram);
|
LOG_INFO(Render_Vulkan, "Available VRAM: {:.2f} GiB", available_vram);
|
||||||
|
|
||||||
|
// FIXED: VRAM leak prevention - Report VRAM management settings
|
||||||
|
const u32 vram_limit_mb = Settings::values.vram_limit_mb.GetValue();
|
||||||
|
const auto gc_level = Settings::values.gc_aggressiveness.GetValue();
|
||||||
|
const u32 texture_eviction = Settings::values.texture_eviction_frames.GetValue();
|
||||||
|
const u32 buffer_eviction = Settings::values.buffer_eviction_frames.GetValue();
|
||||||
|
|
||||||
|
if (vram_limit_mb > 0) {
|
||||||
|
LOG_INFO(Render_Vulkan, "VRAM Limit: {} MB (configured)", vram_limit_mb);
|
||||||
|
} else {
|
||||||
|
LOG_INFO(Render_Vulkan, "VRAM Limit: Auto ({:.0f} MB, 80% of available)",
|
||||||
|
available_vram * 0.8 * 1024.0);
|
||||||
|
}
|
||||||
|
LOG_INFO(Render_Vulkan, "GC Aggressiveness: {}, Texture eviction: {} frames, Buffer eviction: {} frames",
|
||||||
|
static_cast<u32>(gc_level), texture_eviction, buffer_eviction);
|
||||||
|
|
||||||
|
// FIXED: VRAM leak prevention - Report VK_EXT_memory_budget support
|
||||||
|
if (device.CanReportMemoryUsage()) {
|
||||||
|
const auto current_usage = device.GetDeviceMemoryUsage();
|
||||||
|
LOG_INFO(Render_Vulkan, "VK_EXT_memory_budget: Supported, Current usage: {:.2f} GiB",
|
||||||
|
static_cast<f64>(current_usage) / f64{1_GiB});
|
||||||
|
} else {
|
||||||
|
LOG_INFO(Render_Vulkan, "VK_EXT_memory_budget: Not supported (using estimates)");
|
||||||
|
}
|
||||||
|
|
||||||
static constexpr auto field = Common::Telemetry::FieldType::UserSystem;
|
static constexpr auto field = Common::Telemetry::FieldType::UserSystem;
|
||||||
telemetry_session.AddField(field, "GPU_Vendor", vendor_name);
|
telemetry_session.AddField(field, "GPU_Vendor", vendor_name);
|
||||||
telemetry_session.AddField(field, "GPU_Model", model_name);
|
telemetry_session.AddField(field, "GPU_Model", model_name);
|
||||||
|
|||||||
@@ -861,6 +861,17 @@ u64 RasterizerVulkan::GetStagingMemoryUsage() const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXED: VRAM leak prevention - Trigger garbage collection on texture/buffer caches
|
||||||
|
void RasterizerVulkan::TriggerMemoryGC() {
|
||||||
|
std::scoped_lock lock{texture_cache.mutex, buffer_cache.mutex};
|
||||||
|
|
||||||
|
// Trigger GC on both caches
|
||||||
|
texture_cache.TriggerGarbageCollection();
|
||||||
|
buffer_cache.TriggerGarbageCollection();
|
||||||
|
|
||||||
|
LOG_DEBUG(Render_Vulkan, "Manual memory GC triggered");
|
||||||
|
}
|
||||||
|
|
||||||
bool RasterizerVulkan::AccelerateConditionalRendering() {
|
bool RasterizerVulkan::AccelerateConditionalRendering() {
|
||||||
gpu_memory->FlushCaching();
|
gpu_memory->FlushCaching();
|
||||||
return query_cache.AccelerateHostConditionalRendering();
|
return query_cache.AccelerateHostConditionalRendering();
|
||||||
|
|||||||
@@ -125,6 +125,10 @@ public:
|
|||||||
u64 GetBufferMemoryUsage() const;
|
u64 GetBufferMemoryUsage() const;
|
||||||
u64 GetTextureMemoryUsage() const;
|
u64 GetTextureMemoryUsage() const;
|
||||||
u64 GetStagingMemoryUsage() const;
|
u64 GetStagingMemoryUsage() const;
|
||||||
|
|
||||||
|
// FIXED: VRAM leak prevention - Trigger garbage collection on texture/buffer caches
|
||||||
|
void TriggerMemoryGC();
|
||||||
|
|
||||||
bool AccelerateConditionalRendering() override;
|
bool AccelerateConditionalRendering() override;
|
||||||
bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Surface& src,
|
bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Surface& src,
|
||||||
const Tegra::Engines::Fermi2D::Surface& dst,
|
const Tegra::Engines::Fermi2D::Surface& dst,
|
||||||
|
|||||||
@@ -50,21 +50,59 @@ TextureCache<P>::TextureCache(Runtime& runtime_, Tegra::MaxwellDeviceMemoryManag
|
|||||||
void(slot_image_views.insert(runtime, NullImageViewParams{}));
|
void(slot_image_views.insert(runtime, NullImageViewParams{}));
|
||||||
void(slot_samplers.insert(runtime, sampler_descriptor));
|
void(slot_samplers.insert(runtime, sampler_descriptor));
|
||||||
|
|
||||||
|
// FIXED: VRAM leak prevention - Initialize VRAM limit from settings
|
||||||
|
const u32 configured_limit_mb = Settings::values.vram_limit_mb.GetValue();
|
||||||
|
|
||||||
if constexpr (HAS_DEVICE_MEMORY_INFO) {
|
if constexpr (HAS_DEVICE_MEMORY_INFO) {
|
||||||
const s64 device_local_memory = static_cast<s64>(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;
|
// FIXED: VRAM leak prevention - Use configured limit or auto-detect (80% of VRAM)
|
||||||
const s64 mem_threshold = std::min(device_local_memory, TARGET_THRESHOLD);
|
if (configured_limit_mb > 0) {
|
||||||
const s64 min_vacancy_expected = (6 * mem_threshold) / 10;
|
vram_limit_bytes = static_cast<u64>(configured_limit_mb) * 1_MiB;
|
||||||
const s64 min_vacancy_critical = (2 * mem_threshold) / 10;
|
} else {
|
||||||
expected_memory = static_cast<u64>(
|
// Auto-detect: use 80% of available VRAM as limit
|
||||||
std::max(std::min(device_local_memory - min_vacancy_expected, min_spacing_expected),
|
vram_limit_bytes = static_cast<u64>(device_local_memory * 0.80);
|
||||||
DEFAULT_EXPECTED_MEMORY));
|
}
|
||||||
critical_memory = static_cast<u64>(
|
|
||||||
std::max(std::min(device_local_memory - min_vacancy_critical, min_spacing_critical),
|
// Adjust thresholds based on VRAM limit and GC aggressiveness setting
|
||||||
DEFAULT_CRITICAL_MEMORY));
|
const auto gc_level = Settings::values.gc_aggressiveness.GetValue();
|
||||||
minimum_memory = static_cast<u64>((device_local_memory - mem_threshold) / 2);
|
f32 expected_ratio = 0.6f;
|
||||||
|
f32 critical_ratio = 0.8f;
|
||||||
|
|
||||||
|
switch (gc_level) {
|
||||||
|
case Settings::GCAggressiveness::Off:
|
||||||
|
expected_ratio = 0.95f;
|
||||||
|
critical_ratio = 0.99f;
|
||||||
|
break;
|
||||||
|
case Settings::GCAggressiveness::Light:
|
||||||
|
expected_ratio = 0.75f;
|
||||||
|
critical_ratio = 0.90f;
|
||||||
|
break;
|
||||||
|
case Settings::GCAggressiveness::Moderate:
|
||||||
|
expected_ratio = 0.60f;
|
||||||
|
critical_ratio = 0.80f;
|
||||||
|
break;
|
||||||
|
case Settings::GCAggressiveness::Heavy:
|
||||||
|
expected_ratio = 0.50f;
|
||||||
|
critical_ratio = 0.70f;
|
||||||
|
break;
|
||||||
|
case Settings::GCAggressiveness::Extreme:
|
||||||
|
expected_ratio = 0.40f;
|
||||||
|
critical_ratio = 0.60f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
expected_memory = static_cast<u64>(vram_limit_bytes * expected_ratio);
|
||||||
|
critical_memory = static_cast<u64>(vram_limit_bytes * critical_ratio);
|
||||||
|
minimum_memory = static_cast<u64>(vram_limit_bytes * 0.25f);
|
||||||
|
|
||||||
|
LOG_INFO(Render_Vulkan,
|
||||||
|
"VRAM Management initialized: limit={}MB, expected={}MB, critical={}MB, gc_level={}",
|
||||||
|
vram_limit_bytes / 1_MiB, expected_memory / 1_MiB, critical_memory / 1_MiB,
|
||||||
|
static_cast<u32>(gc_level));
|
||||||
} else {
|
} else {
|
||||||
|
vram_limit_bytes = configured_limit_mb > 0 ? static_cast<u64>(configured_limit_mb) * 1_MiB
|
||||||
|
: 6_GiB; // Default 6GB if no info
|
||||||
expected_memory = DEFAULT_EXPECTED_MEMORY + 512_MiB;
|
expected_memory = DEFAULT_EXPECTED_MEMORY + 512_MiB;
|
||||||
critical_memory = DEFAULT_CRITICAL_MEMORY + 1_GiB;
|
critical_memory = DEFAULT_CRITICAL_MEMORY + 1_GiB;
|
||||||
minimum_memory = 0;
|
minimum_memory = 0;
|
||||||
@@ -73,37 +111,111 @@ TextureCache<P>::TextureCache(Runtime& runtime_, Tegra::MaxwellDeviceMemoryManag
|
|||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
void TextureCache<P>::RunGarbageCollector() {
|
void TextureCache<P>::RunGarbageCollector() {
|
||||||
|
// FIXED: VRAM leak prevention - Enhanced garbage collector with settings integration
|
||||||
|
|
||||||
|
const auto gc_level = Settings::values.gc_aggressiveness.GetValue();
|
||||||
|
if (gc_level == Settings::GCAggressiveness::Off) {
|
||||||
|
return; // GC disabled by user
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset per-frame stats
|
||||||
|
if (last_gc_frame != frame_tick) {
|
||||||
|
evicted_this_frame = 0;
|
||||||
|
gc_runs_this_frame = 0;
|
||||||
|
last_gc_frame = frame_tick;
|
||||||
|
}
|
||||||
|
++gc_runs_this_frame;
|
||||||
|
|
||||||
bool high_priority_mode = false;
|
bool high_priority_mode = false;
|
||||||
bool aggressive_mode = false;
|
bool aggressive_mode = false;
|
||||||
|
bool emergency_mode = false;
|
||||||
u64 ticks_to_destroy = 0;
|
u64 ticks_to_destroy = 0;
|
||||||
size_t num_iterations = 0;
|
size_t num_iterations = 0;
|
||||||
|
u64 bytes_freed = 0;
|
||||||
|
|
||||||
const auto Configure = [&](bool allow_aggressive) {
|
// FIXED: VRAM leak prevention - Get eviction frames from settings
|
||||||
|
const u64 eviction_frames = Settings::values.texture_eviction_frames.GetValue();
|
||||||
|
const bool sparse_priority = Settings::values.sparse_texture_priority_eviction.GetValue();
|
||||||
|
|
||||||
|
const auto Configure = [&](bool allow_aggressive, bool allow_emergency) {
|
||||||
high_priority_mode = total_used_memory >= expected_memory;
|
high_priority_mode = total_used_memory >= expected_memory;
|
||||||
aggressive_mode = allow_aggressive && total_used_memory >= critical_memory;
|
aggressive_mode = allow_aggressive && total_used_memory >= critical_memory;
|
||||||
ticks_to_destroy = aggressive_mode ? 10ULL : high_priority_mode ? 25ULL : 50ULL;
|
emergency_mode = allow_emergency && total_used_memory >= static_cast<u64>(vram_limit_bytes * VRAM_USAGE_EMERGENCY_THRESHOLD);
|
||||||
num_iterations = aggressive_mode ? 40 : (high_priority_mode ? 20 : 10);
|
|
||||||
|
// FIXED: VRAM leak prevention - Adjust iterations based on GC level
|
||||||
|
u64 base_ticks = eviction_frames;
|
||||||
|
size_t base_iterations = 10;
|
||||||
|
|
||||||
|
switch (gc_level) {
|
||||||
|
case Settings::GCAggressiveness::Light:
|
||||||
|
base_ticks = eviction_frames * 2;
|
||||||
|
base_iterations = 5;
|
||||||
|
break;
|
||||||
|
case Settings::GCAggressiveness::Moderate:
|
||||||
|
base_ticks = eviction_frames;
|
||||||
|
base_iterations = 10;
|
||||||
|
break;
|
||||||
|
case Settings::GCAggressiveness::Heavy:
|
||||||
|
base_ticks = std::max(1ULL, eviction_frames / 2);
|
||||||
|
base_iterations = 20;
|
||||||
|
break;
|
||||||
|
case Settings::GCAggressiveness::Extreme:
|
||||||
|
base_ticks = 1;
|
||||||
|
base_iterations = 40;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (emergency_mode) {
|
||||||
|
ticks_to_destroy = 1;
|
||||||
|
num_iterations = base_iterations * 4;
|
||||||
|
} else if (aggressive_mode) {
|
||||||
|
ticks_to_destroy = std::max(1ULL, base_ticks / 2);
|
||||||
|
num_iterations = base_iterations * 2;
|
||||||
|
} else if (high_priority_mode) {
|
||||||
|
ticks_to_destroy = base_ticks;
|
||||||
|
num_iterations = static_cast<size_t>(base_iterations * 1.5);
|
||||||
|
} else {
|
||||||
|
ticks_to_destroy = base_ticks * 2;
|
||||||
|
num_iterations = base_iterations;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
const auto Cleanup = [this, &num_iterations, &high_priority_mode,
|
|
||||||
&aggressive_mode](ImageId image_id) {
|
const auto Cleanup = [this, &num_iterations, &high_priority_mode, &aggressive_mode,
|
||||||
|
&emergency_mode, &bytes_freed, sparse_priority](ImageId image_id) {
|
||||||
if (num_iterations == 0) {
|
if (num_iterations == 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
--num_iterations;
|
--num_iterations;
|
||||||
auto& image = slot_images[image_id];
|
auto& image = slot_images[image_id];
|
||||||
|
|
||||||
|
// Skip images being decoded
|
||||||
if (True(image.flags & ImageFlagBits::IsDecoding)) {
|
if (True(image.flags & ImageFlagBits::IsDecoding)) {
|
||||||
// This image is still being decoded, deleting it will invalidate the slot
|
|
||||||
// used by the async decoder thread.
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!aggressive_mode && True(image.flags & ImageFlagBits::CostlyLoad)) {
|
|
||||||
return false;
|
// FIXED: VRAM leak prevention - Prioritize sparse textures if enabled
|
||||||
|
const bool is_sparse = True(image.flags & ImageFlagBits::Sparse);
|
||||||
|
const u64 image_size = std::max(image.guest_size_bytes, image.unswizzled_size_bytes);
|
||||||
|
const bool is_large = image_size >= LARGE_TEXTURE_THRESHOLD;
|
||||||
|
|
||||||
|
// Skip costly loads unless aggressive/emergency mode, unless it's a large sparse texture
|
||||||
|
if (!aggressive_mode && !emergency_mode && True(image.flags & ImageFlagBits::CostlyLoad)) {
|
||||||
|
if (!(sparse_priority && is_sparse && image_size >= SPARSE_EVICTION_PRIORITY_THRESHOLD)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool must_download =
|
const bool must_download =
|
||||||
image.IsSafeDownload() && False(image.flags & ImageFlagBits::BadOverlap);
|
image.IsSafeDownload() && False(image.flags & ImageFlagBits::BadOverlap);
|
||||||
if (!high_priority_mode && must_download) {
|
|
||||||
|
// Skip downloads unless high priority or emergency
|
||||||
|
if (!high_priority_mode && !emergency_mode && must_download) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Perform download if needed
|
||||||
if (must_download) {
|
if (must_download) {
|
||||||
auto map = runtime.DownloadStagingBuffer(image.unswizzled_size_bytes);
|
auto map = runtime.DownloadStagingBuffer(image.unswizzled_size_bytes);
|
||||||
const auto copies = FullDownloadCopies(image.info);
|
const auto copies = FullDownloadCopies(image.info);
|
||||||
@@ -112,16 +224,29 @@ void TextureCache<P>::RunGarbageCollector() {
|
|||||||
SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, map.mapped_span,
|
SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, map.mapped_span,
|
||||||
swizzle_data_buffer);
|
swizzle_data_buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Track eviction statistics
|
||||||
|
bytes_freed += Common::AlignUp(image_size, 1024);
|
||||||
|
if (is_sparse) {
|
||||||
|
sparse_texture_memory -= Common::AlignUp(image_size, 1024);
|
||||||
|
--sparse_texture_count;
|
||||||
|
}
|
||||||
|
if (is_large) {
|
||||||
|
large_texture_memory -= Common::AlignUp(image_size, 1024);
|
||||||
|
}
|
||||||
|
|
||||||
if (True(image.flags & ImageFlagBits::Tracked)) {
|
if (True(image.flags & ImageFlagBits::Tracked)) {
|
||||||
UntrackImage(image, image_id);
|
UntrackImage(image, image_id);
|
||||||
}
|
}
|
||||||
UnregisterImage(image_id);
|
UnregisterImage(image_id);
|
||||||
DeleteImage(image_id, image.scale_tick > frame_tick + 5);
|
DeleteImage(image_id, image.scale_tick > frame_tick + 5);
|
||||||
|
|
||||||
|
// Adjust mode based on remaining memory pressure
|
||||||
if (total_used_memory < critical_memory) {
|
if (total_used_memory < critical_memory) {
|
||||||
if (aggressive_mode) {
|
if (aggressive_mode || emergency_mode) {
|
||||||
// Sink the aggresiveness.
|
|
||||||
num_iterations >>= 2;
|
num_iterations >>= 2;
|
||||||
aggressive_mode = false;
|
aggressive_mode = false;
|
||||||
|
emergency_mode = false;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (high_priority_mode && total_used_memory < expected_memory) {
|
if (high_priority_mode && total_used_memory < expected_memory) {
|
||||||
@@ -132,26 +257,80 @@ void TextureCache<P>::RunGarbageCollector() {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Try to remove anything old enough and not high priority.
|
// FIXED: VRAM leak prevention - First pass: evict sparse textures if priority enabled
|
||||||
Configure(false);
|
if (sparse_priority && sparse_texture_memory > 0 && total_used_memory >= expected_memory) {
|
||||||
|
Configure(false, false);
|
||||||
|
// Target sparse textures specifically
|
||||||
|
lru_cache.ForEachItemBelow(frame_tick - ticks_to_destroy, [this, &Cleanup](ImageId image_id) {
|
||||||
|
auto& image = slot_images[image_id];
|
||||||
|
if (True(image.flags & ImageFlagBits::Sparse)) {
|
||||||
|
return Cleanup(image_id);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal pass: remove anything old enough
|
||||||
|
Configure(false, false);
|
||||||
lru_cache.ForEachItemBelow(frame_tick - ticks_to_destroy, Cleanup);
|
lru_cache.ForEachItemBelow(frame_tick - ticks_to_destroy, Cleanup);
|
||||||
|
|
||||||
// If pressure is still too high, prune aggressively.
|
// Aggressive pass if still above critical
|
||||||
if (total_used_memory >= critical_memory) {
|
if (total_used_memory >= critical_memory) {
|
||||||
Configure(true);
|
Configure(true, false);
|
||||||
lru_cache.ForEachItemBelow(frame_tick - ticks_to_destroy, Cleanup);
|
lru_cache.ForEachItemBelow(frame_tick - ticks_to_destroy, Cleanup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXED: VRAM leak prevention - Emergency pass if still above emergency threshold
|
||||||
|
if (total_used_memory >= static_cast<u64>(vram_limit_bytes * VRAM_USAGE_EMERGENCY_THRESHOLD)) {
|
||||||
|
Configure(true, true);
|
||||||
|
emergency_gc_triggered = true;
|
||||||
|
LOG_WARNING(Render_Vulkan, "VRAM Emergency GC triggered: usage={}MB, limit={}MB",
|
||||||
|
total_used_memory / 1_MiB, vram_limit_bytes / 1_MiB);
|
||||||
|
lru_cache.ForEachItemBelow(frame_tick, Cleanup); // Evict everything below current frame
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update statistics
|
||||||
|
evicted_this_frame += bytes_freed;
|
||||||
|
evicted_total += bytes_freed;
|
||||||
|
|
||||||
|
// FIXED: VRAM leak prevention - Log VRAM usage if enabled
|
||||||
|
if (Settings::values.log_vram_usage.GetValue() && bytes_freed > 0) {
|
||||||
|
LOG_INFO(Render_Vulkan,
|
||||||
|
"VRAM GC: evicted {}MB this frame, total={}MB, usage={}MB/{}MB ({:.1f}%)",
|
||||||
|
bytes_freed / 1_MiB, evicted_total / 1_MiB, total_used_memory / 1_MiB,
|
||||||
|
vram_limit_bytes / 1_MiB,
|
||||||
|
(static_cast<f32>(total_used_memory) / vram_limit_bytes) * 100.0f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
void TextureCache<P>::TickFrame() {
|
void TextureCache<P>::TickFrame() {
|
||||||
|
// FIXED: VRAM leak prevention - Enhanced frame tick with VRAM monitoring
|
||||||
|
|
||||||
|
// Reset emergency flag at start of frame
|
||||||
|
emergency_gc_triggered = false;
|
||||||
|
|
||||||
// If we can obtain the memory info, use it instead of the estimate.
|
// If we can obtain the memory info, use it instead of the estimate.
|
||||||
if (runtime.CanReportMemoryUsage()) {
|
if (runtime.CanReportMemoryUsage()) {
|
||||||
total_used_memory = runtime.GetDeviceMemoryUsage();
|
total_used_memory = runtime.GetDeviceMemoryUsage();
|
||||||
}
|
}
|
||||||
if (total_used_memory > minimum_memory) {
|
|
||||||
|
// FIXED: VRAM leak prevention - Check if GC should run based on settings
|
||||||
|
const auto gc_level = Settings::values.gc_aggressiveness.GetValue();
|
||||||
|
const bool should_gc = gc_level != Settings::GCAggressiveness::Off &&
|
||||||
|
(total_used_memory > minimum_memory ||
|
||||||
|
total_used_memory >= static_cast<u64>(vram_limit_bytes * VRAM_USAGE_WARNING_THRESHOLD));
|
||||||
|
|
||||||
|
if (should_gc) {
|
||||||
RunGarbageCollector();
|
RunGarbageCollector();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXED: VRAM leak prevention - Force additional GC if still above critical after normal GC
|
||||||
|
if (total_used_memory >= critical_memory && gc_level != Settings::GCAggressiveness::Off) {
|
||||||
|
// Run GC again if we're still above critical
|
||||||
|
RunGarbageCollector();
|
||||||
|
}
|
||||||
|
|
||||||
sentenced_images.Tick();
|
sentenced_images.Tick();
|
||||||
sentenced_framebuffers.Tick();
|
sentenced_framebuffers.Tick();
|
||||||
sentenced_image_view.Tick();
|
sentenced_image_view.Tick();
|
||||||
@@ -166,6 +345,183 @@ void TextureCache<P>::TickFrame() {
|
|||||||
}
|
}
|
||||||
async_buffers_death_ring.clear();
|
async_buffers_death_ring.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXED: VRAM leak prevention - Periodic VRAM usage logging
|
||||||
|
if (Settings::values.log_vram_usage.GetValue() && (frame_tick % 300 == 0)) {
|
||||||
|
const f32 usage_ratio = vram_limit_bytes > 0
|
||||||
|
? static_cast<f32>(total_used_memory) / vram_limit_bytes
|
||||||
|
: 0.0f;
|
||||||
|
LOG_INFO(Render_Vulkan,
|
||||||
|
"VRAM Status: {}MB/{}MB ({:.1f}%), textures={}, sparse={}, evicted_total={}MB",
|
||||||
|
total_used_memory / 1_MiB, vram_limit_bytes / 1_MiB, usage_ratio * 100.0f,
|
||||||
|
texture_count, sparse_texture_count, evicted_total / 1_MiB);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXED: VRAM leak prevention - Implementation of new VRAM management methods
|
||||||
|
|
||||||
|
template <class P>
|
||||||
|
void TextureCache<P>::ForceEmergencyGC() {
|
||||||
|
LOG_WARNING(Render_Vulkan, "Force emergency GC triggered: usage={}MB, limit={}MB",
|
||||||
|
total_used_memory / 1_MiB, vram_limit_bytes / 1_MiB);
|
||||||
|
|
||||||
|
emergency_gc_triggered = true;
|
||||||
|
u64 bytes_freed = 0;
|
||||||
|
|
||||||
|
// Evict 10% of textures immediately, prioritizing sparse and large textures
|
||||||
|
const u64 target_bytes = total_used_memory / 10;
|
||||||
|
bytes_freed += EvictSparseTexturesPriority(target_bytes / 2);
|
||||||
|
bytes_freed += EvictToFreeMemory(target_bytes - bytes_freed);
|
||||||
|
|
||||||
|
evicted_this_frame += bytes_freed;
|
||||||
|
evicted_total += bytes_freed;
|
||||||
|
|
||||||
|
LOG_INFO(Render_Vulkan, "Emergency GC freed {}MB", bytes_freed / 1_MiB);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class P>
|
||||||
|
typename TextureCache<P>::VRAMStats TextureCache<P>::GetVRAMStats() const noexcept {
|
||||||
|
const f32 usage_ratio = vram_limit_bytes > 0
|
||||||
|
? static_cast<f32>(total_used_memory) / vram_limit_bytes
|
||||||
|
: 0.0f;
|
||||||
|
return VRAMStats{
|
||||||
|
.total_used_bytes = total_used_memory,
|
||||||
|
.texture_bytes = total_used_memory - sparse_texture_memory,
|
||||||
|
.sparse_texture_bytes = sparse_texture_memory,
|
||||||
|
.evicted_this_frame = evicted_this_frame,
|
||||||
|
.evicted_total = evicted_total,
|
||||||
|
.texture_count = texture_count,
|
||||||
|
.sparse_texture_count = sparse_texture_count,
|
||||||
|
.usage_ratio = usage_ratio,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class P>
|
||||||
|
void TextureCache<P>::SetVRAMLimit(u64 limit_bytes) {
|
||||||
|
vram_limit_bytes = limit_bytes;
|
||||||
|
|
||||||
|
// Recalculate thresholds
|
||||||
|
const auto gc_level = Settings::values.gc_aggressiveness.GetValue();
|
||||||
|
f32 expected_ratio = 0.6f;
|
||||||
|
f32 critical_ratio = 0.8f;
|
||||||
|
|
||||||
|
switch (gc_level) {
|
||||||
|
case Settings::GCAggressiveness::Off:
|
||||||
|
expected_ratio = 0.95f;
|
||||||
|
critical_ratio = 0.99f;
|
||||||
|
break;
|
||||||
|
case Settings::GCAggressiveness::Light:
|
||||||
|
expected_ratio = 0.75f;
|
||||||
|
critical_ratio = 0.90f;
|
||||||
|
break;
|
||||||
|
case Settings::GCAggressiveness::Moderate:
|
||||||
|
expected_ratio = 0.60f;
|
||||||
|
critical_ratio = 0.80f;
|
||||||
|
break;
|
||||||
|
case Settings::GCAggressiveness::Heavy:
|
||||||
|
expected_ratio = 0.50f;
|
||||||
|
critical_ratio = 0.70f;
|
||||||
|
break;
|
||||||
|
case Settings::GCAggressiveness::Extreme:
|
||||||
|
expected_ratio = 0.40f;
|
||||||
|
critical_ratio = 0.60f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
expected_memory = static_cast<u64>(vram_limit_bytes * expected_ratio);
|
||||||
|
critical_memory = static_cast<u64>(vram_limit_bytes * critical_ratio);
|
||||||
|
minimum_memory = static_cast<u64>(vram_limit_bytes * 0.25f);
|
||||||
|
|
||||||
|
LOG_INFO(Render_Vulkan, "VRAM limit updated: {}MB, expected={}MB, critical={}MB",
|
||||||
|
vram_limit_bytes / 1_MiB, expected_memory / 1_MiB, critical_memory / 1_MiB);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class P>
|
||||||
|
bool TextureCache<P>::IsVRAMPressureHigh() const noexcept {
|
||||||
|
return total_used_memory >= expected_memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class P>
|
||||||
|
bool TextureCache<P>::IsVRAMPressureCritical() const noexcept {
|
||||||
|
return total_used_memory >= static_cast<u64>(vram_limit_bytes * VRAM_USAGE_EMERGENCY_THRESHOLD);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class P>
|
||||||
|
u64 TextureCache<P>::EvictToFreeMemory(u64 target_bytes) {
|
||||||
|
u64 bytes_freed = 0;
|
||||||
|
const u64 start_memory = total_used_memory;
|
||||||
|
|
||||||
|
lru_cache.ForEachItemBelow(frame_tick, [this, &bytes_freed, target_bytes](ImageId image_id) {
|
||||||
|
if (bytes_freed >= target_bytes) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& image = slot_images[image_id];
|
||||||
|
if (True(image.flags & ImageFlagBits::IsDecoding)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const u64 image_size = std::max(image.guest_size_bytes, image.unswizzled_size_bytes);
|
||||||
|
|
||||||
|
if (True(image.flags & ImageFlagBits::Tracked)) {
|
||||||
|
UntrackImage(image, image_id);
|
||||||
|
}
|
||||||
|
UnregisterImage(image_id);
|
||||||
|
DeleteImage(image_id, false);
|
||||||
|
|
||||||
|
bytes_freed += Common::AlignUp(image_size, 1024);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
return start_memory - total_used_memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class P>
|
||||||
|
u64 TextureCache<P>::EvictSparseTexturesPriority(u64 target_bytes) {
|
||||||
|
if (!Settings::values.sparse_texture_priority_eviction.GetValue()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 bytes_freed = 0;
|
||||||
|
|
||||||
|
// Collect sparse textures and sort by size (largest first)
|
||||||
|
std::vector<std::pair<ImageId, u64>> sparse_textures;
|
||||||
|
lru_cache.ForEachItemBelow(frame_tick, [this, &sparse_textures](ImageId image_id) {
|
||||||
|
auto& image = slot_images[image_id];
|
||||||
|
if (True(image.flags & ImageFlagBits::Sparse) &&
|
||||||
|
False(image.flags & ImageFlagBits::IsDecoding)) {
|
||||||
|
const u64 size = std::max(image.guest_size_bytes, image.unswizzled_size_bytes);
|
||||||
|
sparse_textures.emplace_back(image_id, size);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sort by size descending (largest first for priority eviction)
|
||||||
|
std::sort(sparse_textures.begin(), sparse_textures.end(),
|
||||||
|
[](const auto& a, const auto& b) { return a.second > b.second; });
|
||||||
|
|
||||||
|
for (const auto& [image_id, size] : sparse_textures) {
|
||||||
|
if (bytes_freed >= target_bytes) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& image = slot_images[image_id];
|
||||||
|
if (True(image.flags & ImageFlagBits::Tracked)) {
|
||||||
|
UntrackImage(image, image_id);
|
||||||
|
}
|
||||||
|
UnregisterImage(image_id);
|
||||||
|
DeleteImage(image_id, false);
|
||||||
|
|
||||||
|
bytes_freed += Common::AlignUp(size, 1024);
|
||||||
|
--sparse_texture_count;
|
||||||
|
sparse_texture_memory -= Common::AlignUp(size, 1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytes_freed > 0) {
|
||||||
|
LOG_DEBUG(Render_Vulkan, "Sparse texture priority eviction freed {}MB", bytes_freed / 1_MiB);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes_freed;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
@@ -2018,7 +2374,22 @@ void TextureCache<P>::RegisterImage(ImageId image_id) {
|
|||||||
True(image.flags & ImageFlagBits::Converted)) {
|
True(image.flags & ImageFlagBits::Converted)) {
|
||||||
tentative_size = TranscodedAstcSize(tentative_size, image.info.format);
|
tentative_size = TranscodedAstcSize(tentative_size, image.info.format);
|
||||||
}
|
}
|
||||||
total_used_memory += Common::AlignUp(tentative_size, 1024);
|
const u64 aligned_size = Common::AlignUp(tentative_size, 1024);
|
||||||
|
total_used_memory += aligned_size;
|
||||||
|
|
||||||
|
// FIXED: VRAM leak prevention - Track texture statistics
|
||||||
|
++texture_count;
|
||||||
|
const bool is_sparse = True(image.flags & ImageFlagBits::Sparse);
|
||||||
|
const bool is_large = aligned_size >= LARGE_TEXTURE_THRESHOLD;
|
||||||
|
|
||||||
|
if (is_sparse) {
|
||||||
|
sparse_texture_memory += aligned_size;
|
||||||
|
++sparse_texture_count;
|
||||||
|
}
|
||||||
|
if (is_large) {
|
||||||
|
large_texture_memory += aligned_size;
|
||||||
|
}
|
||||||
|
|
||||||
image.lru_index = lru_cache.Insert(image_id, frame_tick);
|
image.lru_index = lru_cache.Insert(image_id, frame_tick);
|
||||||
|
|
||||||
ForEachGPUPage(image.gpu_addr, image.guest_size_bytes, [this, image_id](u64 page) {
|
ForEachGPUPage(image.gpu_addr, image.guest_size_bytes, [this, image_id](u64 page) {
|
||||||
|
|||||||
@@ -113,6 +113,14 @@ class TextureCache : public VideoCommon::ChannelSetupCaches<TextureCacheChannelI
|
|||||||
static constexpr s64 DEFAULT_CRITICAL_MEMORY = 1_GiB + 625_MiB;
|
static constexpr s64 DEFAULT_CRITICAL_MEMORY = 1_GiB + 625_MiB;
|
||||||
static constexpr size_t GC_EMERGENCY_COUNTS = 2;
|
static constexpr size_t GC_EMERGENCY_COUNTS = 2;
|
||||||
|
|
||||||
|
// FIXED: VRAM leak prevention - Enhanced eviction constants
|
||||||
|
static constexpr size_t SPARSE_EVICTION_PRIORITY_THRESHOLD = 4_MiB; // Prioritize sparse textures > 4MB
|
||||||
|
static constexpr size_t LARGE_TEXTURE_THRESHOLD = 16_MiB; // Large texture threshold
|
||||||
|
static constexpr u64 DEFAULT_EVICTION_FRAMES = 2; // Default frames before eviction
|
||||||
|
static constexpr f32 VRAM_USAGE_WARNING_THRESHOLD = 0.75f; // 75% - start warning
|
||||||
|
static constexpr f32 VRAM_USAGE_CRITICAL_THRESHOLD = 0.85f; // 85% - aggressive GC
|
||||||
|
static constexpr f32 VRAM_USAGE_EMERGENCY_THRESHOLD = 0.95f; // 95% - emergency eviction
|
||||||
|
|
||||||
using Runtime = typename P::Runtime;
|
using Runtime = typename P::Runtime;
|
||||||
using Image = typename P::Image;
|
using Image = typename P::Image;
|
||||||
using ImageAlloc = typename P::ImageAlloc;
|
using ImageAlloc = typename P::ImageAlloc;
|
||||||
@@ -296,6 +304,42 @@ public:
|
|||||||
RunGarbageCollector();
|
RunGarbageCollector();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXED: VRAM leak prevention - Enhanced public interface for VRAM management
|
||||||
|
|
||||||
|
/// Force emergency garbage collection when VRAM pressure is critical
|
||||||
|
void ForceEmergencyGC();
|
||||||
|
|
||||||
|
/// Get current VRAM usage statistics
|
||||||
|
struct VRAMStats {
|
||||||
|
u64 total_used_bytes;
|
||||||
|
u64 texture_bytes;
|
||||||
|
u64 sparse_texture_bytes;
|
||||||
|
u64 evicted_this_frame;
|
||||||
|
u64 evicted_total;
|
||||||
|
u32 texture_count;
|
||||||
|
u32 sparse_texture_count;
|
||||||
|
f32 usage_ratio; // Current usage / limit
|
||||||
|
};
|
||||||
|
[[nodiscard]] VRAMStats GetVRAMStats() const noexcept;
|
||||||
|
|
||||||
|
/// Get configured VRAM limit in bytes
|
||||||
|
[[nodiscard]] u64 GetVRAMLimit() const noexcept { return vram_limit_bytes; }
|
||||||
|
|
||||||
|
/// Set VRAM limit (0 = auto-detect)
|
||||||
|
void SetVRAMLimit(u64 limit_bytes);
|
||||||
|
|
||||||
|
/// Check if VRAM pressure is high
|
||||||
|
[[nodiscard]] bool IsVRAMPressureHigh() const noexcept;
|
||||||
|
|
||||||
|
/// Check if VRAM pressure is critical (emergency)
|
||||||
|
[[nodiscard]] bool IsVRAMPressureCritical() const noexcept;
|
||||||
|
|
||||||
|
/// Evict oldest textures to free target_bytes of VRAM
|
||||||
|
u64 EvictToFreeMemory(u64 target_bytes);
|
||||||
|
|
||||||
|
/// Evict sparse textures with priority (large unmapped pages first)
|
||||||
|
u64 EvictSparseTexturesPriority(u64 target_bytes);
|
||||||
|
|
||||||
/// Fills image_view_ids in the image views in indices
|
/// Fills image_view_ids in the image views in indices
|
||||||
template <bool has_blacklists>
|
template <bool has_blacklists>
|
||||||
void FillImageViews(DescriptorTable<TICEntry>& table,
|
void FillImageViews(DescriptorTable<TICEntry>& table,
|
||||||
@@ -450,6 +494,18 @@ public:
|
|||||||
u64 expected_memory;
|
u64 expected_memory;
|
||||||
u64 critical_memory;
|
u64 critical_memory;
|
||||||
|
|
||||||
|
// FIXED: VRAM leak prevention - Enhanced memory tracking
|
||||||
|
u64 vram_limit_bytes = 0; // Configured VRAM limit (0 = auto)
|
||||||
|
u64 sparse_texture_memory = 0; // Memory used by sparse textures
|
||||||
|
u64 large_texture_memory = 0; // Memory used by large textures (>16MB)
|
||||||
|
u64 evicted_this_frame = 0; // Bytes evicted in current frame
|
||||||
|
u64 evicted_total = 0; // Total bytes evicted since start
|
||||||
|
u32 gc_runs_this_frame = 0; // Number of GC runs this frame
|
||||||
|
u32 texture_count = 0; // Total texture count
|
||||||
|
u32 sparse_texture_count = 0; // Sparse texture count
|
||||||
|
u64 last_gc_frame = 0; // Last frame GC was run
|
||||||
|
bool emergency_gc_triggered = false; // Emergency GC flag
|
||||||
|
|
||||||
struct BufferDownload {
|
struct BufferDownload {
|
||||||
GPUVAddr address;
|
GPUVAddr address;
|
||||||
size_t size;
|
size_t size;
|
||||||
|
|||||||
Reference in New Issue
Block a user