make memory pressure detection optional and configurable

This commit is contained in:
teoxoy 2025-04-11 20:09:55 +02:00 committed by Teodor Tanasoaia
parent 5bd3a25cf7
commit 3b72d59a3c
18 changed files with 108 additions and 31 deletions

View File

@ -148,6 +148,10 @@ impl GPU {
&wgpu_types::InstanceDescriptor {
backends,
flags: wgpu_types::InstanceFlags::from_build_config(),
memory_budget_thresholds: wgpu_types::MemoryBudgetThresholds {
for_resource_creation: Some(97),
for_device_loss: Some(99),
},
backend_options: wgpu_types::BackendOptions {
dx12: wgpu_types::Dx12BackendOptions {
shader_compiler: wgpu_types::Dx12Compiler::Fxc,

View File

@ -43,6 +43,10 @@ pub fn initialize_instance(backends: wgpu::Backends, params: &TestParameters) ->
Instance::new(&wgpu::InstanceDescriptor {
backends,
flags,
memory_budget_thresholds: wgpu::MemoryBudgetThresholds {
for_resource_creation: Some(99),
for_device_loss: None,
},
backend_options: wgpu::BackendOptions {
dx12: wgpu::Dx12BackendOptions {
shader_compiler: dx12_shader_compiler,

View File

@ -39,6 +39,7 @@ mod request_adapter_error {
wgpu::InstanceDescriptor {
backends,
flags: wgpu::InstanceFlags::default(),
memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
backend_options: wgpu::BackendOptions::default(),
}
}

View File

@ -123,6 +123,7 @@ impl Instance {
let hal_desc = hal::InstanceDescriptor {
name: "wgpu",
flags: self.flags,
memory_budget_thresholds: instance_desc.memory_budget_thresholds,
backend_options: instance_desc.backend_options.clone(),
};

View File

@ -94,6 +94,7 @@ impl<A: hal::Api> Example<A> {
let instance_desc = hal::InstanceDescriptor {
name: "example",
flags: wgpu_types::InstanceFlags::from_build_config().with_env(),
memory_budget_thresholds: wgpu_types::MemoryBudgetThresholds::default(),
// Can't rely on having DXC available, so use FXC instead
backend_options: wgpu_types::BackendOptions::default(),
};

View File

@ -238,6 +238,7 @@ impl<A: hal::Api> Example<A> {
let instance_desc = hal::InstanceDescriptor {
name: "example",
flags: wgpu_types::InstanceFlags::default(),
memory_budget_thresholds: wgpu_types::MemoryBudgetThresholds::default(),
backend_options: wgpu_types::BackendOptions {
dx12: Dx12BackendOptions {
shader_compiler: wgpu_types::Dx12Compiler::default_dynamic_dxc(),

View File

@ -54,6 +54,7 @@ impl super::Adapter {
adapter: DxgiAdapter,
library: &Arc<D3D12Lib>,
instance_flags: wgt::InstanceFlags,
memory_budget_thresholds: wgt::MemoryBudgetThresholds,
dxc_container: Option<Arc<shader_compilation::DxcContainer>>,
) -> Option<crate::ExposedAdapter<super::Api>> {
// Create the device so that we can get the capabilities.
@ -515,6 +516,7 @@ impl super::Adapter {
private_caps,
presentation_timer,
workarounds,
memory_budget_thresholds,
dxc_container,
},
info,
@ -653,6 +655,7 @@ impl crate::Adapter for super::Adapter {
memory_hints,
self.private_caps,
&self.library,
self.memory_budget_thresholds,
self.dxc_container.clone(),
)?;
Ok(crate::OpenDevice {

View File

@ -47,6 +47,7 @@ impl super::Device {
memory_hints: &wgt::MemoryHints,
private_caps: super::PrivateCapabilities,
library: &Arc<D3D12Lib>,
memory_budget_thresholds: wgt::MemoryBudgetThresholds,
dxc_container: Option<Arc<shader_compilation::DxcContainer>>,
) -> Result<Self, crate::DeviceError> {
if private_caps
@ -159,6 +160,7 @@ impl super::Device {
private_caps,
device_memblock_size,
host_memblock_size,
memory_budget_thresholds,
};
let mut rtv_pool =
@ -1937,17 +1939,18 @@ impl crate::Device for super::Device {
),
};
let info = self
.shared
.adapter
.query_video_memory_info(Dxgi::DXGI_MEMORY_SEGMENT_GROUP_LOCAL)?;
if let Some(threshold) = self.shared.memory_budget_thresholds.for_resource_creation {
let info = self
.shared
.adapter
.query_video_memory_info(Dxgi::DXGI_MEMORY_SEGMENT_GROUP_LOCAL)?;
// Assume each query is 256 bytes.
// On an AMD W6800 with driver version 32.0.12030.9, occlusion and pipeline statistics are 256, timestamp is 8.
// Assume each query is 256 bytes.
// On an AMD W6800 with driver version 32.0.12030.9, occlusion and pipeline statistics are 256, timestamp is 8.
// Make sure we don't exceed 90% of the budget
if info.CurrentUsage + desc.count as u64 * 256 >= info.Budget / 10 * 9 {
return Err(crate::DeviceError::OutOfMemory);
if info.CurrentUsage + desc.count as u64 * 256 >= info.Budget / 100 * threshold as u64 {
return Err(crate::DeviceError::OutOfMemory);
}
}
let mut raw = None::<Direct3D12::ID3D12QueryHeap>;
@ -2326,13 +2329,16 @@ impl crate::Device for super::Device {
}
fn check_if_oom(&self) -> Result<(), crate::DeviceError> {
let Some(threshold) = self.shared.memory_budget_thresholds.for_device_loss else {
return Ok(());
};
let info = self
.shared
.adapter
.query_video_memory_info(Dxgi::DXGI_MEMORY_SEGMENT_GROUP_LOCAL)?;
// Make sure we don't exceed 95% of the budget
if info.CurrentUsage >= info.Budget / 100 * 95 {
if info.CurrentUsage >= info.Budget / 100 * threshold as u64 {
return Err(crate::DeviceError::OutOfMemory);
}
@ -2345,8 +2351,7 @@ impl crate::Device for super::Device {
.adapter
.query_video_memory_info(Dxgi::DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL)?;
// Make sure we don't exceed 95% of the budget
if info.CurrentUsage >= info.Budget / 100 * 95 {
if info.CurrentUsage >= info.Budget / 100 * threshold as u64 {
return Err(crate::DeviceError::OutOfMemory);
}
}

View File

@ -109,6 +109,7 @@ impl crate::Instance for super::Instance {
_lib_dxgi: lib_dxgi,
supports_allow_tearing,
flags: desc.flags,
memory_budget_thresholds: desc.memory_budget_thresholds,
dxc_container,
})
}
@ -142,7 +143,13 @@ impl crate::Instance for super::Instance {
adapters
.into_iter()
.filter_map(|raw| {
super::Adapter::expose(raw, &self.library, self.flags, self.dxc_container.clone())
super::Adapter::expose(
raw,
&self.library,
self.flags,
self.memory_budget_thresholds,
self.dxc_container.clone(),
)
})
.collect()
}

View File

@ -460,6 +460,7 @@ pub struct Instance {
supports_allow_tearing: bool,
_lib_dxgi: DxgiLib,
flags: wgt::InstanceFlags,
memory_budget_thresholds: wgt::MemoryBudgetThresholds,
dxc_container: Option<Arc<shader_compilation::DxcContainer>>,
}
@ -591,6 +592,7 @@ pub struct Adapter {
// Note: this isn't used right now, but we'll need it later.
#[allow(unused)]
workarounds: Workarounds,
memory_budget_thresholds: wgt::MemoryBudgetThresholds,
dxc_container: Option<Arc<shader_compilation::DxcContainer>>,
}
@ -635,6 +637,7 @@ struct DeviceShared {
private_caps: PrivateCapabilities,
device_memblock_size: u64,
host_memblock_size: u64,
memory_budget_thresholds: wgt::MemoryBudgetThresholds,
}
unsafe impl Send for DeviceShared {}

View File

@ -526,6 +526,10 @@ impl<'a> DeviceAllocationContext<'a> {
.GetResourceAllocationInfo(0, std::slice::from_ref(desc))
};
let Some(threshold) = self.shared.memory_budget_thresholds.for_resource_creation else {
return Ok(allocation_info);
};
let memory_segment_group = match location {
MemoryLocation::Unknown => unreachable!(),
MemoryLocation::GpuOnly => Dxgi::DXGI_MEMORY_SEGMENT_GROUP_LOCAL,
@ -552,9 +556,8 @@ impl<'a> DeviceAllocationContext<'a> {
MemoryLocation::CpuToGpu | MemoryLocation::GpuToCpu => self.shared.host_memblock_size,
};
// Make sure we don't exceed 90% of the budget
if info.CurrentUsage + allocation_info.SizeInBytes.max(memblock_size)
>= info.Budget / 10 * 9
>= info.Budget / 100 * threshold as u64
{
return Err(crate::DeviceError::OutOfMemory);
}

View File

@ -1741,6 +1741,7 @@ bitflags!(
pub struct InstanceDescriptor<'a> {
pub name: &'a str,
pub flags: wgt::InstanceFlags,
pub memory_budget_thresholds: wgt::MemoryBudgetThresholds,
pub backend_options: wgt::BackendOptions,
}

View File

@ -95,6 +95,7 @@ impl crate::Instance for Context {
},
name: _,
flags: _,
memory_budget_thresholds: _,
} = *desc;
if enable {
Ok(Context)

View File

@ -1027,6 +1027,15 @@ impl super::Device {
needs_host_access: bool,
size: u64,
) -> Result<(), crate::DeviceError> {
let Some(threshold) = self
.shared
.instance
.memory_budget_thresholds
.for_resource_creation
else {
return Ok(());
};
if !self
.shared
.enabled_extensions
@ -1096,8 +1105,7 @@ impl super::Device {
let heap_usage = memory_budget_properties.heap_usage[i];
let heap_budget = memory_budget_properties.heap_budget[i];
// Make sure we don't exceed 90% of the budget
if heap_usage + size >= heap_budget / 100 * 90 {
if heap_usage + size >= heap_budget / 100 * threshold as u64 {
return Err(crate::DeviceError::OutOfMemory);
}
}
@ -3008,6 +3016,15 @@ impl crate::Device for super::Device {
}
fn check_if_oom(&self) -> Result<(), crate::DeviceError> {
let Some(threshold) = self
.shared
.instance
.memory_budget_thresholds
.for_device_loss
else {
return Ok(());
};
if !self
.shared
.enabled_extensions
@ -3041,8 +3058,7 @@ impl crate::Device for super::Device {
let heap_usage = memory_budget_properties.heap_usage[i as usize];
let heap_budget = memory_budget_properties.heap_budget[i as usize];
// Make sure we don't exceed 95% of the budget
if heap_usage >= heap_budget / 100 * 95 {
if heap_usage >= heap_budget / 100 * threshold as u64 {
return Err(crate::DeviceError::OutOfMemory);
}
}

View File

@ -347,6 +347,7 @@ impl super::Instance {
debug_utils_create_info: Option<super::DebugUtilsCreateInfo>,
extensions: Vec<&'static CStr>,
flags: wgt::InstanceFlags,
memory_budget_thresholds: wgt::MemoryBudgetThresholds,
has_nv_optimus: bool,
drop_callback: Option<crate::DropCallback>,
) -> Result<Self, crate::InstanceError> {
@ -397,6 +398,7 @@ impl super::Instance {
extensions,
drop_guard,
flags,
memory_budget_thresholds,
debug_utils,
get_physical_device_properties,
entry,
@ -860,6 +862,7 @@ impl crate::Instance for super::Instance {
debug_utils,
extensions,
desc.flags,
desc.memory_budget_thresholds,
has_nv_optimus,
None,
)

View File

@ -162,6 +162,7 @@ pub struct InstanceShared {
extensions: Vec<&'static CStr>,
drop_guard: Option<crate::DropGuard>,
flags: wgt::InstanceFlags,
memory_budget_thresholds: wgt::MemoryBudgetThresholds,
debug_utils: Option<DebugUtils>,
get_physical_device_properties: Option<khr::get_physical_device_properties2::Instance>,
entry: ash::Entry,

View File

@ -14,6 +14,8 @@ pub struct InstanceDescriptor {
pub backends: Backends,
/// Flags to tune the behavior of the instance.
pub flags: InstanceFlags,
/// Memory budget thresholds used by some backends.
pub memory_budget_thresholds: MemoryBudgetThresholds,
/// Options the control the behavior of various backends.
pub backend_options: BackendOptions,
}
@ -23,6 +25,7 @@ impl Default for InstanceDescriptor {
Self {
backends: Backends::all(),
flags: InstanceFlags::default(),
memory_budget_thresholds: MemoryBudgetThresholds::default(),
backend_options: BackendOptions::default(),
}
}
@ -48,6 +51,7 @@ impl InstanceDescriptor {
Self {
backends,
flags,
memory_budget_thresholds: MemoryBudgetThresholds::default(),
backend_options,
}
}
@ -225,6 +229,24 @@ impl InstanceFlags {
}
}
/// Memory budget thresholds used by backends to try to avoid high memory pressure situations.
///
/// Currently only the D3D12 and (optionally) Vulkan backends support these options.
#[derive(Default, Clone, Debug, Copy)]
pub struct MemoryBudgetThresholds {
/// Threshold at which texture, buffer, query set and acceleration structure creation will start to return OOM errors.
/// This is a percent of the memory budget reported by native APIs.
///
/// If not specified, resource creation might still return OOM errors.
pub for_resource_creation: Option<u8>,
/// Threshold at which devices will become lost due to memory pressure.
/// This is a percent of the memory budget reported by native APIs.
///
/// If not specified, devices might still become lost due to memory pressure.
pub for_device_loss: Option<u8>,
}
/// Options that are passed to a given backend.
///
/// Part of [`InstanceDescriptor`].

View File

@ -77,17 +77,17 @@ pub use wgt::{
Dx12BackendOptions, Dx12Compiler, DxcShaderModel, DynamicOffset, Extent3d, Face, Features,
FeaturesWGPU, FeaturesWebGPU, FilterMode, FrontFace, GlBackendOptions, GlFenceBehavior,
Gles3MinorVersion, HalCounters, ImageSubresourceRange, IndexFormat, InstanceDescriptor,
InstanceFlags, InternalCounters, Limits, MemoryHints, MultisampleState, NoopBackendOptions,
Origin2d, Origin3d, PipelineStatisticsTypes, PollError, PollStatus, PolygonMode,
PowerPreference, PredefinedColorSpace, PresentMode, PresentationTimestamp, PrimitiveState,
PrimitiveTopology, PushConstantRange, QueryType, RenderBundleDepthStencil, RequestAdapterError,
SamplerBindingType, SamplerBorderColor, ShaderLocation, ShaderModel, ShaderRuntimeChecks,
ShaderStages, StencilFaceState, StencilOperation, StencilState, StorageTextureAccess,
SurfaceCapabilities, SurfaceStatus, TexelCopyBufferLayout, TextureAspect, TextureDimension,
TextureFormat, TextureFormatFeatureFlags, TextureFormatFeatures, TextureSampleType,
TextureTransition, TextureUsages, TextureUses, TextureViewDimension, Trace, VertexAttribute,
VertexFormat, VertexStepMode, WasmNotSend, WasmNotSendSync, WasmNotSync, COPY_BUFFER_ALIGNMENT,
COPY_BYTES_PER_ROW_ALIGNMENT, MAP_ALIGNMENT, PUSH_CONSTANT_ALIGNMENT,
InstanceFlags, InternalCounters, Limits, MemoryBudgetThresholds, MemoryHints, MultisampleState,
NoopBackendOptions, Origin2d, Origin3d, PipelineStatisticsTypes, PollError, PollStatus,
PolygonMode, PowerPreference, PredefinedColorSpace, PresentMode, PresentationTimestamp,
PrimitiveState, PrimitiveTopology, PushConstantRange, QueryType, RenderBundleDepthStencil,
RequestAdapterError, SamplerBindingType, SamplerBorderColor, ShaderLocation, ShaderModel,
ShaderRuntimeChecks, ShaderStages, StencilFaceState, StencilOperation, StencilState,
StorageTextureAccess, SurfaceCapabilities, SurfaceStatus, TexelCopyBufferLayout, TextureAspect,
TextureDimension, TextureFormat, TextureFormatFeatureFlags, TextureFormatFeatures,
TextureSampleType, TextureTransition, TextureUsages, TextureUses, TextureViewDimension, Trace,
VertexAttribute, VertexFormat, VertexStepMode, WasmNotSend, WasmNotSendSync, WasmNotSync,
COPY_BUFFER_ALIGNMENT, COPY_BYTES_PER_ROW_ALIGNMENT, MAP_ALIGNMENT, PUSH_CONSTANT_ALIGNMENT,
QUERY_RESOLVE_BUFFER_ALIGNMENT, QUERY_SET_MAX_QUERIES, QUERY_SIZE, VERTEX_STRIDE_ALIGNMENT,
};
#[expect(deprecated)]