use std::collections::HashMap;
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)
}
pub(crate) struct Residency {
set: Option<Set>,
capacity: usize,
wired: HashMap<usize, (Raw, usize)>,
wired_bytes: usize,
unwired: HashMap<usize, (Raw, 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(),
}
}
pub(crate) fn supported(&self) -> bool {
self.set.is_some()
}
pub(crate) fn wired(&self) -> usize {
self.wired_bytes
}
pub(crate) fn capacity(&self) -> usize {
self.capacity
}
pub(crate) fn insert(&mut self, raw: &Raw) {
let Some(set) = &self.set else { return };
let size = raw.allocatedSize();
if self.wired_bytes + size <= self.capacity {
set.addAllocation(allocation(raw));
set.commit();
set.requestResidency();
self.wired.insert(key(raw), (raw.clone(), size));
self.wired_bytes += size;
} else {
self.unwired.insert(key(raw), (raw.clone(), size));
}
}
pub(crate) fn erase(&mut self, raw: &Raw) {
let Some(set) = &self.set else { return };
if self.unwired.remove(&key(raw)).is_some() {
return;
}
if let Some((raw, size)) = self.wired.remove(&key(raw)) {
set.removeAllocation(allocation(&raw));
set.commit();
self.wired_bytes -= size;
}
}
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_bytes += size;
self.wired.insert(k, (raw, size));
}
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.wired_bytes -= size;
self.unwired.insert(k, (raw, size));
}
set.commit();
}
}
}