// ============================================================================ // lib/framebuffer.cpp - Early kernel framebuffer console // // Renders text directly to the linear framebuffer using a minimal // built-in 8x16 bitmap font (PC VGA style, ASCII 32-126). // ============================================================================ #include #include namespace kcon { // ── Character cell dimensions ────────────────────────────────────────────── static constexpr uint32_t CHAR_W = 8; // Font glyph width in pixels static constexpr uint32_t CHAR_H = 16; // Font glyph height in pixels static constexpr uint32_t TAB_WIDTH = 4; // Tab stop width in character cells // ── Colors (0x00RRGGBB) ──────────────────────────────────────────────────── static constexpr uint32_t FG_COLOR = CONFIG_FBCON_FG_COLOR; // Light grey static constexpr uint32_t BG_COLOR = CONFIG_FBCON_BG_COLOR; // Dark blue // ── Console state ────────────────────────────────────────────────────────── // // All mutable state lives in one struct so callers can reason about it as // a unit and we avoid scattered bare globals. #pragma message("when i add drivers abstract, move it to another console interface and use framebuffer as driver") struct Console { public: uint32_t* fb = nullptr; // Framebuffer base (as 32-bit pixels) uint32_t width = 0; // Pixels per row uint32_t height = 0; // Rows in pixels uint32_t pitch = 0; // Bytes per scanline (>= width * 4) uint32_t col = 0; // Cursor column in character cells uint32_t row = 0; // Cursor row in character cells bool is_bgr = false; // C++23 multi-dimensional subscript: direct reference to pixel (x, y). // pitch is in bytes; each pixel is 4 bytes. No bounds checking — callers // must validate coordinates before calling (put_pixel does this). [[nodiscard]] uint32_t& operator[](uint32_t x, uint32_t y) noexcept { auto* row_ptr = reinterpret_cast( reinterpret_cast(fb) + static_cast(y) * pitch); return row_ptr[x]; } }; static Console g_con{}; // ── Minimal 8x16 bitmap font (ASCII 32–126) ─────────────────────────────── // Each character is 16 bytes (one byte per row, 8 pixels wide). // We store a blank (space) for anything outside the range. // clang-format off static const uint8_t g_font[][16] = { // 32: space {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 33: ! {0x00,0x00,0x18,0x3C,0x3C,0x3C,0x18,0x18,0x18,0x00,0x18,0x18,0x00,0x00,0x00,0x00}, // 34: " {0x00,0x66,0x66,0x66,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 35: # {0x00,0x00,0x00,0x6C,0x6C,0xFE,0x6C,0x6C,0xFE,0x6C,0x6C,0x00,0x00,0x00,0x00,0x00}, // 36: $ {0x00,0x10,0x10,0x7C,0xD6,0xD0,0x7C,0x16,0xD6,0x7C,0x10,0x10,0x00,0x00,0x00,0x00}, // 37: % {0x00,0x00,0x00,0x00,0xC2,0xC6,0x0C,0x18,0x30,0x60,0xC6,0x86,0x00,0x00,0x00,0x00}, // 38: & {0x00,0x00,0x38,0x6C,0x6C,0x38,0x76,0xDC,0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00}, // 39: ' {0x00,0x30,0x30,0x30,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 40: ( {0x00,0x00,0x0C,0x18,0x30,0x30,0x30,0x30,0x30,0x30,0x18,0x0C,0x00,0x00,0x00,0x00}, // 41: ) {0x00,0x00,0x30,0x18,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x18,0x30,0x00,0x00,0x00,0x00}, // 42: * {0x00,0x00,0x00,0x00,0x00,0x66,0x3C,0xFF,0x3C,0x66,0x00,0x00,0x00,0x00,0x00,0x00}, // 43: + {0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x7E,0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00}, // 44: , {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x18,0x30,0x00,0x00,0x00}, // 45: - {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 46: . {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00}, // 47: / {0x00,0x00,0x00,0x00,0x02,0x06,0x0C,0x18,0x30,0x60,0xC0,0x80,0x00,0x00,0x00,0x00}, // 48-57: 0-9 {0x00,0x00,0x7C,0xC6,0xC6,0xCE,0xDE,0xF6,0xE6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00}, {0x00,0x00,0x18,0x38,0x78,0x18,0x18,0x18,0x18,0x18,0x18,0x7E,0x00,0x00,0x00,0x00}, {0x00,0x00,0x7C,0xC6,0x06,0x0C,0x18,0x30,0x60,0xC0,0xC6,0xFE,0x00,0x00,0x00,0x00}, {0x00,0x00,0x7C,0xC6,0x06,0x06,0x3C,0x06,0x06,0x06,0xC6,0x7C,0x00,0x00,0x00,0x00}, {0x00,0x00,0x0C,0x1C,0x3C,0x6C,0xCC,0xFE,0x0C,0x0C,0x0C,0x1E,0x00,0x00,0x00,0x00}, {0x00,0x00,0xFE,0xC0,0xC0,0xC0,0xFC,0x06,0x06,0x06,0xC6,0x7C,0x00,0x00,0x00,0x00}, {0x00,0x00,0x38,0x60,0xC0,0xC0,0xFC,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00}, {0x00,0x00,0xFE,0xC6,0x06,0x06,0x0C,0x18,0x30,0x30,0x30,0x30,0x00,0x00,0x00,0x00}, {0x00,0x00,0x7C,0xC6,0xC6,0xC6,0x7C,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00}, {0x00,0x00,0x7C,0xC6,0xC6,0xC6,0x7E,0x06,0x06,0x06,0x0C,0x78,0x00,0x00,0x00,0x00}, // 58: : {0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00,0x00}, // 59: ; {0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x18,0x18,0x30,0x00,0x00,0x00,0x00}, // 60: < {0x00,0x00,0x00,0x06,0x0C,0x18,0x30,0x60,0x30,0x18,0x0C,0x06,0x00,0x00,0x00,0x00}, // 61: = {0x00,0x00,0x00,0x00,0x00,0x7E,0x00,0x00,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 62: > {0x00,0x00,0x00,0x60,0x30,0x18,0x0C,0x06,0x0C,0x18,0x30,0x60,0x00,0x00,0x00,0x00}, // 63: ? {0x00,0x00,0x7C,0xC6,0xC6,0x0C,0x18,0x18,0x18,0x00,0x18,0x18,0x00,0x00,0x00,0x00}, // 64: @ {0x00,0x00,0x7C,0xC6,0xC6,0xDE,0xDE,0xDE,0xDC,0xC0,0xC0,0x7C,0x00,0x00,0x00,0x00}, // 65-90: A-Z {0x00,0x00,0x10,0x38,0x6C,0xC6,0xC6,0xFE,0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00}, {0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x66,0x66,0x66,0x66,0xFC,0x00,0x00,0x00,0x00}, {0x00,0x00,0x3C,0x66,0xC2,0xC0,0xC0,0xC0,0xC0,0xC2,0x66,0x3C,0x00,0x00,0x00,0x00}, {0x00,0x00,0xF8,0x6C,0x66,0x66,0x66,0x66,0x66,0x66,0x6C,0xF8,0x00,0x00,0x00,0x00}, {0x00,0x00,0xFE,0x66,0x62,0x68,0x78,0x68,0x60,0x62,0x66,0xFE,0x00,0x00,0x00,0x00}, {0x00,0x00,0xFE,0x66,0x62,0x68,0x78,0x68,0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00}, {0x00,0x00,0x3C,0x66,0xC2,0xC0,0xC0,0xDE,0xC6,0xC6,0x66,0x3A,0x00,0x00,0x00,0x00}, {0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xFE,0xC6,0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00}, {0x00,0x00,0x3C,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00}, {0x00,0x00,0x1E,0x0C,0x0C,0x0C,0x0C,0x0C,0xCC,0xCC,0xCC,0x78,0x00,0x00,0x00,0x00}, {0x00,0x00,0xE6,0x66,0x66,0x6C,0x78,0x78,0x6C,0x66,0x66,0xE6,0x00,0x00,0x00,0x00}, {0x00,0x00,0xF0,0x60,0x60,0x60,0x60,0x60,0x60,0x62,0x66,0xFE,0x00,0x00,0x00,0x00}, {0x00,0x00,0xC6,0xEE,0xFE,0xFE,0xD6,0xC6,0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00}, {0x00,0x00,0xC6,0xE6,0xF6,0xFE,0xDE,0xCE,0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00}, {0x00,0x00,0x7C,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00}, {0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x60,0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00}, {0x00,0x00,0x7C,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xD6,0xDE,0x7C,0x0C,0x0E,0x00,0x00}, {0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x6C,0x66,0x66,0x66,0xE6,0x00,0x00,0x00,0x00}, {0x00,0x00,0x7C,0xC6,0xC6,0x60,0x38,0x0C,0x06,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00}, {0x00,0x00,0xFF,0xDB,0x99,0x18,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00}, {0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00}, {0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x6C,0x38,0x10,0x00,0x00,0x00,0x00}, {0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xD6,0xD6,0xD6,0xFE,0xEE,0x6C,0x00,0x00,0x00,0x00}, {0x00,0x00,0xC6,0xC6,0x6C,0x7C,0x38,0x38,0x7C,0x6C,0xC6,0xC6,0x00,0x00,0x00,0x00}, {0x00,0x00,0xCC,0xCC,0xCC,0xCC,0x78,0x30,0x30,0x30,0x30,0x78,0x00,0x00,0x00,0x00}, {0x00,0x00,0xFE,0xC6,0x86,0x0C,0x18,0x30,0x60,0xC2,0xC6,0xFE,0x00,0x00,0x00,0x00}, // 91: [ {0x00,0x00,0x3C,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x3C,0x00,0x00,0x00,0x00}, // 92: backslash {0x00,0x00,0x00,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x02,0x00,0x00,0x00,0x00,0x00}, // 93: ] {0x00,0x00,0x3C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x3C,0x00,0x00,0x00,0x00}, // 94: ^ {0x10,0x38,0x6C,0xC6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 95: _ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00}, // 96: ` {0x00,0x30,0x18,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 97-122: a-z {0x00,0x00,0x00,0x00,0x00,0x78,0x0C,0x7C,0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00}, {0x00,0x00,0xE0,0x60,0x60,0x78,0x6C,0x66,0x66,0x66,0x66,0x7C,0x00,0x00,0x00,0x00}, {0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xC0,0xC0,0xC0,0xC6,0x7C,0x00,0x00,0x00,0x00}, {0x00,0x00,0x1C,0x0C,0x0C,0x3C,0x6C,0xCC,0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00}, {0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xFE,0xC0,0xC0,0xC6,0x7C,0x00,0x00,0x00,0x00}, {0x00,0x00,0x38,0x6C,0x64,0x60,0xF0,0x60,0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00}, {0x00,0x00,0x00,0x00,0x00,0x76,0xCC,0xCC,0xCC,0xCC,0xCC,0x7C,0x0C,0xCC,0x78,0x00}, {0x00,0x00,0xE0,0x60,0x60,0x6C,0x76,0x66,0x66,0x66,0x66,0xE6,0x00,0x00,0x00,0x00}, {0x00,0x00,0x18,0x18,0x00,0x38,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00}, {0x00,0x00,0x06,0x06,0x00,0x0E,0x06,0x06,0x06,0x06,0x06,0x06,0x66,0x66,0x3C,0x00}, {0x00,0x00,0xE0,0x60,0x60,0x66,0x6C,0x78,0x78,0x6C,0x66,0xE6,0x00,0x00,0x00,0x00}, {0x00,0x00,0x38,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00}, {0x00,0x00,0x00,0x00,0x00,0xEC,0xFE,0xD6,0xD6,0xD6,0xD6,0xC6,0x00,0x00,0x00,0x00}, {0x00,0x00,0x00,0x00,0x00,0xDC,0x66,0x66,0x66,0x66,0x66,0x66,0x00,0x00,0x00,0x00}, {0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00}, {0x00,0x00,0x00,0x00,0x00,0xDC,0x66,0x66,0x66,0x66,0x66,0x7C,0x60,0x60,0xF0,0x00}, {0x00,0x00,0x00,0x00,0x00,0x76,0xCC,0xCC,0xCC,0xCC,0xCC,0x7C,0x0C,0x0C,0x1E,0x00}, {0x00,0x00,0x00,0x00,0x00,0xDC,0x76,0x66,0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00}, {0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0x60,0x38,0x0C,0xC6,0x7C,0x00,0x00,0x00,0x00}, {0x00,0x00,0x10,0x30,0x30,0xFC,0x30,0x30,0x30,0x30,0x36,0x1C,0x00,0x00,0x00,0x00}, {0x00,0x00,0x00,0x00,0x00,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00}, {0x00,0x00,0x00,0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0x6C,0x38,0x00,0x00,0x00,0x00}, {0x00,0x00,0x00,0x00,0x00,0xC6,0xC6,0xD6,0xD6,0xD6,0xFE,0x6C,0x00,0x00,0x00,0x00}, {0x00,0x00,0x00,0x00,0x00,0xC6,0x6C,0x38,0x38,0x38,0x6C,0xC6,0x00,0x00,0x00,0x00}, {0x00,0x00,0x00,0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x7E,0x06,0x0C,0xF8,0x00}, {0x00,0x00,0x00,0x00,0x00,0xFE,0xCC,0x18,0x30,0x60,0xC6,0xFE,0x00,0x00,0x00,0x00}, // 123: { {0x00,0x00,0x0E,0x18,0x18,0x18,0x70,0x18,0x18,0x18,0x18,0x0E,0x00,0x00,0x00,0x00}, // 124: | {0x00,0x00,0x18,0x18,0x18,0x18,0x00,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00,0x00}, // 125: } {0x00,0x00,0x70,0x18,0x18,0x18,0x0E,0x18,0x18,0x18,0x18,0x70,0x00,0x00,0x00,0x00}, // 126: ~ {0x00,0x00,0x76,0xDC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, }; // clang-format on static constexpr int FONT_FIRST = 32; static constexpr int FONT_LAST = 126; // ── Pixel helpers ────────────────────────────────────────────────────────── static void put_pixel(uint32_t x, uint32_t y, uint32_t rgb) noexcept { if (x >= g_con.width || y >= g_con.height) return; // Swap R and B channels if the framebuffer uses BGR layout. const uint32_t color = g_con.is_bgr ? ((rgb & 0xFFu) << 16) | (rgb & 0xFF00u) | ((rgb >> 16) & 0xFFu) : rgb; g_con[x, y] = color; // C++23 multi-dimensional subscript } // ── Scroll the screen up by one character row ────────────────────────────── static void scroll() noexcept { const uint32_t shift = CHAR_H * g_con.pitch; const uint32_t total = g_con.height * g_con.pitch; auto* const fb_bytes = reinterpret_cast(g_con.fb); // Shift everything up by CHAR_H rows (raw byte memmove equivalent). for (uint32_t i = 0; i < total - shift; ++i) fb_bytes[i] = fb_bytes[i + shift]; // Fill the newly exposed bottom rows with the background color. for (uint32_t row = g_con.height - CHAR_H; row < g_con.height; ++row) for (uint32_t col = 0; col < g_con.width; ++col) put_pixel(col, row, BG_COLOR); } // ── Render a glyph at character-cell position (col, row) ────────────────── static void render_char(uint32_t col, uint32_t row, char c) noexcept { // Treat char as unsigned to avoid negative indices for high-ASCII. int idx = static_cast(static_cast(c)) - FONT_FIRST; if (idx < 0 || idx > (FONT_LAST - FONT_FIRST)) idx = 0; // fall back to space const uint32_t px = col * CHAR_W; const uint32_t py = row * CHAR_H; for (uint32_t y = 0; y < CHAR_H; ++y) { const uint8_t bits = g_font[idx][y]; for (uint32_t x = 0; x < CHAR_W; ++x) { const uint32_t color = (bits & (0x80u >> x)) ? FG_COLOR : BG_COLOR; put_pixel(px + x, py + y, color); } } } // ── Public API ───────────────────────────────────────────────────────────── void init(const FramebufferInfo& fb) { g_con.fb = reinterpret_cast(fb.base); g_con.width = fb.width; g_con.height = fb.height; g_con.pitch = fb.pitch; g_con.is_bgr = (fb.format == PixelFormat::BGR); g_con.col = 0; g_con.row = 0; clear(); } void clear() { for (uint32_t y = 0; y < g_con.height; ++y) for (uint32_t x = 0; x < g_con.width; ++x) put_pixel(x, y, BG_COLOR); g_con.col = 0; g_con.row = 0; } void putchar(char c) { const uint32_t max_cols = g_con.width / CHAR_W; const uint32_t max_rows = g_con.height / CHAR_H; if (c == '\n') { g_con.col = 0; ++g_con.row; } else if (c == '\r') { g_con.col = 0; } else if (c == '\t') { // Advance to the next TAB_WIDTH-aligned column. g_con.col = (g_con.col + TAB_WIDTH) & ~(TAB_WIDTH - 1u); } else { render_char(g_con.col, g_con.row, c); ++g_con.col; } if (g_con.col >= max_cols) { g_con.col = 0; ++g_con.row; } if (g_con.row >= max_rows) { scroll(); g_con.row = max_rows - 1; } } void puts(const char* s) { if (!s) return; while (*s) putchar(*s++); } void println(const char* s) { puts(s); putchar('\n'); } void put_hex(uint64_t val) { puts("0x"); bool leading = true; for (int i = 60; i >= 0; i -= 4) { const uint8_t nibble = static_cast((val >> i) & 0xFu); if (nibble == 0 && leading && i > 0) continue; leading = false; putchar(nibble < 10 ? '0' + nibble : 'A' + nibble - 10); } if (leading) putchar('0'); } void put_dec(uint64_t val) { if (val == 0) { putchar('0'); return; } char buf[20]; int len = 0; while (val > 0) { buf[len++] = static_cast('0' + val % 10); val /= 10; } for (int j = len - 1; j >= 0; --j) putchar(buf[j]); } void kprintf(const char* fmt, ...) { va_list args; va_start(args, fmt); while (*fmt) { if (*fmt != '%') { putchar(*fmt++); continue; } ++fmt; // skip '%' switch (*fmt) { case 's': { const char* s = va_arg(args, const char*); puts(s ? s : "(null)"); break; } case 'd': { int64_t v = va_arg(args, int64_t); if (v < 0) { putchar('-'); v = -v; } put_dec(static_cast(v)); break; } case 'u': { put_dec(va_arg(args, uint64_t)); break; } case 'x': case 'p': { put_hex(va_arg(args, uint64_t)); break; } case '%': putchar('%'); break; case '\0': goto done; default: putchar('%'); putchar(*fmt); break; } ++fmt; } done: va_end(args); } } // namespace kcon