use std::cell::RefCell;
use std::collections::HashMap;
use yog_abi::YogStr;
use yog_sync::Mutex;
use yog_sync::atomic::{AtomicU64, Ordering};
use crate::gpu::{
gfx_buf_data, gfx_buf_subdata, gfx_clear_scissor, gfx_draw_arrays, gfx_draw_elements,
gfx_prog_uniform_1f, gfx_prog_uniform_1i, gfx_prog_uniform_2f, gfx_prog_uniform_3f,
gfx_prog_uniform_4f, gfx_prog_uniform_mat4, gfx_set_blend, gfx_set_depth, gfx_set_scissor,
gfx_set_viewport, gfx_tex_bind,
};
enum Cmd {
BufData {
handle: u32,
data: Vec<u8>,
dynamic: bool,
},
BufSubdata {
handle: u32,
offset: u32,
data: Vec<u8>,
},
Uniform1i {
prog: u32,
name: String,
v: i32,
},
Uniform1f {
prog: u32,
name: String,
v: f32,
},
Uniform2f {
prog: u32,
name: String,
v: [f32; 2],
},
Uniform3f {
prog: u32,
name: String,
v: [f32; 3],
},
Uniform4f {
prog: u32,
name: String,
v: [f32; 4],
},
UniformMat4 {
prog: u32,
name: String,
m: [f32; 16],
},
TexBind {
unit: u32,
handle: u32,
},
DrawArrays {
vao: u32,
prog: u32,
mode: u8,
first: u32,
count: u32,
},
DrawElements {
vao: u32,
ebo: u32,
prog: u32,
mode: u8,
count: u32,
u32_idx: bool,
},
Blend {
enabled: bool,
src: u32,
dst: u32,
},
Depth {
test: bool,
write: bool,
},
Scissor {
x: i32,
y: i32,
w: i32,
h: i32,
},
ClearScissor,
Viewport {
x: i32,
y: i32,
w: i32,
h: i32,
},
}
thread_local! {
static RECORDING: RefCell<Option<Vec<Cmd>>> = const { RefCell::new(None) };
}
static RECORDED: Mutex<Option<HashMap<u64, Vec<Cmd>>>> = Mutex::new(None);
static NEXT: AtomicU64 = AtomicU64::new(1);
pub(crate) fn start() {
RECORDING.with(|r| *r.borrow_mut() = Some(Vec::new()));
}
pub(crate) fn finish() -> u64 {
let Some(cmds) = RECORDING.with(|r| r.borrow_mut().take()) else {
return 0;
};
if !cmds
.iter()
.any(|c| matches!(c, Cmd::DrawArrays { .. } | Cmd::DrawElements { .. }))
{
return 0;
}
let id = NEXT.fetch_add(1, Ordering::Relaxed);
RECORDED
.lock()
.get_or_insert_with(HashMap::new)
.insert(id, cmds);
id
}
pub(crate) fn replay(id: u64) {
let Some(cmds) = RECORDED.lock().as_mut().and_then(|m| m.remove(&id)) else {
return;
};
let s = |name: &String| YogStr::from_str(name);
for c in &cmds {
unsafe {
match c {
Cmd::BufData {
handle,
data,
dynamic,
} => gfx_buf_data(*handle, data.as_ptr(), data.len() as u32, *dynamic),
Cmd::BufSubdata {
handle,
offset,
data,
} => gfx_buf_subdata(*handle, *offset, data.as_ptr(), data.len() as u32),
Cmd::Uniform1i { prog, name, v } => gfx_prog_uniform_1i(*prog, s(name), *v),
Cmd::Uniform1f { prog, name, v } => gfx_prog_uniform_1f(*prog, s(name), *v),
Cmd::Uniform2f { prog, name, v } => gfx_prog_uniform_2f(*prog, s(name), v[0], v[1]),
Cmd::Uniform3f { prog, name, v } => {
gfx_prog_uniform_3f(*prog, s(name), v[0], v[1], v[2])
}
Cmd::Uniform4f { prog, name, v } => {
gfx_prog_uniform_4f(*prog, s(name), v[0], v[1], v[2], v[3])
}
Cmd::UniformMat4 { prog, name, m } => {
gfx_prog_uniform_mat4(*prog, s(name), m.as_ptr())
}
Cmd::TexBind { unit, handle } => gfx_tex_bind(*unit, *handle),
Cmd::DrawArrays {
vao,
prog,
mode,
first,
count,
} => gfx_draw_arrays(*vao, *prog, *mode, *first, *count),
Cmd::DrawElements {
vao,
ebo,
prog,
mode,
count,
u32_idx,
} => gfx_draw_elements(*vao, *ebo, *prog, *mode, *count, *u32_idx),
Cmd::Blend { enabled, src, dst } => gfx_set_blend(*enabled, *src, *dst),
Cmd::Depth { test, write } => gfx_set_depth(*test, *write),
Cmd::Scissor { x, y, w, h } => gfx_set_scissor(*x, *y, *w, *h),
Cmd::ClearScissor => gfx_clear_scissor(),
Cmd::Viewport { x, y, w, h } => gfx_set_viewport(*x, *y, *w, *h),
}
}
}
}
fn kept(cmd: impl FnOnce() -> Cmd) -> bool {
RECORDING.with(|r| match r.borrow_mut().as_mut() {
Some(cmds) => {
cmds.push(cmd());
true
}
None => false,
})
}
unsafe fn owned(s: YogStr) -> String {
unsafe { s.as_str() }.to_owned()
}
unsafe fn floats<const N: usize>(p: *const f32) -> [f32; N] {
unsafe { std::slice::from_raw_parts(p, N) }
.try_into()
.unwrap_or([0.0; N])
}
unsafe fn bytes(p: *const u8, len: u32) -> Vec<u8> {
unsafe { std::slice::from_raw_parts(p, len as usize) }.to_vec()
}
pub(crate) unsafe extern "C" fn rec_buf_data(handle: u32, p: *const u8, len: u32, dynamic: bool) {
if !kept(|| Cmd::BufData {
handle,
data: unsafe { bytes(p, len) },
dynamic,
}) {
unsafe { gfx_buf_data(handle, p, len, dynamic) }
}
}
pub(crate) unsafe extern "C" fn rec_buf_subdata(handle: u32, offset: u32, p: *const u8, len: u32) {
if !kept(|| Cmd::BufSubdata {
handle,
offset,
data: unsafe { bytes(p, len) },
}) {
unsafe { gfx_buf_subdata(handle, offset, p, len) }
}
}
pub(crate) unsafe extern "C" fn rec_uniform_1i(prog: u32, name: YogStr, v: i32) {
if !kept(|| Cmd::Uniform1i {
prog,
name: unsafe { owned(name) },
v,
}) {
unsafe { gfx_prog_uniform_1i(prog, name, v) }
}
}
pub(crate) unsafe extern "C" fn rec_uniform_1f(prog: u32, name: YogStr, v: f32) {
if !kept(|| Cmd::Uniform1f {
prog,
name: unsafe { owned(name) },
v,
}) {
unsafe { gfx_prog_uniform_1f(prog, name, v) }
}
}
pub(crate) unsafe extern "C" fn rec_uniform_2f(prog: u32, name: YogStr, x: f32, y: f32) {
if !kept(|| Cmd::Uniform2f {
prog,
name: unsafe { owned(name) },
v: [x, y],
}) {
unsafe { gfx_prog_uniform_2f(prog, name, x, y) }
}
}
pub(crate) unsafe extern "C" fn rec_uniform_3f(prog: u32, name: YogStr, x: f32, y: f32, z: f32) {
if !kept(|| Cmd::Uniform3f {
prog,
name: unsafe { owned(name) },
v: [x, y, z],
}) {
unsafe { gfx_prog_uniform_3f(prog, name, x, y, z) }
}
}
pub(crate) unsafe extern "C" fn rec_uniform_4f(
prog: u32,
name: YogStr,
x: f32,
y: f32,
z: f32,
w: f32,
) {
if !kept(|| Cmd::Uniform4f {
prog,
name: unsafe { owned(name) },
v: [x, y, z, w],
}) {
unsafe { gfx_prog_uniform_4f(prog, name, x, y, z, w) }
}
}
pub(crate) unsafe extern "C" fn rec_uniform_mat4(prog: u32, name: YogStr, m: *const f32) {
if !kept(|| Cmd::UniformMat4 {
prog,
name: unsafe { owned(name) },
m: unsafe { floats(m) },
}) {
unsafe { gfx_prog_uniform_mat4(prog, name, m) }
}
}
pub(crate) unsafe extern "C" fn rec_tex_bind(unit: u32, handle: u32) {
if !kept(|| Cmd::TexBind { unit, handle }) {
unsafe { gfx_tex_bind(unit, handle) }
}
}
pub(crate) unsafe extern "C" fn rec_draw_arrays(
vao: u32,
prog: u32,
mode: u8,
first: u32,
count: u32,
) {
if !kept(|| Cmd::DrawArrays {
vao,
prog,
mode,
first,
count,
}) {
unsafe { gfx_draw_arrays(vao, prog, mode, first, count) }
}
}
pub(crate) unsafe extern "C" fn rec_draw_elements(
vao: u32,
ebo: u32,
prog: u32,
mode: u8,
count: u32,
u32_idx: bool,
) {
if !kept(|| Cmd::DrawElements {
vao,
ebo,
prog,
mode,
count,
u32_idx,
}) {
unsafe { gfx_draw_elements(vao, ebo, prog, mode, count, u32_idx) }
}
}
pub(crate) unsafe extern "C" fn rec_set_blend(enabled: bool, src: u32, dst: u32) {
if !kept(|| Cmd::Blend { enabled, src, dst }) {
unsafe { gfx_set_blend(enabled, src, dst) }
}
}
pub(crate) unsafe extern "C" fn rec_set_depth(test: bool, write: bool) {
if !kept(|| Cmd::Depth { test, write }) {
unsafe { gfx_set_depth(test, write) }
}
}
pub(crate) unsafe extern "C" fn rec_set_scissor(x: i32, y: i32, w: i32, h: i32) {
if !kept(|| Cmd::Scissor { x, y, w, h }) {
unsafe { gfx_set_scissor(x, y, w, h) }
}
}
pub(crate) unsafe extern "C" fn rec_clear_scissor() {
if !kept(|| Cmd::ClearScissor) {
unsafe { gfx_clear_scissor() }
}
}
pub(crate) unsafe extern "C" fn rec_set_viewport(x: i32, y: i32, w: i32, h: i32) {
if !kept(|| Cmd::Viewport { x, y, w, h }) {
unsafe { gfx_set_viewport(x, y, w, h) }
}
}