code wiki / (root) / nx_raster_sw.nx

nx_raster_sw.nx source

↩ module page · 183 lines · 7242 B

1// nx_raster_sw.nx -- bits-up integer software triangle rasterizer. 2// 3// LINEAGE (per [[feedback-no-tool-proliferation-consolidate-or-justify]] 4// cardinal landed 2026-05-20): 5// 6// Distinct from [[nx_raster.nx]] because: nx_raster.nx ships Bresenham 7// lines + Pineda triangles + barycentric-shaded triangles on the 8// *Image abstraction; this file ships Pineda strict-interior coverage 9// on the *i64 framebuffer directly, AND exposes the edge function + 10// signed-area helpers as public APIs for downstream composition. 11// 12// Distinct from [[nx_raster_triangle.nx]] because: nx_raster_triangle.nx 13// adds a Z-buffer for 3D occlusion; this file is the 2D strict-interior 14// coverage path used by the perceptual-render demos that don't need 15// depth. The two compose: nx_raster_triangle.nx CALLS the edge 16// function exported here in its Pineda inner loop. 17// 18// HONEST PROLIFERATION NOTE: the substrate has three rasterizer 19// primitives by 2026-05-20. Future raster work MUST extend one of 20// these three, not ship a fourth. When the substrate hits 4 21// rasterizers, consolidate per the cardinal. 22// 23// G1 first stone of NISHI_GRAPHICS_ROADMAP.md. Pineda 1988 edge- 24// function method, absorbed clean-room from the ACM SIGGRAPH paper. 25// Pure integer i64 math; no libm; no float; no SIMD; no GPU. 26// 27// Composes: 28// [[NISHI_GRAPHICS_ROADMAP]] G1 29// existing nx_framebuffer.nx (this rasterizer uses nx_fb_set as 30// its pixel write primitive; preserves additively per Cardinal 13) 31// [[feedback-bits-up-exceed-never-match]] (no Cairo, no Skia, no 32// Mesa, no AGG; integer-only Pineda) 33// [[feedback-no-false-ok-substrate-honesty-audit]] (degenerate + 34// CW triangles return 0 pixels deterministically; KAT verifies 35// known triangle -> known pixel count) 36// 37// API: 38// nx_raster_signed_area_2x(x0,y0,x1,y1,x2,y2) -> i64 39// Returns twice the signed area. Positive => front-facing (drawable). 40// Zero => degenerate (collinear). Negative => back-facing (culled). 41// 42// nx_raster_edge(ax,ay,bx,by,px,py) -> i64 43// The Pineda edge function: (bx-ax)*(py-ay) - (by-ay)*(px-ax). 44// Positive when (px,py) is in the half-plane to the left of the 45// directed edge (ax,ay)->(bx,by) under CCW (positive-area) winding. 46// 47// nx_raster_triangle(fb, w, h, x0,y0,x1,y1,x2,y2, color) -> i64 48// Rasterizes the triangle with strict-interior coverage (all 49// three edge functions > 0 at the integer pixel coordinate). 50// Writes color via nx_fb_set for each covered pixel. 51// Returns the number of pixels written (0 if degenerate or 52// back-facing or fully outside the framebuffer). 53// 54// Pixel-center convention: G1 evaluates the edge function at the 55// integer pixel coordinate (the top-left corner of the pixel), 56// strict-greater-than-zero for inclusion. G2 will introduce Q4 57// sub-pixel evaluation at pixel center (x+0.5, y+0.5) with the 58// top-left fill rule from Pineda 1988 + Heckbert 1990. 59// 60// Reference (absorbed clean-room, no code copied): 61// Pineda 1988 "A Parallel Algorithm for Polygon Rasterization" 62// (ACM SIGGRAPH; edge function + half-plane test method) 63// Heckbert 1990 "What Are the Coordinates of a Pixel?" (queued 64// for G2 sub-pixel + top-left fill rule) 65// Akenine-Möller + Haines + Hoffman "Real-Time Rendering" 4ed 66// (chapter 23 reference textbook; concepts, not code) 67// 68// genealogy_id: nishi-graphics-g1-raster + pineda_1988_edge_function + 69// heckbert_1990_pixel_coordinates 70// lineage_id: substrate_raster_sw_v1_bits_up_integer 71 72// nx_safety_envelope: 73// intended_use: "bits-up integer software triangle 74// rasterizer; Pineda 1988 edge-function 75// method; G1 substrate stone for the 76// DirectX-replacement arc" 77// sil_target: SIL2 78// evidence: [kat_known_triangle_pixel_count_36, 79// kat_degenerate_returns_zero, 80// kat_back_facing_returns_zero, 81// no_libm_no_float_no_simd] 82// hazard_register: [bug-tape-edge-tie-breaking, 83// bug-tape-bounding-box-clip, 84// bug-tape-coordinate-convention-mismatch] 85// verdict: NOT_YET_EVALUATED 86 87import "nx_syscalls.nx" 88import "nx_framebuffer.nx" 89 90// ===== Edge + area primitives ===================================== 91 92func nx_raster_edge( 93 ax: i64, ay: i64, 94 bx: i64, by: i64, 95 px: i64, py: i64 96) -> i64 { 97 return (bx - ax) * (py - ay) - (by - ay) * (px - ax) 98} 99 100// Twice the signed area of the triangle (v0, v1, v2). Avoids the 101// /2 to stay in exact integer math. Sign convention: positive => 102// front-facing (drawable); zero => degenerate; negative => back-facing. 103func nx_raster_signed_area_2x( 104 x0: i64, y0: i64, 105 x1: i64, y1: i64, 106 x2: i64, y2: i64 107) -> i64 { 108 return (x1 - x0) * (y2 - y0) - (x2 - x0) * (y1 - y0) 109} 110 111// ===== Bounding-box helpers ====================================== 112 113func nx_raster_min3(a: i64, b: i64, c: i64) -> i64 { 114 var m: i64 = a 115 if b < m { m = b } 116 if c < m { m = c } 117 return m 118} 119 120func nx_raster_max3(a: i64, b: i64, c: i64) -> i64 { 121 var m: i64 = a 122 if b > m { m = b } 123 if c > m { m = c } 124 return m 125} 126 127func nx_raster_clamp(v: i64, lo: i64, hi: i64) -> i64 { 128 if v < lo { return lo } 129 if v > hi { return hi } 130 return v 131} 132 133// ===== Triangle rasterizer ======================================= 134// 135// Rasterizes triangle (v0, v1, v2) into framebuffer fb (w x h). 136// Returns the number of pixels written. Degenerate (area == 0) or 137// back-facing (area < 0) returns 0 with no writes. 138func nx_raster_triangle( 139 fb: *i64, 140 w: nx_int, 141 h: nx_int, 142 x0: i64, y0: i64, 143 x1: i64, y1: i64, 144 x2: i64, y2: i64, 145 color: i64 146) -> i64 { 147 let area2x: i64 = nx_raster_signed_area_2x(x0, y0, x1, y1, x2, y2) 148 if area2x <= 0 { return 0 } 149 150 // Bounding box clipped to framebuffer bounds. 151 let bb_min_x_raw: i64 = nx_raster_min3(x0, x1, x2) 152 let bb_max_x_raw: i64 = nx_raster_max3(x0, x1, x2) 153 let bb_min_y_raw: i64 = nx_raster_min3(y0, y1, y2) 154 let bb_max_y_raw: i64 = nx_raster_max3(y0, y1, y2) 155 let bb_min_x: i64 = nx_raster_clamp(bb_min_x_raw, 0, w - 1) 156 let bb_max_x: i64 = nx_raster_clamp(bb_max_x_raw, 0, w - 1) 157 let bb_min_y: i64 = nx_raster_clamp(bb_min_y_raw, 0, h - 1) 158 let bb_max_y: i64 = nx_raster_clamp(bb_max_y_raw, 0, h - 1) 159 160 // Walk bounding box; for each pixel, test all three edge 161 // functions. Strict-interior coverage: all three > 0. 162 var written: i64 = 0 163 var py: i64 = bb_min_y 164 while py <= bb_max_y { 165 var px: i64 = bb_min_x 166 while px <= bb_max_x { 167 let e0: i64 = nx_raster_edge(x0, y0, x1, y1, px, py) 168 let e1: i64 = nx_raster_edge(x1, y1, x2, y2, px, py) 169 let e2: i64 = nx_raster_edge(x2, y2, x0, y0, px, py) 170 if e0 > 0 { 171 if e1 > 0 { 172 if e2 > 0 { 173 nx_fb_set(fb, w, h, px, py, color) 174 written = written + 1 175 } 176 } 177 } 178 px = px + 1 179 } 180 py = py + 1 181 } 182 return written 183}