mirror of
https://git.citron-emu.org/citron/emulator
synced 2026-02-03 08:03:36 +00:00
Merge pull request 'fix/vram-leak-prevention' (#111) from fix/vram-leak-prevention into main
Reviewed-on: https://git.citron-emu.org/Citron/Emulator/pulls/111
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_PERFORMANCE_GRAPH("show_performance_graph"),
|
||||
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 =
|
||||
NativeConfig.getBoolean(key, needsGlobal)
|
||||
|
||||
@@ -40,6 +40,12 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
|
||||
VRAM_USAGE_MODE("vram_usage_mode"),
|
||||
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
|
||||
CABINET_APPLET_MODE("cabinet_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
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
|
||||
@@ -1036,6 +1036,15 @@ class SettingsFragmentPresenter(
|
||||
add(HeaderSetting(R.string.frame_skipping_header))
|
||||
add(IntSetting.FRAME_SKIPPING.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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -429,15 +429,28 @@
|
||||
<string-array name="vramUsageModeNames">
|
||||
<item>Conservative</item>
|
||||
<item>Aggressive</item>
|
||||
<item>High-End GPU (4090/4080+)</item>
|
||||
<item>Insane (RTX 4090 24GB)</item>
|
||||
</string-array>
|
||||
|
||||
<integer-array name="vramUsageModeValues">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
</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 -->
|
||||
|
||||
@@ -448,6 +448,7 @@
|
||||
<string name="memory_layout_header">Memory Layout</string>
|
||||
<string name="astc_settings_header">ASTC Settings</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>
|
||||
|
||||
<!-- 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_mode_adaptive">Adaptive</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>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -192,6 +192,31 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
|
||||
"of available video memory for performance. Has no effect on integrated graphics. "
|
||||
"Aggressive mode may severely impact the performance of other applications such as "
|
||||
"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(
|
||||
Settings, vsync_mode, tr("VSync Mode:"),
|
||||
tr("FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen "
|
||||
@@ -357,8 +382,6 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QWidget* parent) {
|
||||
{
|
||||
PAIR(VramUsageMode, Conservative, tr("Conservative")),
|
||||
PAIR(VramUsageMode, Aggressive, tr("Aggressive")),
|
||||
PAIR(VramUsageMode, HighEnd, tr("High-End GPU (4090/4080+)")),
|
||||
PAIR(VramUsageMode, Insane, tr("Insane (RTX 4090 24GB)")),
|
||||
}});
|
||||
translations->insert({Settings::EnumMetadata<Settings::ExtendedDynamicState>::Index(),
|
||||
{
|
||||
@@ -367,6 +390,17 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QWidget* parent) {
|
||||
PAIR(ExtendedDynamicState, EDS2, tr("EDS2")),
|
||||
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(),
|
||||
{
|
||||
#ifdef HAS_OPENGL
|
||||
|
||||
@@ -162,8 +162,6 @@ void VramOverlay::DrawVramInfo(QPainter& painter) {
|
||||
switch (Settings::values.vram_usage_mode.GetValue()) {
|
||||
case Settings::VramUsageMode::Conservative: mode_text = QString::fromUtf8("Mode: Conservative"); break;
|
||||
case Settings::VramUsageMode::Aggressive: mode_text = QString::fromUtf8("Mode: Aggressive"); break;
|
||||
case Settings::VramUsageMode::HighEnd: mode_text = QString::fromUtf8("Mode: High-End GPU"); break;
|
||||
case Settings::VramUsageMode::Insane: mode_text = QString::fromUtf8("Mode: Insane"); painter.setPen(leak_warning_color); break;
|
||||
default: mode_text = QString::fromUtf8("Mode: Unknown"); break;
|
||||
}
|
||||
painter.drawText(section_padding, y_offset, mode_text);
|
||||
|
||||
@@ -66,6 +66,7 @@ SWITCHABLE(AstcDecodeMode, true);
|
||||
SWITCHABLE(AstcRecompression, true);
|
||||
SWITCHABLE(AudioMode, true);
|
||||
SWITCHABLE(ExtendedDynamicState, true);
|
||||
SWITCHABLE(GCAggressiveness, true);
|
||||
SWITCHABLE(CpuBackend, true);
|
||||
SWITCHABLE(CpuAccuracy, true);
|
||||
SWITCHABLE(FullscreenMode, true);
|
||||
@@ -498,9 +499,64 @@ struct Values {
|
||||
SwitchableSetting<VramUsageMode, true> vram_usage_mode{linkage,
|
||||
VramUsageMode::Conservative,
|
||||
VramUsageMode::Conservative,
|
||||
VramUsageMode::Insane,
|
||||
VramUsageMode::Aggressive,
|
||||
"vram_usage_mode",
|
||||
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,
|
||||
#ifdef ANDROID
|
||||
true,
|
||||
|
||||
@@ -406,8 +406,6 @@ inline u32 EnumMetadata<VSyncMode>::Index() {
|
||||
enum class VramUsageMode : u32 {
|
||||
Conservative = 0,
|
||||
Aggressive = 1,
|
||||
HighEnd = 2,
|
||||
Insane = 3,
|
||||
};
|
||||
|
||||
template <>
|
||||
@@ -416,8 +414,6 @@ EnumMetadata<VramUsageMode>::Canonicalizations() {
|
||||
return {
|
||||
{"Conservative", VramUsageMode::Conservative},
|
||||
{"Aggressive", VramUsageMode::Aggressive},
|
||||
{"HighEnd", VramUsageMode::HighEnd},
|
||||
{"Insane", VramUsageMode::Insane},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -880,6 +876,32 @@ inline u32 EnumMetadata<ExtendedDynamicState>::Index() {
|
||||
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>
|
||||
inline std::string CanonicalizeEnum(Type id) {
|
||||
|
||||
@@ -26,24 +26,60 @@ BufferCache<P>::BufferCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, R
|
||||
gpu_modified_ranges.Clear();
|
||||
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()) {
|
||||
minimum_memory = DEFAULT_EXPECTED_MEMORY;
|
||||
critical_memory = DEFAULT_CRITICAL_MEMORY;
|
||||
vram_limit_bytes = configured_limit_mb > 0 ? static_cast<u64>(configured_limit_mb) * 1_MiB
|
||||
: 6_GiB;
|
||||
return;
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
// FIXED: VRAM leak prevention - Use configured limit or auto-detect
|
||||
if (configured_limit_mb > 0) {
|
||||
vram_limit_bytes = static_cast<u64>(configured_limit_mb) * 1_MiB;
|
||||
} else {
|
||||
vram_limit_bytes = static_cast<u64>(device_local_memory * 0.80);
|
||||
}
|
||||
|
||||
// Adjust thresholds based on GC aggressiveness setting
|
||||
const auto gc_level = Settings::values.gc_aggressiveness.GetValue();
|
||||
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>
|
||||
@@ -51,20 +87,90 @@ BufferCache<P>::~BufferCache() = default;
|
||||
|
||||
template <class P>
|
||||
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 u64 ticks_to_destroy = aggressive_gc ? 60 : 120;
|
||||
int num_iterations = aggressive_gc ? 64 : 32;
|
||||
const auto clean_up = [this, &num_iterations](BufferId buffer_id) {
|
||||
const bool emergency_gc = total_used_memory >= static_cast<u64>(vram_limit_bytes * BUFFER_VRAM_CRITICAL_THRESHOLD);
|
||||
|
||||
// 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) {
|
||||
return true;
|
||||
}
|
||||
--num_iterations;
|
||||
auto& buffer = slot_buffers[buffer_id];
|
||||
const u64 buffer_size = buffer.SizeBytes();
|
||||
|
||||
DownloadBufferMemory(buffer);
|
||||
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;
|
||||
};
|
||||
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>
|
||||
@@ -96,9 +202,22 @@ void BufferCache<P>::TickFrame() {
|
||||
if (runtime.CanReportMemoryUsage()) {
|
||||
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();
|
||||
}
|
||||
|
||||
// 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;
|
||||
delayed_destruction_ring.Tick();
|
||||
|
||||
@@ -1420,12 +1539,31 @@ template <bool insert>
|
||||
void BufferCache<P>::ChangeRegister(BufferId buffer_id) {
|
||||
Buffer& buffer = slot_buffers[buffer_id];
|
||||
const auto size = buffer.SizeBytes();
|
||||
const u64 aligned_size = Common::AlignUp(size, 1024);
|
||||
const bool is_large = aligned_size >= LARGE_BUFFER_THRESHOLD;
|
||||
|
||||
if (insert) {
|
||||
total_used_memory += Common::AlignUp(size, 1024);
|
||||
total_used_memory += aligned_size;
|
||||
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 {
|
||||
total_used_memory -= Common::AlignUp(size, 1024);
|
||||
total_used_memory -= aligned_size;
|
||||
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_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 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.
|
||||
|
||||
static constexpr bool DISABLE_DOWNLOADS = true;
|
||||
@@ -350,6 +356,31 @@ public:
|
||||
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 BindHostVertexBuffers();
|
||||
@@ -488,6 +519,13 @@ public:
|
||||
u64 critical_memory = 0;
|
||||
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;
|
||||
Common::ScratchBuffer<u8> tmp_buffer;
|
||||
};
|
||||
|
||||
@@ -159,6 +159,25 @@ void RendererVulkan::Composite(std::span<const Tegra::FramebufferConfig> framebu
|
||||
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);
|
||||
|
||||
if (!render_window.IsShown()) {
|
||||
@@ -201,6 +220,30 @@ void RendererVulkan::Report() const {
|
||||
LOG_INFO(Render_Vulkan, "Vulkan: {}", api_version);
|
||||
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;
|
||||
telemetry_session.AddField(field, "GPU_Vendor", vendor_name);
|
||||
telemetry_session.AddField(field, "GPU_Model", model_name);
|
||||
|
||||
@@ -70,40 +70,11 @@ vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allo
|
||||
flags |= VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT;
|
||||
}
|
||||
|
||||
// Optimize buffer size based on VRAM usage mode
|
||||
u64 optimized_size = size;
|
||||
const auto vram_mode = Settings::values.vram_usage_mode.GetValue();
|
||||
|
||||
if (vram_mode == Settings::VramUsageMode::HighEnd) {
|
||||
// High-End GPU mode: Use larger buffer chunks for high-end GPUs to reduce allocation overhead
|
||||
// but still keep them reasonable to avoid excessive VRAM usage
|
||||
if (size > 64_MiB && size < 512_MiB) {
|
||||
// Round up to next 64MB boundary for large buffers
|
||||
optimized_size = Common::AlignUp(size, 64_MiB);
|
||||
} else if (size > 4_MiB && size <= 64_MiB) {
|
||||
// Round up to next 8MB boundary for medium buffers
|
||||
optimized_size = Common::AlignUp(size, 8_MiB);
|
||||
}
|
||||
} else if (vram_mode == Settings::VramUsageMode::Insane) {
|
||||
// Insane mode: Use massive buffer chunks for RTX 4090 to minimize allocation overhead
|
||||
// and maximize performance for shader compilation and caching
|
||||
if (size > 128_MiB && size < 1024_MiB) {
|
||||
// Round up to next 128MB boundary for very large buffers
|
||||
optimized_size = Common::AlignUp(size, 128_MiB);
|
||||
} else if (size > 16_MiB && size <= 128_MiB) {
|
||||
// Round up to next 32MB boundary for large buffers
|
||||
optimized_size = Common::AlignUp(size, 32_MiB);
|
||||
} else if (size > 1_MiB && size <= 16_MiB) {
|
||||
// Round up to next 4MB boundary for medium buffers
|
||||
optimized_size = Common::AlignUp(size, 4_MiB);
|
||||
}
|
||||
}
|
||||
|
||||
const VkBufferCreateInfo buffer_ci = {
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.size = optimized_size,
|
||||
.size = size,
|
||||
.usage = flags,
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
.queueFamilyIndexCount = 0,
|
||||
@@ -115,31 +86,8 @@ vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allo
|
||||
} // Anonymous namespace
|
||||
|
||||
void BufferCacheRuntime::CleanupUnusedBuffers() {
|
||||
// Aggressive cleanup for Insane mode to prevent VRAM leaks
|
||||
const auto vram_mode = Settings::values.vram_usage_mode.GetValue();
|
||||
if (vram_mode == Settings::VramUsageMode::Insane) {
|
||||
// For Insane mode, periodically clean up unused large buffers to prevent memory leaks
|
||||
static u32 cleanup_counter = 0;
|
||||
static u64 last_buffer_memory = 0;
|
||||
cleanup_counter++;
|
||||
|
||||
// Monitor buffer memory usage to detect potential leaks
|
||||
if (cleanup_counter % 120 == 0) {
|
||||
const u64 current_buffer_memory = GetDeviceMemoryUsage();
|
||||
|
||||
// Check for buffer memory leak (usage increasing without corresponding game activity)
|
||||
if (current_buffer_memory > last_buffer_memory + 50_MiB) {
|
||||
LOG_WARNING(Render_Vulkan, "Potential buffer memory leak detected! Usage increased by {} MB",
|
||||
(current_buffer_memory - last_buffer_memory) / (1024 * 1024));
|
||||
|
||||
// Force cleanup of any cached buffers that might be accumulating
|
||||
LOG_INFO(Render_Vulkan, "Performed aggressive buffer cleanup (Insane mode)");
|
||||
}
|
||||
|
||||
last_buffer_memory = current_buffer_memory;
|
||||
LOG_DEBUG(Render_Vulkan, "Buffer memory usage: {} MB (Insane mode)", current_buffer_memory / (1024 * 1024));
|
||||
}
|
||||
}
|
||||
// Cleanup is now handled by the VRAM management system (gc_aggressiveness setting)
|
||||
// This function is kept for compatibility but no longer performs mode-specific cleanup
|
||||
}
|
||||
|
||||
Buffer::Buffer(BufferCacheRuntime& runtime, VideoCommon::NullBufferParams null_params)
|
||||
|
||||
@@ -748,11 +748,8 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
|
||||
const auto runtime_info{MakeRuntimeInfo(programs, key, program, previous_stage)};
|
||||
ConvertLegacyToGeneric(program, runtime_info);
|
||||
std::vector<u32> code = EmitSPIRV(profile, runtime_info, program, binding);
|
||||
// Reserve more space for Insane mode to reduce allocations during shader compilation
|
||||
const size_t reserve_size = Settings::values.vram_usage_mode.GetValue() == Settings::VramUsageMode::Insane
|
||||
? std::max<size_t>(code.size(), 64 * 1024 / sizeof(u32)) // 64KB for Insane mode
|
||||
: std::max<size_t>(code.size(), 16 * 1024 / sizeof(u32)); // 16KB for other modes
|
||||
code.reserve(reserve_size);
|
||||
// Reserve space to reduce allocations during shader compilation
|
||||
code.reserve(std::max<size_t>(code.size(), 16 * 1024 / sizeof(u32)));
|
||||
device.SaveShader(code);
|
||||
modules[stage_index] = BuildShader(device, code);
|
||||
if (device.HasDebuggingToolAttached()) {
|
||||
@@ -854,11 +851,8 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
|
||||
|
||||
auto program{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)};
|
||||
std::vector<u32> code = EmitSPIRV(profile, program);
|
||||
// Reserve more space for Insane mode to reduce allocations during shader compilation
|
||||
const size_t reserve_size = Settings::values.vram_usage_mode.GetValue() == Settings::VramUsageMode::Insane
|
||||
? std::max<size_t>(code.size(), 64 * 1024 / sizeof(u32)) // 64KB for Insane mode
|
||||
: std::max<size_t>(code.size(), 16 * 1024 / sizeof(u32)); // 16KB for other modes
|
||||
code.reserve(reserve_size);
|
||||
// Reserve space to reduce allocations during shader compilation
|
||||
code.reserve(std::max<size_t>(code.size(), 16 * 1024 / sizeof(u32)));
|
||||
device.SaveShader(code);
|
||||
vk::ShaderModule spv_module{BuildShader(device, code)};
|
||||
if (device.HasDebuggingToolAttached()) {
|
||||
|
||||
@@ -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() {
|
||||
gpu_memory->FlushCaching();
|
||||
return query_cache.AccelerateHostConditionalRendering();
|
||||
|
||||
@@ -125,6 +125,10 @@ public:
|
||||
u64 GetBufferMemoryUsage() const;
|
||||
u64 GetTextureMemoryUsage() const;
|
||||
u64 GetStagingMemoryUsage() const;
|
||||
|
||||
// FIXED: VRAM leak prevention - Trigger garbage collection on texture/buffer caches
|
||||
void TriggerMemoryGC();
|
||||
|
||||
bool AccelerateConditionalRendering() override;
|
||||
bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Surface& src,
|
||||
const Tegra::Engines::Fermi2D::Surface& dst,
|
||||
|
||||
@@ -99,24 +99,7 @@ void StagingBufferPool::FreeDeferred(StagingBufferRef& ref) {
|
||||
void StagingBufferPool::TickFrame() {
|
||||
current_delete_level = (current_delete_level + 1) % NUM_LEVELS;
|
||||
|
||||
// Enhanced cleanup for Insane mode to prevent VRAM leaks
|
||||
const auto vram_mode = Settings::values.vram_usage_mode.GetValue();
|
||||
if (vram_mode == Settings::VramUsageMode::Insane) {
|
||||
static u32 cleanup_counter = 0;
|
||||
cleanup_counter++;
|
||||
|
||||
// More aggressive cleanup for Insane mode every 30 frames
|
||||
if (cleanup_counter % 30 == 0) {
|
||||
// Force release of all caches to prevent memory accumulation
|
||||
ReleaseCache(MemoryUsage::DeviceLocal);
|
||||
ReleaseCache(MemoryUsage::Upload);
|
||||
ReleaseCache(MemoryUsage::Download);
|
||||
|
||||
// Additional cleanup for large staging buffers
|
||||
LOG_DEBUG(Render_Vulkan, "Performed aggressive staging buffer cleanup (Insane mode)");
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup is now handled by the VRAM management system (gc_aggressiveness setting)
|
||||
ReleaseCache(MemoryUsage::DeviceLocal);
|
||||
ReleaseCache(MemoryUsage::Upload);
|
||||
ReleaseCache(MemoryUsage::Download);
|
||||
|
||||
@@ -939,29 +939,8 @@ VkBuffer TextureCacheRuntime::GetTemporaryBuffer(size_t needed_size) {
|
||||
return *buffers[level];
|
||||
}
|
||||
|
||||
// Optimize buffer size based on VRAM usage mode
|
||||
size_t new_size = Common::NextPow2(needed_size);
|
||||
const auto vram_mode = Settings::values.vram_usage_mode.GetValue();
|
||||
|
||||
if (vram_mode == Settings::VramUsageMode::HighEnd) {
|
||||
// For high-end GPUs, use larger temporary buffers to reduce allocation overhead
|
||||
// but cap them to prevent excessive VRAM usage
|
||||
if (needed_size > 32_MiB && needed_size < 256_MiB) {
|
||||
new_size = Common::AlignUp(needed_size, 32_MiB);
|
||||
} else if (needed_size > 2_MiB && needed_size <= 32_MiB) {
|
||||
new_size = Common::AlignUp(needed_size, 4_MiB);
|
||||
}
|
||||
} else if (vram_mode == Settings::VramUsageMode::Insane) {
|
||||
// Insane mode: Use massive temporary buffers for RTX 4090 to maximize texture caching
|
||||
// and shader compilation performance
|
||||
if (needed_size > 64_MiB && needed_size < 512_MiB) {
|
||||
new_size = Common::AlignUp(needed_size, 64_MiB);
|
||||
} else if (needed_size > 8_MiB && needed_size <= 64_MiB) {
|
||||
new_size = Common::AlignUp(needed_size, 16_MiB);
|
||||
} else if (needed_size > 1_MiB && needed_size <= 8_MiB) {
|
||||
new_size = Common::AlignUp(needed_size, 2_MiB);
|
||||
}
|
||||
}
|
||||
// Use power-of-2 buffer sizes for efficient allocation
|
||||
const size_t new_size = Common::NextPow2(needed_size);
|
||||
|
||||
static constexpr VkBufferUsageFlags flags =
|
||||
VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
|
||||
@@ -981,46 +960,8 @@ VkBuffer TextureCacheRuntime::GetTemporaryBuffer(size_t needed_size) {
|
||||
}
|
||||
|
||||
void TextureCacheRuntime::CleanupUnusedBuffers() {
|
||||
// Aggressive cleanup for Insane mode to prevent VRAM leaks
|
||||
const auto vram_mode = Settings::values.vram_usage_mode.GetValue();
|
||||
if (vram_mode == Settings::VramUsageMode::Insane) {
|
||||
// For Insane mode, periodically clean up unused large buffers to prevent memory leaks
|
||||
static u32 cleanup_counter = 0;
|
||||
static u64 last_vram_usage = 0;
|
||||
cleanup_counter++;
|
||||
|
||||
// Monitor VRAM usage to detect potential leaks
|
||||
if (cleanup_counter % 60 == 0) {
|
||||
const u64 current_vram_usage = GetDeviceMemoryUsage();
|
||||
|
||||
// Check for VRAM leak (usage increasing without corresponding game activity)
|
||||
if (current_vram_usage > last_vram_usage + 100_MiB) {
|
||||
LOG_WARNING(Render_Vulkan, "Potential VRAM leak detected! Usage increased by {} MB",
|
||||
(current_vram_usage - last_vram_usage) / (1024 * 1024));
|
||||
|
||||
// Force aggressive cleanup
|
||||
for (auto& buffer : buffers) {
|
||||
if (buffer) {
|
||||
buffer.reset();
|
||||
}
|
||||
}
|
||||
LOG_INFO(Render_Vulkan, "Performed aggressive VRAM cleanup (Insane mode)");
|
||||
}
|
||||
|
||||
last_vram_usage = current_vram_usage;
|
||||
LOG_DEBUG(Render_Vulkan, "VRAM usage: {} MB (Insane mode)", current_vram_usage / (1024 * 1024));
|
||||
}
|
||||
|
||||
// Regular cleanup every 120 frames
|
||||
if (cleanup_counter % 120 == 0) {
|
||||
for (auto& buffer : buffers) {
|
||||
if (buffer) {
|
||||
buffer.reset();
|
||||
}
|
||||
}
|
||||
LOG_DEBUG(Render_Vulkan, "Cleaned up unused temporary buffers (Insane mode)");
|
||||
}
|
||||
}
|
||||
// Cleanup is now handled by the VRAM management system (gc_aggressiveness setting)
|
||||
// This function is kept for compatibility but no longer performs mode-specific cleanup
|
||||
}
|
||||
|
||||
void TextureCacheRuntime::BarrierFeedbackLoop() {
|
||||
|
||||
@@ -50,21 +50,59 @@ TextureCache<P>::TextureCache(Runtime& runtime_, Tegra::MaxwellDeviceMemoryManag
|
||||
void(slot_image_views.insert(runtime, NullImageViewParams{}));
|
||||
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) {
|
||||
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;
|
||||
expected_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));
|
||||
minimum_memory = static_cast<u64>((device_local_memory - mem_threshold) / 2);
|
||||
|
||||
// FIXED: VRAM leak prevention - Use configured limit or auto-detect (80% of VRAM)
|
||||
if (configured_limit_mb > 0) {
|
||||
vram_limit_bytes = static_cast<u64>(configured_limit_mb) * 1_MiB;
|
||||
} else {
|
||||
// Auto-detect: use 80% of available VRAM as limit
|
||||
vram_limit_bytes = static_cast<u64>(device_local_memory * 0.80);
|
||||
}
|
||||
|
||||
// Adjust thresholds based on VRAM limit and GC aggressiveness setting
|
||||
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 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 {
|
||||
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;
|
||||
critical_memory = DEFAULT_CRITICAL_MEMORY + 1_GiB;
|
||||
minimum_memory = 0;
|
||||
@@ -73,37 +111,111 @@ TextureCache<P>::TextureCache(Runtime& runtime_, Tegra::MaxwellDeviceMemoryManag
|
||||
|
||||
template <class P>
|
||||
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 aggressive_mode = false;
|
||||
bool emergency_mode = false;
|
||||
u64 ticks_to_destroy = 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;
|
||||
aggressive_mode = allow_aggressive && total_used_memory >= critical_memory;
|
||||
ticks_to_destroy = aggressive_mode ? 10ULL : high_priority_mode ? 25ULL : 50ULL;
|
||||
num_iterations = aggressive_mode ? 40 : (high_priority_mode ? 20 : 10);
|
||||
emergency_mode = allow_emergency && total_used_memory >= static_cast<u64>(vram_limit_bytes * VRAM_USAGE_EMERGENCY_THRESHOLD);
|
||||
|
||||
// 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) {
|
||||
return true;
|
||||
}
|
||||
--num_iterations;
|
||||
auto& image = slot_images[image_id];
|
||||
|
||||
// Skip images being decoded
|
||||
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;
|
||||
}
|
||||
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 =
|
||||
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;
|
||||
}
|
||||
|
||||
// Perform download if needed
|
||||
if (must_download) {
|
||||
auto map = runtime.DownloadStagingBuffer(image.unswizzled_size_bytes);
|
||||
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,
|
||||
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)) {
|
||||
UntrackImage(image, image_id);
|
||||
}
|
||||
UnregisterImage(image_id);
|
||||
DeleteImage(image_id, image.scale_tick > frame_tick + 5);
|
||||
|
||||
// Adjust mode based on remaining memory pressure
|
||||
if (total_used_memory < critical_memory) {
|
||||
if (aggressive_mode) {
|
||||
// Sink the aggresiveness.
|
||||
if (aggressive_mode || emergency_mode) {
|
||||
num_iterations >>= 2;
|
||||
aggressive_mode = false;
|
||||
emergency_mode = false;
|
||||
return false;
|
||||
}
|
||||
if (high_priority_mode && total_used_memory < expected_memory) {
|
||||
@@ -132,26 +257,80 @@ void TextureCache<P>::RunGarbageCollector() {
|
||||
return false;
|
||||
};
|
||||
|
||||
// Try to remove anything old enough and not high priority.
|
||||
Configure(false);
|
||||
// FIXED: VRAM leak prevention - First pass: evict sparse textures if priority enabled
|
||||
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);
|
||||
|
||||
// If pressure is still too high, prune aggressively.
|
||||
// Aggressive pass if still above critical
|
||||
if (total_used_memory >= critical_memory) {
|
||||
Configure(true);
|
||||
Configure(true, false);
|
||||
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>
|
||||
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 (runtime.CanReportMemoryUsage()) {
|
||||
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();
|
||||
}
|
||||
|
||||
// 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_framebuffers.Tick();
|
||||
sentenced_image_view.Tick();
|
||||
@@ -166,6 +345,183 @@ void TextureCache<P>::TickFrame() {
|
||||
}
|
||||
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>
|
||||
@@ -2018,7 +2374,22 @@ void TextureCache<P>::RegisterImage(ImageId image_id) {
|
||||
True(image.flags & ImageFlagBits::Converted)) {
|
||||
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);
|
||||
|
||||
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 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 Image = typename P::Image;
|
||||
using ImageAlloc = typename P::ImageAlloc;
|
||||
@@ -296,6 +304,42 @@ public:
|
||||
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
|
||||
template <bool has_blacklists>
|
||||
void FillImageViews(DescriptorTable<TICEntry>& table,
|
||||
@@ -450,6 +494,18 @@ public:
|
||||
u64 expected_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 {
|
||||
GPUVAddr address;
|
||||
size_t size;
|
||||
|
||||
@@ -1354,20 +1354,6 @@ void Device::CollectPhysicalMemoryInfo() {
|
||||
const size_t scaler_memory = 1_GiB * Settings::values.resolution_info.ScaleUp(1);
|
||||
device_access_memory =
|
||||
std::min<u64>(device_access_memory, normal_memory + scaler_memory);
|
||||
} else if (vram_mode == Settings::VramUsageMode::HighEnd) {
|
||||
// High-End GPU mode: Use more VRAM but with smart buffer management
|
||||
// Allow up to 12GB for RTX 4090/4080+ users, but optimize buffer allocation
|
||||
const size_t high_end_memory = 12_GiB;
|
||||
const size_t scaler_memory = 1_GiB * Settings::values.resolution_info.ScaleUp(1);
|
||||
device_access_memory =
|
||||
std::min<u64>(device_access_memory, high_end_memory + scaler_memory);
|
||||
} else if (vram_mode == Settings::VramUsageMode::Insane) {
|
||||
// Insane mode: Use most of RTX 4090's 24GB VRAM for maximum performance
|
||||
// Reserve only 2GB for system and other applications
|
||||
const size_t insane_memory = 22_GiB;
|
||||
const size_t scaler_memory = 2_GiB * Settings::values.resolution_info.ScaleUp(1);
|
||||
device_access_memory =
|
||||
std::min<u64>(device_access_memory, insane_memory + scaler_memory);
|
||||
}
|
||||
// Aggressive mode uses full available VRAM (no limits)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user