oxihuman_viewer/
gpu_readback.rs1#![allow(dead_code)]
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9#[allow(dead_code)]
10pub enum ReadbackFormat {
11 Rgba8Unorm,
12 Rgba16Float,
13 R32Float,
14 Depth32Float,
15}
16
17#[derive(Debug, Clone)]
19#[allow(dead_code)]
20pub struct ReadbackRequest {
21 pub format: ReadbackFormat,
22 pub width: u32,
23 pub height: u32,
24 pub offset: u64,
26}
27
28#[derive(Debug, Clone)]
30#[allow(dead_code)]
31pub struct ReadbackResult {
32 pub format: ReadbackFormat,
33 pub width: u32,
34 pub height: u32,
35 pub data: Vec<u8>,
36}
37
38#[allow(dead_code)]
40pub fn bytes_per_pixel(fmt: ReadbackFormat) -> usize {
41 match fmt {
42 ReadbackFormat::Rgba8Unorm => 4,
43 ReadbackFormat::Rgba16Float => 8,
44 ReadbackFormat::R32Float => 4,
45 ReadbackFormat::Depth32Float => 4,
46 }
47}
48
49#[allow(dead_code)]
51pub fn staging_buffer_size(req: &ReadbackRequest) -> u64 {
52 let bpp = bytes_per_pixel(req.format) as u64;
53 bpp * req.width as u64 * req.height as u64
54}
55
56#[allow(dead_code)]
58pub fn blank_result(fmt: ReadbackFormat, width: u32, height: u32) -> ReadbackResult {
59 let size = bytes_per_pixel(fmt) * width as usize * height as usize;
60 ReadbackResult {
61 format: fmt,
62 width,
63 height,
64 data: vec![0u8; size],
65 }
66}
67
68#[allow(dead_code)]
70pub fn rgba8_to_f32(data: &[u8], pixel_index: usize) -> [f32; 4] {
71 let base = pixel_index * 4;
72 [
73 data[base] as f32 / 255.0,
74 data[base + 1] as f32 / 255.0,
75 data[base + 2] as f32 / 255.0,
76 data[base + 3] as f32 / 255.0,
77 ]
78}
79
80#[allow(dead_code)]
82pub fn r32_to_f32(data: &[u8], pixel_index: usize) -> f32 {
83 let base = pixel_index * 4;
84 let bytes: [u8; 4] = data[base..base + 4].try_into().unwrap_or([0; 4]);
85 f32::from_le_bytes(bytes)
86}
87
88#[allow(dead_code)]
90pub fn validate_result(res: &ReadbackResult) -> bool {
91 let expected = bytes_per_pixel(res.format) * res.width as usize * res.height as usize;
92 res.data.len() == expected
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 #[test]
100 fn bytes_per_pixel_rgba8() {
101 assert_eq!(bytes_per_pixel(ReadbackFormat::Rgba8Unorm), 4);
102 }
103
104 #[test]
105 fn bytes_per_pixel_rgba16() {
106 assert_eq!(bytes_per_pixel(ReadbackFormat::Rgba16Float), 8);
107 }
108
109 #[test]
110 fn staging_buffer_size_correct() {
111 let req = ReadbackRequest {
112 format: ReadbackFormat::Rgba8Unorm,
113 width: 4,
114 height: 4,
115 offset: 0,
116 };
117 assert_eq!(staging_buffer_size(&req), 64);
118 }
119
120 #[test]
121 fn blank_result_correct_size() {
122 let r = blank_result(ReadbackFormat::Rgba8Unorm, 2, 2);
123 assert!(validate_result(&r));
124 }
125
126 #[test]
127 fn rgba8_to_f32_white() {
128 let data = vec![255u8; 4];
129 let px = rgba8_to_f32(&data, 0);
130 assert!((px[0] - 1.0).abs() < 1e-3);
131 }
132
133 #[test]
134 fn rgba8_to_f32_black() {
135 let data = vec![0u8; 8];
136 let px = rgba8_to_f32(&data, 0);
137 assert_eq!(px[0], 0.0);
138 }
139
140 #[test]
141 fn r32_to_f32_roundtrip() {
142 let val = std::f32::consts::PI;
143 let bytes = val.to_le_bytes();
144 let data: Vec<u8> = bytes.to_vec();
145 assert!((r32_to_f32(&data, 0) - val).abs() < 1e-6);
146 }
147
148 #[test]
149 fn validate_result_correct() {
150 let r = blank_result(ReadbackFormat::R32Float, 3, 3);
151 assert!(validate_result(&r));
152 }
153
154 #[test]
155 fn validate_result_wrong_size() {
156 let mut r = blank_result(ReadbackFormat::Rgba8Unorm, 2, 2);
157 r.data.pop();
158 assert!(!validate_result(&r));
159 }
160}