dfrg / zeno

High performance, low level 2D path rasterization library in pure Rust.
Apache License 2.0
101 stars 9 forks source link

subtract overflow → Infinite loop #1

Open NathanRoyer opened 2 years ago

NathanRoyer commented 2 years ago

Hi, I was looking for a crate with which I could render SVG commands cpu-side, and I'm very satisfied with the performance of zeno, also with the fact that it has no dependency to other crates.

However, I encountered a problem in one specific configuration. I'm drawing circles using successive calls to line_to (which is not not the canonical way, one would normally use arc). When drawing a circle of radius 40px on a 2000x2000 canvas centered at 1500x500, the program:

The panic says attempt to subtract with overflow @ zeno-0.2.2/src/raster.rs:285:18

Using GDB, I was able to determine that sin_half is the first f32 to become NaN, then the NaNs propagate in the following computations. I am not sure which loop never ends exactly.

Here is a minimal reproduction of my code which is enough to trigger the bug on my machine:

use zeno::PathBuilder;
use zeno::Vector;
use zeno::Stroke;
use zeno::Mask;

use std::f32::consts::TAU;

fn main() {
    let w = 2000;
    let h = 2000;
    let mut zeno_path = Vec::new();
    let center = Vector::new(1500.0, 500.0);
    let radius: f32 = 40.0;
    // steps is complex to compute in my real code,
    // so I just put it that way to be sure of its value
    let steps = f32::from_be_bytes([67, 123, 0, 0]); // ~251
    let a_delta = TAU / steps;
    // angle which will go from 0 to TAU
    let mut a: f32 = 0.0;
    let p = |a: f32| {
        let (y, x) = a.sin_cos();
        Vector::new(center.x + x * radius, center.y + y * radius)
    };
    zeno_path.move_to(p(a));
    while a < TAU {
        a += a_delta;
        zeno_path.line_to(p(a));
    }
    zeno_path.close();
    let mut zeno_mask = Mask::new(&zeno_path);
    zeno_mask.size(w, h);
    zeno_mask.style(Stroke::new(10.0));
    let mut buffer = vec![0; (w * h) as usize];
    // infinite loop w/ release and crash with debug:
    zeno_mask.render_into(&mut buffer, None);
}

I'm currently figuring out the way Segments work to eventually figure out a solution to this problem; if you have clues or thoughts about this, please let me know.

NathanRoyer commented 2 years ago

I was not able to fix the bug, however I found a workaround: setting zeno::Join::Bevel via the join setter on a Stroke object prevents the buggy code to run.

markusmoenig commented 2 years ago

Sadly it seems they have abandoned this project.