BlueGit

golden-age paperdoll-renderer

Render live preview shows depth as written (no translation)

timfa Grok Bot · pushed by timfa

Authored 2026-11-07 · 6 days ago

Hash
4ad976cd1f789ac1238feec737cdd8307bf2b7e8
Parent
f928d455
2 files changed +102 −18
README.md +1 −1
@@ -51,7 +51,7 @@ Vanuit **Body** of de bodyweergave opent **2D-preview** de gerenderde compositor
51 51 - Kies **Man / Vrouw**. Alleen exportsets met dezelfde project-/rig-/exportcontract (`CompatKey`) zijn combineerbaar; equipment verschijnt als multiselect wanneer die contracten overeenkomen.
52 52 - Kies een clip uit de body-exportinventaris. **Speel / Pauze**, snelheid en export-fps sturen een looping frame-tijd (elapsed × fps); ontbrekende maps/frames tonen een diagnose in plaats van een stille vervanging.
53 53 - **Yaw − / Yaw +** wrappen over 0…330° (stap 30). **Pitch − / Pitch +** kiezen −60 / −30 / 0 / +30 / +60 met klemmen (start op 0). Hoekwijzigingen behouden de animatietijd.
54 - Kies weergavestand **Resultaat** (standaard; belichte compositie), **Alleen diffuse**, **Alleen normal map**, **Alleen depth map** of **Alleen specular map**. Mapstanden tonen per pixel de winnende laag van de dieptecompositie, onbelicht en precies zoals in de atlas opgeslagen, zonder decodering: diffuse, normal en specular als hun RGBA, depth als zijn opgeslagen kanalen (`gray` als grijs, `rgb_bands` en `rgb_packed` als ruwe RGB). 16-bit depth wordt op gewone weergaveprecisie (8 bit per kanaal, afgerond) getoond. Alleen Resultaat decodeert de diepte voor compositie en belichting. Playback, hoek en equipment werken in elke stand; licht alleen in Resultaat.
54 - Kies weergavestand **Resultaat** (standaard; belichte compositie), **Alleen diffuse**, **Alleen normal map**, **Alleen depth map** of **Alleen specular map**. Mapstanden tonen per pixel de winnende laag van de dieptecompositie, onbelicht en precies zoals in de atlas opgeslagen, zonder decodering: diffuse, normal en specular als hun RGBA, depth als zijn opgeslagen kanalen (`gray` als grijs, `rgb_bands` en `rgb_packed` als ruwe RGB). 16-bit depth wordt op gewone weergaveprecisie (8 bit per kanaal, afgerond) getoond. Alleen Resultaat decodeert de diepte voor compositie en belichting. Ook het live passbeeld tijdens het renderen toont de depth-map precies zoals hij geschreven wordt (zelfde weergaveregel). Playback, hoek en equipment werken in elke stand; licht alleen in Resultaat.
55 55 - Houd **Azimut / Elevatie** ingedrukt voor continue lichtrotatie in °/s (delta-time); azimut wrapt over 360° zonder klem, elevatie blijft −80…+80. De knoppen volgen de echte muisknopstatus, dus ze blijven draaien zolang je vasthoudt (ook langer dan een seconde). Loslaten, van de knop af bewegen, focusverlies of de pointer die het venster verlaat stopt de beweging. Belichting is alleen voor Resultaat; opgeslagen diffuse blijft unlit albedo.
56 56 - Playback loopt over exact `frames / fps` seconden, zodat ieder geëxporteerd frame (inclusief het laatste) even lang duurt (`1/fps`).
57 57 - Het samengestelde frame gebruikt nearest-neighbor weergave met uniforme schaal en gecentreerde letterboxing, gelijk aan de productie-passpreviews.
@@ -51,7 +51,7 @@ Vanuit **Body** of de bodyweergave opent **2D-preview** de gerenderde compositor
51- Kies **Man / Vrouw**. Alleen exportsets met dezelfde project-/rig-/exportcontract (`CompatKey`) zijn combineerbaar; equipment verschijnt als multiselect wanneer die contracten overeenkomen.
51- Kies **Man / Vrouw**. Alleen exportsets met dezelfde project-/rig-/exportcontract (`CompatKey`) zijn combineerbaar; equipment verschijnt als multiselect wanneer die contracten overeenkomen.
52- Kies een clip uit de body-exportinventaris. **Speel / Pauze**, snelheid en export-fps sturen een looping frame-tijd (elapsed × fps); ontbrekende maps/frames tonen een diagnose in plaats van een stille vervanging.
52- Kies een clip uit de body-exportinventaris. **Speel / Pauze**, snelheid en export-fps sturen een looping frame-tijd (elapsed × fps); ontbrekende maps/frames tonen een diagnose in plaats van een stille vervanging.
53- **Yaw − / Yaw +** wrappen over 0…330° (stap 30). **Pitch − / Pitch +** kiezen −60 / −30 / 0 / +30 / +60 met klemmen (start op 0). Hoekwijzigingen behouden de animatietijd.
53- **Yaw − / Yaw +** wrappen over 0…330° (stap 30). **Pitch − / Pitch +** kiezen −60 / −30 / 0 / +30 / +60 met klemmen (start op 0). Hoekwijzigingen behouden de animatietijd.
54- Kies weergavestand **Resultaat** (standaard; belichte compositie), **Alleen diffuse**, **Alleen normal map**, **Alleen depth map** of **Alleen specular map**. Mapstanden tonen per pixel de winnende laag van de dieptecompositie, onbelicht en precies zoals in de atlas opgeslagen, zonder decodering: diffuse, normal en specular als hun RGBA, depth als zijn opgeslagen kanalen (`gray` als grijs, `rgb_bands` en `rgb_packed` als ruwe RGB). 16-bit depth wordt op gewone weergaveprecisie (8 bit per kanaal, afgerond) getoond. Alleen Resultaat decodeert de diepte voor compositie en belichting. Playback, hoek en equipment werken in elke stand; licht alleen in Resultaat.
54- Kies weergavestand **Resultaat** (standaard; belichte compositie), **Alleen diffuse**, **Alleen normal map**, **Alleen depth map** of **Alleen specular map**. Mapstanden tonen per pixel de winnende laag van de dieptecompositie, onbelicht en precies zoals in de atlas opgeslagen, zonder decodering: diffuse, normal en specular als hun RGBA, depth als zijn opgeslagen kanalen (`gray` als grijs, `rgb_bands` en `rgb_packed` als ruwe RGB). 16-bit depth wordt op gewone weergaveprecisie (8 bit per kanaal, afgerond) getoond. Alleen Resultaat decodeert de diepte voor compositie en belichting. Ook het live passbeeld tijdens het renderen toont de depth-map precies zoals hij geschreven wordt (zelfde weergaveregel). Playback, hoek en equipment werken in elke stand; licht alleen in Resultaat.
55- Houd **Azimut / Elevatie** ingedrukt voor continue lichtrotatie in °/s (delta-time); azimut wrapt over 360° zonder klem, elevatie blijft −80…+80. De knoppen volgen de echte muisknopstatus, dus ze blijven draaien zolang je vasthoudt (ook langer dan een seconde). Loslaten, van de knop af bewegen, focusverlies of de pointer die het venster verlaat stopt de beweging. Belichting is alleen voor Resultaat; opgeslagen diffuse blijft unlit albedo.
55- Houd **Azimut / Elevatie** ingedrukt voor continue lichtrotatie in °/s (delta-time); azimut wrapt over 360° zonder klem, elevatie blijft −80…+80. De knoppen volgen de echte muisknopstatus, dus ze blijven draaien zolang je vasthoudt (ook langer dan een seconde). Loslaten, van de knop af bewegen, focusverlies of de pointer die het venster verlaat stopt de beweging. Belichting is alleen voor Resultaat; opgeslagen diffuse blijft unlit albedo.
56- Playback loopt over exact `frames / fps` seconden, zodat ieder geëxporteerd frame (inclusief het laatste) even lang duurt (`1/fps`).
56- Playback loopt over exact `frames / fps` seconden, zodat ieder geëxporteerd frame (inclusief het laatste) even lang duurt (`1/fps`).
57- Het samengestelde frame gebruikt nearest-neighbor weergave met uniforme schaal en gecentreerde letterboxing, gelijk aan de productie-passpreviews.
57- Het samengestelde frame gebruikt nearest-neighbor weergave met uniforme schaal en gecentreerde letterboxing, gelijk aan de productie-passpreviews.
src/production.rs +101 −17
@@ -66,7 +66,6 @@ pub struct Target {
66 66 pub depth: Vec<u16>,
67 67 /// Unquantized normalized depth t (0..1) per pixel, for the export encoding.
68 68 pub depth_t: Vec<f32>,
69 pub depth_preview: ImageBuf,
70 69 }
71 70 /// Content fingerprint of a source file used for a production job.
72 71 #[derive(Debug, Clone, PartialEq, Eq)]
@@ -851,6 +850,24 @@ vec3 encode_srgb(vec3 x){return mix(x*12.92,1.055*pow(max(x,vec3(0.0)),vec3(1.0/
851 850 void main(){vec3 n=normalize(v_normal);if(u_flat){n=normalize(cross(dFdx(v_view_position),dFdy(v_view_position)));if(!gl_FrontFacing)n=-n;}vec4 c=u_color*v_color;if(u_albedo_enabled)c*=texture(u_albedo,v_uv);if(c.a<u_cutoff)discard;float s=u_specular;if(u_shiny_enabled)s*=dot(texture(u_shiny,v_uv).rgb,vec3(0.2126,0.7152,0.0722));diffuse=vec4(encode_srgb(clamp(c.rgb,0.0,1.0)),c.a);normal=vec4(n*0.5+0.5,c.a);specular=vec4(vec3(clamp(s,0.0,1.0)),c.a);}
852 851 "#;
853 852
853 /// Live-preview image of a depth PNG exactly as written (Tim t1005u): the
854 /// stored channels in display form, no depth decoding.
855 pub fn stored_depth_preview(
856 png_bytes: &[u8],
857 w: u32,
858 h: u32,
859 fmt: crate::camera::DepthFormat,
860 ) -> Result<ImageBuf, String> {
861 let native =
862 crate::export_set::decode_depth_native(png_bytes, w, h, crate::export_set::MAX_TARGET, fmt)
863 .map_err(|e| e.to_string())?;
864 Ok(image(
865 w,
866 h,
867 crate::export_set::depth_display_rgba(&native, fmt),
868 ))
869 }
870
854 871 fn image(w: u32, h: u32, rgba: Vec<u8>) -> ImageBuf {
855 872 ImageBuf {
856 873 width: w,
@@ -964,7 +981,6 @@ pub fn resolve(raw: &Buffers, detail: &[u8], w: u32, h: u32) -> Result<Target, S
964 981 let mut specular = diffuse.clone();
965 982 let mut depth = vec![0u16; count * 2];
966 983 let mut depth_t = vec![0f32; count];
967 let mut depth_preview = diffuse.clone();
968 984 for y in 0..h {
969 985 for x in 0..w {
970 986 let i = (y * w + x) as usize;
@@ -1017,14 +1033,8 @@ pub fn resolve(raw: &Buffers, detail: &[u8], w: u32, h: u32) -> Result<Target, S
1017 1033 diffuse[i * 4 + c] = srgb(color[c] / sum);
1018 1034 normals[i * 4 + c] = ((n[c] * 0.5 + 0.5) * 255.).round().clamp(0., 255.) as u8;
1019 1035 specular[i * 4 + c] = (shiny / sum).round().clamp(0., 255.) as u8;
1020 depth_preview[i * 4 + c] = (d >> 8) as u8;
1021 1036 }
1022 for map in [
1023 &mut diffuse,
1024 &mut normals,
1025 &mut specular,
1026 &mut depth_preview,
1027 ] {
1037 for map in [&mut diffuse, &mut normals, &mut specular] {
1028 1038 map[i * 4 + 3] = alpha;
1029 1039 }
1030 1040 depth[i * 2] = d;
@@ -1038,7 +1048,6 @@ pub fn resolve(raw: &Buffers, detail: &[u8], w: u32, h: u32) -> Result<Target, S
1038 1048 specular: image(w, h, specular),
1039 1049 depth,
1040 1050 depth_t,
1041 depth_preview: image(w, h, depth_preview),
1042 1051 })
1043 1052 }
1044 1053
@@ -1726,11 +1735,12 @@ fn execute_target(
1726 1735 label: format!("{pose} · target-res / diffuse"),
1727 1736 });
1728 1737 let target = resolve(&raw, &detail, job.target_width, job.target_height)?;
1729 for ((map, preview), rel) in [
1730 ("diffuse", target.diffuse),
1731 ("depth", target.depth_preview),
1732 ("normal", target.normal),
1733 ("specular", target.specular),
1738 let depth_placeholder = image(job.target_width, job.target_height, Vec::new());
1739 for ((map, mut preview), rel) in [
1740 ("diffuse", target.diffuse.clone()),
1741 ("depth", depth_placeholder),
1742 ("normal", target.normal.clone()),
1743 ("specular", target.specular.clone()),
1734 1744 ]
1735 1745 .into_iter()
1736 1746 .zip(paths)
@@ -1746,6 +1756,16 @@ fn execute_target(
1746 1756 debug_assert!(rel.ends_with(&format!("_{map}.png")));
1747 1757 let kept = session.verified.contains_key(rel);
1748 1758 let digest = if kept {
1759 if map == "depth" {
1760 let stored = std::fs::read(session.root.join(rel))
1761 .map_err(|e| format!("{rel}: {e}"))?;
1762 preview = stored_depth_preview(
1763 &stored,
1764 job.target_width,
1765 job.target_height,
1766 job.depth_format,
1767 )?;
1768 }
1749 1769 session.verified[rel].clone()
1750 1770 } else {
1751 1771 let bytes = if map == "depth" {
@@ -1757,12 +1777,19 @@ fn execute_target(
1757 1777 (f64::from(*t), f64::from(target.depth[i * 2 + 1]) / 65535.)
1758 1778 })
1759 1779 .collect();
1760 encode_depth_png_fmt(
1780 let bytes = encode_depth_png_fmt(
1761 1781 job.target_width,
1762 1782 job.target_height,
1763 1783 &pixels,
1764 1784 job.depth_format,
1765 )?
1785 )?;
1786 preview = stored_depth_preview(
1787 &bytes,
1788 job.target_width,
1789 job.target_height,
1790 job.depth_format,
1791 )?;
1792 bytes
1766 1793 } else {
1767 1794 encode_rgba_png(&preview, map == "diffuse")?
1768 1795 };
@@ -2828,6 +2855,63 @@ mod tests {
2828 2855 assert!(gone.is_err());
2829 2856 }
2830 2857
2858 #[test]
2859 fn live_depth_preview_shows_the_written_depth_bytes_in_every_format() {
2860 use crate::camera::{DepthEncoding, DepthFormat};
2861 for bits in [8u8, 16] {
2862 for encoding in DepthEncoding::ALL {
2863 let fmt = DepthFormat::new(bits, encoding).unwrap();
2864 let tmp = Temp::new();
2865 let mut job = resume_job(&tmp.0);
2866 job.depth_format = fmt;
2867 let (result, events) = drive(job, RunTarget::Fresh, None);
2868 result.unwrap();
2869 let run = tmp.0.join("male-body-atlas");
2870 let export = crate::atlas::AtlasSet::open_run(&run)
2871 .unwrap()
2872 .ppda
2873 .export
2874 .unwrap();
2875 let written: Vec<Vec<u8>> = export
2876 .png_sha256
2877 .keys()
2878 .filter(|k| k.ends_with("_depth.png"))
2879 .map(|k| {
2880 let bytes = std::fs::read(run.join(k)).unwrap();
2881 let native = crate::export_set::decode_depth_native(
2882 &bytes,
2883 2,
2884 2,
2885 crate::export_set::MAX_TARGET,
2886 fmt,
2887 )
2888 .unwrap();
2889 crate::export_set::depth_display_rgba(&native, fmt)
2890 })
2891 .collect();
2892 let shown: Vec<&ImageBuf> = events
2893 .iter()
2894 .filter_map(|e| match e {
2895 RenderEvent::Pass {
2896 label,
2897 image: Some(img),
2898 ..
2899 } if label.ends_with("· depth") => Some(img),
2900 _ => None,
2901 })
2902 .collect();
2903 assert_eq!(shown.len(), written.len(), "{fmt:?}");
2904 for img in shown {
2905 assert_eq!((img.width, img.height), (2, 2));
2906 assert!(
2907 written.contains(&img.rgba),
2908 "{fmt:?}: preview is not the written bytes"
2909 );
2910 }
2911 }
2912 }
2913 }
2914
2831 2915 #[test]
2832 2916 #[cfg(unix)]
2833 2917 fn fresh_render_discards_older_unfinished_builds_of_the_same_target_only() {
@@ -66,7 +66,6 @@ pub struct Target {
66 pub depth: Vec<u16>,
66 pub depth: Vec<u16>,
67 /// Unquantized normalized depth t (0..1) per pixel, for the export encoding.
67 /// Unquantized normalized depth t (0..1) per pixel, for the export encoding.
68 pub depth_t: Vec<f32>,
68 pub depth_t: Vec<f32>,
69 pub depth_preview: ImageBuf,
70}
69}
71/// Content fingerprint of a source file used for a production job.
70/// Content fingerprint of a source file used for a production job.
72#[derive(Debug, Clone, PartialEq, Eq)]
71#[derive(Debug, Clone, PartialEq, Eq)]
@@ -851,6 +850,24 @@ vec3 encode_srgb(vec3 x){return mix(x*12.92,1.055*pow(max(x,vec3(0.0)),vec3(1.0/
851void main(){vec3 n=normalize(v_normal);if(u_flat){n=normalize(cross(dFdx(v_view_position),dFdy(v_view_position)));if(!gl_FrontFacing)n=-n;}vec4 c=u_color*v_color;if(u_albedo_enabled)c*=texture(u_albedo,v_uv);if(c.a<u_cutoff)discard;float s=u_specular;if(u_shiny_enabled)s*=dot(texture(u_shiny,v_uv).rgb,vec3(0.2126,0.7152,0.0722));diffuse=vec4(encode_srgb(clamp(c.rgb,0.0,1.0)),c.a);normal=vec4(n*0.5+0.5,c.a);specular=vec4(vec3(clamp(s,0.0,1.0)),c.a);}
850void main(){vec3 n=normalize(v_normal);if(u_flat){n=normalize(cross(dFdx(v_view_position),dFdy(v_view_position)));if(!gl_FrontFacing)n=-n;}vec4 c=u_color*v_color;if(u_albedo_enabled)c*=texture(u_albedo,v_uv);if(c.a<u_cutoff)discard;float s=u_specular;if(u_shiny_enabled)s*=dot(texture(u_shiny,v_uv).rgb,vec3(0.2126,0.7152,0.0722));diffuse=vec4(encode_srgb(clamp(c.rgb,0.0,1.0)),c.a);normal=vec4(n*0.5+0.5,c.a);specular=vec4(vec3(clamp(s,0.0,1.0)),c.a);}
852"#;
851"#;
853
852
853/// Live-preview image of a depth PNG exactly as written (Tim t1005u): the
854/// stored channels in display form, no depth decoding.
855pub fn stored_depth_preview(
856 png_bytes: &[u8],
857 w: u32,
858 h: u32,
859 fmt: crate::camera::DepthFormat,
860) -> Result<ImageBuf, String> {
861 let native =
862 crate::export_set::decode_depth_native(png_bytes, w, h, crate::export_set::MAX_TARGET, fmt)
863 .map_err(|e| e.to_string())?;
864 Ok(image(
865 w,
866 h,
867 crate::export_set::depth_display_rgba(&native, fmt),
868 ))
869}
870
854fn image(w: u32, h: u32, rgba: Vec<u8>) -> ImageBuf {
871fn image(w: u32, h: u32, rgba: Vec<u8>) -> ImageBuf {
855 ImageBuf {
872 ImageBuf {
856 width: w,
873 width: w,
@@ -964,7 +981,6 @@ pub fn resolve(raw: &Buffers, detail: &[u8], w: u32, h: u32) -> Result<Target, S
964 let mut specular = diffuse.clone();
981 let mut specular = diffuse.clone();
965 let mut depth = vec![0u16; count * 2];
982 let mut depth = vec![0u16; count * 2];
966 let mut depth_t = vec![0f32; count];
983 let mut depth_t = vec![0f32; count];
967 let mut depth_preview = diffuse.clone();
968 for y in 0..h {
984 for y in 0..h {
969 for x in 0..w {
985 for x in 0..w {
970 let i = (y * w + x) as usize;
986 let i = (y * w + x) as usize;
@@ -1017,14 +1033,8 @@ pub fn resolve(raw: &Buffers, detail: &[u8], w: u32, h: u32) -> Result<Target, S
1017 diffuse[i * 4 + c] = srgb(color[c] / sum);
1033 diffuse[i * 4 + c] = srgb(color[c] / sum);
1018 normals[i * 4 + c] = ((n[c] * 0.5 + 0.5) * 255.).round().clamp(0., 255.) as u8;
1034 normals[i * 4 + c] = ((n[c] * 0.5 + 0.5) * 255.).round().clamp(0., 255.) as u8;
1019 specular[i * 4 + c] = (shiny / sum).round().clamp(0., 255.) as u8;
1035 specular[i * 4 + c] = (shiny / sum).round().clamp(0., 255.) as u8;
1020 depth_preview[i * 4 + c] = (d >> 8) as u8;
1021 }
1036 }
1022 for map in [
1037 for map in [&mut diffuse, &mut normals, &mut specular] {
1023 &mut diffuse,
1024 &mut normals,
1025 &mut specular,
1026 &mut depth_preview,
1027 ] {
1028 map[i * 4 + 3] = alpha;
1038 map[i * 4 + 3] = alpha;
1029 }
1039 }
1030 depth[i * 2] = d;
1040 depth[i * 2] = d;
@@ -1038,7 +1048,6 @@ pub fn resolve(raw: &Buffers, detail: &[u8], w: u32, h: u32) -> Result<Target, S
1038 specular: image(w, h, specular),
1048 specular: image(w, h, specular),
1039 depth,
1049 depth,
1040 depth_t,
1050 depth_t,
1041 depth_preview: image(w, h, depth_preview),
1042 })
1051 })
1043}
1052}
1044
1053
@@ -1726,11 +1735,12 @@ fn execute_target(
1726 label: format!("{pose} · target-res / diffuse"),
1735 label: format!("{pose} · target-res / diffuse"),
1727 });
1736 });
1728 let target = resolve(&raw, &detail, job.target_width, job.target_height)?;
1737 let target = resolve(&raw, &detail, job.target_width, job.target_height)?;
1729 for ((map, preview), rel) in [
1738 let depth_placeholder = image(job.target_width, job.target_height, Vec::new());
1730 ("diffuse", target.diffuse),
1739 for ((map, mut preview), rel) in [
1731 ("depth", target.depth_preview),
1740 ("diffuse", target.diffuse.clone()),
1732 ("normal", target.normal),
1741 ("depth", depth_placeholder),
1733 ("specular", target.specular),
1742 ("normal", target.normal.clone()),
1743 ("specular", target.specular.clone()),
1734 ]
1744 ]
1735 .into_iter()
1745 .into_iter()
1736 .zip(paths)
1746 .zip(paths)
@@ -1746,6 +1756,16 @@ fn execute_target(
1746 debug_assert!(rel.ends_with(&format!("_{map}.png")));
1756 debug_assert!(rel.ends_with(&format!("_{map}.png")));
1747 let kept = session.verified.contains_key(rel);
1757 let kept = session.verified.contains_key(rel);
1748 let digest = if kept {
1758 let digest = if kept {
1759 if map == "depth" {
1760 let stored = std::fs::read(session.root.join(rel))
1761 .map_err(|e| format!("{rel}: {e}"))?;
1762 preview = stored_depth_preview(
1763 &stored,
1764 job.target_width,
1765 job.target_height,
1766 job.depth_format,
1767 )?;
1768 }
1749 session.verified[rel].clone()
1769 session.verified[rel].clone()
1750 } else {
1770 } else {
1751 let bytes = if map == "depth" {
1771 let bytes = if map == "depth" {
@@ -1757,12 +1777,19 @@ fn execute_target(
1757 (f64::from(*t), f64::from(target.depth[i * 2 + 1]) / 65535.)
1777 (f64::from(*t), f64::from(target.depth[i * 2 + 1]) / 65535.)
1758 })
1778 })
1759 .collect();
1779 .collect();
1760 encode_depth_png_fmt(
1780 let bytes = encode_depth_png_fmt(
1761 job.target_width,
1781 job.target_width,
1762 job.target_height,
1782 job.target_height,
1763 &pixels,
1783 &pixels,
1764 job.depth_format,
1784 job.depth_format,
1765 )?
1785 )?;
1786 preview = stored_depth_preview(
1787 &bytes,
1788 job.target_width,
1789 job.target_height,
1790 job.depth_format,
1791 )?;
1792 bytes
1766 } else {
1793 } else {
1767 encode_rgba_png(&preview, map == "diffuse")?
1794 encode_rgba_png(&preview, map == "diffuse")?
1768 };
1795 };
@@ -2828,6 +2855,63 @@ mod tests {
2828 assert!(gone.is_err());
2855 assert!(gone.is_err());
2829 }
2856 }
2830
2857
2858 #[test]
2859 fn live_depth_preview_shows_the_written_depth_bytes_in_every_format() {
2860 use crate::camera::{DepthEncoding, DepthFormat};
2861 for bits in [8u8, 16] {
2862 for encoding in DepthEncoding::ALL {
2863 let fmt = DepthFormat::new(bits, encoding).unwrap();
2864 let tmp = Temp::new();
2865 let mut job = resume_job(&tmp.0);
2866 job.depth_format = fmt;
2867 let (result, events) = drive(job, RunTarget::Fresh, None);
2868 result.unwrap();
2869 let run = tmp.0.join("male-body-atlas");
2870 let export = crate::atlas::AtlasSet::open_run(&run)
2871 .unwrap()
2872 .ppda
2873 .export
2874 .unwrap();
2875 let written: Vec<Vec<u8>> = export
2876 .png_sha256
2877 .keys()
2878 .filter(|k| k.ends_with("_depth.png"))
2879 .map(|k| {
2880 let bytes = std::fs::read(run.join(k)).unwrap();
2881 let native = crate::export_set::decode_depth_native(
2882 &bytes,
2883 2,
2884 2,
2885 crate::export_set::MAX_TARGET,
2886 fmt,
2887 )
2888 .unwrap();
2889 crate::export_set::depth_display_rgba(&native, fmt)
2890 })
2891 .collect();
2892 let shown: Vec<&ImageBuf> = events
2893 .iter()
2894 .filter_map(|e| match e {
2895 RenderEvent::Pass {
2896 label,
2897 image: Some(img),
2898 ..
2899 } if label.ends_with("· depth") => Some(img),
2900 _ => None,
2901 })
2902 .collect();
2903 assert_eq!(shown.len(), written.len(), "{fmt:?}");
2904 for img in shown {
2905 assert_eq!((img.width, img.height), (2, 2));
2906 assert!(
2907 written.contains(&img.rgba),
2908 "{fmt:?}: preview is not the written bytes"
2909 );
2910 }
2911 }
2912 }
2913 }
2914
2831 #[test]
2915 #[test]
2832 #[cfg(unix)]
2916 #[cfg(unix)]
2833 fn fresh_render_discards_older_unfinished_builds_of_the_same_target_only() {
2917 fn fresh_render_discards_older_unfinished_builds_of_the_same_target_only() {