add memoryMapPlaced and memoryMapRangePlaced features of VK_EXT_map_memory_placed extension (#2514)

* add partial support for VK_EXT_map_memory_placed extension

implements the memoryMapPlaced and memoryMapRangePlaced features

* fix clippy lint

* add tests

I don't love these tests, they probably fail silently in CI

* fix conflicting requirement

* update test

* fmt

* fix memory selection in test

intersects does not do what I thought it does

* remove incorrect assertion

I don't think this is correct. The offset and size are both DeviceSize or u64. They are added together as positive numbers.

* remove unused

* put back non-range test

* only run test on unix

* wrap stuff to 100 cols

this is actually really nice for reading

* clippy

* tweak impl

* scope

* remove unused

* use NonNull

* fmt

* add flags field

* update position

* PLACED_EXT -> PLACED

* removed unnecessary validation

* Update vulkano/src/memory/device_memory.rs

Co-authored-by: marc0246 <40955683+marc0246@users.noreply.github.com>

* Update vulkano/src/memory/device_memory.rs

Co-authored-by: marc0246 <40955683+marc0246@users.noreply.github.com>

* Update vulkano/src/memory/device_memory.rs

Co-authored-by: marc0246 <40955683+marc0246@users.noreply.github.com>

* Update vulkano/src/memory/device_memory.rs

Co-authored-by: marc0246 <40955683+marc0246@users.noreply.github.com>

* Update vulkano/src/memory/device_memory.rs

Co-authored-by: marc0246 <40955683+marc0246@users.noreply.github.com>

* remove more

* typo

* Update vulkano/src/memory/device_memory.rs

Co-authored-by: Rua <ruawhitepaw@gmail.com>

* add validate_device call

* Update vulkano/src/memory/device_memory.rs

Co-authored-by: marc0246 <40955683+marc0246@users.noreply.github.com>

* Update vulkano/src/memory/device_memory.rs

Co-authored-by: marc0246 <40955683+marc0246@users.noreply.github.com>

* Update vulkano/src/memory/device_memory.rs

Co-authored-by: marc0246 <40955683+marc0246@users.noreply.github.com>

* fix links

* remove extra branch

* use WHOLE_SIZE

* use getter

* fix boolean condition

* Update vulkano/src/memory/device_memory.rs

Co-authored-by: marc0246 <40955683+marc0246@users.noreply.github.com>

* add specific changes marc asked for

i've concluded that this choice is roughly semantically equivalent and this comes down to opinion. I can't be bothered to have an opinion here, imo this is a bikeshed.

also it is temporary. until the docs are updated and the impls are updated. also i don't even really care about the ranged version of this feature, implemented it mostly for completeness than anything else

* Incorporate latest spec updates

---------

Co-authored-by: marc0246 <40955683+marc0246@users.noreply.github.com>
Co-authored-by: Rua <ruawhitepaw@gmail.com>
This commit is contained in:
Martin Charles 2024-06-07 05:00:16 -05:00 committed by GitHub
parent 978c922e1b
commit 64f3b1e7bc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 299 additions and 14 deletions

5
Cargo.lock generated
View File

@ -1069,9 +1069,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.152" version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]] [[package]]
name = "libloading" name = "libloading"
@ -2347,6 +2347,7 @@ dependencies = [
"half", "half",
"heck", "heck",
"indexmap", "indexmap",
"libc",
"libloading 0.8.1", "libloading 0.8.1",
"nom", "nom",
"objc", "objc",

View File

@ -46,6 +46,9 @@ serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true } serde_json = { workspace = true }
vk-parse = { workspace = true } vk-parse = { workspace = true }
[dev-dependencies]
libc = "0.2.153"
[features] [features]
default = ["macros"] default = ["macros"]
macros = ["dep:vulkano-macros"] macros = ["dep:vulkano-macros"]

View File

@ -231,7 +231,8 @@ fn properties_members(types: &HashMap<&str, (&Type, Vec<&str>)>) -> Vec<Properti
| "robustStorageBufferAccessSizeAlignment" | "robustStorageBufferAccessSizeAlignment"
| "robustUniformBufferAccessSizeAlignment" | "robustUniformBufferAccessSizeAlignment"
| "storageTexelBufferOffsetAlignmentBytes" | "storageTexelBufferOffsetAlignmentBytes"
| "uniformTexelBufferOffsetAlignmentBytes" => { | "uniformTexelBufferOffsetAlignmentBytes"
| "minPlacedMemoryMapAlignment" => {
quote! { DeviceAlignment } quote! { DeviceAlignment }
} }
_ => vulkano_type(ty, len), _ => vulkano_type(ty, len),

View File

@ -219,8 +219,8 @@ pub use self::{
}; };
use super::{ use super::{
DedicatedAllocation, DeviceAlignment, DeviceMemory, ExternalMemoryHandleTypes, DedicatedAllocation, DeviceAlignment, DeviceMemory, ExternalMemoryHandleTypes,
MemoryAllocateFlags, MemoryAllocateInfo, MemoryMapInfo, MemoryProperties, MemoryPropertyFlags, MemoryAllocateFlags, MemoryAllocateInfo, MemoryMapFlags, MemoryMapInfo, MemoryProperties,
MemoryRequirements, MemoryType, MemoryPropertyFlags, MemoryRequirements, MemoryType,
}; };
use crate::{ use crate::{
device::{Device, DeviceOwned}, device::{Device, DeviceOwned},
@ -1094,6 +1094,7 @@ impl<S> GenericMemoryAllocator<S> {
// - Mapping the whole range is always valid. // - Mapping the whole range is always valid.
unsafe { unsafe {
memory.map_unchecked(MemoryMapInfo { memory.map_unchecked(MemoryMapInfo {
flags: MemoryMapFlags::empty(),
offset: 0, offset: 0,
size: memory.allocation_size(), size: memory.allocation_size(),
_ne: crate::NonExhaustive(()), _ne: crate::NonExhaustive(()),

View File

@ -418,12 +418,61 @@ impl DeviceMemory {
/// `self` must not be host-mapped already and must be allocated from host-visible memory. /// `self` must not be host-mapped already and must be allocated from host-visible memory.
#[inline] #[inline]
pub fn map(&mut self, map_info: MemoryMapInfo) -> Result<(), Validated<VulkanError>> { pub fn map(&mut self, map_info: MemoryMapInfo) -> Result<(), Validated<VulkanError>> {
self.validate_map(&map_info)?; self.validate_map(&map_info, None)?;
unsafe { Ok(self.map_unchecked(map_info)?) } unsafe { Ok(self.map_unchecked(map_info)?) }
} }
fn validate_map(&self, map_info: &MemoryMapInfo) -> Result<(), Box<ValidationError>> { #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
#[inline]
pub unsafe fn map_unchecked(&mut self, map_info: MemoryMapInfo) -> Result<(), VulkanError> {
unsafe { self.map_unchecked_inner(map_info, None) }
}
/// Maps a range of memory to be accessed by the host at the specified `placed_address`.
///
/// Requires the [`memory_map_placed`] feature to be enabled on the device.
///
/// `placed_address` must be aligned to the [`min_placed_memory_map_alignment`] device
/// property.
///
/// `self` must not be host-mapped already and must be allocated from host-visible memory.
///
/// # Safety
///
/// - The memory mapping specified by `placed_address` and `map_info.size` will be replaced,
/// including mappings made by the Rust allocator, a library, or your application itself.
/// - The memory mapping specified by `placed_address` and `map_info.size` must not overlap any
/// existing Vulkan memory mapping.
///
/// [`memory_map_placed`]: crate::device::DeviceFeatures::memory_map_placed
/// [`min_placed_memory_map_alignment`]: crate::device::DeviceProperties::min_placed_memory_map_alignment
#[inline]
pub unsafe fn map_placed(
&mut self,
map_info: MemoryMapInfo,
placed_address: NonNull<c_void>,
) -> Result<(), Validated<VulkanError>> {
self.validate_map(&map_info, Some(placed_address))?;
unsafe { Ok(self.map_placed_unchecked(map_info, placed_address)?) }
}
#[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
#[inline]
pub unsafe fn map_placed_unchecked(
&mut self,
map_info: MemoryMapInfo,
placed_address: NonNull<c_void>,
) -> Result<(), VulkanError> {
unsafe { self.map_unchecked_inner(map_info, Some(placed_address)) }
}
fn validate_map(
&self,
map_info: &MemoryMapInfo,
placed_address: Option<NonNull<c_void>>,
) -> Result<(), Box<ValidationError>> {
if self.mapping_state.is_some() { if self.mapping_state.is_some() {
return Err(Box::new(ValidationError { return Err(Box::new(ValidationError {
problem: "this device memory is already host-mapped".into(), problem: "this device memory is already host-mapped".into(),
@ -433,7 +482,7 @@ impl DeviceMemory {
} }
map_info map_info
.validate(self) .validate(self, placed_address)
.map_err(|err| err.add_context("map_info"))?; .map_err(|err| err.add_context("map_info"))?;
let memory_type = &self let memory_type = &self
@ -458,9 +507,13 @@ impl DeviceMemory {
Ok(()) Ok(())
} }
#[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] unsafe fn map_unchecked_inner(
pub unsafe fn map_unchecked(&mut self, map_info: MemoryMapInfo) -> Result<(), VulkanError> { &mut self,
map_info: MemoryMapInfo,
placed_address: Option<NonNull<c_void>>,
) -> Result<(), VulkanError> {
let MemoryMapInfo { let MemoryMapInfo {
flags,
offset, offset,
size, size,
_ne: _, _ne: _,
@ -477,13 +530,22 @@ impl DeviceMemory {
if device.enabled_extensions().khr_map_memory2 { if device.enabled_extensions().khr_map_memory2 {
let map_info_vk = ash::vk::MemoryMapInfoKHR { let map_info_vk = ash::vk::MemoryMapInfoKHR {
flags: ash::vk::MemoryMapFlags::empty(), flags: flags.into(),
memory: self.handle(), memory: self.handle(),
offset, offset,
size, size,
..Default::default() ..Default::default()
}; };
let mut map_placed_info_vk = ash::vk::MemoryMapPlacedInfoEXT::default();
let map_info_vk = if let Some(it) = placed_address {
map_placed_info_vk.p_placed_address = it.as_ptr();
map_info_vk.push_next(&mut map_placed_info_vk)
} else {
map_info_vk
};
(fns.khr_map_memory2.map_memory2_khr)( (fns.khr_map_memory2.map_memory2_khr)(
device.handle(), device.handle(),
&map_info_vk, &map_info_vk,
@ -1363,6 +1425,8 @@ vulkan_bitflags! {
/// Parameters of a memory map operation. /// Parameters of a memory map operation.
#[derive(Debug)] #[derive(Debug)]
pub struct MemoryMapInfo { pub struct MemoryMapInfo {
pub flags: MemoryMapFlags,
/// The offset (in bytes) from the beginning of the `DeviceMemory`, where the mapping starts. /// The offset (in bytes) from the beginning of the `DeviceMemory`, where the mapping starts.
/// ///
/// Must be less than the [`allocation_size`] of the device memory. If the the memory was not /// Must be less than the [`allocation_size`] of the device memory. If the the memory was not
@ -1392,13 +1456,25 @@ pub struct MemoryMapInfo {
} }
impl MemoryMapInfo { impl MemoryMapInfo {
pub(crate) fn validate(&self, memory: &DeviceMemory) -> Result<(), Box<ValidationError>> { pub(crate) fn validate(
&self,
memory: &DeviceMemory,
placed_address: Option<NonNull<c_void>>,
) -> Result<(), Box<ValidationError>> {
let &Self { let &Self {
flags,
offset, offset,
size, size,
_ne: _, _ne: _,
} = self; } = self;
let device = memory.device();
flags.validate_device(device).map_err(|err| {
err.add_context("flags")
.set_vuids(&["VUID-VkMemoryMapInfoKHR-flags-parameter"])
})?;
if !(offset < memory.allocation_size()) { if !(offset < memory.allocation_size()) {
return Err(Box::new(ValidationError { return Err(Box::new(ValidationError {
context: "offset".into(), context: "offset".into(),
@ -1427,6 +1503,124 @@ impl MemoryMapInfo {
})); }));
} }
if flags.contains(MemoryMapFlags::PLACED) {
if !device.enabled_features().memory_map_placed {
return Err(Box::new(ValidationError {
context: "flags".into(),
problem: "contains `MemoryMapFlags::PLACED`".into(),
requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::DeviceFeature(
"memory_map_placed",
)])]),
vuids: &["VUID-VkMemoryMapInfoKHR-flags-09569"],
}));
}
let Some(placed_address) = placed_address else {
return Err(Box::new(ValidationError {
context: "flags".into(),
problem:
"contains `MemoryMapFlags::PLACED`, but `DeviceMemory::map_placed` isn't \
used to specify the placed address"
.into(),
vuids: &["VUID-VkMemoryMapInfoKHR-flags-09570"],
..Default::default()
}));
};
// min_placed_memory_map_alignment is always provided when the device extension
// ext_map_memory_placed is available.
let min_placed_memory_map_alignment = device
.physical_device()
.properties()
.min_placed_memory_map_alignment
.unwrap();
if !is_aligned(
placed_address.as_ptr() as DeviceSize,
min_placed_memory_map_alignment,
) {
return Err(Box::new(ValidationError {
context: "placed_address".into(),
problem: "is not aligned to an integer multiple of the \
`min_placed_memory_map_alignment` device property"
.into(),
vuids: &["VUID-VkMemoryMapPlacedInfoEXT-pPlacedAddress-09577"],
..Default::default()
}));
}
if device.enabled_features().memory_map_range_placed {
if !is_aligned(offset, min_placed_memory_map_alignment) {
return Err(Box::new(ValidationError {
context: "offset".into(),
problem: "is not aligned to an integer multiple of the \
`min_placed_memory_map_alignment` device property"
.into(),
vuids: &["VUID-VkMemoryMapInfoKHR-flags-09573"],
..Default::default()
}));
}
if !is_aligned(size, min_placed_memory_map_alignment) {
return Err(Box::new(ValidationError {
context: "size".into(),
problem: "is not aligned to an integer multiple of the \
`min_placed_memory_map_alignment` device property"
.into(),
vuids: &["VUID-VkMemoryMapInfoKHR-flags-09574"],
..Default::default()
}));
}
} else {
if offset != 0 {
return Err(Box::new(ValidationError {
context: "offset".into(),
problem: "is not zero".into(),
vuids: &["VUID-VkMemoryMapInfoKHR-flags-09571"],
..Default::default()
}));
}
if size != memory.allocation_size() {
return Err(Box::new(ValidationError {
context: "size".into(),
problem: "is not `self.allocation_size()`".into(),
vuids: &["VUID-VkMemoryMapInfoKHR-flags-09572"],
..Default::default()
}));
}
if !is_aligned(memory.allocation_size(), min_placed_memory_map_alignment) {
return Err(Box::new(ValidationError {
context: "flags".into(),
problem: "contains `MemoryMapFlags::PLACED`, but `self.allocation_size()` \
is not aligned to an integer multiple of the \
`min_placed_memory_map_alignment` device property"
.into(),
vuids: &["VUID-VkMemoryMapInfoKHR-flags-09651"],
..Default::default()
}));
}
}
if let Some(handle_type) = memory.imported_handle_type() {
if handle_type == ExternalMemoryHandleType::HostAllocation
|| handle_type == ExternalMemoryHandleType::HostMappedForeignMemory
{
return Err(Box::new(ValidationError {
context: "flags".into(),
problem: "contains `MemoryMapFlags::PLACED`, but \
`self.imported_handle_type()` is \
`ExternalMemoryHandleType::HostAllocation` or \
`ExternalMemoryHandleType::HostMappedForeignMemory`"
.into(),
vuids: &["VUID-VkMemoryMapInfoKHR-flags-09575"],
..Default::default()
}));
}
}
}
let atom_size = memory.atom_size(); let atom_size = memory.atom_size();
// Not required for merely mapping, but without this check the user can end up with // Not required for merely mapping, but without this check the user can end up with
@ -1458,6 +1652,7 @@ impl Default for MemoryMapInfo {
#[inline] #[inline]
fn default() -> Self { fn default() -> Self {
MemoryMapInfo { MemoryMapInfo {
flags: MemoryMapFlags::empty(),
offset: 0, offset: 0,
size: 0, size: 0,
_ne: crate::NonExhaustive(()), _ne: crate::NonExhaustive(()),
@ -1465,6 +1660,13 @@ impl Default for MemoryMapInfo {
} }
} }
vulkan_bitflags! {
#[non_exhaustive]
MemoryMapFlags = MemoryMapFlags(u32);
PLACED = PLACED_EXT,
}
/// Parameters of a memory unmap operation. /// Parameters of a memory unmap operation.
#[derive(Debug)] #[derive(Debug)]
pub struct MemoryUnmapInfo { pub struct MemoryUnmapInfo {
@ -2142,7 +2344,8 @@ unsafe impl Sync for MappedDeviceMemory {}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::MemoryAllocateInfo; use super::MemoryAllocateInfo;
use crate::memory::{DeviceMemory, MemoryPropertyFlags}; use crate::memory::{DeviceMemory, MemoryMapFlags, MemoryMapInfo, MemoryPropertyFlags};
use std::{ptr, ptr::NonNull};
#[test] #[test]
fn create() { fn create() {
@ -2271,4 +2474,69 @@ mod tests {
} }
assert_eq!(device.allocation_count(), 1); assert_eq!(device.allocation_count(), 1);
} }
#[test]
#[cfg(unix)]
fn map_placed() {
let (device, _) = gfx_dev_and_queue!(memory_map_placed; ext_map_memory_placed);
let memory_type_index = {
let physical_device = device.physical_device();
let memory_properties = physical_device.memory_properties();
let (idx, _) = memory_properties
.memory_types
.iter()
.enumerate()
.find(|(_idx, it)| {
it.property_flags.contains(
MemoryPropertyFlags::HOST_COHERENT
| MemoryPropertyFlags::HOST_VISIBLE
| MemoryPropertyFlags::DEVICE_LOCAL,
)
})
.unwrap();
idx as u32
};
let mut memory = DeviceMemory::allocate(
device.clone(),
MemoryAllocateInfo {
allocation_size: 16 * 1024,
memory_type_index,
..Default::default()
},
)
.unwrap();
let address = unsafe {
let address = libc::mmap(
ptr::null_mut(),
16 * 1024,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
-1,
0,
);
if address as i64 == -1 {
panic!("failed to map memory")
}
address
};
unsafe {
memory
.map_placed(
MemoryMapInfo {
flags: MemoryMapFlags::PLACED,
size: memory.allocation_size,
..Default::default()
},
NonNull::new(address).unwrap(),
)
.unwrap();
}
}
} }

View File

@ -19,13 +19,24 @@ macro_rules! instance {
/// Creates a device and a queue for graphics operations. /// Creates a device and a queue for graphics operations.
macro_rules! gfx_dev_and_queue { macro_rules! gfx_dev_and_queue {
() => ({
gfx_dev_and_queue!(;)
});
($($feature:ident),*) => ({ ($($feature:ident),*) => ({
gfx_dev_and_queue!($($feature),*;)
});
($($feature:ident),*; $($extension:ident),*) => ({
use crate::device::physical::PhysicalDeviceType; use crate::device::physical::PhysicalDeviceType;
use crate::device::{Device, DeviceCreateInfo, DeviceExtensions, QueueCreateInfo}; use crate::device::{Device, DeviceCreateInfo, DeviceExtensions, QueueCreateInfo};
use crate::device::DeviceFeatures; use crate::device::DeviceFeatures;
let instance = instance!(); let instance = instance!();
let enabled_extensions = DeviceExtensions::empty(); let enabled_extensions = DeviceExtensions {
$(
$extension: true,
)*
.. DeviceExtensions::empty()
};
let enabled_features = DeviceFeatures { let enabled_features = DeviceFeatures {
$( $(
$feature: true, $feature: true,