nx_swapchain.nx source
↩ module page · 147 lines · 5679 B
1// nx_swapchain.nx -- presentation destination abstraction.
2//
3// G1/G2 transitional brick of NISHI_GRAPHICS_ROADMAP.md. Replaces
4// DXGI's swapchain role: takes a rendered framebuffer and delivers
5// its pixels to a destination (PPM byte buffer for now; KMS direct
6// scanout queued for G2; nishi-os direct scanout queued post nishi-
7// silicon arc).
8//
9// Composes:
10// [[NISHI_GRAPHICS_ROADMAP]] G1/G2 (this is the bridge brick)
11// existing nx_framebuffer.nx (reads i64 cells, RGBA packed in low 32 bits)
12// existing nx_ppm_writer.nx (ppm_write_p6 consumes *u8 RGB interleaved)
13// [[feedback-multi-species-perceptual-substrate-not-anthropocentric]]
14// (PPM presentation extracts the HUMAN-NORMAL view today; G2 adds
15// per-profile views including UV-channel PPM-extension files)
16// [[feedback-no-false-ok-substrate-honesty-audit]] (KMS stub returns
17// NX_SWAP_ERR_NOT_IMPL deterministically; never silently no-ops)
18//
19// Destination sealed-enum (queued backends marked):
20// NX_SWAP_DEST_PPM_BUFFER -- in-memory PPM P6 bytes (SHIPPED)
21// NX_SWAP_DEST_KMS_DIRECT -- Linux KMS direct scanout (G2; STUB returns ENOTIMPL)
22// NX_SWAP_DEST_NISHI_OS -- nishi-os direct scanout (post-silicon; STUB)
23// NX_SWAP_DEST_DISCARD -- /dev/null equivalent; counts frame only (SHIPPED)
24//
25// Why this sealed enum NOW (G1 transitional): pulls the existing
26// nx_raster + nx_ppm_writer + nx_framebuffer bricks under a single
27// Nishi-grade API surface so downstream code (G2 demos, G3 GPU
28// fallback, conductor multi-tier scenarios) all target the same
29// destination interface. G2 swaps in the KMS backend without
30// breaking any caller. G-ETG cardinal applies: when KMS lands,
31// per-display empirical measurement of scanout latency / DRM ioctl
32// roundtrip / vsync jitter replaces "the doc says vsync is 60Hz."
33//
34// genealogy_id: nishi-graphics-g1-swapchain + iso_dxgi_role_abstraction
35// lineage_id: substrate_swapchain_v1_bits_up_integer
36
37// nx_safety_envelope:
38// intended_use: "presentation destination abstraction; PPM
39// buffer backend SHIPPED; KMS direct + nishi-os
40// backends honestly stubbed; refuses silent
41// no-op"
42// sil_target: SIL2
43// evidence: [kat_ppm_p6_header_and_pixel_bytes,
44// kat_kms_stub_returns_not_impl,
45// kat_discard_returns_zero_no_writes]
46// hazard_register: [bug-tape-stride-vs-width-confusion,
47// bug-tape-rgba-vs-rgb-channel-order,
48// bug-tape-silent-stub-no-op]
49// verdict: NOT_YET_EVALUATED
50
51import "nx_syscalls.nx"
52import "nx_framebuffer.nx"
53import "nx_ppm_writer.nx"
54
55// ===== Destination sealed enum ====================================
56
57const NX_SWAP_DEST_NONE: i64 = 0
58const NX_SWAP_DEST_PPM_BUFFER: i64 = 1
59const NX_SWAP_DEST_KMS_DIRECT: i64 = 2
60const NX_SWAP_DEST_NISHI_OS: i64 = 3
61const NX_SWAP_DEST_DISCARD: i64 = 4
62const NX_SWAP_DEST_N: i64 = 5
63
64func nx_swap_dest_is_valid(d: i64) -> i64 {
65 if d <= NX_SWAP_DEST_NONE { return 0 }
66 if d >= NX_SWAP_DEST_N { return 0 }
67 return 1
68}
69
70// ===== Status / error codes =======================================
71
72const NX_SWAP_ERR_BAD_DEST: i64 = -100
73const NX_SWAP_ERR_BAD_SIZE: i64 = -101
74const NX_SWAP_ERR_SHORT_BUF: i64 = -102
75const NX_SWAP_ERR_NOT_IMPL: i64 = -103
76
77// ===== RGB channel extractor =====================================
78//
79// Reads RGBA-packed i64 framebuffer cells and writes interleaved
80// RGB bytes into the caller's *u8 buffer. Alpha discarded (PPM
81// has no alpha channel; PNG/Netpbm-PAM queued for G2).
82//
83// Layout: cells[y*w + x] = R | G<<8 | B<<16 | A<<24
84// Output: rgb_out[3*(y*w + x) + 0] = R, +1 = G, +2 = B
85func nx_swap_extract_rgb_bytes(
86 fb: *i64,
87 w: nx_int,
88 h: nx_int,
89 rgb_out: *u8
90) -> i64 {
91 let n: i64 = w * h
92 var i: i64 = 0
93 while i < n {
94 let cell: i64 = fb[i]
95 rgb_out[3 * i + 0] = cell & 0xFF
96 rgb_out[3 * i + 1] = (cell >> 8) & 0xFF
97 rgb_out[3 * i + 2] = (cell >> 16) & 0xFF
98 i = i + 1
99 }
100 return n
101}
102
103// ===== Present =====================================================
104//
105// Delivers a framebuffer to `dest_kind`. For PPM_BUFFER: writes the
106// full PPM P6 file (header + RGB bytes) into `out` of capacity
107// `out_cap`; returns total bytes written. For DISCARD: returns 0.
108// For KMS_DIRECT / NISHI_OS: returns NX_SWAP_ERR_NOT_IMPL today; the
109// stub is substrate-honest -- never silently no-ops.
110//
111// The intermediate RGB-interleaved buffer for PPM is allocated here
112// via sys_mmap; caller's `out` only holds the final PPM bytes.
113func nx_swap_present(
114 fb: *i64,
115 w: nx_int,
116 h: nx_int,
117 dest_kind: i64,
118 out: *u8,
119 out_cap: i64
120) -> i64 {
121 if nx_swap_dest_is_valid(dest_kind) != 1 { return NX_SWAP_ERR_BAD_DEST }
122 if w <= 0 { return NX_SWAP_ERR_BAD_SIZE }
123 if h <= 0 { return NX_SWAP_ERR_BAD_SIZE }
124
125 if dest_kind == NX_SWAP_DEST_DISCARD {
126 return 0
127 }
128
129 if dest_kind == NX_SWAP_DEST_PPM_BUFFER {
130 let data_bytes: i64 = w * h * 3
131 if out_cap < 64 + data_bytes { return NX_SWAP_ERR_SHORT_BUF }
132 let rgb_buf: *u8 = sys_mmap(data_bytes)
133 let n: i64 = nx_swap_extract_rgb_bytes(fb, w, h, rgb_buf)
134 if n != w * h { return NX_SWAP_ERR_BAD_SIZE }
135 return ppm_write_p6(out, out_cap, w, h, rgb_buf)
136 }
137
138 if dest_kind == NX_SWAP_DEST_KMS_DIRECT {
139 return NX_SWAP_ERR_NOT_IMPL
140 }
141
142 if dest_kind == NX_SWAP_DEST_NISHI_OS {
143 return NX_SWAP_ERR_NOT_IMPL
144 }
145
146 return NX_SWAP_ERR_BAD_DEST
147}