Skip to main content

vk/mem/
binding.rs

1//! Existing-resource allocation and checked binding, following pinned VMA 3.4.0
2//! `vmaAllocateMemoryForBuffer`, `vmaAllocateMemoryForImage`, and ordinary binds.
3//! See license/vma/MIT.txt. Raw handles reveal no creation usage/tiling; automatic
4//! usage policies are rejected and unknown image placement is conservative.
5use super::*;
6
7struct Requirements {
8	memory: vk::MemoryRequirements,
9	dedication: Dedication,
10	dedicated_supported: bool,
11}
12
13unsafe fn requirements(
14	allocator: &Allocator<'_>,
15	resource: Resource,
16) -> Result<Requirements, AllocError> {
17	// SAFETY: caller supplies a live ordinary resource; no disjoint plane is requested.
18	unsafe { requirements_with_plane(allocator, resource, None) }
19}
20
21unsafe fn requirements_with_plane(
22	allocator: &Allocator<'_>,
23	resource: Resource,
24	plane: Option<vk::ImageAspectFlagBits>,
25) -> Result<Requirements, AllocError> {
26	let route = allocator.requirements_route(matches!(resource, Resource::Image(_)))?;
27	// SAFETY: same live native resource/plane query contract.
28	unsafe { query_requirements(allocator.device.as_device(), route, resource, plane) }
29}
30
31unsafe fn query_requirements(
32	device: &vk::Device,
33	route: RequirementsRoute,
34	resource: Resource,
35	plane: Option<vk::ImageAspectFlagBits>,
36) -> Result<Requirements, AllocError> {
37	if plane.is_some() && matches!(route, RequirementsRoute::Legacy) {
38		return Err(AllocError::UnsupportedCapability(
39			"disjoint planes require memory requirements2",
40		));
41	}
42	let plane_info = vk::ImagePlaneMemoryRequirementsInfo {
43		plane_aspect: plane.unwrap_or_default(),
44		..Default::default()
45	};
46	let mut dedication = vk::MemoryDedicatedRequirements::new();
47	let mut output = vk::MemoryRequirements2 {
48		p_next: std::ptr::from_mut(&mut dedication).cast(),
49		..Default::default()
50	};
51	// SAFETY: caller supplies a live ordinary resource from this device; exact
52	// route preflights its PFN and both writable output records remain in scope.
53	unsafe {
54		match resource {
55			Resource::Buffer(buffer) => {
56				let query = vk::BufferMemoryRequirementsInfo2 {
57					buffer,
58					..Default::default()
59				};
60				match route {
61					RequirementsRoute::Legacy => {
62						output.memory_requirements = device.get_buffer_memory_requirements(buffer)?
63					}
64					RequirementsRoute::Core => device.get_buffer_memory_requirements2(&query, &mut output)?,
65					RequirementsRoute::Khr => {
66						device.get_buffer_memory_requirements2_khr(&query, &mut output)?
67					}
68				}
69			}
70			Resource::Image(image) => {
71				let query = vk::ImageMemoryRequirementsInfo2 {
72					p_next: if plane.is_some() {
73						std::ptr::from_ref(&plane_info).cast()
74					} else {
75						std::ptr::null()
76					},
77					image,
78					..Default::default()
79				};
80				match route {
81					RequirementsRoute::Legacy => {
82						device.get_image_memory_requirements(image, &mut output.memory_requirements)?
83					}
84					RequirementsRoute::Core => device.get_image_memory_requirements2(&query, &mut output)?,
85					RequirementsRoute::Khr => {
86						device.get_image_memory_requirements2_khr(&query, &mut output)?
87					}
88				}
89			}
90		}
91	}
92	if output.memory_requirements.size == 0 || !output.memory_requirements.alignment.is_power_of_two()
93	{
94		return Err(AllocError::InvalidRequest(
95			"invalid native resource requirements",
96		));
97	}
98	Ok(Requirements {
99		memory: output.memory_requirements,
100		dedication: Dedication::new(&dedication),
101		dedicated_supported: !matches!(route, RequirementsRoute::Legacy),
102	})
103}
104
105/// Plane slot shared by the local association index and the synchronized owner
106/// registry: ordinary bindings occupy slot zero and disjoint planes occupy
107/// slots one through three, so one resource can own independent plane storage.
108pub(in crate::mem) fn plane_slot(plane: Option<vk::ImageAspectFlagBits>) -> usize {
109	match plane {
110		Some(vk::ImageAspectFlagBits::PLANE_0_BIT) => 1,
111		Some(vk::ImageAspectFlagBits::PLANE_1_BIT) => 2,
112		Some(vk::ImageAspectFlagBits::PLANE_2_BIT) => 3,
113		None => 0,
114		_ => unreachable!("canonical binding validates plane aspects"),
115	}
116}
117
118/// Raw-handle association index mirroring each allocator's `live` resource and
119/// pending-binding claims. Values hold the claiming allocation serial per plane
120/// slot (zero means unclaimed). An independent allocator indexes its bound
121/// resources here; a partitioned child indexes only its own pending bindings
122/// because the shared owner registry already covers bound resources.
123pub(super) struct Associations<'device> {
124	buffers: HostMap<'device, [u64; 4]>,
125	images: HostMap<'device, [u64; 4]>,
126}
127
128impl<'device> Associations<'device> {
129	pub fn new(host: HostMemory<'device>) -> Self {
130		Self {
131			buffers: HostMap::new(host),
132			images: HostMap::new(host),
133		}
134	}
135
136	fn registry(&self, resource: Resource) -> (&HostMap<'device, [u64; 4]>, u64) {
137		match resource {
138			Resource::Buffer(buffer) => (&self.buffers, buffer.as_raw()),
139			Resource::Image(image) => (&self.images, image.as_raw()),
140		}
141	}
142
143	fn registry_mut(&mut self, resource: Resource) -> (&mut HostMap<'device, [u64; 4]>, u64) {
144		match resource {
145			Resource::Buffer(buffer) => (&mut self.buffers, buffer.as_raw()),
146			Resource::Image(image) => (&mut self.images, image.as_raw()),
147		}
148	}
149
150	// Keys with no claimed slot are always removed, so presence is the check.
151	pub fn associated(&self, resource: Resource) -> bool {
152		let (registry, key) = self.registry(resource);
153		registry.get(&key).is_some()
154	}
155
156	/// Duplicate rule for a raw binding of `resource` at `plane`: an ordinary
157	/// claim rejects every owner, while a plane claim rejects the ordinary slot
158	/// and the matching plane slot only.
159	pub fn conflicts(&self, resource: Resource, plane: Option<vk::ImageAspectFlagBits>) -> bool {
160		let (registry, key) = self.registry(resource);
161		let Some(slots) = registry.get(&key) else {
162			return false;
163		};
164		match plane {
165			None => true,
166			Some(_) => slots[0] != 0 || slots[plane_slot(plane)] != 0,
167		}
168	}
169
170	pub fn try_reserve_for(&mut self, resource: Resource) -> Result<(), AllocError> {
171		let (registry, _) = self.registry_mut(resource);
172		registry.try_reserve(1)
173	}
174
175	/// Reserve `additional` keys in both registries. The shared index uses this
176	/// because up to `domains` uncommitted claims can be outstanding at once.
177	pub fn try_reserve(&mut self, additional: usize) -> Result<(), AllocError> {
178		self.buffers.try_reserve(additional)?;
179		self.images.try_reserve(additional)
180	}
181
182	// Capacity for the resource key must already be reserved.
183	pub fn record(
184		&mut self,
185		resource: Resource,
186		plane: Option<vk::ImageAspectFlagBits>,
187		serial: u64,
188	) {
189		let (registry, key) = self.registry_mut(resource);
190		registry.get_or_insert_reserved_with(key, || [0; 4])[plane_slot(plane)] = serial;
191	}
192
193	/// Release the claim owned by `serial`; other serials keep the slot so a
194	/// recycled native handle cannot lose a newer association.
195	pub fn release(
196		&mut self,
197		resource: Resource,
198		plane: Option<vk::ImageAspectFlagBits>,
199		serial: u64,
200	) {
201		let (registry, key) = self.registry_mut(resource);
202		registry.remove_if(&key, |slots| {
203			let slot = plane_slot(plane);
204			if slots[slot] == serial {
205				slots[slot] = 0;
206			}
207			slots.iter().all(|owner| *owner == 0)
208		});
209	}
210}
211
212pub(super) fn associated(allocator: &Allocator<'_>, resource: Resource) -> bool {
213	if let Some(shared) = allocator.parallel_state() {
214		let domain = allocator.partition_id;
215		if shared.associated(resource, domain) {
216			return true;
217		}
218	}
219	allocator.associated.associated(resource)
220}
221
222/// Continue a resource transaction after options, mapping PFNs and publication
223/// capacity have been admitted. The exclusive owner cannot be reentered by
224/// native callbacks, so creation does not invalidate that admission.
225///
226/// # Safety
227/// The resource is live/non-null; the caller has validated these options and
228/// reserved publication before effects. Native requirements are still queried
229/// and checked here, and association reservation remains fallible before binding.
230pub(super) unsafe fn allocate_prepared_resource(
231	allocator: &mut Allocator<'_>,
232	resource: Resource,
233	options: &AllocationCreateInfo,
234	preferences: (
235		vk::MemoryPropertyFlags,
236		vk::MemoryPropertyFlags,
237		vk::MemoryPropertyFlags,
238	),
239	class: ResourceClass,
240	device_address: bool,
241) -> Result<u64, AllocError> {
242	let (required, preferred, avoided) = preferences;
243	if allocator.parallel.is_none() {
244		// Independent allocators publish bound resources into the local index;
245		// partitioned claims publish the shared index instead.
246		allocator.associated.try_reserve_for(resource)?;
247	}
248	let claim = parallel::claim(allocator, resource, None, None)?;
249	let imported = options
250		.memory_allocate_next
251		.and_then(|chain| chain.data.import_info);
252	let requirements = if let Some(info) = imported.filter(|info| info.resource.is_some()) {
253		if import_resource(info.resource) != Some(resource) {
254			return Err(AllocError::WrongResource);
255		}
256		// The unsafe import constructor admits exact external/resource metadata.
257		// In particular, Android image requirements cannot be queried before binding.
258		Requirements {
259			memory: vk::MemoryRequirements {
260				size: info.allocation_size,
261				alignment: 1,
262				memory_type_bits: info.memory_type_bits,
263			},
264			dedication: Dedication::Required,
265			dedicated_supported: true,
266		}
267	} else {
268		if options
269			.memory_allocate_next
270			.is_some_and(|chain| chain.data.import_kind.requires_info())
271		{
272			return Err(AllocError::InvalidRequest(
273				"platform resource imports require exact resource metadata",
274			));
275		}
276		// SAFETY: exact live ordinary resource supplied by caller; no platform pre-bind query.
277		unsafe { requirements(allocator, resource) }?
278	};
279	let request = AllocRequest {
280		size: requirements.memory.size,
281		alignment: requirements
282			.memory
283			.alignment
284			.max(options.min_allocation_alignment),
285		memory_type_bits: requirements.memory.memory_type_bits & options.memory_type_bits,
286		required_flags: required,
287		preferred_flags: preferred,
288	};
289	let mut dedicated = vk::MemoryDedicatedAllocateInfo::new();
290	match resource {
291		Resource::Buffer(buffer) => dedicated.buffer = buffer,
292		Resource::Image(image) => dedicated.image = image,
293	}
294	// SAFETY: matching queried requirements, exact supported dedication, and admitted class.
295	let serial = unsafe {
296		allocate_managed(
297			allocator,
298			&request,
299			StorageOptions {
300				chain: &super::super::chain::ChainData::EMPTY,
301				dedicated: requirements.dedicated_supported.then_some(&dedicated),
302				device_address,
303				priority: options.priority,
304				within_budget: options.within_budget,
305			},
306			requirements.dedication,
307			options,
308			class,
309			avoided,
310		)
311	}?;
312	let live = allocator.live.get_mut(&serial).expect("new allocation");
313	live.resource = Some(resource);
314	live.class = if options.can_alias {
315		ResourceClass::Unknown
316	} else {
317		class
318	};
319	live.can_alias = options.can_alias;
320	if let Some(claim) = claim {
321		claim.commit(serial);
322	} else {
323		allocator.associated.record(resource, None, serial);
324	}
325	Ok(serial)
326}
327
328pub(super) unsafe fn persistent(
329	allocator: &mut Allocator<'_>,
330	allocation: &Allocation,
331	options: &AllocationCreateInfo,
332) -> Result<(), AllocError> {
333	// SAFETY: unpublished unbound storage; debug mapping/checks retain no user references.
334	unsafe { guard::initialize(allocator, allocation) }?;
335	if options.persistent_map {
336		// SAFETY: caller supplies new host-visible storage with preflighted inverse PFN.
337		unsafe { map(allocator, allocation) }?;
338		let live = allocator
339			.live
340			.get_mut(&allocation.serial)
341			.expect("new allocation");
342		live.map_count = 0;
343		live.persistent = true;
344	}
345	Ok(())
346}
347
348unsafe fn allocate_existing(
349	allocator: &mut Allocator<'_>,
350	resource: Resource,
351	options: &AllocationCreateInfo,
352) -> Result<Allocation, AllocError> {
353	allocator.ensure_open()?;
354	if associated(allocator, resource) {
355		return Err(AllocError::InvalidRequest(
356			"resource already has an allocation",
357		));
358	}
359
360	if options.usage != super::super::MemoryUsage::Explicit {
361		return Err(AllocError::UnsupportedCapability(
362			"existing resource usage is unknown; use explicit memory flags",
363		));
364	}
365	let preferences = super::super::policy::preferences(options, false, allocator.integrated)?;
366	let class = match resource {
367		Resource::Buffer(_) => ResourceClass::Buffer,
368		Resource::Image(_) => ResourceClass::UnknownImage,
369	};
370	let address = matches!(resource, Resource::Buffer(_)) && allocator.buffer_device_address;
371	if resource.is_null() {
372		return Err(AllocError::InvalidRequest("null resource"));
373	}
374	request::validate_options(allocator, options, preferences.0)?;
375	if options.persistent_map {
376		require(allocator.device.fp().map_memory.is_some(), "vkMapMemory")?;
377		require(
378			allocator.device.fp().unmap_memory.is_some(),
379			"vkUnmapMemory",
380		)?;
381	}
382	allocator.reserve()?;
383	// SAFETY: caller guarantees ordinary resource semantics; unknown usage never enables a feature.
384	let serial = unsafe {
385		allocate_prepared_resource(allocator, resource, options, preferences, class, address)
386	}?;
387	let mut allocation = allocator.published(serial);
388	// SAFETY: unpublished new allocation; no binding or external access exists.
389	if let Err(error) = unsafe { persistent(allocator, &allocation, options) } {
390		// SAFETY: unbound storage only; the caller's resource remains untouched.
391		unsafe { rollback_allocation(allocator, &mut allocation) }
392			.expect("preflighted allocation rollback");
393		return Err(error);
394	}
395	Ok(allocation)
396}
397
398/// Allocate for a caller-created unbound ordinary buffer without creating, binding or destroying it.
399///
400/// Queries exact legacy/core/KHR requirements and dedication. Imports carrying
401/// resource-specific ImportMemoryInfo instead use those admitted external
402/// requirements without an otherwise illegal pre-bind native query. Policy must use
403/// `MemoryUsage::Explicit`; creation usage is unavailable from the opaque handle.
404/// BDA-enabled allocators flag separate buffer storage conservatively, following VMA.
405/// Allocation/map failure retains the caller's resource. Release unbound storage
406/// with `free`; bind it explicitly with `bind_buffer_memory` when ready.
407/// # Errors
408/// Rejects null/already-associated resources, unsupported policy, identity/type/
409/// limit failures or preserves the native allocation/map/query error.
410/// # Safety
411/// Buffer must be live, unbound, from this device and remain so until binding or
412/// allocation release. Supply ordinary non-sparse,
413/// non-capture/replay resource semantics; admitted import chains retain their
414/// exact external metadata/handle obligations. Address usage requires enabled core/KHR
415/// BDA metadata. Protected storage requires enabled features and explicit flags.
416/// The caller owns resource lifetime, callbacks and synchronization.
417pub unsafe fn allocate_for_buffer(
418	allocator: &mut Allocator<'_>,
419	buffer: vk::Buffer,
420	options: &AllocationCreateInfo,
421) -> Result<Allocation, AllocError> {
422	// SAFETY: identical native resource/ownership contract.
423	unsafe { allocate_existing(allocator, Resource::Buffer(buffer), options) }
424}
425
426/// Allocate for a caller-created unbound ordinary image; no creation, binding or destruction.
427///
428/// Unknown tiling conflicts with every native class, including another unknown
429/// image, on a granularity page. Explicit memory flags are required. Native
430/// requirements/dedication, pool limits, persistent mapping and rollback share
431/// the managed-resource implementation. Resource-specific import metadata
432/// replaces pre-bind queries where native external-memory rules forbid them. Failure retains the caller's handle.
433/// # Errors
434/// Same checked policy, identity/type/limit and native errors as `allocate_for_buffer`.
435/// # Safety
436/// Image must be live, unbound and from this device. Sparse, disjoint, DRM-modifier,
437/// implicit aliasing and unsupported allocation capabilities are excluded. Ordinary
438/// external allocation profiles retain caller-owned native compatibility obligations.
439/// Protected storage requires captured enablement and explicit required flags.
440/// The caller owns resource lifetime, callbacks, valid usage and synchronization.
441pub unsafe fn allocate_for_image(
442	allocator: &mut Allocator<'_>,
443	image: vk::Image,
444	options: &AllocationCreateInfo,
445) -> Result<Allocation, AllocError> {
446	// SAFETY: identical native resource/ownership contract.
447	unsafe { allocate_existing(allocator, Resource::Image(image), options) }
448}
449
450struct BindChain {
451	status: Option<*mut vk::Result>,
452	plane: Option<vk::ImageAspectFlagBits>,
453}
454
455#[inline]
456unsafe fn binding_status(next: *const std::ffi::c_void) -> Result<BindChain, AllocError> {
457	if next.is_null() {
458		return Ok(BindChain {
459			status: None,
460			plane: None,
461		});
462	}
463	// SAFETY: caller supplies the same complete readable/writable chain records.
464	unsafe { nonempty_binding_status(next) }
465}
466
467#[inline(never)]
468unsafe fn nonempty_binding_status(
469	mut next: *const std::ffi::c_void,
470) -> Result<BindChain, AllocError> {
471	let mut seen = [std::ptr::null(); 64];
472	let mut count = 0;
473	let mut status = None;
474	let mut plane = None;
475	let mut tags = [vk::StructureType::from_raw(0); 64];
476	while !next.is_null() {
477		if count == seen.len() || seen[..count].contains(&next) {
478			return Err(AllocError::InvalidRequest("cyclic/excessive bind chain"));
479		}
480		seen[count] = next;
481		// SAFETY: caller supplies readable native headers and correctly tagged complete records.
482		let node = unsafe { &*next.cast::<vk::BaseInStructure>() };
483		if tags[..count].contains(&node.s_type) {
484			return Err(AllocError::InvalidRequest("duplicate bind chain record"));
485		}
486		tags[count] = node.s_type;
487		count += 1;
488		if node.s_type == vk::StructureType::BIND_IMAGE_PLANE_MEMORY_INFO {
489			// SAFETY: caller supplies a complete correctly tagged native record.
490			let aspect = unsafe { (*next.cast::<vk::BindImagePlaneMemoryInfo>()).plane_aspect };
491			use crate::ImageAspectFlagBits as A;
492			if !matches!(aspect, A::PLANE_0_BIT | A::PLANE_1_BIT | A::PLANE_2_BIT) {
493				return Err(AllocError::UnsupportedCapability(
494					"ordinary disjoint image plane aspect required",
495				));
496			}
497			plane = Some(aspect);
498		}
499		if node.s_type == vk::StructureType::BIND_MEMORY_STATUS {
500			// SAFETY: caller supplies the complete native status record matching the tag.
501			let pointer = unsafe { (*next.cast::<vk::BindMemoryStatus>()).p_result };
502			if status.is_some() || pointer.is_null() {
503				return Err(AllocError::InvalidRequest(
504					"duplicate/null bind status output",
505				));
506			}
507			status = Some(pointer);
508		}
509		next = node.p_next.cast();
510	}
511	Ok(BindChain { status, plane })
512}
513
514/// Bind one ordinary buffer with a checked allocation-local offset and borrowed native chain.
515///
516/// Uses exact core 1.1 or enabled KHR bind_memory2. Associated resource allocations
517/// require offset zero; raw allocations re-query type/size/alignment. Any supplied
518/// per-bind status must report success before bound state is published.
519/// # Errors
520/// Reports state/range/capability, malformed chain, per-item status or native errors.
521/// # Safety
522/// Buffer must be live, ordinary and unbound on this device. Chain records and
523/// outputs must be valid and enabled for this exact native call; caller owns their
524/// extents/lifetimes and native error/device-loss recovery. No aliasing is admitted.
525pub unsafe fn bind_buffer_memory2(
526	allocator: &mut Allocator<'_>,
527	buffer: vk::Buffer,
528	allocation: &Allocation,
529	local_offset: u64,
530	next: *const std::ffi::c_void,
531) -> Result<(), AllocError> {
532	// SAFETY: caller establishes canonical native binding/chain guarantees.
533	unsafe {
534		bind_resource(
535			allocator,
536			Resource::Buffer(buffer),
537			allocation,
538			local_offset,
539			next,
540			true,
541			false,
542		)
543	}
544}
545
546/// Bind an ordinary image or one disjoint plane with an allocation-local offset and native chain.
547/// A plane uses unassociated raw storage allocated from caller-queried plane
548/// requirements. The allocator re-queries that exact plane before binding and
549/// tracks each plane independently; DRM modifier plane aspects remain excluded.
550/// # Errors
551/// Same checked state/range/chain, per-item and native errors as `bind_buffer_memory2`.
552/// # Safety
553/// Same native chain/lifetime/completion contract as `bind_buffer_memory2`; image
554/// semantics must meet native valid usage for the selected plane. Destroy a
555/// disjoint image once, then free all its plane allocations; do not use the
556/// ordinary paired `destroy_image` helper for a plane allocation.
557pub unsafe fn bind_image_memory2(
558	allocator: &mut Allocator<'_>,
559	image: vk::Image,
560	allocation: &Allocation,
561	local_offset: u64,
562	next: *const std::ffi::c_void,
563) -> Result<(), AllocError> {
564	// SAFETY: caller establishes canonical native image binding/chain guarantees.
565	unsafe {
566		bind_resource(
567			allocator,
568			Resource::Image(image),
569			allocation,
570			local_offset,
571			next,
572			true,
573			false,
574		)
575	}
576}
577
578pub(super) unsafe fn bind_alias(
579	allocator: &mut Allocator<'_>,
580	resource: Resource,
581	allocation: &Allocation,
582	local_offset: u64,
583) -> Result<(), AllocError> {
584	// SAFETY: caller establishes native alias/resource valid usage; backing storage is explicitly aliasable.
585	unsafe {
586		bind_resource(
587			allocator,
588			resource,
589			allocation,
590			local_offset,
591			std::ptr::null(),
592			false,
593			true,
594		)
595	}
596}
597
598unsafe fn bind_resource(
599	allocator: &mut Allocator<'_>,
600	resource: Resource,
601	allocation: &Allocation,
602	local_offset: u64,
603	next: *const std::ffi::c_void,
604	extended: bool,
605	alias: bool,
606) -> Result<(), AllocError> {
607	// SAFETY: canonical operation checks and borrowed native-chain contract.
608	let operation = unsafe {
609		prepare_bind(
610			allocator,
611			resource,
612			allocation,
613			local_offset,
614			next,
615			extended,
616			alias,
617		)
618	}?;
619	// The exclusive ordinary owner and synchronized backing leases share this
620	// exact execution/publication route. No registry guard is needed by execute.
621	let backing = allocator.mapping(allocation);
622	// SAFETY: caller owns this native resource and meets bind synchronization.
623	unsafe { operation.execute(allocator.device.as_device()) }?;
624	drop(backing);
625	operation.commit(allocator)
626}
627
628/// Complete binding for a resource claimed and published by
629/// `allocate_prepared_resource` under the same exclusive borrow. Publication
630/// already recorded the exact resource association, so identity, claim,
631/// reservation and chain stages are not repeated; the backing lease, native
632/// call and publication commit keep the canonical operation order. Failure
633/// leaves the claim and unpublished allocation to the caller's rollback.
634/// SAFETY: `allocation` is the fresh result for this exact `resource`, created
635/// unbound within this borrow on a preflighted legacy bind route; the caller
636/// owns the native resource and meets bind synchronization.
637pub(super) unsafe fn bind_prepared(
638	allocator: &mut Allocator<'_>,
639	resource: Resource,
640	allocation: &Allocation,
641) -> Result<(), AllocError> {
642	let operation = BindOperation {
643		route: RequirementsRoute::Legacy,
644		requirements_route: None,
645		resource,
646		serial: allocation.serial,
647		handle: allocation.handle,
648		native_offset: allocation.offset,
649		remaining_size: allocation.size,
650		memory_type_index: allocation.memory_type_index,
651		next: std::ptr::null(),
652		status: None,
653		plane: None,
654		alias: false,
655		claim: None,
656	};
657	let backing = allocator.mapping(allocation);
658	// SAFETY: identical exclusive execution/publication route as `bind_resource`.
659	unsafe { operation.execute(allocator.device.as_device()) }?;
660	drop(backing);
661	operation.commit(allocator)
662}
663
664pub(super) struct BindOperation<'device> {
665	route: RequirementsRoute,
666	requirements_route: Option<RequirementsRoute>,
667	resource: Resource,
668	serial: u64,
669	handle: vk::DeviceMemory,
670	native_offset: u64,
671	remaining_size: u64,
672	memory_type_index: u32,
673	next: *const std::ffi::c_void,
674	status: Option<*mut vk::Result>,
675	pub(super) plane: Option<vk::ImageAspectFlagBits>,
676	alias: bool,
677	claim: Option<parallel::resource::Claim<'device>>,
678}
679
680pub(super) unsafe fn prepare_bind<'device>(
681	allocator: &mut Allocator<'device>,
682	resource: Resource,
683	allocation: &Allocation,
684	local_offset: u64,
685	next: *const std::ffi::c_void,
686	extended: bool,
687	alias: bool,
688) -> Result<BindOperation<'device>, AllocError> {
689	let route = if !extended {
690		RequirementsRoute::Legacy
691	} else {
692		let capabilities =
693			allocator
694				.device
695				.as_device()
696				.capabilities()
697				.ok_or(AllocError::UnsupportedCapability(
698					"extended binding requires creation metadata",
699				))?;
700		if capabilities.api_version() >= vk::API_VERSION_1_1 {
701			RequirementsRoute::Core
702		} else if super::super::resource::extension(
703			allocator.device.as_device(),
704			c"VK_KHR_bind_memory2",
705		) {
706			RequirementsRoute::Khr
707		} else {
708			return Err(AllocError::UnsupportedCapability(
709				"bind_memory2 is not enabled",
710			));
711		}
712	};
713	// SAFETY: caller supplies valid readable/writable native bind-chain records for this call.
714	let BindChain { status, plane } = unsafe { binding_status(next) }?;
715	let live = allocator.validate(allocation)?;
716	if live.native_users != 0 {
717		return Err(AllocError::InvalidRequest("native operation in progress"));
718	}
719	if plane.is_some() {
720		if !extended || !matches!(resource, Resource::Image(_)) || live.resource.is_some() || alias {
721			return Err(AllocError::InvalidRequest(
722				"plane binding requires unassociated raw storage and image bind2",
723			));
724		}
725		if !allocator
726			.device
727			.as_device()
728			.capabilities()
729			.is_some_and(|caps| caps.api_version() >= vk::API_VERSION_1_1)
730			&& !super::super::resource::extension(
731				allocator.device.as_device(),
732				c"VK_KHR_sampler_ycbcr_conversion",
733			) {
734			return Err(AllocError::UnsupportedCapability(
735				"disjoint plane binding is not enabled",
736			));
737		}
738	}
739	if resource.is_null() {
740		return Err(AllocError::InvalidRequest("null resource"));
741	}
742	if alias && !live.can_alias {
743		return Err(AllocError::UnsupportedCapability(
744			"allocation was not created for aliasing",
745		));
746	}
747	if !alias && live.bound {
748		return Err(AllocError::InvalidRequest("allocation already bound"));
749	}
750	if !alias && live.resource.is_some_and(|target| target != resource) {
751		return Err(AllocError::WrongResource);
752	}
753	let raw = alias || live.resource.is_none();
754	// Snapshot authoritative live metadata once. Binding needs neither a public
755	// token reconstruction nor a scan for its block size/host mapping properties.
756	let (handle, offset, size, memory_type_index) =
757		(live.handle, live.offset, live.size, live.memory_type_index);
758	// Partitioned children cover bound resources through the shared owner claim
759	// below; the local index always covers this allocator's own pending binds.
760	if raw && allocator.associated.conflicts(resource, plane) {
761		return Err(AllocError::WrongResource);
762	}
763	allocator.associated.try_reserve_for(resource)?;
764	let claim = parallel::claim(
765		allocator,
766		resource,
767		plane,
768		(!raw).then_some(allocation.serial),
769	)?;
770	let fp = allocator.device.fp();
771	match (resource, route) {
772		(Resource::Buffer(_), RequirementsRoute::Legacy) => {
773			require(fp.bind_buffer_memory.is_some(), "vkBindBufferMemory")?
774		}
775		(Resource::Image(_), RequirementsRoute::Legacy) => {
776			require(fp.bind_image_memory.is_some(), "vkBindImageMemory")?
777		}
778		(Resource::Buffer(_), RequirementsRoute::Core) => {
779			require(fp.bind_buffer_memory2.is_some(), "vkBindBufferMemory2")?
780		}
781		(Resource::Image(_), RequirementsRoute::Core) => {
782			require(fp.bind_image_memory2.is_some(), "vkBindImageMemory2")?
783		}
784		(Resource::Buffer(_), RequirementsRoute::Khr) => require(
785			fp.bind_buffer_memory2_khr.is_some(),
786			"vkBindBufferMemory2KHR",
787		)?,
788		(Resource::Image(_), RequirementsRoute::Khr) => {
789			require(fp.bind_image_memory2_khr.is_some(), "vkBindImageMemory2KHR")?
790		}
791	}
792	let native_offset = offset
793		.checked_add(local_offset)
794		.ok_or(AllocError::InvalidRange)?;
795	if local_offset > size || (!raw && local_offset != 0) {
796		return Err(AllocError::InvalidRange);
797	}
798	let requirements_route = if raw {
799		Some(allocator.requirements_route(matches!(resource, Resource::Image(_)))?)
800	} else {
801		None
802	};
803	Ok(BindOperation {
804		route,
805		requirements_route,
806		resource,
807		serial: allocation.serial,
808		handle,
809		native_offset,
810		remaining_size: size - local_offset,
811		memory_type_index,
812		next,
813		status,
814		plane,
815		alias,
816		claim,
817	})
818}
819
820impl BindOperation<'_> {
821	pub(super) unsafe fn execute(&self, device: &vk::Device) -> Result<(), AllocError> {
822		let (route, resource, handle, native_offset, next, status, plane) = (
823			self.route,
824			self.resource,
825			self.handle,
826			self.native_offset,
827			self.next,
828			self.status,
829			self.plane,
830		);
831
832		if let Some(requirements_route) = self.requirements_route {
833			// SAFETY: caller guarantees the live ordinary resource and native identity.
834			let requirements =
835				unsafe { query_requirements(device, requirements_route, resource, plane) }?;
836			if requirements.dedication == Dedication::Required {
837				return Err(AllocError::UnsupportedCapability(
838					"raw allocation cannot satisfy required resource dedication",
839				));
840			}
841			if requirements.memory.memory_type_bits & (1 << self.memory_type_index) == 0 {
842				return Err(AllocError::NoCompatibleMemoryType);
843			}
844			if requirements.memory.size > self.remaining_size
845				|| !native_offset.is_multiple_of(requirements.memory.alignment)
846			{
847				return Err(AllocError::InvalidRange);
848			}
849		}
850		if let Some(status) = status {
851			// SAFETY: validated writable output pointer from caller-owned live bind chain.
852			unsafe {
853				*status = vk::Result::NOT_READY;
854			}
855		}
856		// SAFETY: one matching ordinary resource, checked storage offset/extent, admitted exact PFN and live caller chain.
857		unsafe {
858			match resource {
859				Resource::Buffer(buffer) => {
860					let info = vk::BindBufferMemoryInfo {
861						buffer,
862						memory: handle,
863						memory_offset: native_offset,
864						p_next: next,
865						..Default::default()
866					};
867					match route {
868						RequirementsRoute::Legacy => {
869							device.bind_buffer_memory(buffer, handle, native_offset)?
870						}
871						RequirementsRoute::Core => device.bind_buffer_memory2(&[info])?,
872						RequirementsRoute::Khr => device.bind_buffer_memory2_khr(&[info])?,
873					}
874				}
875				Resource::Image(image) => {
876					let info = vk::BindImageMemoryInfo {
877						image,
878						memory: handle,
879						memory_offset: native_offset,
880						p_next: next,
881						..Default::default()
882					};
883					match route {
884						RequirementsRoute::Legacy => device.bind_image_memory(image, handle, native_offset)?,
885						RequirementsRoute::Core => device.bind_image_memory2(&[info])?,
886						RequirementsRoute::Khr => device.bind_image_memory2_khr(&[info])?,
887					}
888				}
889			}
890		}
891		if let Some(status) = status {
892			// SAFETY: caller-owned status storage remains live after the synchronous native call.
893			let result = unsafe { *status };
894			if result != vk::Result::SUCCESS {
895				return Err(
896					if result.as_raw() < 0 {
897						CallError::Vulkan(result)
898					} else {
899						CallError::InvalidOutput("native binding did not publish success status")
900					}
901					.into(),
902				);
903			}
904		}
905		Ok(())
906	}
907
908	pub(super) fn commit(self, allocator: &mut Allocator<'_>) -> Result<(), AllocError> {
909		if self.alias {
910			return Ok(());
911		}
912		let live = allocator
913			.live
914			.get_mut(&self.serial)
915			.expect("validated allocation");
916		let already_associated = live.resource == Some(self.resource) && live.plane == self.plane;
917		live.resource = Some(self.resource);
918		live.plane = self.plane;
919		live.bound = true;
920		if let Some(claim) = self.claim {
921			claim.commit(self.serial);
922		} else if !already_associated {
923			allocator
924				.associated
925				.record(self.resource, self.plane, self.serial);
926		}
927		Ok(())
928	}
929}
930
931/// Bind at the allocation's actual native offset. Failed binds retain allocation and resource for retry.
932///
933/// Allocations for a resource accept only that target. Raw allocations
934/// query size/type/alignment and reject required dedication. Repeated library
935/// binding and aliasing are rejected. No pointer lookup or resource destruction occurs.
936/// # Errors
937/// Returns identity/state/association, requirement/type/range or native bind errors.
938/// # Safety
939/// Buffer must be live, unbound, ordinary and from this device. Meet allocation/
940/// feature/lifetime and external host synchronization valid usage. Native calls
941/// performed outside this allocator remain caller-owned state; do not rebind them.
942pub unsafe fn bind_buffer_memory(
943	allocator: &mut Allocator<'_>,
944	buffer: vk::Buffer,
945	allocation: &Allocation,
946) -> Result<(), AllocError> {
947	// SAFETY: identical native binding contract.
948	unsafe {
949		bind_resource(
950			allocator,
951			Resource::Buffer(buffer),
952			allocation,
953			0,
954			std::ptr::null(),
955			false,
956			false,
957		)
958	}
959}
960
961/// Bind a live ordinary image at its allocation's native offset; failure retains both objects.
962/// # Errors
963/// Same checked identity/association/requirement/type/range and native errors as buffer binding.
964/// # Safety
965/// Image must be live, unbound, non-disjoint/non-sparse/non-external,
966/// non-aliased and from this device. Meet native feature and synchronization valid
967/// usage. Never infer linear pixel layout from mapping optimal/unknown image storage.
968pub unsafe fn bind_image_memory(
969	allocator: &mut Allocator<'_>,
970	image: vk::Image,
971	allocation: &Allocation,
972) -> Result<(), AllocError> {
973	// SAFETY: identical native binding contract.
974	unsafe {
975		bind_resource(
976			allocator,
977			Resource::Image(image),
978			allocation,
979			0,
980			std::ptr::null(),
981			false,
982			false,
983		)
984	}
985}
986
987impl Allocator<'_> {
988	/// Bind an ordinary buffer with explicit local offset and borrowed native bind chain.
989	/// # Safety
990	/// Same native chain/resource contract as `mem::bind_buffer_memory2`.
991	pub unsafe fn bind_buffer_memory2(
992		&mut self,
993		buffer: vk::Buffer,
994		allocation: &Allocation,
995		local_offset: u64,
996		next: *const std::ffi::c_void,
997	) -> Result<(), AllocError> {
998		// SAFETY: caller supplies canonical binding/chain guarantees.
999		unsafe { bind_buffer_memory2(self, buffer, allocation, local_offset, next) }
1000	}
1001
1002	/// Bind an ordinary image with explicit local offset and borrowed native bind chain.
1003	/// # Safety
1004	/// Same native chain/resource contract as `mem::bind_image_memory2`.
1005	pub unsafe fn bind_image_memory2(
1006		&mut self,
1007		image: vk::Image,
1008		allocation: &Allocation,
1009		local_offset: u64,
1010		next: *const std::ffi::c_void,
1011	) -> Result<(), AllocError> {
1012		// SAFETY: caller supplies canonical image binding/chain guarantees.
1013		unsafe { bind_image_memory2(self, image, allocation, local_offset, next) }
1014	}
1015	/// Allocate for an existing unbound ordinary buffer; retains caller ownership on error.
1016	/// # Safety
1017	/// Same resource/feature/callback/lifetime contract as `mem::allocate_for_buffer`.
1018	pub unsafe fn allocate_for_buffer(
1019		&mut self,
1020		buffer: vk::Buffer,
1021		options: &AllocationCreateInfo,
1022	) -> Result<Allocation, AllocError> {
1023		// SAFETY: identical native contract to the canonical operation.
1024		unsafe { allocate_for_buffer(self, buffer, options) }
1025	}
1026
1027	/// Allocate for an existing ordinary image using conservative unknown-tiling placement.
1028	/// # Safety
1029	/// Same resource/feature/callback/lifetime contract as `mem::allocate_for_image`.
1030	pub unsafe fn allocate_for_image(
1031		&mut self,
1032		image: vk::Image,
1033		options: &AllocationCreateInfo,
1034	) -> Result<Allocation, AllocError> {
1035		// SAFETY: identical native contract to the canonical operation.
1036		unsafe { allocate_for_image(self, image, options) }
1037	}
1038
1039	/// Bind a matching buffer/allocation pair; failure retains both for explicit retry.
1040	/// # Safety
1041	/// Same native unbound-resource and synchronization contract as `mem::bind_buffer_memory`.
1042	pub unsafe fn bind_buffer_memory(
1043		&mut self,
1044		buffer: vk::Buffer,
1045		allocation: &Allocation,
1046	) -> Result<(), AllocError> {
1047		// SAFETY: identical native contract to the canonical operation.
1048		unsafe { bind_buffer_memory(self, buffer, allocation) }
1049	}
1050
1051	/// Bind a matching ordinary image/allocation pair; no implicit destruction on failure.
1052	/// # Safety
1053	/// Same native image/lifetime/synchronization contract as `mem::bind_image_memory`.
1054	pub unsafe fn bind_image_memory(
1055		&mut self,
1056		image: vk::Image,
1057		allocation: &Allocation,
1058	) -> Result<(), AllocError> {
1059		// SAFETY: identical native contract to the canonical operation.
1060		unsafe { bind_image_memory(self, image, allocation) }
1061	}
1062}
1063
1064#[cfg(test)]
1065#[path = "../../../test/rs/mem/test_associated.rs"]
1066mod tests;