code wiki / (root) / nx_swapchain.nx

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}