use std::collections::{HashMap, HashSet};
use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2::sel;
use objc2_foundation::NSObjectProtocol;
use objc2_metal::{
MTLAllocation, MTLCommandQueue, MTLDevice, MTLGPUFamily, MTLResidencySet,
MTLResidencySetDescriptor,
};
use super::allocator::Raw;
type Set = Retained<ProtocolObject<dyn MTLResidencySet>>;
fn key(raw: &Raw) -> usize {
Retained::as_ptr(raw).addr()
}
fn allocation(raw: &Raw) -> &ProtocolObject<dyn MTLAllocation> {
ProtocolObject::from_ref(&**raw)
}
pub(crate) fn attach(
device: &ProtocolObject<dyn MTLDevice>,
queue: &ProtocolObject<dyn MTLCommandQueue>,
) -> Option<Set> {
if !device.respondsToSelector(sel!(newResidencySetWithDescriptor:error:))
|| !device.supportsFamily(MTLGPUFamily::Metal3)
{
return None;
}
let set = device
.newResidencySetWithDescriptor_error(&MTLResidencySetDescriptor::new())
.ok()?;
queue.addResidencySet(&set);
Some(set)
}
fn count_wired(
mapped: &HashSet<usize>,
(total, of_mapped): (&mut usize, &mut usize),
k: usize,
size: usize,
added: bool,
) {
let part = if mapped.contains(&k) { size } else { 0 };
if added {
*total += size;
*of_mapped += part;
} else {
*total -= size;
*of_mapped -= part;
}
}
pub(crate) struct Residency {
set: Option<Set>,
capacity: usize,
wired: HashMap<usize, (Raw, usize)>,
wired_bytes: usize,
unwired: HashMap<usize, (Raw, usize)>,
mapped: HashSet<usize>,
wired_mapped_bytes: usize,
}
unsafe impl Send for Residency {}
impl Residency {
pub(crate) fn new(set: Option<Set>) -> Self {
Residency {
set,
capacity: 0,
wired: HashMap::new(),
wired_bytes: 0,
unwired: HashMap::new(),
mapped: HashSet::new(),
wired_mapped_bytes: 0,
}
}
pub(crate) fn supported(&self) -> bool {
self.set.is_some()
}
pub(crate) fn wired(&self) -> usize {
self.wired_bytes
}
pub(crate) fn wired_mapped(&self) -> usize {
self.wired_mapped_bytes
}
pub(crate) fn capacity(&self) -> usize {
self.capacity
}
pub(crate) fn insert(&mut self, raw: &Raw, mapped: bool) {
let Some(set) = &self.set else { return };
let size = raw.allocatedSize();
if mapped {
self.mapped.insert(key(raw));
}
if self.wired_bytes + size <= self.capacity {
set.addAllocation(allocation(raw));
set.commit();
set.requestResidency();
self.wired.insert(key(raw), (raw.clone(), size));
count_wired(
&self.mapped,
(&mut self.wired_bytes, &mut self.wired_mapped_bytes),
key(raw),
size,
true,
);
} else {
self.unwired.insert(key(raw), (raw.clone(), size));
}
}
pub(crate) fn erase(&mut self, raw: &Raw) {
if self.set.is_none() {
return;
}
let k = key(raw);
if self.unwired.remove(&k).is_none()
&& let Some((raw, size)) = self.wired.remove(&k)
{
let set = self.set.as_ref().expect("checked");
set.removeAllocation(allocation(&raw));
set.commit();
count_wired(
&self.mapped,
(&mut self.wired_bytes, &mut self.wired_mapped_bytes),
k,
size,
false,
);
}
self.mapped.remove(&k);
}
pub(crate) fn resize(&mut self, capacity: usize) {
self.capacity = capacity;
let Some(set) = &self.set else { return };
if self.wired_bytes < capacity {
let mut fitting = Vec::new();
let mut room = capacity - self.wired_bytes;
for (&k, &(_, size)) in &self.unwired {
if size <= room {
room -= size;
fitting.push(k);
}
}
if fitting.is_empty() {
return;
}
for k in fitting {
let (raw, size) = self.unwired.remove(&k).expect("listed above");
set.addAllocation(allocation(&raw));
self.wired.insert(k, (raw, size));
count_wired(
&self.mapped,
(&mut self.wired_bytes, &mut self.wired_mapped_bytes),
k,
size,
true,
);
}
set.commit();
set.requestResidency();
} else if self.wired_bytes > capacity {
let mut leaving = Vec::new();
let mut bytes = self.wired_bytes;
for (&k, &(_, size)) in &self.wired {
if bytes <= capacity {
break;
}
bytes -= size;
leaving.push(k);
}
for k in leaving {
let (raw, size) = self.wired.remove(&k).expect("listed above");
set.removeAllocation(allocation(&raw));
self.unwired.insert(k, (raw, size));
count_wired(
&self.mapped,
(&mut self.wired_bytes, &mut self.wired_mapped_bytes),
k,
size,
false,
);
}
set.commit();
}
}
}