forked from 1b-agent/fieldcraft-upstream
gpu: captures draw name labels; a shared capturer and a PNG with the CPU as last tier
render::draw_labels draws a scene's labels over finished pixels in the CPU rasterizer's font. capture::shared opens the process's device once by the FIELDCRAFT_GPU policy; capture::png draws on it or on the CPU and reports which (Renderer). Refs #422 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
1 parent
fee978c7e4
commit
c9163e98ad
5 files changed
+134
-20
No files matched your search
@@ -19,6 +19,6 @@ pub use camera::{Camera, FIT_MARGIN, FULL_VIEW, Mat4, Orientation, StandardView}
|
||||
pub use display::{DisplayEdge, DisplayLine, DisplayMesh, DisplayNode, DisplayTri, SurfaceCurve, SurfaceTri};
|
||||
pub use raster::{
|
||||
Canvas, LABEL_OFFSET, MAX_DIM, MAX_PIXELS, MAX_PRIMITIVES, RenderError, Rgb, Scene, SceneFill, SceneLabel, SceneLine, ScenePoint, SceneTri,
|
||||
fit_size, png, render, render_layers, render_layers_png, render_png, shade,
|
||||
draw_labels, fit_size, png, render, render_layers, render_layers_png, render_png, shade,
|
||||
};
|
||||
pub use vec::{Aabb3, Vec3};
|
||||
@@ -336,6 +336,36 @@ pub fn render_layers(scene: &Scene, overlays: &[&Scene], cam: &Camera, w: usize,
|
||||
downsample(&c, w, h)
|
||||
}
|
||||
|
||||
/// `layers`' labels drawn over finished `w × h` RGBA pixels (a GPU picture), as [`render_layers`]
|
||||
/// draws them: the pixels are drawn at the supersampled size, so pixels away from a label come
|
||||
/// back unchanged. `radius` is the scene's clipping radius.
|
||||
pub fn draw_labels(rgba: &[u8], w: usize, h: usize, layers: &[&Scene], cam: &Camera, radius: f64) -> Result<Vec<u8>, RenderError> {
|
||||
if !size_ok(w, h) || rgba.len() != w * h * 4 {
|
||||
return Err(RenderError::Size { w, h });
|
||||
}
|
||||
let labels: Vec<&SceneLabel> = layers.iter().flat_map(|s| &s.labels).take(MAX_LABELS).collect();
|
||||
if labels.is_empty() {
|
||||
return Ok(rgba.to_vec());
|
||||
}
|
||||
let (sw, sh) = (w * SS, h * SS);
|
||||
let mut c = Canvas::new(sw, sh)?;
|
||||
for y in 0..sh {
|
||||
for x in 0..sw {
|
||||
let (i, o) = (((y / SS) * w + x / SS) * 4, (y * sw + x) * 4);
|
||||
if let (Some(src), Some(dst)) = (rgba.get(i..i + 4), c.rgba.get_mut(o..o + 4)) {
|
||||
dst.copy_from_slice(src);
|
||||
}
|
||||
}
|
||||
}
|
||||
let proj = Projector::new(cam, sw as f64, sh as f64, radius);
|
||||
for l in labels {
|
||||
if let Some(q) = proj.screen(l.p) {
|
||||
label(&mut c, q, &l.text, l.color.f());
|
||||
}
|
||||
}
|
||||
Ok(downsample(&c, w, h)?.rgba)
|
||||
}
|
||||
|
||||
/// Cover a triangle with screen corners `a`, `b`, `c` (x, y, depth) on a `w × h` pixel grid:
|
||||
/// `pixel(x, y, depth)` for every pixel whose centre is inside it (its edges included), the depth
|
||||
/// interpolated. Degenerate and off-grid triangles cover nothing. The renderer and the picking
|
||||
@@ -614,6 +644,20 @@ mod tests {
|
||||
let _ = render(&wild, &cam, 32, 32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn labels_draw_over_finished_pixels() {
|
||||
let (w, h) = (64, 32);
|
||||
let px: Vec<u8> = (0..w * h).flat_map(|i| [(i % 251) as u8, 40, 200, 255]).collect();
|
||||
let cam = Camera::default();
|
||||
let mut scene = Scene::default();
|
||||
assert_eq!(draw_labels(&px, w, h, &[&scene], &cam, 10.0).unwrap(), px);
|
||||
scene.labels.push(SceneLabel { p: Vec3::ZERO, text: "RP".into(), color: Rgb(0, 0, 0) });
|
||||
let out = draw_labels(&px, w, h, &[&scene], &cam, 10.0).unwrap();
|
||||
let changed = out.chunks(4).zip(px.chunks(4)).filter(|(a, b)| a != b).count();
|
||||
assert!(changed > 10 && changed < w * h / 2, "{changed}");
|
||||
assert!(draw_labels(&px, w + 1, h, &[&scene], &cam, 10.0).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn labels_draw_over_everything_beside_their_point() {
|
||||
let mut scene = red_triangle();
|
||||
|
||||
Reference in new issue
Block a user