code wiki / (root) / nx_fb_1bpp.nx

nx_fb_1bpp.nx source

↩ module page · 183 lines · 5978 B

1// nx_fb_1bpp.nx -- 1-bit-per-pixel framebuffer. 2// 3// Monochrome framebuffer for OLED / E-ink / sub-watt displays. 4// 128×64 = 1024 bytes; the canonical T1/MCU display size. Per 5// substrate-tier-ladder cardinal: minimum-hardware-floor primitives 6// must scale to NX_TIER_MCU; 1bpp is the floor. 7// 8// Bit-pack layout (compatible with SSD1306 page layout — the canonical 9// OLED controller): 8 vertically-adjacent pixels share one byte, with 10// bit 0 = top pixel, bit 7 = bottom pixel. width pixels per row, 11// height pixels per column. byte_count = (height/8) * width. 12// 13// Composes: 14// nx_palette -- caller's palette index maps to ON / OFF bit 15// nx_raycast_voxel -- hit face brightness maps to pixel ON/OFF 16// via a 4-level dither pattern 17 18import "nx_syscalls.nx" 19import "nx_tier.nx" 20 21const NX_FB_OK: nx_int = 0 22const NX_FB_ERR_BAD_DIM: nx_int = 1 23const NX_FB_ERR_BAD_COORD: nx_int = 2 24 25// ===== Struct: NxFb1bpp ============================================ 26 27struct NxFb1bpp { 28 bytes: *u8, 29 width: nx_size, 30 height: nx_size, 31 byte_count: nx_size, 32} 33 34func nx_fb_1bpp_new(width: nx_size, height: nx_size) -> *NxFb1bpp { 35 // height must be multiple of 8 for clean page layout 36 if width == 0 { return (0 as i64) as *NxFb1bpp } 37 if height == 0 { return (0 as i64) as *NxFb1bpp } 38 let h_mod_8: nx_size = height - (height / 8) * 8 39 if h_mod_8 != 0 { return (0 as i64) as *NxFb1bpp } 40 let f: *NxFb1bpp = (sys_mmap(32)) as *NxFb1bpp 41 f.width = width 42 f.height = height 43 f.byte_count = (height / 8) * width 44 f.bytes = (sys_mmap(f.byte_count)) as *u8 45 return f 46} 47 48// ===== nx_fb_1bpp_clear ============================================ 49 50func nx_fb_1bpp_clear(f: *NxFb1bpp) -> nx_int { 51 var i: nx_size = 0 52 while i < f.byte_count { 53 f.bytes[i] = 0 as u8 54 i = i + 1 55 } 56 return NX_FB_OK 57} 58 59// ===== nx_fb_1bpp_fill ============================================ 60 61func nx_fb_1bpp_fill(f: *NxFb1bpp) -> nx_int { 62 var i: nx_size = 0 63 while i < f.byte_count { 64 f.bytes[i] = 255 as u8 65 i = i + 1 66 } 67 return NX_FB_OK 68} 69 70// ===== nx_fb_1bpp_set_pixel ======================================= 71// 72// Set pixel at (x, y) to ON (1) or OFF (0). Coordinates outside 73// the framebuffer are silently clipped (graphics convention). 74 75func nx_fb_1bpp_set_pixel(f: *NxFb1bpp, x: nx_size, y: nx_size, on: nx_int) -> nx_int { 76 if x >= f.width { return NX_FB_ERR_BAD_COORD } 77 if y >= f.height { return NX_FB_ERR_BAD_COORD } 78 let page: nx_size = y / 8 79 let bit_in_page: nx_size = y - (page * 8) 80 let byte_off: nx_size = page * f.width + x 81 let cur: nx_int = (f.bytes[byte_off] as i64) & 255 82 if on != 0 { 83 let mask_on: nx_int = 1 << bit_in_page 84 f.bytes[byte_off] = (cur | mask_on) as u8 85 } else { 86 let mask_off: nx_int = (255 ^ (1 << bit_in_page)) & 255 87 f.bytes[byte_off] = (cur & mask_off) as u8 88 } 89 return NX_FB_OK 90} 91 92// ===== nx_fb_1bpp_get_pixel ======================================= 93 94func nx_fb_1bpp_get_pixel(f: *NxFb1bpp, x: nx_size, y: nx_size) -> nx_int { 95 if x >= f.width { return 0 } 96 if y >= f.height { return 0 } 97 let page: nx_size = y / 8 98 let bit_in_page: nx_size = y - (page * 8) 99 let byte_off: nx_size = page * f.width + x 100 let cur: nx_int = (f.bytes[byte_off] as i64) & 255 101 let mask: nx_int = 1 << bit_in_page 102 if (cur & mask) != 0 { return 1 } 103 return 0 104} 105 106// ===== nx_fb_1bpp_fill_rect ======================================= 107// 108// Fill a rectangle [x0..x0+w) x [y0..y0+h) with on/off. Clipped to 109// framebuffer bounds. 110 111func nx_fb_1bpp_fill_rect(f: *NxFb1bpp, 112 x0: nx_size, y0: nx_size, 113 w: nx_size, h: nx_size, 114 on: nx_int) -> nx_int { 115 var y: nx_size = y0 116 while y < y0 + h { 117 if y >= f.height { return NX_FB_OK } 118 var x: nx_size = x0 119 while x < x0 + w { 120 if x < f.width { nx_fb_1bpp_set_pixel(f, x, y, on) } 121 x = x + 1 122 } 123 y = y + 1 124 } 125 return NX_FB_OK 126} 127 128// ===== nx_fb_1bpp_dither_4 ========================================= 129// 130// 4-level brightness encoded via 2x2 dither pattern. brightness: 131// 0 = all OFF, 1 = quarter ON, 2 = half ON, 3 = all ON. Used by 132// raycast renderer: face-normal magnitude maps to 0-3 dither level 133// even on 1bpp display. 134 135func nx_fb_1bpp_dither_4(f: *NxFb1bpp, 136 x0: nx_size, y0: nx_size, 137 w: nx_size, h: nx_size, 138 brightness: nx_int) -> nx_int { 139 var y: nx_size = y0 140 while y < y0 + h { 141 if y >= f.height { return NX_FB_OK } 142 var x: nx_size = x0 143 while x < x0 + w { 144 if x < f.width { 145 let pattern_x: nx_size = (x - x0) - ((x - x0) / 2) * 2 146 let pattern_y: nx_size = (y - y0) - ((y - y0) / 2) * 2 147 var on: nx_int = 0 148 if brightness >= 3 { on = 1 } 149 if brightness == 2 { 150 if pattern_x == pattern_y { on = 1 } 151 } 152 if brightness == 1 { 153 if pattern_x == 0 { 154 if pattern_y == 0 { on = 1 } 155 } 156 } 157 nx_fb_1bpp_set_pixel(f, x, y, on) 158 } 159 x = x + 1 160 } 161 y = y + 1 162 } 163 return NX_FB_OK 164} 165 166// ===== nx_fb_1bpp_count_on ========================================= 167// 168// How many ON pixels in the framebuffer? Used for tests + debug. 169 170func nx_fb_1bpp_count_on(f: *NxFb1bpp) -> nx_int { 171 var count: nx_int = 0 172 var i: nx_size = 0 173 while i < f.byte_count { 174 let b: nx_int = (f.bytes[i] as i64) & 255 175 var bit: nx_int = 0 176 while bit < 8 { 177 if (b & (1 << bit)) != 0 { count = count + 1 } 178 bit = bit + 1 179 } 180 i = i + 1 181 } 182 return count 183}