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}