use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use locum_capture::tiles::{Layout, Pages};
use serde_json::Value;
use windows::Win32::Foundation::RECT;
use windows::Win32::Graphics::Direct3D12::*;
use super::D3d12;
use crate::open::Capture;
#[derive(Clone, Copy, Debug)]
pub(super) enum Placement {
Committed,
Placed {
heap: i64,
offset: u64,
},
Reserved,
}
impl Placement {
pub(super) fn of(d: &Value) -> Placement {
match d["heap"].as_i64().filter(|h| *h != 0) {
Some(heap) => Placement::Placed {
heap,
offset: d["heap_offset"].as_u64().unwrap_or(0),
},
None if !d["heap_properties"].is_object() => Placement::Reserved,
None => Placement::Committed,
}
}
}
struct Mapping {
cpu_seq: i64,
call: String,
resource: i64,
source: Option<i64>,
args: Value,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub(super) struct Span {
pub(super) start: u64,
pub(super) end: Option<u64>,
pub(super) bytes: u64,
pub(super) alignment: u64,
}
impl Span {
fn game_end(&self) -> u64 {
self.end.unwrap_or(self.start + self.bytes)
}
}
#[derive(Clone, Debug, PartialEq)]
pub(super) struct Relocation {
points: Vec<(u64, u64)>,
pub(super) size: u64,
}
impl Relocation {
pub(super) fn new(spans: &[Span], size: u64, alignment: u64) -> Relocation {
let mut starts: HashMap<u64, u64> = HashMap::new();
let mut ends: HashMap<u64, Vec<(u64, u64)>> = HashMap::new();
let mut points = BTreeSet::from([0, size]);
for s in spans.iter().filter(|s| s.game_end() > s.start) {
let a = starts.entry(s.start).or_insert(1);
*a = (*a).max(s.alignment);
ends.entry(s.game_end()).or_default().push((s.start, s.bytes));
points.insert(s.start);
points.insert(s.game_end());
}
let mut out: Vec<(u64, u64)> = Vec::with_capacity(points.len());
let mut here: HashMap<u64, u64> = HashMap::new();
for p in points {
let mut at = out.last().map_or(p, |&(q, r)| r + (p - q));
for &(start, bytes) in ends.get(&p).into_iter().flatten() {
at = at.max(here[&start] + bytes);
}
let at = at.next_multiple_of(starts.get(&p).copied().unwrap_or(1).max(1));
here.insert(p, at);
out.push((p, at));
}
let end = out.last().map_or(0, |&(_, r)| r);
Relocation {
points: out,
size: end.next_multiple_of(alignment.max(1)),
}
}
pub(super) fn at(&self, game: u64) -> u64 {
match self.points.partition_point(|&(p, _)| p <= game) {
0 => game,
i => {
let (p, r) = self.points[i - 1];
r + (game - p)
}
}
}
pub(super) fn tile(&self, tile: u32) -> u32 {
let tile_bytes = locum_capture::tiles::TILE_BYTES;
(self.at(u64::from(tile) * tile_bytes) / tile_bytes) as u32
}
pub(super) fn moved(&self) -> bool {
self.points.iter().any(|(p, r)| p != r)
}
pub(super) fn lost(&self, spans: &[Span]) -> usize {
let mut sorted: Vec<&Span> = spans.iter().filter(|s| s.game_end() > s.start).collect();
sorted.sort_by_key(|s| s.start);
let mut lost = 0;
for (i, a) in sorted.iter().enumerate() {
let at = self.at(a.start);
for b in &sorted[i + 1..] {
if b.start >= a.game_end() {
break;
}
let bt = self.at(b.start);
if bt >= at + a.bytes || at >= bt + b.bytes {
lost += 1;
}
}
}
lost
}
}
fn allocation(
device: &ID3D12Device,
desc: &D3D12_RESOURCE_DESC,
) -> Option<(D3D12_RESOURCE_DESC, D3D12_RESOURCE_ALLOCATION_INFO)> {
let info = unsafe { device.GetResourceAllocationInfo(0, &[*desc]) };
if info.SizeInBytes != u64::MAX {
return Some((*desc, info));
}
let default = D3D12_RESOURCE_DESC {
Alignment: 0,
..*desc
};
let info = unsafe { device.GetResourceAllocationInfo(0, &[default]) };
(desc.Alignment != 0 && info.SizeInBytes != u64::MAX).then_some((default, info))
}
#[derive(Default)]
pub(super) struct Memory {
heaps: HashMap<i64, Result<(ID3D12Heap, std::rc::Rc<Relocation>), String>>,
relocated: usize,
moved: usize,
lost: usize,
pub(super) shared: HashSet<i64>,
pending: HashMap<i64, Vec<(i64, String)>>,
waiting: HashMap<i64, i64>,
blobs: Option<locum_capture::blobs::Blobs>,
mappings: Option<Vec<Mapping>>,
reached: usize,
pub(super) placed: usize,
pub(super) reserved: usize,
committed: BTreeMap<String, usize>,
inferred: usize,
pub(super) remaps: usize,
pub(super) not_remapped: usize,
}
impl Memory {
pub(super) fn warnings(&self) -> Vec<String> {
let mut out: Vec<String> = self
.committed
.iter()
.map(|(why, n)| {
format!("{n} placed or reserved resources were made committed, memory the game shared not shared: {why}")
})
.collect();
if self.inferred > 0 {
out.push(format!(
"{} heaps the capture does not describe (captured before heaps were described) were made from what \
the resources in them record: their heap properties and flags, and a size that holds each at its \
offset on this device",
self.inferred
));
}
if self.not_remapped > 0 {
out.push(format!(
"the frame's {} changes to the tile mappings of reserved resources made committed are not run",
self.not_remapped
));
}
if self.lost > 0 {
out.push(format!(
"{} pairs of resources that shared memory in the game do not here: this device takes more of their \
heaps for one of them than the game's did, past where the other starts",
self.lost
));
}
out
}
pub(super) fn notes(&self) -> Vec<String> {
if self.relocated == 0 {
return Vec::new();
}
vec![format!(
"{} heaps were laid out again for this device, which takes more of them or another alignment for some of \
what is placed in them than the game's did: {} placed resources and mapped tiles are at other offsets, \
each sharing memory with what it shared in the game{}",
self.relocated,
self.moved,
if self.lost > 0 {
" but for the pairs above"
} else {
""
}
)]
}
pub(super) fn fell_back(&mut self, why: String) {
*self.committed.entry(why).or_default() += 1;
}
}
fn heap_properties(p: &Value) -> D3D12_HEAP_PROPERTIES {
let n = |v: &Value| v.as_i64().unwrap_or(0) as i32;
D3D12_HEAP_PROPERTIES {
Type: D3D12_HEAP_TYPE(n(&p["type"])),
CPUPageProperty: D3D12_CPU_PAGE_PROPERTY(n(&p["cpu_page"])),
MemoryPoolPreference: D3D12_MEMORY_POOL(n(&p["memory_pool"])),
..Default::default()
}
}
fn heap_flags(v: &Value) -> D3D12_HEAP_FLAGS {
const KEPT: i32 = 0x4 | 0x40 | 0x80 | 0x400;
D3D12_HEAP_FLAGS(v.as_i64().unwrap_or(0) as i32 & KEPT)
}
fn page_tables(c: &Capture) -> Vec<(i64, Pages)> {
let mut tables: Vec<(i64, String)> = Vec::new();
for table in ["d12_tile_pages", "d12_tile_mappings"] {
if c.has_table(table) {
tables.extend(c.rows(
&format!("SELECT resource, pages_json FROM {table} WHERE pages_json IS NOT NULL"),
[],
|r| Ok((r.get(0)?, r.get(1)?)),
));
}
}
tables
.into_iter()
.filter_map(|(id, json)| {
let pages = serde_json::from_str(&json)
.ok()
.and_then(|v| Pages::from_runs(&v))?;
Some((id, pages))
})
.collect()
}
fn ranges_here(
relocation: &Relocation,
flags: Option<&[D3D12_TILE_RANGE_FLAGS]>,
at: &[u32],
counts: &[u32],
) -> (Vec<D3D12_TILE_RANGE_FLAGS>, Vec<u32>, Vec<u32>) {
let mut out: Vec<(D3D12_TILE_RANGE_FLAGS, u32, u32)> = Vec::new();
for (i, (&start, &count)) in at.iter().zip(counts).enumerate() {
let flag = flags
.and_then(|f| f.get(i).copied())
.unwrap_or(D3D12_TILE_RANGE_FLAG_NONE);
if flag.0 & (D3D12_TILE_RANGE_FLAG_NULL.0 | D3D12_TILE_RANGE_FLAG_SKIP.0) != 0 {
out.push((flag, start, count));
} else if flag.0 & D3D12_TILE_RANGE_FLAG_REUSE_SINGLE_TILE.0 != 0
|| (0..count).all(|k| relocation.tile(start + k) == relocation.tile(start) + k)
{
out.push((flag, relocation.tile(start), count));
} else {
out.extend((0..count).map(|k| (flag, relocation.tile(start + k), 1)));
}
}
let f = out.iter().map(|r| r.0).collect();
let o = out.iter().map(|r| r.1).collect();
let t = out.iter().map(|r| r.2).collect();
(f, o, t)
}
fn coordinate(v: &Value) -> D3D12_TILED_RESOURCE_COORDINATE {
let n = |i: usize| v[i].as_u64().unwrap_or(0) as u32;
D3D12_TILED_RESOURCE_COORDINATE {
X: n(0),
Y: n(1),
Z: n(2),
Subresource: n(3),
}
}
fn size(v: &Value) -> D3D12_TILE_REGION_SIZE {
let n = |i: usize| v[i].as_u64().unwrap_or(0) as u32;
D3D12_TILE_REGION_SIZE {
NumTiles: n(0),
UseBox: v[1].as_bool().unwrap_or(false).into(),
Width: n(2),
Height: n(3) as u16,
Depth: n(4) as u16,
}
}
fn array<T>(v: &Value, f: impl Fn(&Value) -> T) -> Option<Vec<T>> {
v.as_array().map(|v| v.iter().map(f).collect())
}
impl D3d12 {
fn game_heap(&mut self, c: &Capture, id: i64) -> Result<(ID3D12Heap, std::rc::Rc<Relocation>), String> {
if !self.memory.heaps.contains_key(&id) {
let made = self.heap_desc(c, id).and_then(|(mut desc, inferred)| {
let spans = self.spans(c, id);
let widest = spans.iter().map(|s| s.alignment).max().unwrap_or(0);
desc.Alignment = desc.Alignment.max(widest).max(64 << 10);
let relocation = Relocation::new(&spans, desc.SizeInBytes, desc.Alignment);
let moved = spans.iter().filter(|s| relocation.at(s.start) != s.start).count();
let lost = relocation.lost(&spans);
desc.SizeInBytes = relocation.size;
let mut heap: Option<ID3D12Heap> = None;
unsafe { self.device.CreateHeap(&desc, &mut heap) }
.map_err(|e| format!("their heap could not be made on this device: {e}"))?;
let heap = heap.ok_or("CreateHeap gave no heap")?;
super::name(&heap, format!("#{id}"));
Ok((heap, relocation, inferred, moved, lost))
});
let made = made.map(|(heap, relocation, inferred, moved, lost)| {
self.memory.inferred += usize::from(inferred);
if relocation.moved() {
self.memory.relocated += 1;
self.memory.moved += moved;
self.memory.lost += lost;
}
(heap, std::rc::Rc::new(relocation))
});
self.memory.heaps.insert(id, made);
}
self.memory.heaps[&id].clone()
}
fn spans(&self, c: &Capture, id: i64) -> Vec<Span> {
let mut spans: Vec<Span> = c
.rows(
"SELECT description FROM d12_objects WHERE kind LIKE 'ID3D12Resource%' \
AND json_extract(description, '$.heap') = ?1",
[id],
|r| r.get::<_, String>(0),
)
.iter()
.filter_map(|d| serde_json::from_str::<Value>(d).ok())
.filter_map(|d| {
let (_, info) = allocation(&self.device, &super::resource_desc(&d))?;
let start = d["heap_offset"].as_u64().unwrap_or(0);
Some(Span {
start,
end: d["bytes"].as_u64().map(|b| start + b),
bytes: info.SizeInBytes,
alignment: info.Alignment,
})
})
.collect();
let tile = locum_capture::tiles::TILE_BYTES;
let tiles: BTreeSet<u32> = page_tables(c)
.iter()
.flat_map(|(_, pages)| pages.tiles.values())
.filter(|&&(heap, _)| heap as i64 == id)
.map(|&(_, at)| at)
.collect();
spans.extend(tiles.into_iter().map(|at| Span {
start: u64::from(at) * tile,
end: Some((u64::from(at) + 1) * tile),
bytes: tile,
alignment: tile,
}));
spans
}
fn heap_desc(&self, c: &Capture, id: i64) -> Result<(D3D12_HEAP_DESC, bool), String> {
let d: Value = c
.row(
"SELECT description FROM d12_objects WHERE id = ?1 AND kind LIKE 'ID3D12Heap%'",
[id],
|r| r.get::<_, String>(0),
)
.and_then(|d| serde_json::from_str(&d).ok())
.unwrap_or(Value::Null);
let Some(bytes) = d["bytes"].as_u64() else {
return self.inferred_heap(c, id).map(|desc| (desc, true));
};
let p = &d["heap_properties"];
Ok((
D3D12_HEAP_DESC {
SizeInBytes: bytes,
Properties: heap_properties(p),
Alignment: d["alignment"].as_u64().unwrap_or(0),
Flags: heap_flags(&d["flags"]),
},
false,
))
}
fn inferred_heap(&self, c: &Capture, id: i64) -> Result<D3D12_HEAP_DESC, String> {
const UNDESCRIBED: &str = "the capture does not describe their heap (captured before heaps were described), nor does any resource \
placed in it";
let placed: Vec<Value> = c
.rows(
"SELECT description FROM d12_objects WHERE kind LIKE 'ID3D12Resource%' \
AND json_extract(description, '$.heap') = ?1",
[id],
|r| r.get::<_, String>(0),
)
.iter()
.filter_map(|d| serde_json::from_str(d).ok())
.collect();
let said: BTreeSet<String> = placed.iter().map(|d| d["heap_properties"].to_string()).collect();
let p = match (placed.first(), said.len()) {
(Some(d), 1) if d["heap_properties"].is_object() => &d["heap_properties"],
(_, 0 | 1) => return Err(UNDESCRIBED.into()),
_ => {
return Err(
"the capture does not describe their heap, and the resources placed in it say different heap \
properties"
.into(),
)
}
};
let mut end = 0u64;
let mut multisampled = false;
for d in &placed {
let desc = super::resource_desc(d);
multisampled |= desc.SampleDesc.Count > 1;
let Some((_, info)) = allocation(&self.device, &desc) else {
continue;
};
let bytes = info.SizeInBytes.max(d["bytes"].as_u64().unwrap_or(0));
end = end.max(d["heap_offset"].as_u64().unwrap_or(0) + bytes);
}
for (_, pages) in page_tables(c) {
for &(heap, at) in pages.tiles.values() {
if heap as i64 == id {
end = end.max((u64::from(at) + 1) * locum_capture::tiles::TILE_BYTES);
}
}
}
let alignment: u64 = if multisampled { 4 << 20 } else { 64 << 10 };
Ok(D3D12_HEAP_DESC {
SizeInBytes: end.max(1).next_multiple_of(alignment),
Properties: heap_properties(p),
Alignment: alignment,
Flags: heap_flags(&p["flags"]),
})
}
pub(super) fn make_shared(
&mut self,
c: &Capture,
id: i64,
placement: Placement,
desc: &D3D12_RESOURCE_DESC,
state: D3D12_RESOURCE_STATES,
) -> Result<ID3D12Resource, String> {
let mut r: Option<ID3D12Resource> = None;
match placement {
Placement::Committed => return Err("committed".into()),
Placement::Placed { heap, offset } => {
let (h, relocation) = self.game_heap(c, heap)?;
let (desc, info) =
allocation(&self.device, desc).ok_or("this device refuses their descriptions")?;
let at = relocation.at(offset);
if info.Alignment == 0 || at % info.Alignment != 0 || at + info.SizeInBytes > relocation.size
{
return Err("their offsets do not fit their heaps on this device".into());
}
unsafe {
self.device
.CreatePlacedResource(&h, at, &desc, state, None, &mut r)
}
.map_err(|e| format!("CreatePlacedResource refused them: {e}"))?;
self.memory.placed += 1;
}
Placement::Reserved => {
if !c.has_table("d12_tile_pages") {
return Err(
"the capture holds no page table of them (captured before they were followed)".into(),
);
}
let (layout, pages): (String, String) = c
.row(
"SELECT layout_json, pages_json FROM d12_tile_pages WHERE resource = ?1 \
AND layout_json IS NOT NULL AND pages_json IS NOT NULL",
[id],
|r| Ok((r.get(0)?, r.get(1)?)),
)
.ok_or("the capture holds no page table of them")?;
let recorded = serde_json::from_str(&layout)
.ok()
.and_then(|v| Layout::from_json(&v))
.ok_or("their page tables do not read")?;
let pages = serde_json::from_str(&pages)
.ok()
.and_then(|v| Pages::from_runs(&v))
.ok_or("their page tables do not read")?;
unsafe { self.device.CreateReservedResource(desc, state, None, &mut r) }
.map_err(|e| format!("CreateReservedResource refused them: {e}"))?;
let resource = r.as_ref().ok_or("CreateReservedResource gave no resource")?;
let layout = locum_capture::tiles::d3d12(resource)?;
if layout != recorded {
return Err("this device tiles them otherwise than the game's".into());
}
let mut by_heap: BTreeMap<i64, (Vec<D3D12_TILED_RESOURCE_COORDINATE>, Vec<u32>)> =
BTreeMap::new();
for (&tile, &(heap, at)) in &pages.tiles {
let [x, y, z, sub] = layout.coordinate(tile)?;
let e = by_heap.entry(heap as i64).or_default();
e.0.push(D3D12_TILED_RESOURCE_COORDINATE {
X: x,
Y: y,
Z: z,
Subresource: sub,
});
e.1.push(at);
}
for (heap, (starts, offsets)) in by_heap {
let (h, relocation) = self.game_heap(c, heap)?;
let offsets: Vec<u32> = offsets.iter().map(|&at| relocation.tile(at)).collect();
let counts = vec![1u32; starts.len()];
unsafe {
self.queue.UpdateTileMappings(
resource,
starts.len() as u32,
Some(starts.as_ptr()),
None,
&h,
starts.len() as u32,
None,
Some(offsets.as_ptr()),
Some(counts.as_ptr()),
D3D12_TILE_MAPPING_FLAG_NONE,
)
};
}
self.memory.reserved += 1;
}
}
self.memory.shared.insert(id);
r.ok_or_else(|| "no resource made".into())
}
pub(super) fn defer(&mut self, c: &Capture, id: i64) -> bool {
let found: Option<(i64, String)> = c.row(
"SELECT f.submission, r.content FROM d12_first_use f JOIN resources r ON r.id = f.resource \
WHERE f.resource = ?1 AND r.content IS NOT NULL",
[id],
|r| Ok((r.get(0)?, r.get(1)?)),
);
let Some((submission, key)) = found else {
return false;
};
self.memory.pending.entry(submission).or_default().push((id, key));
self.memory.waiting.insert(id, submission);
self.memory.blobs.get_or_insert_with(|| c.blobs.clone());
true
}
fn waited(&self, key: &str) -> Result<Vec<u8>, String> {
let blobs = self.memory.blobs.as_ref().ok_or("no blob store")?;
blobs
.get(key)
.map_err(|e| format!("blob {key} is not in {} ({e})", blobs.root().display()))
}
pub(super) fn first_uses(&mut self, submission: i64) -> Result<(), String> {
for (id, key) in self.memory.pending.remove(&submission).unwrap_or_default() {
self.memory.waiting.remove(&id);
let bytes = self.waited(&key)?;
self.put(id, &bytes)?;
}
Ok(())
}
pub(super) fn first_use_now(&mut self, id: i64) -> Result<(), String> {
let Some(submission) = self.memory.waiting.remove(&id) else {
return Ok(());
};
let list = self.memory.pending.entry(submission).or_default();
let Some(i) = list.iter().position(|(r, _)| *r == id) else {
return Ok(());
};
let (_, key) = list.remove(i);
let bytes = self.waited(&key)?;
self.put(id, &bytes)
}
pub(super) fn remap_before(&mut self, c: &Capture, cpu_seq: i64) -> Result<(), String> {
let followed = c.has_table("d12_tile_mappings");
let mappings = self.memory.mappings.get_or_insert_with(|| {
if !followed {
return Vec::new();
}
c.rows(
"SELECT cpu_seq, call, resource, source, args_json FROM d12_tile_mappings ORDER BY cpu_seq, id",
[],
|r| {
Ok(Mapping {
cpu_seq: r.get(0)?,
call: r.get(1)?,
resource: r.get(2)?,
source: r.get(3)?,
args: r
.get::<_, Option<String>>(4)?
.and_then(|a| serde_json::from_str(&a).ok())
.unwrap_or(Value::Null),
})
},
)
});
let due: Vec<usize> = (self.memory.reached..mappings.len())
.take_while(|&i| mappings[i].cpu_seq < cpu_seq)
.collect();
for i in due {
self.memory.reached = i + 1;
let m = &self.memory.mappings.as_ref().expect("loaded")[i];
let (call, resource, source, a) = (m.call.clone(), m.resource, m.source, m.args.clone());
let shared = |id: i64| self.memory.shared.contains(&id) && self.resources.contains_key(&id);
if !shared(resource) || source.is_some_and(|s| !shared(s)) {
if self.resources.contains_key(&resource) {
self.memory.not_remapped += 1;
}
continue;
}
self.remap(c, &call, resource, source, &a)
.map_err(|e| format!("{call} of #{resource}: {e}"))?;
self.memory.remaps += 1;
}
Ok(())
}
fn remap(
&mut self,
c: &Capture,
call: &str,
resource: i64,
source: Option<i64>,
a: &Value,
) -> Result<(), String> {
let r = self.resource(resource)?;
let n = |v: &Value| v.as_u64().unwrap_or(0) as u32;
match call {
"UpdateTileMappings" => {
let heap = match a["heap"].as_i64().filter(|h| *h != 0) {
Some(h) => Some(self.game_heap(c, h)?),
None => None,
};
let starts = array(&a["starts"], coordinate);
let sizes = array(&a["sizes"], size);
let mut flags = array(&a["range_flags"], |v| D3D12_TILE_RANGE_FLAGS(n(v) as i32));
let mut offsets = array(&a["offsets"], n);
let mut ranges = n(&a["ranges"]);
let mut counts =
array(&a["counts"], n).or_else(|| (ranges > 1).then(|| vec![1; ranges as usize]));
if let (Some((_, relocation)), Some(at)) = (&heap, &offsets) {
let regions = n(&a["regions"]);
let tiles = counts.clone().unwrap_or_else(|| {
vec![
sizes
.as_ref()
.map_or(regions, |s| s.iter().map(|s| s.NumTiles).sum()),
]
});
let (f, o, t) = ranges_here(relocation, flags.as_deref(), at, &tiles);
ranges = o.len() as u32;
(flags, offsets, counts) = (Some(f), Some(o), Some(t));
}
let heap = heap.map(|(h, _)| h);
unsafe {
self.queue.UpdateTileMappings(
&r,
n(&a["regions"]),
starts.as_ref().map(|v| v.as_ptr()),
sizes.as_ref().map(|v| v.as_ptr()),
heap.as_ref(),
ranges,
flags.as_ref().map(|v| v.as_ptr()),
offsets.as_ref().map(|v| v.as_ptr()),
counts.as_ref().map(|v| v.as_ptr()),
D3D12_TILE_MAPPING_FLAGS(n(&a["flags"]) as i32),
)
};
}
"CopyTileMappings" => {
let s = self.resource(source.ok_or("no source")?)?;
unsafe {
self.queue.CopyTileMappings(
&r,
&coordinate(&a["dst_at"]),
&s,
&coordinate(&a["src_at"]),
&size(&a["size"]),
D3D12_TILE_MAPPING_FLAGS(n(&a["flags"]) as i32),
)
};
}
other => return Err(format!("unknown tile mapping call {other}")),
}
Ok(())
}
pub(super) fn aliasing(&mut self, a: &Value) {
let shared = |id: i64| {
self.memory
.shared
.contains(&id)
.then(|| self.resources.get(&id).map(|m| m.resource.clone()))
.flatten()
};
let barriers: Vec<D3D12_RESOURCE_BARRIER> = a[0]
.as_array()
.into_iter()
.flatten()
.filter(|b| b["type"].as_i64() == Some(D3D12_RESOURCE_BARRIER_TYPE_ALIASING.0 as i64))
.filter_map(|b| {
let p = &b["payload"];
let (before, after) = (
shared(p["before"].as_i64().unwrap_or(0)),
shared(p["after"].as_i64().unwrap_or(0)),
);
(before.is_some() || after.is_some()).then(|| D3D12_RESOURCE_BARRIER {
Type: D3D12_RESOURCE_BARRIER_TYPE_ALIASING,
Flags: D3D12_RESOURCE_BARRIER_FLAG_NONE,
Anonymous: D3D12_RESOURCE_BARRIER_0 {
Aliasing: std::mem::ManuallyDrop::new(D3D12_RESOURCE_ALIASING_BARRIER {
pResourceBefore: std::mem::ManuallyDrop::new(before),
pResourceAfter: std::mem::ManuallyDrop::new(after),
}),
},
})
})
.collect();
if barriers.is_empty() {
return;
}
unsafe { self.list.ResourceBarrier(&barriers) };
for mut b in barriers {
unsafe {
let a = &mut b.Anonymous.Aliasing;
std::mem::ManuallyDrop::drop(&mut a.pResourceBefore);
std::mem::ManuallyDrop::drop(&mut a.pResourceAfter);
}
}
}
pub(super) fn discard(&mut self, a: &Value) {
let id = a[0].as_i64().unwrap_or(0);
let Some(m) = self
.memory
.shared
.contains(&id)
.then(|| self.resources.get(&id))
.flatten()
else {
return;
};
let state = match m.desc.Flags.0 & 0x3 {
0x1 => 0x4,
0x2 => 0x10,
_ => return,
};
let (r, count) = (m.resource.clone(), m.extent.count());
let region = &a[1];
let rects: Vec<RECT> = super::rects(®ion["rects"]);
let (first, subs) = if region.is_object() {
(
region["first_subresource"].as_u64().unwrap_or(0) as u32,
region["subresources"].as_u64().unwrap_or(0) as u32,
)
} else {
(0, count)
};
let needs = [super::Need {
resource: id,
subs: (first..first + subs).collect(),
state,
}];
let d = D3D12_DISCARD_REGION {
NumRects: rects.len() as u32,
pRects: if rects.is_empty() {
std::ptr::null()
} else {
rects.as_ptr()
},
FirstSubresource: first,
NumSubresources: subs,
};
self.barriers(&needs, false);
unsafe { self.list.DiscardResource(&r, Some(&d)) };
self.barriers(&needs, true);
}
}
pub(super) fn aliases(c: &Capture) -> HashMap<i64, BTreeSet<i64>> {
let mut ranges: BTreeMap<i64, Vec<(u64, u64, i64)>> = BTreeMap::new();
for (id, d) in c.rows(
"SELECT id, description FROM d12_objects WHERE kind LIKE 'ID3D12Resource%' \
AND json_extract(description, '$.heap') != 0",
[],
|r| Ok((r.get::<_, i64>(0)?, r.get::<_, String>(1)?)),
) {
let Ok(d) = serde_json::from_str::<Value>(&d) else {
continue;
};
let (Some(heap), offset) = (d["heap"].as_i64(), d["heap_offset"].as_u64().unwrap_or(0)) else {
continue;
};
let bytes = d["bytes"]
.as_u64()
.or_else(|| crate::describe::shape(c, id).map(|s| locum_capture::layout::total(&s.subs())));
if let Some(bytes) = bytes.filter(|b| *b > 0) {
ranges.entry(heap).or_default().push((offset, offset + bytes, id));
}
}
let tile = locum_capture::tiles::TILE_BYTES;
let mut tiles: HashSet<(i64, u32, i64)> = HashSet::new();
for (id, pages) in page_tables(c) {
for &(heap, at) in pages.tiles.values() {
tiles.insert((heap as i64, at, id));
}
}
for (heap, at, id) in tiles {
let start = u64::from(at) * tile;
ranges.entry(heap).or_default().push((start, start + tile, id));
}
let mut out: HashMap<i64, BTreeSet<i64>> = HashMap::new();
for list in ranges.values_mut() {
list.sort_unstable();
for (i, &(_, end, a)) in list.iter().enumerate() {
for &(start, _, b) in &list[i + 1..] {
if start >= end {
break;
}
if a != b {
out.entry(a).or_default().insert(b);
out.entry(b).or_default().insert(a);
}
}
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
const K: u64 = 64 << 10;
fn span(start: u64, end: u64, bytes: u64, alignment: u64) -> Span {
Span {
start: start * K,
end: Some(end * K),
bytes: bytes * K,
alignment,
}
}
#[test]
fn a_heap_that_fits_is_the_games() {
let spans = [span(0, 2, 2, K), span(1, 3, 2, K), span(3, 4, 1, 4096)];
let r = Relocation::new(&spans, 4 * K, K);
assert!(!r.moved());
assert_eq!((r.at(K), r.at(3 * K), r.size), (K, 3 * K, 4 * K));
assert_eq!(r.lost(&spans), 0);
}
#[test]
fn what_takes_more_here_stretches_the_heap_and_keeps_what_shared_memory() {
let (a, b, c) = (span(0, 1, 2, K), span(0, 1, 1, K), span(1, 2, 1, K));
let r = Relocation::new(&[a, b, c], 2 * K, K);
assert!(r.moved());
assert_eq!((r.at(0), r.at(K), r.size), (0, 2 * K, 3 * K));
assert_eq!(r.lost(&[a, b, c]), 0);
assert_eq!(r.tile(1), 2);
}
#[test]
fn an_offset_this_device_cannot_align_to_moves_to_one_it_can() {
let d = Span {
start: 4096,
end: Some(8192),
bytes: 4096,
alignment: K,
};
let r = Relocation::new(&[d], K, K);
assert_eq!((r.at(4096), r.size), (K, 2 * K));
}
#[test]
fn sharing_that_cannot_be_kept_is_counted() {
let (c, a, b) = (span(0, 1, 3, K), span(0, 2, 2, K), span(1, 3, 2, K));
let r = Relocation::new(&[c, a, b], 3 * K, K);
assert_eq!(r.at(K), 3 * K);
assert_eq!(r.lost(&[c, a, b]), 1);
}
#[test]
fn tile_ranges_follow_their_tiles() {
let r = Relocation::new(&[span(0, 1, 2, K)], 4 * K, K);
let none = D3D12_TILE_RANGE_FLAG_NONE;
let (null, reuse) = (
D3D12_TILE_RANGE_FLAG_NULL,
D3D12_TILE_RANGE_FLAG_REUSE_SINGLE_TILE,
);
assert_eq!(
ranges_here(&r, None, &[0, 1], &[3, 2]),
(vec![none; 4], vec![0, 2, 3, 2], vec![1, 1, 1, 2])
);
assert_eq!(
ranges_here(&r, Some(&[null, reuse]), &[1, 1], &[4, 4]),
(vec![null, reuse], vec![1, 2], vec![4, 4])
);
}
}