use core::ffi::c_void;
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex, RwLock};
use std::time::SystemTime;
use windows::Win32::Graphics::Direct3D::ID3DBlob;
use windows::Win32::Graphics::Direct3D11::*;
use windows::core::{HRESULT, Interface, PCSTR};
use locum_capture::rules::{self, Action};
use crate::d3d11::shaders::{self, Stage};
use crate::d3d11::stages::{self, CbBinding};
use crate::log::{folder, log};
use crate::system::{export, load_system_dll};
static ACTIVE: AtomicBool = AtomicBool::new(false);
pub fn active() -> bool {
ACTIVE.load(Ordering::Relaxed)
}
const STAGES: [Stage; 6] = [
Stage::Vertex,
Stage::Hull,
Stage::Domain,
Stage::Geometry,
Stage::Pixel,
Stage::Compute,
];
struct Rule {
rule: rules::Rule,
stage: Stage,
applied: AtomicU64,
made: Mutex<Option<Option<ID3D11DeviceChild>>>,
values: Mutex<Option<Option<ID3D11Buffer>>>,
scratch: Mutex<HashMap<u32, ID3D11Buffer>>,
}
struct Rules {
rules: Vec<Rule>,
loaded: Option<SystemTime>,
}
static RULES: RwLock<Option<Arc<Rules>>> = RwLock::new(None);
fn current() -> Option<Arc<Rules>> {
RULES.read().unwrap_or_else(|e| e.into_inner()).clone()
}
pub fn poll(frame: u64) {
if frame.is_multiple_of(600)
&& let Some(rules) = current()
{
for r in &rules.rules {
log!(
"override {:?}: applied {} times",
r.rule.text,
r.applied.load(Ordering::Relaxed)
);
}
}
if frame.is_multiple_of(30) {
reload();
}
}
pub fn reload() {
let Some(dir) = folder() else { return };
let path = dir.join("overrides.txt");
let modified = std::fs::metadata(&path).and_then(|m| m.modified()).ok();
let loaded = current().and_then(|r| r.loaded);
if modified == loaded {
return;
}
let rules = match (modified, std::fs::read_to_string(&path)) {
(Some(_), Ok(text)) => load(&text, &dir),
_ => Vec::new(),
};
log!("overrides: {} rules from {}", rules.len(), path.display());
ACTIVE.store(!rules.is_empty(), Ordering::Relaxed);
*RULES.write().unwrap_or_else(|e| e.into_inner()) = Some(Arc::new(Rules {
rules,
loaded: modified,
}));
}
fn load(text: &str, dir: &std::path::Path) -> Vec<Rule> {
let (parsed, errors) = rules::parse_all(text, dir, &rules::hex);
for (line, e) in errors {
log!("overrides.txt line {line}: {e}: ignored");
}
parsed
.into_iter()
.map(|rule| Rule {
stage: STAGES[rule.stage],
rule,
applied: AtomicU64::new(0),
made: Mutex::new(None),
values: Mutex::new(None),
scratch: Mutex::new(HashMap::new()),
})
.collect()
}
struct Bound<'a> {
c: &'a ID3D11DeviceContext4,
hashes: [Option<Option<u64>>; 6],
}
impl Bound<'_> {
fn hash(&mut self, stage: Stage) -> Option<u64> {
*self.hashes[stage as usize].get_or_insert_with(|| {
let shader = unsafe { stages::shader(self.c, stage) }?;
unsafe { shaders::hash_of(shader.as_raw()) }
})
}
fn holds(&mut self, rule: &Rule) -> bool {
rule.rule.holds(|stage| self.hash(STAGES[stage]).map(u128::from))
}
}
enum Undo {
Shader(Stage, Option<ID3D11DeviceChild>),
Cb(Stage, u32, CbBinding),
}
pub unsafe fn around(
c: &ID3D11DeviceContext4,
compute: bool,
mut applied: impl FnMut(&str),
draw: impl FnOnce(),
) {
let Some(rules) = current() else { return draw() };
let mut bound = Bound { c, hashes: [None; 6] };
let applies = |r: &Rule| r.rule.applies(compute);
if rules.rules.iter().any(|r| {
matches!(r.rule.action, Action::Skip) && applies(r) && bound.holds(r) && {
r.applied.fetch_add(1, Ordering::Relaxed);
applied(&r.rule.text);
true
}
}) {
return;
}
let mut undo = Vec::new();
let mut replaced = [false; 6];
for r in &rules.rules {
if matches!(r.rule.action, Action::Skip) || !applies(r) || !bound.holds(r) {
continue;
}
match &r.rule.action {
Action::Solid(_) | Action::Shader(_) if !replaced[r.stage as usize] => {
let Some(shader) = (unsafe { replacement(c, r) }) else {
continue;
};
replaced[r.stage as usize] = true;
undo.push(Undo::Shader(r.stage, unsafe { stages::shader(c, r.stage) }));
unsafe { stages::set_shader(c, r.stage, Some(&shader)) };
}
Action::Constant { slot, index, bits } => {
let Some(original) = (unsafe { patched(c, r, *slot, *index, bits) }) else {
continue;
};
undo.push(Undo::Cb(r.stage, *slot, original));
}
_ => continue,
}
r.applied.fetch_add(1, Ordering::Relaxed);
applied(&r.rule.text);
}
draw();
for u in undo.into_iter().rev() {
match u {
Undo::Shader(stage, shader) => unsafe { stages::set_shader(c, stage, shader.as_ref()) },
Undo::Cb(stage, slot, binding) => unsafe {
stages::set_constant_buffer(c, stage, slot, &binding)
},
}
}
}
unsafe fn replacement(c: &ID3D11DeviceContext4, r: &Rule) -> Option<ID3D11DeviceChild> {
let mut made = r.made.lock().unwrap_or_else(|e| e.into_inner());
made.get_or_insert_with(|| {
let device = unsafe { c.GetDevice() }.ok()?;
let code = match &r.rule.action {
Action::Solid(colour) => solid_ps(colour)?,
Action::Shader(code) => code.clone(),
_ => return None,
};
match unsafe { stages::create_shader(&device, r.stage, &code) } {
Ok(s) => Some(s),
Err(e) => {
log!("override {:?}: the shader could not be made: {e}", r.rule.text);
None
}
}
})
.clone()
}
unsafe fn patched(
c: &ID3D11DeviceContext4,
r: &Rule,
slot: u32,
index: u32,
bits: &[u32; 4],
) -> Option<CbBinding> {
let original = unsafe { stages::constant_buffers(c, r.stage) }
.into_iter()
.nth(slot as usize)?;
let source = original.buffer.clone()?;
let mut desc = D3D11_BUFFER_DESC::default();
unsafe { source.GetDesc(&mut desc) };
let at = (original.first + index) * 16;
if at + 16 > desc.ByteWidth {
return None;
}
let device = unsafe { c.GetDevice() }.ok()?;
let values = r
.values
.lock()
.unwrap_or_else(|e| e.into_inner())
.get_or_insert_with(|| unsafe { buffer(&device, 16, Some(bits)) })
.clone()?;
let scratch = {
let mut scratch = r.scratch.lock().unwrap_or_else(|e| e.into_inner());
match scratch.get(&desc.ByteWidth) {
Some(b) => b.clone(),
None => {
let b = unsafe { buffer(&device, desc.ByteWidth, None) }?;
scratch.insert(desc.ByteWidth, b.clone());
b
}
}
};
let whole = D3D11_BOX {
left: 0,
top: 0,
front: 0,
right: desc.ByteWidth,
bottom: 1,
back: 1,
};
let one = D3D11_BOX {
left: 0,
top: 0,
front: 0,
right: 16,
bottom: 1,
back: 1,
};
unsafe {
c.CopySubresourceRegion(&scratch, 0, 0, 0, 0, &source, 0, Some(&whole));
c.CopySubresourceRegion(&scratch, 0, at, 0, 0, &values, 0, Some(&one));
let binding = CbBinding {
buffer: Some(scratch),
first: original.first,
count: original.count,
};
stages::set_constant_buffer(c, r.stage, slot, &binding);
}
Some(original)
}
unsafe fn buffer(device: &ID3D11Device, bytes: u32, bits: Option<&[u32; 4]>) -> Option<ID3D11Buffer> {
let desc = D3D11_BUFFER_DESC {
ByteWidth: bytes,
Usage: D3D11_USAGE_DEFAULT,
BindFlags: D3D11_BIND_CONSTANT_BUFFER.0 as u32,
..Default::default()
};
let init = bits.map(|b| D3D11_SUBRESOURCE_DATA {
pSysMem: b.as_ptr().cast(),
..Default::default()
});
let mut out = None;
unsafe { device.CreateBuffer(&desc, init.as_ref().map(|i| i as *const _), Some(&mut out)) }.ok()?;
out
}
type CompileFn = unsafe extern "system" fn(
*const c_void,
usize,
PCSTR,
*const c_void,
*mut c_void,
PCSTR,
PCSTR,
u32,
u32,
*mut *mut c_void,
*mut *mut c_void,
) -> HRESULT;
fn solid_ps(colour: &[f32; 4]) -> Option<Vec<u8>> {
let source = rules::solid_source(colour);
let module = load_system_dll("d3dcompiler_47.dll")?;
let compile = unsafe { export::<CompileFn>(module, c"D3DCompile") }?;
let (mut code, mut errors) = (core::ptr::null_mut(), core::ptr::null_mut());
let hr = unsafe {
compile(
source.as_ptr().cast(),
source.len(),
PCSTR(c"locum-solid".as_ptr().cast()),
core::ptr::null(),
core::ptr::null_mut(),
PCSTR(c"main".as_ptr().cast()),
PCSTR(c"ps_5_0".as_ptr().cast()),
0,
0,
&mut code,
&mut errors,
)
};
let blob = |p: *mut c_void| (!p.is_null()).then(|| unsafe { ID3DBlob::from_raw(p) });
let bytes = |b: &ID3DBlob| unsafe {
core::slice::from_raw_parts(b.GetBufferPointer() as *const u8, b.GetBufferSize()).to_vec()
};
if let Some(e) = blob(errors) {
log!("solid shader: {}", String::from_utf8_lossy(&bytes(&e)));
}
let code = blob(code);
if hr.is_err() {
log!("solid shader did not compile: {hr:?}");
return None;
}
code.map(|c| bytes(&c))
}