Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion cts_runner/fail.lst
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ webgpu:api,validation,image_copy,buffer_texture_copies:* // https://github.com/g
webgpu:api,validation,layout_shader_compat:pipeline_layout_shader_exact_match:* // dx12, https://bugzilla.mozilla.org/show_bug.cgi?id=2017725
webgpu:api,validation,non_filterable_texture:non_filterable_texture_with_filtering_sampler:* // 80%, depth textures with filtering samplers
webgpu:api,validation,query_set,create:count:* // 0%, wgpu incorrectly rejects zero-count query sets
webgpu:api,validation,queue,buffer_mapped:* // ***, vulkan
webgpu:api,validation,queue,destroyed,* // 71%, writeBuffer/writeTexture return value, destroyed query set
webgpu:api,validation,queue,writeBuffer:ranges:* // 0%, missing OperationError for invalid ranges
webgpu:api,validation,render_pass,attachment_compatibility:render_pass_or_bundle_and_pipeline,depth_stencil_read_only_write_state:* // fails on dx12
Expand Down
12 changes: 4 additions & 8 deletions cts_runner/test.lst
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ webgpu:api,validation,createBindGroup:binding_must_contain_resource_defined_in_l
webgpu:api,validation,createBindGroup:binding_resources,device_mismatch:*
webgpu:api,validation,createBindGroup:buffer_offset_and_size_for_bind_groups_match:*
webgpu:api,validation,createBindGroup:buffer,effective_buffer_binding_size:*
webgpu:api,validation,createBindGroup:buffer,resource_binding_size:*
// Fails with OOM if maxBindingSize is large. https://github.com/gfx-rs/wgpu/issues/9642
fails-if(vulkan) webgpu:api,validation,createBindGroup:buffer,resource_binding_size:*
webgpu:api,validation,createBindGroup:buffer,resource_offset:*
webgpu:api,validation,createBindGroup:buffer,usage:*
webgpu:api,validation,createBindGroup:minBindingSize:*
Expand All @@ -121,7 +122,7 @@ webgpu:api,validation,createBindGroup:texture_must_have_correct_dimension:*
webgpu:api,validation,createBindGroupLayout:duplicate_bindings:*
webgpu:api,validation,createBindGroupLayout:max_dynamic_buffers:*
// Doesn't actually fail, but is very slow. https://github.com/gfx-rs/wgpu/issues/9229
fails-if(dx12) webgpu:api,validation,createBindGroupLayout:max_resources_per_stage,*
fails-if(dx12,vulkan) webgpu:api,validation,createBindGroupLayout:max_resources_per_stage,*
webgpu:api,validation,createBindGroupLayout:maximum_binding_limit:*
webgpu:api,validation,createBindGroupLayout:multisampled_validation:*
webgpu:api,validation,createBindGroupLayout:storage_texture,formats:*
Expand Down Expand Up @@ -232,12 +233,7 @@ webgpu:api,validation,image_copy,layout_related:rows_per_image_alignment:*
webgpu:api,validation,image_copy,texture_related:*
fails-if(dx12) webgpu:api,validation,layout_shader_compat:pipeline_layout_shader_exact_match:*
webgpu:api,validation,query_set,destroy:*
webgpu:api,validation,queue,buffer_mapped:copyBufferToBuffer:*
webgpu:api,validation,queue,buffer_mapped:copyBufferToTexture:*
webgpu:api,validation,queue,buffer_mapped:copyTextureToBuffer:*
webgpu:api,validation,queue,buffer_mapped:map_command_recording_order:*
// `vulkan` failure: https://github.com/gfx-rs/wgpu/issues/????
fails-if(vulkan) webgpu:api,validation,queue,buffer_mapped:writeBuffer:*
webgpu:api,validation,queue,buffer_mapped:*
webgpu:api,validation,queue,submit:command_buffer,*
webgpu:api,validation,queue,writeBuffer:buffer_state:*
webgpu:api,validation,queue,writeBuffer:buffer,device_mismatch:*
Expand Down
266 changes: 164 additions & 102 deletions wgpu-hal/src/vulkan/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,34 @@ struct CompiledStage {
temp_raw_module: Option<vk::ShaderModule>,
}

struct MemoryProperties {
base: vk::PhysicalDeviceMemoryProperties,
heap_budget: ArrayVec<vk::DeviceSize, { vk::MAX_MEMORY_HEAPS }>,
heap_usage: ArrayVec<vk::DeviceSize, { vk::MAX_MEMORY_HEAPS }>,
}

impl MemoryProperties {
fn types(&self) -> &[vk::MemoryType] {
let count = self.base.memory_type_count as usize;
&self.base.memory_types[0..count]
}

fn heaps(&self) -> &[vk::MemoryHeap] {
let count = self.base.memory_heap_count as usize;
&self.base.memory_heaps[0..count]
}

fn heap_budget(&self) -> &[vk::DeviceSize] {
let count = self.base.memory_heap_count as usize;
&self.heap_budget[0..count]
}

fn heap_usage(&self) -> &[vk::DeviceSize] {
let count = self.base.memory_heap_count as usize;
&self.heap_usage[0..count]
}
}

impl super::Device {
/// # Safety
///
Expand Down Expand Up @@ -813,26 +841,13 @@ impl super::Device {
&self.shared.instance
}

fn error_if_would_oom_on_resource_allocation(
&self,
needs_host_access: bool,
size: u64,
) -> Result<(), crate::DeviceError> {
let Some(threshold) = self
.shared
.instance
.memory_budget_thresholds
.for_resource_creation
else {
return Ok(());
};

fn get_memory_properties(&self) -> Option<MemoryProperties> {
if !self
.shared
.enabled_extensions
.contains(&ext::memory_budget::NAME)
{
return Ok(());
return None;
}

let get_physical_device_properties = self
Expand All @@ -842,66 +857,125 @@ impl super::Device {
.as_ref()
.unwrap();

let mut memory_budget_properties = vk::PhysicalDeviceMemoryBudgetPropertiesEXT::default();

let mut memory_properties =
vk::PhysicalDeviceMemoryProperties2::default().push_next(&mut memory_budget_properties);
let mut budget = vk::PhysicalDeviceMemoryBudgetPropertiesEXT::default();
let mut props = vk::PhysicalDeviceMemoryProperties2::default().push_next(&mut budget);

unsafe {
get_physical_device_properties.get_physical_device_memory_properties2(
self.shared.physical_device,
&mut memory_properties,
);
get_physical_device_properties
.get_physical_device_memory_properties2(self.shared.physical_device, &mut props);
}

let mut host_visible_heaps = [false; vk::MAX_MEMORY_HEAPS];
let mut device_local_heaps = [false; vk::MAX_MEMORY_HEAPS];
let vk::PhysicalDeviceMemoryProperties2 {
memory_properties, ..
} = props;

let memory_properties = memory_properties.memory_properties;
let mut heap_budget = ArrayVec::from(budget.heap_budget);
let mut heap_usage = ArrayVec::from(budget.heap_usage);
heap_budget.truncate(memory_properties.memory_heap_count as usize);
heap_usage.truncate(memory_properties.memory_heap_count as usize);

for i in 0..memory_properties.memory_type_count {
let memory_type = memory_properties.memory_types[i as usize];
let flags = memory_type.property_flags;

if flags.intersects(
vk::MemoryPropertyFlags::LAZILY_ALLOCATED | vk::MemoryPropertyFlags::PROTECTED,
) {
continue; // not used by gpu-alloc
}
Some(MemoryProperties {
base: memory_properties,
heap_budget,
heap_usage,
})
}

if flags.contains(vk::MemoryPropertyFlags::HOST_VISIBLE) {
host_visible_heaps[memory_type.heap_index as usize] = true;
}
/// Predict whether a proposed allocation will result in an OOM condition.
///
/// If so, returns `Err(crate::DeviceError::OutOfMemory)`. If not, returns
/// `Ok(())`.
///
/// The prediction quality depends on accurately selecting the heap that
/// [`gpu_allocator`] will use for the allocation, and is subject to
/// deteriorate if the logic in [`gpu_allocator`] changes.
fn error_if_would_oom_on_resource_allocation(
&self,
location: gpu_allocator::MemoryLocation,
requirements: &vk::MemoryRequirements,
) -> Result<(), crate::DeviceError> {
use gpu_allocator::MemoryLocation;

if flags.contains(vk::MemoryPropertyFlags::DEVICE_LOCAL) {
device_local_heaps[memory_type.heap_index as usize] = true;
}
}
let Some(threshold) = self
.shared
.instance
.memory_budget_thresholds
.for_resource_creation
else {
return Ok(());
};

let heaps = if needs_host_access {
host_visible_heaps
} else {
device_local_heaps
let Some(memory_properties) = self.get_memory_properties() else {
return Ok(());
};

// NOTE: We might end up checking multiple heaps since gpu-alloc doesn't have a way
// for us to query the heap the resource will end up on. But this is unlikely,
// there is usually only one heap on integrated GPUs and two on dedicated GPUs.
// memory types not used by gpu-allocator
let invalid_flags =
vk::MemoryPropertyFlags::LAZILY_ALLOCATED | vk::MemoryPropertyFlags::PROTECTED;

for (i, check) in heaps.iter().enumerate() {
if !check {
continue;
let preferred_flags = match location {
MemoryLocation::GpuOnly => vk::MemoryPropertyFlags::DEVICE_LOCAL,
MemoryLocation::CpuToGpu => {
vk::MemoryPropertyFlags::HOST_VISIBLE
| vk::MemoryPropertyFlags::HOST_COHERENT
| vk::MemoryPropertyFlags::DEVICE_LOCAL
}
MemoryLocation::GpuToCpu => {
vk::MemoryPropertyFlags::HOST_VISIBLE
| vk::MemoryPropertyFlags::HOST_COHERENT
| vk::MemoryPropertyFlags::HOST_CACHED
}
MemoryLocation::Unknown => vk::MemoryPropertyFlags::empty(),
};

let heap_usage = memory_budget_properties.heap_usage[i];
let heap_budget = memory_budget_properties.heap_budget[i];
let mut selected_heap = memory_properties
.types()
.iter()
.enumerate()
.find(|(i, ty)| {
(1 << i) & requirements.memory_type_bits != 0
&& ty.property_flags.contains(preferred_flags)
&& !ty.property_flags.intersects(invalid_flags)
});

if heap_usage + size >= heap_budget / 100 * threshold as u64 {
return Err(crate::DeviceError::OutOfMemory);
}
if selected_heap.is_none() {
let required_flags = match location {
MemoryLocation::GpuOnly => vk::MemoryPropertyFlags::DEVICE_LOCAL,
MemoryLocation::CpuToGpu | MemoryLocation::GpuToCpu => {
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT
}
MemoryLocation::Unknown => vk::MemoryPropertyFlags::empty(),
};
selected_heap = memory_properties
.types()
.iter()
.enumerate()
.find(|(i, ty)| {
(1 << i) & requirements.memory_type_bits != 0
&& ty.property_flags.contains(required_flags)
&& !ty.property_flags.intersects(invalid_flags)
});
}

Ok(())
if let Some((_, ty)) = selected_heap {
let i = ty.heap_index as usize;
let heap_usage = memory_properties.heap_usage()[i];
let heap_budget = memory_properties.heap_budget()[i];
if heap_usage + requirements.size < heap_budget / 100 * threshold as u64 {
Ok(())
} else {
log::warn!(
"Allocation would result in an OOM condition\n\
Request: {requirements:?}\n\
Heap {index} had {heap_usage}B used of {heap_budget}B total before this request.",
index = ty.heap_index,
);
Err(crate::DeviceError::OutOfMemory)
}
} else {
log::warn!("Failed to find a suitable heap for {requirements:?}");
Err(crate::DeviceError::OutOfMemory)
}
}
}

Expand Down Expand Up @@ -936,9 +1010,7 @@ impl crate::Device for super::Device {
(false, false) => gpu_allocator::MemoryLocation::GpuOnly,
};

let needs_host_access = is_cpu_read || is_cpu_write;

self.error_if_would_oom_on_resource_allocation(needs_host_access, requirements.size)
self.error_if_would_oom_on_resource_allocation(location, &requirements)
.inspect_err(|_| {
unsafe { self.shared.raw.destroy_buffer(raw, None) };
})?;
Expand Down Expand Up @@ -1105,10 +1177,13 @@ impl crate::Device for super::Device {
) -> Result<super::Texture, crate::DeviceError> {
let image = self.create_image_without_memory(desc, None)?;

self.error_if_would_oom_on_resource_allocation(false, image.requirements.size)
.inspect_err(|_| {
unsafe { self.shared.raw.destroy_image(image.raw, None) };
})?;
self.error_if_would_oom_on_resource_allocation(
gpu_allocator::MemoryLocation::GpuOnly,
&image.requirements,
)
.inspect_err(|_| {
unsafe { self.shared.raw.destroy_image(image.raw, None) };
})?;

let name = desc.label.unwrap_or("Unlabeled texture");

Expand Down Expand Up @@ -2226,9 +2301,18 @@ impl crate::Device for super::Device {
&self,
desc: &wgt::QuerySetDescriptor<crate::Label>,
) -> Result<super::QuerySet, crate::DeviceError> {
// Assume each query is 256 bytes.
// On an AMD W6800 with driver version 32.0.12030.9, occlusion queries are 256.
self.error_if_would_oom_on_resource_allocation(true, desc.count as u64 * 256)?;
// Assume each query is 256 bytes. This is the case for occlusion
// queries on an AMD W6800 with driver version 32.0.12030.9. The
// size and allocation policy may vary; this is an approximate
// check only.
self.error_if_would_oom_on_resource_allocation(
gpu_allocator::MemoryLocation::GpuToCpu,
&vk::MemoryRequirements {
size: desc.count as u64 * 256,
alignment: 256,
memory_type_bits: self.valid_ash_memory_types,
},
)?;

let (vk_type, pipeline_statistics) = match desc.ty {
wgt::QueryType::Occlusion => (
Expand Down Expand Up @@ -2567,10 +2651,13 @@ impl crate::Device for super::Device {

let requirements = self.shared.raw.get_buffer_memory_requirements(raw_buffer);

self.error_if_would_oom_on_resource_allocation(false, requirements.size)
.inspect_err(|_| {
self.shared.raw.destroy_buffer(raw_buffer, None);
})?;
self.error_if_would_oom_on_resource_allocation(
gpu_allocator::MemoryLocation::GpuOnly,
&requirements,
)
.inspect_err(|_| {
self.shared.raw.destroy_buffer(raw_buffer, None);
})?;

let name = desc
.label
Expand Down Expand Up @@ -2745,38 +2832,13 @@ impl crate::Device for super::Device {
return Ok(());
};

if !self
.shared
.enabled_extensions
.contains(&ext::memory_budget::NAME)
{
let Some(memory_properties) = self.get_memory_properties() else {
return Ok(());
}

let get_physical_device_properties = self
.shared
.instance
.get_physical_device_properties
.as_ref()
.unwrap();

let mut memory_budget_properties = vk::PhysicalDeviceMemoryBudgetPropertiesEXT::default();

let mut memory_properties =
vk::PhysicalDeviceMemoryProperties2::default().push_next(&mut memory_budget_properties);

unsafe {
get_physical_device_properties.get_physical_device_memory_properties2(
self.shared.physical_device,
&mut memory_properties,
);
}

let memory_properties = memory_properties.memory_properties;
};

for i in 0..memory_properties.memory_heap_count {
let heap_usage = memory_budget_properties.heap_usage[i as usize];
let heap_budget = memory_budget_properties.heap_budget[i as usize];
for i in 0..memory_properties.heaps().len() {
let heap_usage = memory_properties.heap_usage()[i];
let heap_budget = memory_properties.heap_budget()[i];

if heap_usage >= heap_budget / 100 * threshold as u64 {
return Err(crate::DeviceError::OutOfMemory);
Expand Down
Loading