From 252bbf67aaf50efddc7846845c759c18b119f5e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Kryczka?= <60490378+kryczkal@users.noreply.github.com> Date: Fri, 5 Dec 2025 22:49:02 +0100 Subject: [PATCH 01/56] Kryczkal/io 01 (#226) Super small PR for making a generic SerialDriver. This was created with the intention of integrating the IO Stream API / MultiplexerWriter into the current Trace framework / print / etc. But I decided what's already there, can just stay for now. I'm just going to implement the new Console using this API. This is the leftover from this idea, before I stopped myself. The lone generic UART16550 driver (can be used to writing to QEMU) --- kernel/arch/x86_64/src/hal/impl/io.hpp | 29 +++++ kernel/src/drivers/serial/uart_16550.cpp | 144 +++++++++++++++++++++++ kernel/src/drivers/serial/uart_16550.hpp | 64 ++++++++++ kernel/src/hal/api/io.hpp | 25 ++++ kernel/src/hal/io.hpp | 15 +++ kernel/src/io/error.hpp | 4 +- kernel/src/io/register.hpp | 49 ++++++++ kernel/src/modules/memory.hpp | 1 + 8 files changed, 329 insertions(+), 2 deletions(-) create mode 100644 kernel/arch/x86_64/src/hal/impl/io.hpp create mode 100644 kernel/src/drivers/serial/uart_16550.cpp create mode 100644 kernel/src/drivers/serial/uart_16550.hpp create mode 100644 kernel/src/hal/api/io.hpp create mode 100644 kernel/src/hal/io.hpp create mode 100644 kernel/src/io/register.hpp diff --git a/kernel/arch/x86_64/src/hal/impl/io.hpp b/kernel/arch/x86_64/src/hal/impl/io.hpp new file mode 100644 index 00000000..0e4e2c5a --- /dev/null +++ b/kernel/arch/x86_64/src/hal/impl/io.hpp @@ -0,0 +1,29 @@ +#ifndef KERNEL_ARCH_X86_64_SRC_HAL_IMPL_IO_HPP_ +#define KERNEL_ARCH_X86_64_SRC_HAL_IMPL_IO_HPP_ + +#include + +#include + +#include "include/io.hpp" + +namespace arch +{ + +template +void IoWrite(size_t addr, T value) +{ + ASSERT_LE(addr, std::numeric_limits::max()); + io::out(static_cast(addr), value); +} + +template +T IoRead(size_t addr) +{ + ASSERT_LE(addr, std::numeric_limits::max()); + return io::in(static_cast(addr)); +} + +} // namespace arch + +#endif /* KERNEL_ARCH_X86_64_SRC_HAL_IMPL_IO_HPP_ */ diff --git a/kernel/src/drivers/serial/uart_16550.cpp b/kernel/src/drivers/serial/uart_16550.cpp new file mode 100644 index 00000000..ddb722ff --- /dev/null +++ b/kernel/src/drivers/serial/uart_16550.cpp @@ -0,0 +1,144 @@ +#include "drivers/serial/uart_16550.hpp" + +#include +#include + +#include +#include + +namespace Drivers::Serial +{ + +// ---------------------------------------------------------------------------- +// Register Offsets & Bit Definitions +// ---------------------------------------------------------------------------- + +static constexpr size_t kRegData = 0; +static constexpr size_t kRegIntEnable = 1; +static constexpr size_t kRegFifoCtrl = 2; +static constexpr size_t kRegLineCtrl = 3; +static constexpr size_t kRegModemCtrl = 4; +static constexpr size_t kRegLineStatus = 5; + +// Line Control Register Bits +static constexpr u8 kLcrDataBits8 = 0x03; // 8 bits per character +static constexpr u8 kLcrStopBits1 = 0x00; // 1 stop bit +static constexpr u8 kLcrParityNone = 0x00; // No parity +static constexpr u8 kLcrDlab = 0x80; // Divisor Latch Access Bit + +// FIFO Control Register Bits +static constexpr u8 kFcrEnable = 0x01; +static constexpr u8 kFcrClearRx = 0x02; +static constexpr u8 kFcrClearTx = 0x04; +static constexpr u8 kFcrTrigger14 = 0xC0; // 14-byte trigger level + +// Modem Control Register Bits +static constexpr u8 kMcrDtr = 0x01; // Data Terminal Ready +static constexpr u8 kMcrRts = 0x02; // Request To Send +static constexpr u8 kMcrOut2 = 0x08; // Aux Output 2 (Used for IRQs) + +// Line Status Register Bits +static constexpr u8 kLsrDataReady = 0x01; +static constexpr u8 kLsrTxEmpty = 0x20; // Transmit Holding Register Empty + +// Base clock for UART (115200 Hz) +static constexpr u32 kUartBaseClock = 115200; + +// ---------------------------------------------------------------------------- +// Implementation +// ---------------------------------------------------------------------------- + +Uart16550::Uart16550(size_t base_addr) + : data_reg_(base_addr, kRegData), + int_enable_reg_(base_addr, kRegIntEnable), + fifo_ctrl_reg_(base_addr, kRegFifoCtrl), + line_ctrl_reg_(base_addr, kRegLineCtrl), + modem_ctrl_reg_(base_addr, kRegModemCtrl), + line_status_reg_(base_addr, kRegLineStatus) +{ +} + +void Uart16550::Init(uint32_t baud_rate) +{ + // Disable interrupts initially + int_enable_reg_.Write(0x00); + + // Set Baud Rate + SetBaudRate(baud_rate); + + // Set Frame Format: 8 bits, 1 stop bit, no parity (8N1) + // We clear DLAB here as well (bit 7 = 0) + line_ctrl_reg_.Write(kLcrDataBits8 | kLcrStopBits1 | kLcrParityNone); + + // Enable and Clear FIFOs + fifo_ctrl_reg_.Write(kFcrEnable | kFcrClearRx | kFcrClearTx | kFcrTrigger14); + + // Enable RTS/DTR and Out2 (needed for interrupts on some boards, generally good practice) + modem_ctrl_reg_.Write(kMcrDtr | kMcrRts | kMcrOut2); +} + +void Uart16550::SetBaudRate(uint32_t baud_rate) +{ + R_ASSERT_NOT_ZERO(baud_rate); + + u32 divisor = kUartBaseClock / baud_rate; + + // Enable DLAB to set divisor + u8 lcr = line_ctrl_reg_.Read(); + line_ctrl_reg_.Write(lcr | kLcrDlab); + + // Write divisor (Low byte then High byte) + data_reg_.Write(static_cast(divisor & kBitMask8)); + int_enable_reg_.Write(static_cast((divisor >> 8) & kBitMask8)); + + // Disable DLAB (restore previous LCR state, ensuring bit 7 is 0) + line_ctrl_reg_.Write(lcr & ~kLcrDlab); +} + +bool Uart16550::IsTransmitEmpty() const { return (line_status_reg_.Read() & kLsrTxEmpty) != 0; } + +bool Uart16550::IsDataReady() const { return (line_status_reg_.Read() & kLsrDataReady) != 0; } + +IO::IoResult Uart16550::Read(std::span buffer) +{ + size_t bytes_read = 0; + + for (size_t i = 0; i < buffer.size(); ++i) { + // If the hardware has no data, we stop immediately. + if (!IsDataReady()) { + // If we haven't read anything yet, tell the caller to try again later. + if (bytes_read == 0) { + return std::unexpected(IO::Error::Retry); + } + // Otherwise, return what we managed to grab so far (short read). + break; + } + + buffer[i] = static_cast(data_reg_.Read()); + bytes_read++; + } + + return bytes_read; +} + +IO::IoResult Uart16550::Write(std::span buffer) +{ + size_t bytes_written = 0; + + for (const byte b : buffer) { + // If the transmit holding register/FIFO is full, stop. + if (!IsTransmitEmpty()) { + if (bytes_written == 0) { + return std::unexpected(IO::Error::Retry); + } + break; + } + + data_reg_.Write(static_cast(b)); + bytes_written++; + } + + return bytes_written; +} + +} // namespace Drivers::Serial diff --git a/kernel/src/drivers/serial/uart_16550.hpp b/kernel/src/drivers/serial/uart_16550.hpp new file mode 100644 index 00000000..847a6c08 --- /dev/null +++ b/kernel/src/drivers/serial/uart_16550.hpp @@ -0,0 +1,64 @@ +#ifndef KERNEL_SRC_DRIVERS_SERIAL_UART_16550_HPP_ +#define KERNEL_SRC_DRIVERS_SERIAL_UART_16550_HPP_ + +#include +#include +#include + +namespace Drivers::Serial +{ + +class Uart16550 final : public IO::IStream +{ + public: + // Standard COM ports base addresses for reference + static constexpr size_t kCom1Base = 0x3F8; + static constexpr size_t kCom2Base = 0x2F8; + + explicit Uart16550(size_t base_addr); + + /** + * @brief Initializes the UART controller. + * Sets baud rate, frame format (8N1), and enables FIFO. + * @param baud_rate Target baud rate (e.g., 38400, 115200). + */ + void Init(uint32_t baud_rate = 38400); + + // IO::IStream implementation + IO::IoResult Write(std::span buffer) override; + IO::IoResult Read(std::span buffer) override; + + private: + // ------------------------------ + // Registers + // ------------------------------ + + // Read/Write Data (DLAB=0) or Divisor Low (DLAB=1) + IO::Register data_reg_; + + // Interrupt Enable (DLAB=0) or Divisor High (DLAB=1) + IO::Register int_enable_reg_; + + // FIFO Control (Write) / ISR (Read) + IO::Register fifo_ctrl_reg_; + + // Line Control Register + IO::Register line_ctrl_reg_; + + // Modem Control Register + IO::Register modem_ctrl_reg_; + + // Line Status Register + IO::Register line_status_reg_; + + // ------------------------------ + // Helpers + // ------------------------------ + NODISCARD bool IsTransmitEmpty() const; + NODISCARD bool IsDataReady() const; + void SetBaudRate(uint32_t baud_rate); +}; + +} // namespace Drivers::Serial + +#endif // KERNEL_SRC_DRIVERS_SERIAL_UART_16550_HPP_ diff --git a/kernel/src/hal/api/io.hpp b/kernel/src/hal/api/io.hpp new file mode 100644 index 00000000..0e8a43d0 --- /dev/null +++ b/kernel/src/hal/api/io.hpp @@ -0,0 +1,25 @@ +#ifndef KERNEL_SRC_HAL_API_IO_HPP_ +#define KERNEL_SRC_HAL_API_IO_HPP_ + +#include +#include + +namespace arch +{ + +template +concept IoT = std::is_same_v || std::is_same_v || std::is_same_v; + +template +void IoWrite(size_t addr, T value); +template +T IoRead(size_t addr); + +} // namespace arch + +namespace hal +{ +using arch::IoT; +} + +#endif // KERNEL_SRC_HAL_API_IO_HPP_ diff --git a/kernel/src/hal/io.hpp b/kernel/src/hal/io.hpp new file mode 100644 index 00000000..1d71bfcb --- /dev/null +++ b/kernel/src/hal/io.hpp @@ -0,0 +1,15 @@ +#ifndef KERNEL_SRC_HAL_IO_HPP_ +#define KERNEL_SRC_HAL_IO_HPP_ + +#include + +namespace hal +{ + +using arch::IoRead; +using arch::IoT; +using arch::IoWrite; + +} // namespace hal + +#endif // KERNEL_SRC_HAL_IO_HPP_ diff --git a/kernel/src/io/error.hpp b/kernel/src/io/error.hpp index 08bb0dca..9607d92e 100644 --- a/kernel/src/io/error.hpp +++ b/kernel/src/io/error.hpp @@ -1,7 +1,7 @@ #ifndef KERNEL_SRC_IO_ERROR_HPP_ #define KERNEL_SRC_IO_ERROR_HPP_ -namespace Io +namespace IO { enum class Error { @@ -14,4 +14,4 @@ enum class Error { } -#endif /* KERNEL_SRC_IO_ERROR_HPP_ */ +#endif // KERNEL_SRC_IO_ERROR_HPP_ diff --git a/kernel/src/io/register.hpp b/kernel/src/io/register.hpp new file mode 100644 index 00000000..98a2f1d4 --- /dev/null +++ b/kernel/src/io/register.hpp @@ -0,0 +1,49 @@ +#ifndef KERNEL_SRC_IO_REGISTER_HPP_ +#define KERNEL_SRC_IO_REGISTER_HPP_ + +#include "hal/io.hpp" + +namespace IO +{ + +/** + * @class Register + * @brief Abstraction for a specific hardware I/O register or port. + * + * This class encapsulates the address of a hardware register (e.g., an I/O port number + * on x86 or a physical memory address on MMIO architectures) and provides a type-safe + * interface for data access. + * + * It acts as a wrapper around the Hardware Abstraction Layer (HAL), delegating + * specific read/write instructions (like `inb`/`outb` or volatile pointer access) + * to `hal::IoRead` and `hal::IoWrite`. This allows device drivers to remain + * architecture-agnostic. + * + * @note Operations are restricted to valid I/O types (u8, u16, u32) via the + * `hal::IoT` concept. + */ +class Register +{ + public: + explicit Register(size_t addr) : addr_{addr} {}; + Register(size_t base_addr, size_t offset) : addr_{base_addr + offset} {}; + + template + void Write(T val) const + { + hal::IoWrite(addr_, val); + } + + template + T Read() const + { + return hal::IoRead(addr_); + } + + private: + size_t addr_; +}; + +} // namespace IO + +#endif // KERNEL_SRC_IO_REGISTER_HPP_ diff --git a/kernel/src/modules/memory.hpp b/kernel/src/modules/memory.hpp index e0de6af8..592c8b1d 100644 --- a/kernel/src/modules/memory.hpp +++ b/kernel/src/modules/memory.hpp @@ -5,6 +5,7 @@ #include "boot_args.hpp" #include "hal/boot_args.hpp" +#include "hal/io.hpp" #include "hal/mmu.hpp" #include "hal/tlb.hpp" #include "mem/heap.hpp" From bfae95dea0afdd66a7a1c68e0c7cf69c9cc6816c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Kryczka?= <60490378+kryczkal@users.noreply.github.com> Date: Sat, 6 Dec 2025 12:20:51 +0100 Subject: [PATCH 02/56] Kryczkal/framebuffer (#227) ## Problem We want to have a graphic IO with the kernel. ## Solution image - Implement a driver for Framebuffer. - Enable framebuffer finding in boot modules. - Init Framebuffer from KernelArgs - Implement a Video module - Create an abstract non-owning Surface type (for double/tripple buffering) - Create a painter class that knows how to paint a Surface. - Implement Graphics related primitives --- .../x86_64/boot/lib/abi/transition_data.hpp | 19 ++ .../x86_64/boot/lib/multiboot2/multiboot2.h | 4 +- .../loader_32/build/multiboot_header.nasm | 4 +- .../arch/x86_64/boot/loader_64/src/main.cpp | 89 ++++++++- kernel/arch/x86_64/src/drivers/vga/vga.cpp | 187 ------------------ kernel/arch/x86_64/src/drivers/vga/vga.hpp | 25 --- kernel/arch/x86_64/src/hal/impl/terminal.hpp | 10 - kernel/src/boot_args.cpp | 48 +++++ kernel/src/boot_args.hpp | 21 +- kernel/src/drivers/video/framebuffer.cpp | 33 ++++ kernel/src/drivers/video/framebuffer.hpp | 41 ++++ kernel/src/graphics/color.hpp | 38 ++++ kernel/src/graphics/painter.cpp | 118 +++++++++++ kernel/src/graphics/painter.hpp | 39 ++++ kernel/src/graphics/surface.hpp | 92 +++++++++ kernel/src/hal/api/boot_args.hpp | 19 +- kernel/src/init.cpp | 15 +- kernel/src/kernel.cpp | 31 +++ kernel/src/modules/video.cpp | 36 ++++ kernel/src/modules/video.hpp | 34 ++++ kernel/src/trace_framework.hpp | 5 + 21 files changed, 660 insertions(+), 248 deletions(-) delete mode 100644 kernel/arch/x86_64/src/drivers/vga/vga.cpp delete mode 100644 kernel/arch/x86_64/src/drivers/vga/vga.hpp create mode 100644 kernel/src/drivers/video/framebuffer.cpp create mode 100644 kernel/src/drivers/video/framebuffer.hpp create mode 100644 kernel/src/graphics/color.hpp create mode 100644 kernel/src/graphics/painter.cpp create mode 100644 kernel/src/graphics/painter.hpp create mode 100644 kernel/src/graphics/surface.hpp create mode 100644 kernel/src/modules/video.cpp create mode 100644 kernel/src/modules/video.hpp diff --git a/kernel/arch/x86_64/boot/lib/abi/transition_data.hpp b/kernel/arch/x86_64/boot/lib/abi/transition_data.hpp index fc3f495c..8944153e 100644 --- a/kernel/arch/x86_64/boot/lib/abi/transition_data.hpp +++ b/kernel/arch/x86_64/boot/lib/abi/transition_data.hpp @@ -18,14 +18,33 @@ struct alignas(64) TransitionData { }; struct PACK alignas(64) KernelArguments { + /// Kernel Mem Layout u64 kernel_start_addr; u64 kernel_end_addr; + /// VMem u64 pml_4_table_phys_addr; + /// Memory Bitmap u64 mem_info_bitmap_addr; u64 mem_info_total_pages; + /// Framebuffer + u64 fb_addr; + u32 fb_width; + u32 fb_height; + u32 fb_pitch; + u32 fb_bpp; // Bits per pixel + + // RGB Format + u8 fb_red_pos; + u8 fb_red_mask; + u8 fb_green_pos; + u8 fb_green_mask; + u8 fb_blue_pos; + u8 fb_blue_mask; + + /// Multiboot u64 multiboot_info_addr; u64 multiboot_header_start_addr; u64 multiboot_header_end_addr; diff --git a/kernel/arch/x86_64/boot/lib/multiboot2/multiboot2.h b/kernel/arch/x86_64/boot/lib/multiboot2/multiboot2.h index d08dcba4..0e1879c2 100644 --- a/kernel/arch/x86_64/boot/lib/multiboot2/multiboot2.h +++ b/kernel/arch/x86_64/boot/lib/multiboot2/multiboot2.h @@ -271,9 +271,7 @@ struct TagFramebufferCommon { static constexpr u8 kFramebufferTypeEgaText = 2; }; -struct TagFramebuffer { - struct TagFramebufferCommon common; - +struct TagFramebuffer : public TagFramebufferCommon { union { struct { u16 framebuffer_palette_num_colors; diff --git a/kernel/arch/x86_64/boot/loader_32/build/multiboot_header.nasm b/kernel/arch/x86_64/boot/loader_32/build/multiboot_header.nasm index 97663235..fb43a08f 100644 --- a/kernel/arch/x86_64/boot/loader_32/build/multiboot_header.nasm +++ b/kernel/arch/x86_64/boot/loader_32/build/multiboot_header.nasm @@ -53,6 +53,8 @@ MULTIBOOT_CONSOLE_FLAGS_CONSOLE_REQUIRED equ 1 MULTIBOOT_CONSOLE_FLAGS_EGA_TEXT_SUPPORTED equ 2 + FRAMEBUFFER_REQUIRED_DEPTH equ 32 + ; Multiboot header section .multiboot align 8 @@ -80,7 +82,7 @@ multiboot_start: dd (.framebuffer_tag_end - .framebuffer_tag_start) dd 0 ; width - no preference dd 0 ; height - no preference - dd 0 ; depth - no preference + dd FRAMEBUFFER_REQUIRED_DEPTH ; depth .framebuffer_tag_end: ; End tag (required) diff --git a/kernel/arch/x86_64/boot/loader_64/src/main.cpp b/kernel/arch/x86_64/boot/loader_64/src/main.cpp index 8238d66d..68d7b9a1 100644 --- a/kernel/arch/x86_64/boot/loader_64/src/main.cpp +++ b/kernel/arch/x86_64/boot/loader_64/src/main.cpp @@ -175,15 +175,15 @@ static void EstablishDirectMemMapping(MemoryManagers &mms) ); } -NO_RET static void TransitionToKernel( - u64 kernel_entry_point, const TransitionData *transition_data, - const KernelModuleInfo &kernel_info, MemoryManagers mem_managers +static void PrepareKernelArgs( + const TransitionData *transition_data, const KernelModuleInfo &kernel_info, + MemoryManagers mem_managers, MultibootInfo &multiboot_info ) { auto &pmm = mem_managers.pmm; auto &vmm = mem_managers.vmm; - TRACE_INFO("Preparing to transition to kernel..."); + TRACE_INFO("Preparing kernel arguments..."); // Prepare parameters for the kernel gKernelInitialParams.kernel_start_addr = kernel_info.lower_bound; @@ -196,6 +196,64 @@ NO_RET static void TransitionToKernel( gKernelInitialParams.mem_info_total_pages = pmm.GetTotalPages(); gKernelInitialParams.pml_4_table_phys_addr = vmm.GetPml4Table().Value(); + // Framebuffer + auto fb_tag_res = multiboot_info.FindTag(); + if (fb_tag_res) { + const auto *fb_tag = fb_tag_res.value(); + bool is_rgb = fb_tag->framebuffer_type == TagFramebufferCommon::kFramebufferTypeRgb; + bool is_32bpp = fb_tag->framebuffer_bpp == 32; + + if (is_rgb && is_32bpp) { + gKernelInitialParams.fb_addr = fb_tag->framebuffer_addr; + gKernelInitialParams.fb_width = fb_tag->framebuffer_width; + gKernelInitialParams.fb_height = fb_tag->framebuffer_height; + gKernelInitialParams.fb_pitch = fb_tag->framebuffer_pitch; + gKernelInitialParams.fb_bpp = fb_tag->framebuffer_bpp; + + gKernelInitialParams.fb_red_pos = fb_tag->framebuffer_red_field_position; + gKernelInitialParams.fb_red_mask = fb_tag->framebuffer_red_mask_size; + gKernelInitialParams.fb_green_pos = fb_tag->framebuffer_green_field_position; + gKernelInitialParams.fb_green_mask = fb_tag->framebuffer_green_mask_size; + gKernelInitialParams.fb_blue_pos = fb_tag->framebuffer_blue_field_position; + gKernelInitialParams.fb_blue_mask = fb_tag->framebuffer_blue_mask_size; + + TRACE_INFO( + "Framebuffer found: addr=0x%llX, res=%dx%d, bpp=%d", gKernelInitialParams.fb_addr, + gKernelInitialParams.fb_width, gKernelInitialParams.fb_height, + gKernelInitialParams.fb_bpp + ); + } else { + TRACE_WARNING( + "Framebuffer found but not RGB 32-bit (type=%d, bpp=%d). Ignoring.", + fb_tag->framebuffer_type, fb_tag->framebuffer_bpp + ); + gKernelInitialParams.fb_addr = 0; + gKernelInitialParams.fb_width = 0; + gKernelInitialParams.fb_height = 0; + gKernelInitialParams.fb_pitch = 0; + gKernelInitialParams.fb_bpp = 0; + } + } else { + TRACE_WARNING( + "No framebuffer tag found in multiboot info, framebuffer will not be available" + ); + gKernelInitialParams.fb_addr = 0; + gKernelInitialParams.fb_width = 0; + gKernelInitialParams.fb_height = 0; + gKernelInitialParams.fb_pitch = 0; + gKernelInitialParams.fb_bpp = 0; + } +} + +NO_RET static void TransitionToKernel( + u64 kernel_entry_point, const TransitionData *transition_data, + const KernelModuleInfo &kernel_info, MemoryManagers mem_managers +) +{ + auto &pmm = mem_managers.pmm; + + TRACE_INFO("Preparing to transition to kernel..."); + const u64 ld_start_addr = reinterpret_cast(loader_64_start); const u64 ld_end_addr = reinterpret_cast(loader_64_end); const u64 al_ld_start_addr = AlignDown(ld_start_addr, PageSize()); @@ -212,12 +270,29 @@ NO_RET static void TransitionToKernel( " mem_info_total_pages: %llu\n" " multiboot_info_addr: 0x%llX\n" " multiboot_header_start_addr: 0x%llX\n" - " multiboot_header_end_addr: 0x%llX\n", + " multiboot_header_end_addr: 0x%llX\n" + " Framebuffer Arguments:\n" + " fb_addr: 0x%llX\n" + " fb_width: %u\n" + " fb_height: %u\n" + " fb_pitch: %u\n" + " fb_bpp: %u\n" + " fb_red_pos: %hhu\n" + " fb_red_mask: %hhu\n" + " fb_green_pos: %hhu\n" + " fb_green_mask: %hhu\n" + " fb_blue_pos: %hhu\n" + " fb_blue_mask: %hhu\n", gKernelInitialParams.kernel_start_addr, gKernelInitialParams.kernel_end_addr, gKernelInitialParams.pml_4_table_phys_addr, gKernelInitialParams.mem_info_bitmap_addr, gKernelInitialParams.mem_info_total_pages, gKernelInitialParams.multiboot_info_addr, gKernelInitialParams.multiboot_header_start_addr, - gKernelInitialParams.multiboot_header_end_addr + gKernelInitialParams.multiboot_header_end_addr, gKernelInitialParams.fb_addr, + gKernelInitialParams.fb_width, gKernelInitialParams.fb_height, + gKernelInitialParams.fb_pitch, gKernelInitialParams.fb_bpp, gKernelInitialParams.fb_red_pos, + gKernelInitialParams.fb_red_mask, gKernelInitialParams.fb_green_pos, + gKernelInitialParams.fb_green_mask, gKernelInitialParams.fb_blue_pos, + gKernelInitialParams.fb_blue_mask ); TRACE_INFO("Jumping to kernel at entry point: 0x%llX", kernel_entry_point); @@ -240,5 +315,7 @@ extern "C" void MainLoader64(TransitionData *transition_data) EstablishDirectMemMapping(mms); + PrepareKernelArgs(transition_data, kernel_info, mms, multiboot_info); + TransitionToKernel(kernel_entry_point, transition_data, kernel_info, mms); } diff --git a/kernel/arch/x86_64/src/drivers/vga/vga.cpp b/kernel/arch/x86_64/src/drivers/vga/vga.cpp deleted file mode 100644 index 7310c7d0..00000000 --- a/kernel/arch/x86_64/src/drivers/vga/vga.cpp +++ /dev/null @@ -1,187 +0,0 @@ -/* internal includes */ -#include "drivers/vga/vga.hpp" -#include -#include -#include - -/** - * @file vga.cpp - * @brief Implementation of VGA text mode terminal driver - */ - -// ------------------------------ -// Types and variables -// ------------------------------ - -/** - * @name VGA Hardware Text Mode Colors - * Standard 16-color VGA text mode palette - * @{ - */ -enum VgaColor { - VGA_COLOR_BLACK = 0, ///< Standard black - VGA_COLOR_BLUE = 1, ///< Standard blue - VGA_COLOR_GREEN = 2, ///< Standard green - VGA_COLOR_CYAN = 3, ///< Standard cyan - VGA_COLOR_RED = 4, ///< Standard red - VGA_COLOR_MAGENTA = 5, ///< Standard magenta - VGA_COLOR_BROWN = 6, ///< Standard brown - VGA_COLOR_LIGHT_GREY = 7, ///< Standard light grey - VGA_COLOR_DARK_GREY = 8, ///< Standard dark grey - VGA_COLOR_LIGHT_BLUE = 9, ///< Bright blue - VGA_COLOR_LIGHT_GREEN = 10, ///< Bright green - VGA_COLOR_LIGHT_CYAN = 11, ///< Bright cyan - VGA_COLOR_LIGHT_RED = 12, ///< Bright red - VGA_COLOR_LIGHT_MAGENTA = 13, ///< Bright magenta - VGA_COLOR_LIGHT_BROWN = 14, ///< Yellow (bright brown) - VGA_COLOR_WHITE = 15, ///< Bright white -}; -/** @} */ - -/** - * @name VGA Display Constants - * Standard VGA text mode dimensions - * @{ - */ -static constexpr size_t kVgaWidth = 80; ///< Standard VGA text mode width -static constexpr size_t kVgaHeight = 25; ///< Standard VGA text mode height -/** @} */ - -/** - * @name Terminal State Variables - * Global state tracking for the terminal - * @{ - */ -static size_t g_TerminalRow; ///< Current cursor row position -static size_t g_TerminalColumn; ///< Current cursor column position -static uint8_t g_TerminalColor; ///< Current text color attributes -static uint16_t *g_TerminalBuffer; ///< Pointer to VGA memory buffer (0xB8000) -/** @} */ - -/** - * @brief Create a VGA color attribute byte - * - * @param fg Foreground color - * @param bg Background color - * @return Combined color attribute byte - * - * Combines foreground and background colors into a single byte - * where the background color is in the high nibble. - */ -static FORCE_INLINE_F uint8_t VgaEntryColor(const VgaColor fg, const VgaColor bg) -{ - return fg | bg << 4; -} - -/** - * @brief Create a VGA character entry - * - * @param uc Character to display - * @param color Color attribute byte - * @return Combined VGA character entry - * - * Creates a 16-bit VGA character entry with the character in the low byte - * and the color attribute in the high byte. - */ -static FORCE_INLINE_F uint16_t VgaEntry(const unsigned char uc, const uint8_t color) -{ - return static_cast(uc) | static_cast(color) << 8; -} - -/** - * @brief Initialize the VGA text mode terminal - * - * Sets up a 80x25 text mode display with light grey text on black background. - * Clears the screen and positions the cursor at (0,0). - */ -void VgaTerminalInit() -{ - g_TerminalRow = 0; - g_TerminalColumn = 0; - g_TerminalColor = VgaEntryColor(VGA_COLOR_LIGHT_GREY, VGA_COLOR_BLACK); - g_TerminalBuffer = reinterpret_cast(0xB8000); - - for (size_t y = 0; y < kVgaHeight; y++) { - for (size_t x = 0; x < kVgaWidth; x++) { - const size_t index = y * kVgaWidth + x; - g_TerminalBuffer[index] = VgaEntry(' ', g_TerminalColor); - } - } -} - -/** - * @brief Set the current text color - * - * @param color Combined foreground and background color value - * (foreground in lower 4 bits, background in upper 4 bits) - */ -void VgaTerminalSetColor(const uint8_t color) { g_TerminalColor = color; } - -/** - * @brief Write a character with specified color at given coordinates - * - * @param c Character to display - * @param color Color attribute for the character - * @param x X-coordinate (0 to 79) - * @param y Y-coordinate (0 to 24) - */ -void VgaTerminalPutEntryAt(const char c, const uint8_t color, const size_t x, const size_t y) -{ - const size_t index = y * kVgaWidth + x; - g_TerminalBuffer[index] = VgaEntry(c, color); -} - -/** - * @brief Write a character at the current cursor position - * - * @param c Character to display - * - * Automatically advances the cursor and handles wraparound - * when reaching the end of a line or screen. - */ -void VgaTerminalPutChar(const char c) -{ - VgaTerminalPutEntryAt(c, g_TerminalColor, g_TerminalColumn, g_TerminalRow); - if (++g_TerminalColumn == kVgaWidth) { - g_TerminalColumn = 0; - if (++g_TerminalRow == kVgaHeight) - g_TerminalRow = 0; - } -} - -/** - * @brief Write a string of specified length - * - * @param data Pointer to the string data - * @param size Number of characters to write - */ -void VgaTerminalWrite(const char *data, const size_t size) -{ - for (size_t i = 0; i < size; i++) VgaTerminalPutChar(data[i]); -} - -/** - * @brief Write a null-terminated string in default color (light grey on black) - * - * @param data Pointer to the null-terminated string - */ -void VgaTerminalWriteString(const char *data) -{ - VgaTerminalSetColor(VgaEntryColor(VGA_COLOR_LIGHT_GREY, VGA_COLOR_BLACK)); - VgaTerminalWrite(data, strlen(data)); -} - -/** - * @brief Write a null-terminated string in error color (light red on black) - * - * @param data Pointer to the null-terminated string - * - * Temporarily changes color to light red, writes the string, - * then restores the previous color. - */ -void VgaTerminalWriteError(const char *data) -{ - VgaTerminalSetColor(VgaEntryColor(VGA_COLOR_LIGHT_RED, VGA_COLOR_BLACK)); - VgaTerminalWrite(data, strlen(data)); - VgaTerminalSetColor(VgaEntryColor(VGA_COLOR_LIGHT_GREY, VGA_COLOR_BLACK)); -} diff --git a/kernel/arch/x86_64/src/drivers/vga/vga.hpp b/kernel/arch/x86_64/src/drivers/vga/vga.hpp deleted file mode 100644 index bf4cf2f0..00000000 --- a/kernel/arch/x86_64/src/drivers/vga/vga.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef KERNEL_ARCH_X86_64_SRC_DRIVERS_VGA_VGA_HPP_ -#define KERNEL_ARCH_X86_64_SRC_DRIVERS_VGA_VGA_HPP_ - -#include - -/** - * @file vga.hpp - * @brief Interface for VGA text mode terminal driver - * - * This header provides the interface for initializing and controlling a VGA text mode - * terminal. It supports basic text output operations with color control in a - * standard 80x25 text mode display. - */ - -extern "C" { -void VgaTerminalInit(); -void VgaTerminalSetColor(uint8_t color); -void VgaTerminalPutEntryAt(char c, uint8_t color, size_t x, size_t y); -void VgaTerminalPutChar(char c); -void VgaTerminalWrite(const char *data, size_t size); -void VgaTerminalWriteString(const char *data); -void VgaTerminalWriteError(const char *data); -} - -#endif // KERNEL_ARCH_X86_64_SRC_DRIVERS_VGA_VGA_HPP_ diff --git a/kernel/arch/x86_64/src/hal/impl/terminal.hpp b/kernel/arch/x86_64/src/hal/impl/terminal.hpp index 512ece14..e1f89036 100644 --- a/kernel/arch/x86_64/src/hal/impl/terminal.hpp +++ b/kernel/arch/x86_64/src/hal/impl/terminal.hpp @@ -7,7 +7,6 @@ #include #include "drivers/serial/qemu.hpp" -#include "drivers/vga/vga.hpp" namespace arch { @@ -21,9 +20,6 @@ WRAP_CALL void TerminalInit() WRAP_CALL void TerminalPutChar(const char c) { - /* Put char to VGA terminal -> when multiboot allows: TODO */ - // VgaTerminalPutChar(c); - if constexpr (FeatureEnabled) { QemuTerminalPutChar(c); } @@ -31,9 +27,6 @@ WRAP_CALL void TerminalPutChar(const char c) WRAP_CALL void TerminalWriteString(const char *data) { - /* Write string to VGA terminal -> when multiboot allows: TODO */ - // VgaTerminalWriteString(data); - if constexpr (FeatureEnabled) { QemuTerminalWriteString(data); } @@ -41,9 +34,6 @@ WRAP_CALL void TerminalWriteString(const char *data) WRAP_CALL void TerminalWriteError(const char *data) { - /* Write error string to VGA terminal -> when multiboot allows: TODO */ - // VgaTerminalWriteError(data); - if constexpr (FeatureEnabled) { QemuTerminalWriteString(data); } diff --git a/kernel/src/boot_args.cpp b/kernel/src/boot_args.cpp index c469d200..44fc65a5 100644 --- a/kernel/src/boot_args.cpp +++ b/kernel/src/boot_args.cpp @@ -4,6 +4,7 @@ #include "boot_args.hpp" #include "hal/boot_args.hpp" #include "mem/types.hpp" +#include "trace_framework.hpp" using namespace Mem; @@ -19,6 +20,20 @@ BootArguments SanitizeBootArgs(const hal::RawBootArguments &raw_args) ); R_ASSERT_GT(raw_args.mem_info_total_pages, 0UL, "Total memory pages is zero."); + FramebufferArgs fb_args{ + .base_address = UptrToPtr(raw_args.fb_addr), + .width = raw_args.fb_width, + .height = raw_args.fb_height, + .pitch = raw_args.fb_pitch, + .bpp = raw_args.fb_bpp, + .red_pos = raw_args.fb_red_pos, + .red_mask_size = raw_args.fb_red_mask, + .green_pos = raw_args.fb_green_pos, + .green_mask_size = raw_args.fb_green_mask, + .blue_pos = raw_args.fb_blue_pos, + .blue_mask_size = raw_args.fb_blue_mask, + }; + BootArguments sanitized_k_args{ .kernel_start = UptrToPtr(raw_args.kernel_start_addr), .kernel_end = UptrToPtr(raw_args.kernel_end_addr), @@ -26,7 +41,40 @@ BootArguments SanitizeBootArgs(const hal::RawBootArguments &raw_args) .mem_bitmap = UptrToPtr(raw_args.mem_info_bitmap_phys_addr), .total_page_frames = static_cast(raw_args.mem_info_total_pages), .multiboot_info = UptrToPtr(raw_args.multiboot_info_phys_addr), + .fb_args = fb_args, }; + DEBUG_INFO_BOOT("Sanitized boot arguments:"); + DEBUG_INFO_BOOT( + " Boot Arguments:\n" + " kernel_start: %p\n" + " kernel_end: %p\n" + " root_page_table: %p\n" + " mem_bitmap: %p\n" + " total_page_frames: %zu\n" + " multiboot_info: %p\n" + " Framebuffer Arguments:\n" + " base_address: %p\n" + " width: %u\n" + " height: %u\n" + " pitch: %u\n" + " bpp: %u\n" + " red_pos: %hhu\n" + " red_mask_size: %hhu\n" + " green_pos: %hhu\n" + " green_mask_size: %hhu\n" + " blue_pos: %hhu\n" + " blue_mask_size: %hhu\n", + sanitized_k_args.kernel_start, sanitized_k_args.kernel_end, + sanitized_k_args.root_page_table, sanitized_k_args.mem_bitmap, + sanitized_k_args.total_page_frames, sanitized_k_args.multiboot_info, + sanitized_k_args.fb_args.base_address, sanitized_k_args.fb_args.width, + sanitized_k_args.fb_args.height, sanitized_k_args.fb_args.pitch, + sanitized_k_args.fb_args.bpp, sanitized_k_args.fb_args.red_pos, + sanitized_k_args.fb_args.red_mask_size, sanitized_k_args.fb_args.green_pos, + sanitized_k_args.fb_args.green_mask_size, sanitized_k_args.fb_args.blue_pos, + sanitized_k_args.fb_args.blue_mask_size + ); + return sanitized_k_args; } diff --git a/kernel/src/boot_args.hpp b/kernel/src/boot_args.hpp index 5ecb805b..6830646a 100644 --- a/kernel/src/boot_args.hpp +++ b/kernel/src/boot_args.hpp @@ -1,9 +1,23 @@ -#ifndef ALKOS_KERNEL_INCLUDE_BOOT_ARGS_HPP_ -#define ALKOS_KERNEL_INCLUDE_BOOT_ARGS_HPP_ +#ifndef KERNEL_SRC_BOOT_ARGS_HPP_ +#define KERNEL_SRC_BOOT_ARGS_HPP_ #include "hal/boot_args.hpp" #include "mem/types.hpp" +struct FramebufferArgs { + Mem::PPtr base_address; + u32 width; + u32 height; + u32 pitch; + u32 bpp; + u8 red_pos; + u8 red_mask_size; + u8 green_pos; + u8 green_mask_size; + u8 blue_pos; + u8 blue_mask_size; +}; + struct BootArguments { Mem::VPtr kernel_start; Mem::VPtr kernel_end; @@ -11,8 +25,9 @@ struct BootArguments { Mem::PPtr mem_bitmap; size_t total_page_frames; Mem::PPtr multiboot_info; + FramebufferArgs fb_args; }; BootArguments SanitizeBootArgs(const hal::RawBootArguments &raw_args); -#endif // ALKOS_KERNEL_INCLUDE_BOOT_ARGS_HPP_ +#endif // KERNEL_SRC_BOOT_ARGS_HPP_ diff --git a/kernel/src/drivers/video/framebuffer.cpp b/kernel/src/drivers/video/framebuffer.cpp new file mode 100644 index 00000000..cc116da9 --- /dev/null +++ b/kernel/src/drivers/video/framebuffer.cpp @@ -0,0 +1,33 @@ +#include "drivers/video/framebuffer.hpp" +#include "defines.hpp" +#include "mem/heap.hpp" +#include "trace_framework.hpp" + +using namespace Drivers::Video; + +void Framebuffer::Init(Graphics::Surface s, Graphics::PixelFormat pf, Mem::Heap &hp) +{ + front_surface_ = s; + format_ = pf; + + // Backbuffer allocation + size_t width = front_surface_.GetWidth(); + size_t height = front_surface_.GetHeight(); + size_t buffer_size = width * height * sizeof(u32); + + auto alloc_res = hp.Malloc(buffer_size); + if (!alloc_res) { + TRACE_WARN_VIDEO("Backbuffer allocation failed, running in single-buffer mode"); + return; + } + + backbuffer_mem_ = static_cast>(*alloc_res); + back_surface_ = Graphics::Surface(backbuffer_mem_, width, height, width * sizeof(u32)); +} + +void Framebuffer::Flush() +{ + if (back_surface_.IsValid()) { + front_surface_.CopyFrom(back_surface_); + } +} diff --git a/kernel/src/drivers/video/framebuffer.hpp b/kernel/src/drivers/video/framebuffer.hpp new file mode 100644 index 00000000..184fffea --- /dev/null +++ b/kernel/src/drivers/video/framebuffer.hpp @@ -0,0 +1,41 @@ +#ifndef KERNEL_SRC_DRIVERS_VIDEO_FRAMEBUFFER_HPP_ +#define KERNEL_SRC_DRIVERS_VIDEO_FRAMEBUFFER_HPP_ + +#include +#include "graphics/color.hpp" +#include "graphics/surface.hpp" +#include "mem/heap.hpp" + +namespace Drivers::Video +{ + +class Framebuffer +{ + public: + Framebuffer() = default; + + void Init(Graphics::Surface s, Graphics::PixelFormat pf, Mem::Heap &hp); + + // Swap the backbuffer to the front (physical) buffer + void Flush(); + + // Accessors + NODISCARD Graphics::Surface &GetSurface() + { + return back_surface_.IsValid() ? back_surface_ : front_surface_; + } + NODISCARD Graphics::Surface &GetFrontSurface() { return front_surface_; } + NODISCARD const Graphics::PixelFormat &GetFormat() const { return format_; } + + private: + Graphics::Surface front_surface_{}; // Physical VRAM + Graphics::Surface back_surface_{}; // RAM Buffer + Graphics::PixelFormat format_{}; + + // We own the backbuffer memory + Mem::VPtr backbuffer_mem_{nullptr}; +}; + +} // namespace Drivers::Video + +#endif // KERNEL_SRC_DRIVERS_VIDEO_FRAMEBUFFER_HPP_ diff --git a/kernel/src/graphics/color.hpp b/kernel/src/graphics/color.hpp new file mode 100644 index 00000000..1b728229 --- /dev/null +++ b/kernel/src/graphics/color.hpp @@ -0,0 +1,38 @@ +#ifndef KERNEL_SRC_GRAPHICS_COLOR_HPP_ +#define KERNEL_SRC_GRAPHICS_COLOR_HPP_ + +#include +#include + +namespace Graphics +{ + +struct PACK Color { + u8 r; + u8 g; + u8 b; + u8 a; + + constexpr Color(u8 r, u8 g, u8 b, u8 a = 255) : r(r), g(g), b(b), a(a) {} + constexpr Color() : r(0), g(0), b(0), a(255) {} + + static constexpr Color Red() { return {255, 0, 0, 255}; } + static constexpr Color Green() { return {0, 255, 0, 255}; } + static constexpr Color Blue() { return {0, 0, 255, 255}; } + static constexpr Color White() { return {255, 255, 255, 255}; } + static constexpr Color Black() { return {0, 0, 0, 255}; } +}; + +struct PixelFormat { + u8 red_pos; + u8 red_mask_size; + u8 green_pos; + u8 green_mask_size; + u8 blue_pos; + u8 blue_mask_size; + // We currently assume 32-bpp +}; + +} // namespace Graphics + +#endif // KERNEL_SRC_GRAPHICS_COLOR_HPP_ diff --git a/kernel/src/graphics/painter.cpp b/kernel/src/graphics/painter.cpp new file mode 100644 index 00000000..4017039e --- /dev/null +++ b/kernel/src/graphics/painter.cpp @@ -0,0 +1,118 @@ +#include "graphics/painter.hpp" +#include +#include + +namespace Graphics +{ + +Painter::Painter(Surface &target, const PixelFormat &format) : target_(target), format_(format) +{ + ASSERT_TRUE(target_.IsValid(), "Painter initialized with invalid surface"); + SetColor(Color::White()); +} + +void Painter::SetColor(Color color) { packed_color_ = PackColor(color); } + +u32 Painter::PackColor(Color c) const +{ + const u32 r_mask = (1 << format_.red_mask_size) - 1; + const u32 g_mask = (1 << format_.green_mask_size) - 1; + const u32 b_mask = (1 << format_.blue_mask_size) - 1; + + return (static_cast(c.r & r_mask) << format_.red_pos) | + (static_cast(c.g & g_mask) << format_.green_pos) | + (static_cast(c.b & b_mask) << format_.blue_pos); +} + +void Painter::DrawPixel(i32 x, i32 y) +{ + if (x < 0 || y < 0 || static_cast(x) >= target_.GetWidth() || + static_cast(y) >= target_.GetHeight()) { + return; + } + target_.Pixel(static_cast(x), static_cast(y)) = packed_color_; +} + +void Painter::FillScanline(u32 *dest, u32 count, u32 color) +{ + // Check for byte-repeating pattern for memset optimization + // e.g. 0x00000000, 0xFFFFFFFF, 0xABABABAB + if ((color & 0xFF) == ((color >> 8) & 0xFF) && (color & 0xFF) == ((color >> 16) & 0xFF) && + (color & 0xFF) == ((color >> 24) & 0xFF)) { + memset(dest, color & 0xFF, count * sizeof(u32)); + } else { + // Compiler auto-vectorization usually handles this loop well + for (u32 i = 0; i < count; ++i) { + dest[i] = color; + } + } +} + +void Painter::Clear(Color color) +{ + u32 raw = PackColor(color); + u32 height = target_.GetHeight(); + u32 width = target_.GetWidth(); + u32 pitch = target_.GetPitch(); + + // Optimization: if buffer is contiguous (pitch == width * 4 for 32bpp) + // we can clear it in one go. + if (pitch == width * sizeof(u32)) { + FillScanline(target_.GetScanline(0), width * height, raw); + } else { + for (u32 y = 0; y < height; ++y) { + FillScanline(target_.GetScanline(y), width, raw); + } + } +} + +void Painter::FillRect(i32 x, i32 y, i32 w, i32 h) +{ + // Clipping Logic + if (x < 0) { + w += x; + x = 0; + } + if (y < 0) { + h += y; + y = 0; + } + + if (static_cast(x) >= target_.GetWidth()) { + return; + } + if (static_cast(y) >= target_.GetHeight()) { + return; + } + + // Clamp width/height to edges + if (static_cast(x + w) > target_.GetWidth()) { + w = target_.GetWidth() - x; + } + if (static_cast(y + h) > target_.GetHeight()) { + h = target_.GetHeight() - y; + } + + if (w <= 0 || h <= 0) { + return; + } + + for (i32 row = 0; row < h; ++row) { + u32 *line = target_.GetScanline(static_cast(y + row)); + FillScanline(line + x, static_cast(w), packed_color_); + } +} + +void Painter::DrawRect(i32 x, i32 y, i32 w, i32 h) +{ + // Top + FillRect(x, y, w, 1); + // Bottom + FillRect(x, y + h - 1, w, 1); + // Left + FillRect(x, y, 1, h); + // Right + FillRect(x + w - 1, y, 1, h); +} + +} // namespace Graphics diff --git a/kernel/src/graphics/painter.hpp b/kernel/src/graphics/painter.hpp new file mode 100644 index 00000000..4265af7d --- /dev/null +++ b/kernel/src/graphics/painter.hpp @@ -0,0 +1,39 @@ +#ifndef KERNEL_SRC_GRAPHICS_PAINTER_HPP_ +#define KERNEL_SRC_GRAPHICS_PAINTER_HPP_ + +#include "graphics/color.hpp" +#include "graphics/surface.hpp" + +namespace Graphics +{ + +class Painter +{ + public: + Painter(Surface &target, const PixelFormat &format); + + void SetColor(Color color); + + // ------------------------------------------------------------------------- + // Primitives + // ------------------------------------------------------------------------- + + void Clear(Color color); + + // Safe drawing with clipping + void DrawPixel(i32 x, i32 y); + void DrawRect(i32 x, i32 y, i32 w, i32 h); + void FillRect(i32 x, i32 y, i32 w, i32 h); + + private: + void FillScanline(u32 *dest, u32 count, u32 color); + NODISCARD FORCE_INLINE_F u32 PackColor(Color c) const; + + Surface &target_; + PixelFormat format_; + u32 packed_color_; +}; + +} // namespace Graphics + +#endif // KERNEL_SRC_GRAPHICS_PAINTER_HPP_ diff --git a/kernel/src/graphics/surface.hpp b/kernel/src/graphics/surface.hpp new file mode 100644 index 00000000..54b2dd4c --- /dev/null +++ b/kernel/src/graphics/surface.hpp @@ -0,0 +1,92 @@ +#ifndef KERNEL_SRC_GRAPHICS_SURFACE_HPP_ +#define KERNEL_SRC_GRAPHICS_SURFACE_HPP_ + +#include +#include +#include + +namespace Graphics +{ + +/** + * @brief A non-owning view into a linear framebuffer or memory buffer. + * Assumes 32-bit pixel depth (BPP). + */ +class Surface +{ + public: + Surface() = default; + + Surface(Mem::VPtr buffer, u32 width, u32 height, u32 pitch_bytes) + : buffer_(buffer), width_(width), height_(height), pitch_bytes_(pitch_bytes) + { + R_ASSERT_NOT_NULL(buffer_); + R_ASSERT_NOT_ZERO(width_); + R_ASSERT_NOT_ZERO(height_); + R_ASSERT_NOT_ZERO(pitch_bytes_); + } + + // ------------------------------------------------------------------------- + // Methods + // ------------------------------------------------------------------------- + + void CopyFrom(const Surface &src) + { + // TODO: support clipping/scaling + ASSERT_EQ(width_, src.width_); + ASSERT_EQ(height_, src.height_); + + for (u32 y = 0; y < height_; ++y) { + u32 *dest_row = GetScanline(y); + const u32 *src_row = src.GetScanline(y); + + // We use the width for the copy size, not the pitch, + // to handle stride differences correctly. + memcpy(dest_row, src_row, width_ * sizeof(u32)); + } + } + + // ------------------------------------------------------------------------- + // Fast Accessors + // ------------------------------------------------------------------------- + + NODISCARD FORCE_INLINE_F Mem::VPtr GetScanline(u32 y) + { + ASSERT_LT(y, height_); + // Pointer arithmetic on byte level for pitch + uptr addr = Mem::PtrToUptr(buffer_) + (static_cast(y) * pitch_bytes_); + return Mem::UptrToPtr(addr); + } + + NODISCARD FORCE_INLINE_F const Mem::VPtr GetScanline(u32 y) const + { + ASSERT_LT(y, height_); + uptr addr = Mem::PtrToUptr(buffer_) + (static_cast(y) * pitch_bytes_); + return Mem::UptrToPtr(addr); + } + + FORCE_INLINE_F u32 &Pixel(u32 x, u32 y) + { + ASSERT_LT(x, width_); + return GetScanline(y)[x]; + } + + // ------------------------------------------------------------------------- + // Properties + // ------------------------------------------------------------------------- + + NODISCARD FORCE_INLINE_F u32 GetWidth() const { return width_; } + NODISCARD FORCE_INLINE_F u32 GetHeight() const { return height_; } + NODISCARD FORCE_INLINE_F u32 GetPitch() const { return pitch_bytes_; } + NODISCARD FORCE_INLINE_F bool IsValid() const { return buffer_ != nullptr; } + + private: + Mem::VPtr buffer_{nullptr}; + u32 width_{0}; + u32 height_{0}; + u32 pitch_bytes_{0}; // Stride in bytes +}; + +} // namespace Graphics + +#endif // KERNEL_SRC_GRAPHICS_SURFACE_HPP_ diff --git a/kernel/src/hal/api/boot_args.hpp b/kernel/src/hal/api/boot_args.hpp index ab8d11b5..314a3d5f 100644 --- a/kernel/src/hal/api/boot_args.hpp +++ b/kernel/src/hal/api/boot_args.hpp @@ -7,14 +7,31 @@ namespace arch { struct RawBootArguments; -struct PACK RawBootArgumentsAPI { +struct RawBootArgumentsAPI { + /// Mem Layout u64 kernel_start_addr; u64 kernel_end_addr; + /// VMem u64 page_table_phys_addr; + // Mem Bitmap u64 mem_info_bitmap_phys_addr; u64 mem_info_total_pages; + + // Framebuffer + u64 fb_addr; + u32 fb_width; + u32 fb_height; + u32 fb_pitch; + u32 fb_bpp; + + u8 fb_red_pos; + u8 fb_red_mask; + u8 fb_green_pos; + u8 fb_green_mask; + u8 fb_blue_pos; + u8 fb_blue_mask; }; } // namespace arch diff --git a/kernel/src/init.cpp b/kernel/src/init.cpp index 6e84ee6a..5a43010f 100644 --- a/kernel/src/init.cpp +++ b/kernel/src/init.cpp @@ -10,6 +10,7 @@ #include "modules/hardware.hpp" #include "modules/memory.hpp" #include "modules/timing.hpp" +#include "modules/video.hpp" #include "trace_framework.hpp" /* GCC CXX provided function initializing global constructors */ @@ -26,18 +27,6 @@ void KernelInit(const hal::RawBootArguments &raw_args) hal::ArchInit(raw_args); BootArguments args = SanitizeBootArgs(raw_args); - DEBUG_INFO_BOOT("Sanitized boot arguments received by kernel:"); - DEBUG_INFO_BOOT( - " Boot Arguments:\n" - " kernel_start: 0x%p\n" - " kernel_end: 0x%p\n" - " root_page_table: 0x%p\n" - " mem_bitmap: 0x%p\n" - " total_page_frames: %zu\n" - " multiboot_info: 0x%p\n", - args.kernel_start, args.kernel_end, args.root_page_table, args.mem_bitmap, - args.total_page_frames, args.multiboot_info - ); MemoryModule::Init(args); @@ -65,4 +54,6 @@ void KernelInit(const hal::RawBootArguments &raw_args) // TimingModule::Init(); MemoryModule::Get().RegisterPageFault(HardwareModule::Get()); + + VideoModule::Init(args, MemoryModule::Get().GetHeap()); } diff --git a/kernel/src/kernel.cpp b/kernel/src/kernel.cpp index 1e863cc3..d5f2f473 100644 --- a/kernel/src/kernel.cpp +++ b/kernel/src/kernel.cpp @@ -8,8 +8,10 @@ #include #include "boot_args.hpp" +#include "graphics/painter.hpp" #include "hal/boot_args.hpp" #include "mem/heap.hpp" +#include "modules/video.hpp" #include "todo.hpp" extern void KernelInit(const hal::RawBootArguments &); @@ -19,6 +21,7 @@ static void KernelRun() TRACE_INFO_GENERAL("Hello from AlkOS!"); trace::DumpAllBuffersOnFailure(); TODO_MMU_MINIMAL + // static constexpr size_t kBuffSize = 256; // char buff[kBuffSize]; // @@ -26,6 +29,34 @@ static void KernelRun() // strftime(buff, kBuffSize, "%Y-%m-%d %H:%M:%S", localtime(&t)); // // KernelTraceSuccess("Hello from AlkOS! Today we have: %s", buff); + + auto &video = VideoModule::Get(); + auto &screen = video.GetScreen(); + auto fmt = video.GetFormat(); + + Graphics::Painter p(screen, fmt); + + // Animation Loop + i32 x = 0; + i32 y = 0; + u16 speed = 1; + while (true) { + p.Clear(Graphics::Color::Black()); + p.SetColor(Graphics::Color::Green()); + p.FillRect(x, 100, 50, 50); + p.SetColor(Graphics::Color::Blue()); + p.FillRect(100, y, 70, 70); + video.Flush(); + + x += (speed / 255) + 1; + y += (speed / 255) + 1; + x = x % screen.GetWidth(); + y = y % screen.GetHeight(); + + // crude delay + for (volatile int i = 0; i < 1000000; i++); + speed = speed + 80; + } } extern "C" void KernelMain(const hal::RawBootArguments *raw_args) diff --git a/kernel/src/modules/video.cpp b/kernel/src/modules/video.cpp new file mode 100644 index 00000000..49782801 --- /dev/null +++ b/kernel/src/modules/video.cpp @@ -0,0 +1,36 @@ +#include "modules/video.hpp" +#include "graphics/painter.hpp" +#include "mem/types.hpp" +#include "trace_framework.hpp" + +using namespace Graphics; +using namespace Drivers; +using namespace Mem; + +internal::VideoModule::VideoModule(const BootArguments &args, Heap &hp) noexcept +{ + DEBUG_INFO_GENERAL("VideoModule::VideoModule()"); + + const auto &fb_args = args.fb_args; + auto *fb_pptr = static_cast>(fb_args.base_address); + + R_ASSERT_NOT_NULL(fb_pptr, "VideoModule: Framebuffer is null"); + + auto s = Surface(Mem::PhysToVirt(fb_pptr), fb_args.width, fb_args.height, fb_args.pitch); + auto pf = PixelFormat{ + .red_pos = fb_args.red_pos, + .red_mask_size = fb_args.red_mask_size, + .green_pos = fb_args.green_pos, + .green_mask_size = fb_args.green_mask_size, + .blue_pos = fb_args.blue_pos, + .blue_mask_size = fb_args.blue_mask_size, + }; + + Framebuffer_.Init(s, pf, hp); + Graphics::Surface &screen = GetScreen(); + R_ASSERT_TRUE(screen.IsValid()); + + Graphics::Painter p(screen, Framebuffer_.GetFormat()); + p.Clear(Graphics::Color::Black()); + Flush(); +} diff --git a/kernel/src/modules/video.hpp b/kernel/src/modules/video.hpp new file mode 100644 index 00000000..cdfa5639 --- /dev/null +++ b/kernel/src/modules/video.hpp @@ -0,0 +1,34 @@ +#ifndef KERNEL_SRC_MODULES_VIDEO_HPP_ +#define KERNEL_SRC_MODULES_VIDEO_HPP_ + +#include + +#include "boot_args.hpp" +#include "drivers/video/framebuffer.hpp" +#include "modules/helpers.hpp" + +namespace internal +{ + +class VideoModule : template_lib::StaticSingletonHelper +{ + protected: + explicit VideoModule(const BootArguments &args, Mem::Heap &hp) noexcept; + + DEFINE_MODULE_FIELD(Drivers::Video, Framebuffer); + + public: + /// Returns the Surface for the physical screen + Graphics::Surface &GetScreen() { return Framebuffer_.GetSurface(); } + + /// Returns the format required by the hardware (used to init Painters) + const Graphics::PixelFormat &GetFormat() const { return Framebuffer_.GetFormat(); } + + void Flush() { Framebuffer_.Flush(); } +}; + +} // namespace internal + +using VideoModule = template_lib::StaticSingleton; + +#endif // KERNEL_SRC_MODULES_VIDEO_HPP_ diff --git a/kernel/src/trace_framework.hpp b/kernel/src/trace_framework.hpp index 350902cb..d49f37ac 100644 --- a/kernel/src/trace_framework.hpp +++ b/kernel/src/trace_framework.hpp @@ -38,6 +38,7 @@ enum class TraceModule { kGeneral, kTime, kLast, + kVideo, }; // ------------------------------ @@ -99,6 +100,10 @@ CREATE_TRACE_HELPERS(kGeneral, GENERAL) CREATE_DEBUG_HELPERS(kTime, TIME) CREATE_TRACE_HELPERS(kTime, TIME) +// VIDEO +CREATE_DEBUG_HELPERS(kVideo, VIDEO) +CREATE_TRACE_HELPERS(kVideo, VIDEO) + #include "trace_framework.tpp" #endif // KERNEL_SRC_TRACE_FRAMEWORK_HPP_ From b1064d0b71f33b541a19e7dbff87db74d0ba079d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Kryczka?= <60490378+kryczkal@users.noreply.github.com> Date: Sun, 7 Dec 2025 17:39:28 +0100 Subject: [PATCH 03/56] Kryczkal/console-01 (#228) ## Problem There is no way to display text on the screen ## Solution Implement font support in the kernel, `Font` is a concept of an object that returns `Glyphs` `Glyphs` are bitmaps of speciifc "characters". These are then handled by `Painter` to draw them on a `Surfrace`. - Added support for PSF2 type fonts, so they can be easily added to the kernel. - Added drdos8x8 as a font No truetype font support. No fancy stuff. Just plain old bitmasks image image ## Note This is not an implementation of a Console yet. A console would be a `Writer` that you just put text into and don't care about where it's gonna be on screen, at what position to display it etc. This is just adding `DrawString` to `Painter` class. Console is still on the way --- .concat.conf | 2 - kernel/src/graphics/font/glyph.hpp | 23 ++ kernel/src/graphics/font/psf2_font.hpp | 94 +++++ kernel/src/graphics/fonts/drdos8x8.hpp | 321 ++++++++++++++++++ kernel/src/graphics/geometry.hpp | 38 +++ kernel/src/graphics/painter.cpp | 29 +- kernel/src/graphics/painter.hpp | 41 ++- kernel/src/graphics/painter.tpp | 70 ++++ kernel/src/io/register.hpp | 10 +- kernel/src/kernel.cpp | 33 +- .../data_structures/array_structures.hpp | 16 +- 11 files changed, 646 insertions(+), 31 deletions(-) delete mode 100644 .concat.conf create mode 100644 kernel/src/graphics/font/glyph.hpp create mode 100644 kernel/src/graphics/font/psf2_font.hpp create mode 100644 kernel/src/graphics/fonts/drdos8x8.hpp create mode 100644 kernel/src/graphics/geometry.hpp create mode 100644 kernel/src/graphics/painter.tpp diff --git a/.concat.conf b/.concat.conf deleted file mode 100644 index eb1182c1..00000000 --- a/.concat.conf +++ /dev/null @@ -1,2 +0,0 @@ -alkos/ -scripts/ diff --git a/kernel/src/graphics/font/glyph.hpp b/kernel/src/graphics/font/glyph.hpp new file mode 100644 index 00000000..5a5d5b17 --- /dev/null +++ b/kernel/src/graphics/font/glyph.hpp @@ -0,0 +1,23 @@ +#ifndef KERNEL_SRC_GRAPHICS_FONT_GLYPH_HPP_ +#define KERNEL_SRC_GRAPHICS_FONT_GLYPH_HPP_ + +#include + +namespace Graphics +{ + +/** + * @brief Represents a lightweight view into a specific character's bitmap data. + * This struct does not own the memory. + */ +struct Glyph { + const byte *buffer; // Pointer to the raw bitmap bits + u32 width; // Width of the glyph in pixels + u32 height; // Height of the glyph in pixels + u32 stride; // Number of bytes per row in the buffer + u32 advance; // Horizontal pixels to advance cursor after drawing +}; + +} // namespace Graphics + +#endif // KERNEL_SRC_GRAPHICS_FONT_GLYPH_HPP_ diff --git a/kernel/src/graphics/font/psf2_font.hpp b/kernel/src/graphics/font/psf2_font.hpp new file mode 100644 index 00000000..491ff543 --- /dev/null +++ b/kernel/src/graphics/font/psf2_font.hpp @@ -0,0 +1,94 @@ +#ifndef KERNEL_SRC_GRAPHICS_FONT_PSF2_FONT_HPP_ +#define KERNEL_SRC_GRAPHICS_FONT_PSF2_FONT_HPP_ + +#include +#include +#include +#include +#include "graphics/font/glyph.hpp" + +namespace Graphics +{ + +/** + * @brief PC Screen Font v2 Header Layout + */ +struct PACK Psf2Header { + u32 magic; // Magic bytes: 0x864ab572 (Little Endian) + u32 version; // Zero + u32 header_size; // Offset to glyphs + u32 flags; // Font flags + u32 length; // Number of glyphs + u32 char_size; // Number of bytes per glyph + u32 height; // Height in pixels + u32 width; // Width in pixels +}; + +/** + * @brief Zero-overhead parser for PSF2 font data. + * @note This class does not own the data buffer. + */ +class Psf2Font +{ + public: + static constexpr u32 kPsf2Magic = 0x864ab572; + + constexpr Psf2Font(std::span data) : data_(data) + { + // TODO: In a constexpr context, static assert + // TODO: Runtime checks can be added if instantiated dynamically. + } + + [[nodiscard]] FORCE_INLINE_F const Psf2Header &Header() const + { + return *reinterpret_cast(data_.data()); + } + + [[nodiscard]] bool IsValid() const + { + if (data_.size() < sizeof(Psf2Header)) { + return false; + } + return Header().magic == kPsf2Magic; + } + + [[nodiscard]] FORCE_INLINE_F u32 GetWidth() const { return Header().width; } + [[nodiscard]] FORCE_INLINE_F u32 GetHeight() const { return Header().height; } + + /** + * @brief Retrieves the bitmap view for a specific character. + * @param c The character code (ASCII/Extended ASCII). + * @return Glyph structure containing geometry and data pointer. + */ + [[nodiscard]] Glyph GetGlyph(char c) const + { + const auto &hdr = Header(); + u32 index = static_cast(c); + + // Fallback for out of range + if (index >= hdr.length) { + index = 0; + } + + const u8 *glyph_start = data_.data() + hdr.header_size + (index * hdr.char_size); + + // PSF2 rows are byte-aligned. + // Stride = ceil(width / 8) + u32 stride = (hdr.width + 7) >> 3; + + return Glyph{ + .buffer = glyph_start, + .width = hdr.width, + .height = hdr.height, + .stride = stride, + .advance = hdr.width // PSF2 is fundamentally a fixed-width format + }; + } + + private: + std::span data_; +}; + +} // namespace Graphics + +#endif // KERNEL_SRC_GRAPHICS_FONT_PSF2_FONT_HPP_ diff --git a/kernel/src/graphics/fonts/drdos8x8.hpp b/kernel/src/graphics/fonts/drdos8x8.hpp new file mode 100644 index 00000000..59e49902 --- /dev/null +++ b/kernel/src/graphics/fonts/drdos8x8.hpp @@ -0,0 +1,321 @@ +#ifndef KERNEL_SRC_GRAPHICS_FONTS_DRDOS8X8_HPP_ +#define KERNEL_SRC_GRAPHICS_FONTS_DRDOS8X8_HPP_ + +// Converted via `xxd -i` +static constexpr unsigned char drdos8x8_psfu[] = { + 0x72, 0xb5, 0x4a, 0x86, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0xc0, 0x01, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x81, 0xa5, 0x81, 0xbd, 0x99, 0x81, 0x7e, + 0x7e, 0xff, 0xdb, 0xff, 0xc3, 0xe7, 0xff, 0x7e, 0x6c, 0xfe, 0xfe, 0xfe, 0x7c, 0x38, 0x10, 0x00, + 0x10, 0x38, 0x7c, 0xfe, 0x7c, 0x38, 0x10, 0x00, 0x18, 0x3c, 0x3c, 0xe7, 0xe7, 0x18, 0x3c, 0x00, + 0x10, 0x38, 0x7c, 0xfe, 0x7c, 0x10, 0x38, 0x00, 0x00, 0x00, 0x18, 0x3c, 0x18, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xe7, 0xc3, 0xe7, 0xff, 0xff, 0xff, 0x00, 0x00, 0x18, 0x24, 0x18, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xe7, 0xdb, 0xe7, 0xff, 0xff, 0xff, 0x1c, 0x0c, 0x10, 0x7c, 0x82, 0x82, 0x7c, 0x00, + 0x7c, 0x82, 0x82, 0x7c, 0x10, 0x38, 0x10, 0x00, 0x00, 0x1f, 0x1e, 0x10, 0x10, 0xf0, 0xe0, 0x00, + 0x00, 0x3e, 0x3e, 0x22, 0x2e, 0xec, 0xc0, 0x00, 0x10, 0x54, 0x38, 0x6c, 0x38, 0x54, 0x10, 0x00, + 0x00, 0x40, 0x70, 0x7c, 0x70, 0x40, 0x00, 0x00, 0x00, 0x04, 0x1c, 0x7c, 0x1c, 0x04, 0x00, 0x00, + 0x18, 0x3c, 0x7e, 0x18, 0x18, 0x7e, 0x3c, 0x18, 0x28, 0x28, 0x28, 0x28, 0x28, 0x00, 0x28, 0x00, + 0x7f, 0xdb, 0xdb, 0x5b, 0x1b, 0x1b, 0x1b, 0x1b, 0x3c, 0x42, 0x78, 0x24, 0x24, 0x1e, 0x42, 0x3c, + 0x00, 0x00, 0x00, 0x00, 0xfe, 0xfe, 0xfe, 0x00, 0x18, 0x3c, 0x7e, 0x18, 0x7e, 0x3c, 0x18, 0x7e, + 0x18, 0x3c, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x3c, 0x18, + 0x00, 0x18, 0x0c, 0xfe, 0x0c, 0x18, 0x00, 0x00, 0x00, 0x30, 0x60, 0xfe, 0x60, 0x30, 0x00, 0x00, + 0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xfe, 0x00, 0x00, 0x00, 0x24, 0x66, 0xff, 0x66, 0x24, 0x00, 0x00, + 0x00, 0x18, 0x18, 0x3c, 0x3c, 0x7e, 0x7e, 0x00, 0x00, 0x7e, 0x7e, 0x3c, 0x3c, 0x18, 0x18, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x3c, 0x3c, 0x18, 0x18, 0x00, 0x18, 0x00, + 0x6c, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x44, 0xfe, 0x44, 0xfe, 0x44, 0x44, 0x00, + 0x30, 0x7c, 0xc0, 0x78, 0x0c, 0xf8, 0x30, 0x00, 0x00, 0xc6, 0xcc, 0x18, 0x30, 0x66, 0xc6, 0x00, + 0x38, 0x6c, 0x38, 0x76, 0xdc, 0xcc, 0x76, 0x00, 0x70, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x30, 0x60, 0x60, 0x60, 0x30, 0x18, 0x00, 0x60, 0x30, 0x18, 0x18, 0x18, 0x30, 0x60, 0x00, + 0x00, 0xcc, 0x78, 0xfe, 0x78, 0xcc, 0x00, 0x00, 0x00, 0x30, 0x30, 0xfc, 0x30, 0x30, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x30, 0x60, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0x80, 0x00, + 0x7c, 0xc6, 0xce, 0xd6, 0xe6, 0xc6, 0x7c, 0x00, 0x30, 0x70, 0x30, 0x30, 0x30, 0x30, 0xfc, 0x00, + 0x78, 0xcc, 0x0c, 0x38, 0x60, 0xc0, 0xfc, 0x00, 0x78, 0xcc, 0x0c, 0x38, 0x0c, 0xcc, 0x78, 0x00, + 0x1c, 0x34, 0x6c, 0xcc, 0xfe, 0x0c, 0x0c, 0x00, 0xfc, 0xc0, 0xf8, 0x0c, 0x0c, 0xcc, 0x78, 0x00, + 0x38, 0x60, 0xc0, 0xf8, 0xcc, 0xcc, 0x78, 0x00, 0xfc, 0x0c, 0x0c, 0x18, 0x30, 0x60, 0x60, 0x00, + 0x78, 0xcc, 0xcc, 0x78, 0xcc, 0xcc, 0x78, 0x00, 0x78, 0xcc, 0xcc, 0x7c, 0x0c, 0x18, 0x70, 0x00, + 0x00, 0x00, 0x30, 0x30, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00, 0x70, 0x30, 0x60, + 0x18, 0x30, 0x60, 0xc0, 0x60, 0x30, 0x18, 0x00, 0x00, 0x00, 0xfc, 0x00, 0xfc, 0x00, 0x00, 0x00, + 0x60, 0x30, 0x18, 0x0c, 0x18, 0x30, 0x60, 0x00, 0x78, 0xcc, 0x0c, 0x18, 0x30, 0x00, 0x30, 0x00, + 0x7c, 0x82, 0x9e, 0xb6, 0x9e, 0x80, 0x78, 0x00, 0x30, 0x78, 0xcc, 0xcc, 0xfc, 0xcc, 0xcc, 0x00, + 0xfc, 0x66, 0x66, 0x7c, 0x66, 0x66, 0xfc, 0x00, 0x3c, 0x66, 0xc0, 0xc0, 0xc0, 0x66, 0x3c, 0x00, + 0xf8, 0x6c, 0x66, 0x66, 0x66, 0x6c, 0xf8, 0x00, 0xfe, 0xc2, 0xc8, 0xf8, 0xc8, 0xc2, 0xfe, 0x00, + 0xfe, 0xc2, 0xc8, 0xf8, 0xc8, 0xc0, 0xc0, 0x00, 0x3c, 0x66, 0xc0, 0xc0, 0xce, 0x66, 0x3e, 0x00, + 0xc6, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0x00, 0x3c, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, + 0x0e, 0x06, 0x06, 0x06, 0x66, 0x66, 0x3c, 0x00, 0xc6, 0xcc, 0xd8, 0xf0, 0xd8, 0xcc, 0xc6, 0x00, + 0xf0, 0x60, 0x60, 0x60, 0x60, 0x62, 0xfe, 0x00, 0x82, 0xc6, 0xee, 0xd6, 0xd6, 0xc6, 0xc6, 0x00, + 0xc6, 0xe6, 0xf6, 0xde, 0xce, 0xc6, 0xc6, 0x00, 0x38, 0x6c, 0xc6, 0xc6, 0xc6, 0x6c, 0x38, 0x00, + 0xfc, 0x66, 0x66, 0x66, 0x7c, 0x60, 0xf0, 0x00, 0x38, 0x6c, 0xc6, 0xc6, 0xd6, 0x6c, 0x3c, 0x06, + 0xf8, 0xcc, 0xcc, 0xf8, 0xd8, 0xcc, 0xc6, 0x00, 0x7c, 0xc6, 0xe0, 0x3c, 0x06, 0xc6, 0x7c, 0x00, + 0x7e, 0x5a, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, + 0xc6, 0xc6, 0xc6, 0xc6, 0x6c, 0x38, 0x10, 0x00, 0xc6, 0xc6, 0xc6, 0xd6, 0xd6, 0x6c, 0x6c, 0x00, + 0xc6, 0xc6, 0x6c, 0x38, 0x6c, 0xc6, 0xc6, 0x00, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x18, 0x18, 0x00, + 0xfe, 0x8c, 0x18, 0x30, 0x60, 0xc2, 0xfe, 0x00, 0x3c, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3c, 0x00, + 0xc0, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x02, 0x00, 0x3c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x3c, 0x00, + 0x10, 0x38, 0x6c, 0xc6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, + 0x38, 0x30, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x0c, 0x7c, 0xcc, 0x76, 0x00, + 0xe0, 0x60, 0x7c, 0x66, 0x66, 0x66, 0xdc, 0x00, 0x00, 0x00, 0x7c, 0xc6, 0xc0, 0xc6, 0x7c, 0x00, + 0x1c, 0x0c, 0x7c, 0xcc, 0xcc, 0xcc, 0x76, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x7e, 0x60, 0x3c, 0x00, + 0x1c, 0x36, 0x30, 0x7e, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x76, 0xcc, 0xcc, 0x7c, 0x0c, 0xf8, + 0x60, 0x60, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x00, 0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x7e, 0x00, + 0x0c, 0x00, 0x3c, 0x0c, 0x0c, 0x0c, 0x6c, 0x38, 0x60, 0x60, 0x66, 0x6c, 0x78, 0x6c, 0x66, 0x00, + 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x00, 0x00, 0x00, 0xcc, 0xfe, 0xd6, 0xd6, 0xc6, 0x00, + 0x00, 0x00, 0xdc, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, + 0x00, 0x00, 0xdc, 0x66, 0x66, 0x7c, 0x60, 0xe0, 0x00, 0x00, 0x76, 0xcc, 0xcc, 0x7c, 0x0c, 0x0e, + 0x00, 0x00, 0xdc, 0x66, 0x60, 0x60, 0xf0, 0x00, 0x00, 0x00, 0x3e, 0x60, 0x3c, 0x06, 0x7c, 0x00, + 0x00, 0x30, 0x7e, 0x30, 0x30, 0x36, 0x1c, 0x00, 0x00, 0x00, 0xcc, 0xcc, 0xcc, 0xcc, 0x76, 0x00, + 0x00, 0x00, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x00, 0x00, 0x00, 0xc6, 0xd6, 0xd6, 0x6c, 0x6c, 0x00, + 0x00, 0x00, 0xc6, 0x6c, 0x38, 0x6c, 0xc6, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x70, + 0x00, 0x00, 0x7e, 0x4c, 0x18, 0x32, 0x7e, 0x00, 0x0e, 0x18, 0x18, 0x70, 0x18, 0x18, 0x0e, 0x00, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x70, 0x18, 0x18, 0x0e, 0x18, 0x18, 0x70, 0x00, + 0x62, 0x92, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x6c, 0xc6, 0xc6, 0xc6, 0xfe, 0x00, + 0x3c, 0x66, 0xc0, 0xc0, 0x66, 0x3c, 0x0c, 0x38, 0x66, 0x00, 0x66, 0x66, 0x66, 0x66, 0x7e, 0x00, + 0x06, 0x08, 0x3c, 0x66, 0x7e, 0x60, 0x3c, 0x00, 0x18, 0x66, 0x00, 0x3c, 0x06, 0x76, 0x3e, 0x00, + 0x66, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x3e, 0x00, 0x20, 0x10, 0x3c, 0x06, 0x3e, 0x66, 0x3e, 0x00, + 0x08, 0x14, 0x3c, 0x06, 0x3e, 0x66, 0x3e, 0x00, 0x00, 0x00, 0x3e, 0x60, 0x60, 0x3e, 0x0c, 0x38, + 0x18, 0x66, 0x00, 0x3c, 0x6e, 0x60, 0x3c, 0x00, 0x66, 0x00, 0x3c, 0x66, 0x7e, 0x60, 0x3c, 0x00, + 0x20, 0x10, 0x3c, 0x66, 0x7e, 0x60, 0x3c, 0x00, 0x66, 0x00, 0x38, 0x18, 0x18, 0x18, 0x7e, 0x00, + 0x18, 0x66, 0x00, 0x38, 0x18, 0x18, 0x7e, 0x00, 0x20, 0x10, 0x38, 0x18, 0x18, 0x18, 0x7e, 0x00, + 0x66, 0x18, 0x3c, 0x66, 0x66, 0x7e, 0x66, 0x00, 0x3c, 0x66, 0x3c, 0x66, 0x66, 0x7e, 0x66, 0x00, + 0x06, 0x0c, 0x7e, 0x60, 0x7c, 0x60, 0x7e, 0x00, 0x00, 0x00, 0xec, 0x12, 0x7e, 0x90, 0xee, 0x00, + 0x3e, 0x6c, 0xcc, 0xfe, 0xcc, 0xcc, 0xce, 0x00, 0x18, 0x66, 0x00, 0x3c, 0x66, 0x66, 0x3c, 0x00, + 0x66, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x60, 0x10, 0x3c, 0x66, 0x66, 0x66, 0x3c, 0x00, + 0x18, 0x66, 0x00, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x60, 0x10, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, + 0x66, 0x00, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x70, 0x63, 0x1c, 0x36, 0x63, 0x63, 0x36, 0x1c, 0x00, + 0x66, 0x00, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x18, 0x18, 0x3c, 0x60, 0x60, 0x3c, 0x18, 0x18, + 0x1c, 0x36, 0x30, 0x7c, 0x30, 0x20, 0x7e, 0x00, 0x66, 0x66, 0x3c, 0x7e, 0x18, 0x7e, 0x18, 0x18, + 0xf0, 0xd8, 0xd8, 0xfc, 0xce, 0xcc, 0xce, 0x00, 0x0e, 0x18, 0x18, 0x7e, 0x18, 0x18, 0x70, 0x00, + 0x06, 0x08, 0x3c, 0x06, 0x3e, 0x66, 0x3e, 0x00, 0x06, 0x08, 0x38, 0x18, 0x18, 0x18, 0x7e, 0x00, + 0x06, 0x08, 0x3c, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x06, 0x08, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, + 0x72, 0x4e, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x00, 0x32, 0x4c, 0xe6, 0xb6, 0xda, 0xce, 0xc6, 0x00, + 0x78, 0x0c, 0x7c, 0xcc, 0x7c, 0x00, 0xfc, 0x00, 0x78, 0xcc, 0xcc, 0xcc, 0x78, 0x00, 0xfc, 0x00, + 0x00, 0x30, 0x00, 0x30, 0x60, 0xc0, 0xcc, 0x78, 0x00, 0x00, 0x00, 0xfc, 0xc0, 0xc0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xfc, 0x0c, 0x0c, 0x00, 0x00, 0x80, 0x80, 0x80, 0xbc, 0x86, 0x1c, 0x30, 0x3e, + 0x80, 0x80, 0x80, 0x8e, 0x96, 0x26, 0x3e, 0x06, 0x00, 0x30, 0x00, 0x30, 0x30, 0x78, 0x78, 0x30, + 0x00, 0x00, 0x36, 0x6c, 0xd8, 0x6c, 0x36, 0x00, 0x00, 0x00, 0xd8, 0x6c, 0x36, 0x6c, 0xd8, 0x00, + 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, + 0xbb, 0xee, 0xbb, 0xee, 0xbb, 0xee, 0xbb, 0xee, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0xf8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf8, 0x18, 0xf8, 0x18, 0x18, + 0x36, 0x36, 0x36, 0x36, 0xf6, 0x36, 0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x36, 0x36, 0x36, + 0x00, 0x00, 0x00, 0xf8, 0x18, 0xf8, 0x18, 0x18, 0x36, 0x36, 0x36, 0xf6, 0x06, 0xf6, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x00, 0x00, 0x00, 0xfe, 0x06, 0xf6, 0x36, 0x36, + 0x36, 0x36, 0x36, 0xf6, 0x06, 0xfe, 0x00, 0x00, 0x36, 0x36, 0x36, 0x36, 0xfe, 0x00, 0x00, 0x00, + 0x18, 0x18, 0x18, 0xf8, 0x18, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x1f, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0xff, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1f, 0x18, 0x18, 0x18, + 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0xff, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x1f, 0x18, 0x1f, 0x18, 0x18, 0x36, 0x36, 0x36, 0x36, 0x37, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x37, 0x30, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x30, 0x37, 0x36, 0x36, + 0x36, 0x36, 0x36, 0xf7, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xf7, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x37, 0x30, 0x37, 0x36, 0x36, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, + 0x36, 0x36, 0x36, 0xf7, 0x00, 0xf7, 0x36, 0x36, 0x18, 0x18, 0x18, 0xff, 0x00, 0xff, 0x00, 0x00, + 0x36, 0x36, 0x36, 0x36, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0x18, 0x18, + 0x00, 0x00, 0x00, 0x00, 0xff, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x3f, 0x00, 0x00, 0x00, + 0x18, 0x18, 0x18, 0x1f, 0x18, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x18, 0x1f, 0x18, 0x18, + 0x00, 0x00, 0x00, 0x00, 0x3f, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0xff, 0x36, 0x36, 0x36, + 0x18, 0x18, 0x18, 0xff, 0x18, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf8, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1f, 0x18, 0x18, 0x18, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x76, 0xdc, 0xc8, 0xdc, 0x76, 0x00, 0x78, 0xcc, 0xd8, 0xcc, 0xcc, 0xf8, 0xc0, 0xc0, + 0xfe, 0xc6, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x00, 0x00, 0x00, 0xfe, 0x6c, 0x6c, 0x6c, 0x66, 0x00, + 0xfe, 0x60, 0x30, 0x18, 0x30, 0x60, 0xfe, 0x00, 0x00, 0x00, 0x7e, 0xc8, 0xcc, 0xcc, 0x78, 0x00, + 0x00, 0x00, 0xcc, 0xcc, 0xcc, 0xf8, 0xc0, 0x80, 0x00, 0x00, 0xfe, 0x30, 0x30, 0x30, 0x1c, 0x00, + 0x3c, 0x18, 0x7e, 0xc3, 0x7e, 0x18, 0x3c, 0x00, 0x38, 0x6c, 0xc6, 0xfe, 0xc6, 0x6c, 0x38, 0x00, + 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0x6c, 0xee, 0x00, 0x1c, 0x30, 0x18, 0x7c, 0xcc, 0xcc, 0x78, 0x00, + 0x00, 0x00, 0x66, 0xdb, 0xdb, 0x66, 0x00, 0x00, 0x03, 0x06, 0x7e, 0xdb, 0xdb, 0x7e, 0x60, 0xc0, + 0x3c, 0x60, 0xc0, 0xfc, 0xc0, 0x60, 0x3c, 0x00, 0x78, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x00, + 0x00, 0xfc, 0x00, 0xfc, 0x00, 0xfc, 0x00, 0x00, 0x30, 0x30, 0xfc, 0x30, 0x30, 0x00, 0xfc, 0x00, + 0x60, 0x30, 0x1c, 0x30, 0x60, 0x00, 0xfc, 0x00, 0x18, 0x30, 0xe0, 0x30, 0x18, 0x00, 0xfc, 0x00, + 0x0e, 0x1b, 0x1b, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xd8, 0xd8, 0x70, + 0x30, 0x30, 0x00, 0xfc, 0x00, 0x30, 0x30, 0x00, 0x00, 0x72, 0x9c, 0x00, 0x72, 0x9c, 0x00, 0x00, + 0x38, 0x6c, 0x6c, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x08, 0x08, 0x88, 0xc8, 0x68, 0x38, 0x18, + 0x78, 0x6c, 0x6c, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x78, 0x0c, 0x38, 0x60, 0x7c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x78, 0x78, 0x78, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x06, 0x7c, 0xce, 0xd6, 0xe6, 0x7c, 0xc0, 0x06, 0x7c, 0xce, 0xde, 0xf6, 0xe6, 0x7c, 0xc0, + 0x00, 0x00, 0x44, 0x6c, 0x38, 0x6c, 0x44, 0x00, 0x7e, 0x81, 0xb9, 0xad, 0xb9, 0xa5, 0x81, 0x7e, + 0x30, 0x60, 0x30, 0x78, 0xcc, 0xfc, 0xcc, 0x00, 0x78, 0xcc, 0x30, 0x78, 0xcc, 0xfc, 0xcc, 0x00, + 0x30, 0x18, 0x30, 0x78, 0xcc, 0xfc, 0xcc, 0x00, 0x7e, 0x81, 0x9d, 0xa1, 0xa1, 0x9d, 0x81, 0x7e, + 0x72, 0x4e, 0x00, 0x3c, 0x06, 0x76, 0x3e, 0x00, 0x72, 0x4e, 0x18, 0x3c, 0x66, 0x7e, 0x66, 0x00, + 0x42, 0x3c, 0x66, 0x66, 0x66, 0x3c, 0x42, 0x00, 0x34, 0x18, 0x2c, 0x06, 0x7e, 0xc6, 0x7c, 0x00, + 0xf8, 0x6c, 0x66, 0xf6, 0x66, 0x6c, 0xf8, 0x00, 0x38, 0x6c, 0xfe, 0xc0, 0xf8, 0xc0, 0xfe, 0x00, + 0xc6, 0x00, 0xfe, 0xc0, 0xf8, 0xc0, 0xfe, 0x00, 0x30, 0x18, 0xfe, 0xc0, 0xf8, 0xc0, 0xfe, 0x00, + 0x00, 0x00, 0x00, 0x10, 0x30, 0x10, 0x38, 0x00, 0x0c, 0x18, 0x3c, 0x18, 0x18, 0x18, 0x3c, 0x00, + 0x3c, 0x66, 0x3c, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x66, 0x00, 0x3c, 0x18, 0x18, 0x18, 0x3c, 0x00, + 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00, 0x30, 0x18, 0x3c, 0x18, 0x18, 0x18, 0x3c, 0x00, + 0x0c, 0x18, 0x7c, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x7c, 0xc6, 0x7c, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, + 0x60, 0x30, 0x7c, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x72, 0x9c, 0x00, 0x7c, 0xc6, 0xc6, 0x7c, 0x00, + 0x72, 0x9c, 0x7c, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0xf0, 0x60, 0x7c, 0x66, 0x66, 0x7c, 0x60, 0xf0, + 0xf0, 0x60, 0x7c, 0x66, 0x7c, 0x60, 0xf0, 0x00, 0x0c, 0x18, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, + 0x3c, 0x66, 0x00, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x30, 0x18, 0x00, 0x66, 0x66, 0x66, 0x3c, 0x00, + 0x0c, 0x18, 0x00, 0x66, 0x66, 0x3c, 0x18, 0x70, 0x0c, 0x18, 0x66, 0x66, 0x3c, 0x18, 0x18, 0x00, + 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0xfe, + 0xc0, 0x20, 0xc0, 0x2e, 0xd6, 0x26, 0x3e, 0x06, 0x4c, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xc6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x60, 0x20, 0x20, 0x70, 0x00, 0x00, 0x00, + 0xe0, 0x10, 0x60, 0x10, 0xe0, 0x00, 0x00, 0x00, 0xfc, 0xc0, 0xc0, 0xfc, 0xc6, 0xc6, 0xfc, 0x00, + 0xfe, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x00, 0x7e, 0x66, 0x66, 0x66, 0x66, 0x66, 0xff, 0xc3, + 0xdb, 0xdb, 0x7e, 0x18, 0x7e, 0xdb, 0xdb, 0x00, 0x3c, 0x66, 0x06, 0x3c, 0x06, 0xc6, 0x7c, 0x00, + 0xc6, 0xce, 0xde, 0xf6, 0xe6, 0xc6, 0xc6, 0x00, 0xd6, 0xce, 0xde, 0xf6, 0xe6, 0xc6, 0xc6, 0x00, + 0x0e, 0x1e, 0x36, 0x66, 0xc6, 0xc6, 0xc6, 0x00, 0xfe, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, + 0xc6, 0xc6, 0xc6, 0x7e, 0x06, 0xc6, 0x7c, 0x00, 0x10, 0x7c, 0xd6, 0xd6, 0xd6, 0x7c, 0x10, 0x00, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xfe, 0x03, 0xc6, 0xc6, 0xc6, 0x7e, 0x06, 0x06, 0x06, 0x00, + 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xfe, 0x00, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xfe, 0x03, + 0xe0, 0xe0, 0x60, 0x7c, 0x66, 0x66, 0x7c, 0x00, 0xc2, 0xc2, 0xc2, 0xf2, 0xda, 0xda, 0xf2, 0x00, + 0xc0, 0xc0, 0xc0, 0xfc, 0xc6, 0xc6, 0xfc, 0x00, 0x7c, 0xc6, 0x06, 0x3e, 0x06, 0xc6, 0x7c, 0x00, + 0xce, 0xdb, 0xdb, 0xfb, 0xdb, 0xdb, 0xce, 0x00, 0x7e, 0xc6, 0xc6, 0x7e, 0x36, 0x66, 0xc6, 0x00, + 0x06, 0x7c, 0xc0, 0xfc, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0xfc, 0xc6, 0xfc, 0xc6, 0xfc, 0x00, + 0x00, 0x00, 0xfe, 0xc0, 0xc0, 0xc0, 0xc0, 0x00, 0x00, 0x00, 0x7e, 0x66, 0x66, 0x66, 0xff, 0xc3, + 0x00, 0x00, 0xdb, 0x7e, 0x18, 0x7e, 0xdb, 0x00, 0x00, 0x00, 0x7c, 0x86, 0x3c, 0x86, 0x7c, 0x00, + 0x00, 0x00, 0xce, 0xde, 0xf6, 0xe6, 0xc6, 0x00, 0x38, 0x00, 0xce, 0xde, 0xf6, 0xe6, 0xc6, 0x00, + 0x00, 0x00, 0xc6, 0xcc, 0xf8, 0xcc, 0xc6, 0x00, 0x00, 0x00, 0x1e, 0x36, 0x66, 0xc6, 0xc6, 0x00, + 0x00, 0x00, 0x82, 0xc6, 0xee, 0xfe, 0xd6, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0x00, + 0x00, 0x00, 0xfe, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x00, + 0x00, 0x00, 0xc6, 0xc6, 0x7e, 0x06, 0x7c, 0x00, 0x00, 0x10, 0x7c, 0xd6, 0xd6, 0x7c, 0x10, 0x10, + 0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xfe, 0x03, 0x00, 0x00, 0xc6, 0xc6, 0x7e, 0x06, 0x06, 0x00, + 0x00, 0x00, 0xd6, 0xd6, 0xd6, 0xd6, 0xfe, 0x00, 0x00, 0x00, 0xd6, 0xd6, 0xd6, 0xd6, 0xfe, 0x03, + 0x00, 0x00, 0xe0, 0xe0, 0x7c, 0x66, 0x7c, 0x00, 0x00, 0x00, 0xc2, 0xc2, 0xf2, 0xda, 0xf2, 0x00, + 0x00, 0x00, 0xc0, 0xc0, 0xfc, 0xc6, 0xfc, 0x00, 0x00, 0x00, 0x7c, 0xc6, 0x1e, 0xc6, 0x7c, 0x00, + 0x00, 0x00, 0xce, 0xdb, 0xfb, 0xdb, 0xce, 0x00, 0x00, 0x00, 0x7e, 0xc6, 0x7e, 0x66, 0xc6, 0x00, + 0x7c, 0xc6, 0xc0, 0xf8, 0xc0, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x7c, 0xc2, 0xf8, 0xc2, 0x7c, 0x00, + 0x28, 0xc6, 0xc6, 0x7e, 0x06, 0xc6, 0x7c, 0x00, 0x96, 0xd9, 0xd6, 0xb0, 0xb7, 0x90, 0x90, 0x00, + 0x06, 0x0e, 0x1e, 0x36, 0x7e, 0xc6, 0xc6, 0x00, 0xfc, 0x66, 0x7c, 0x66, 0x66, 0x66, 0xfc, 0x00, + 0xfe, 0x62, 0x78, 0x60, 0x60, 0x62, 0xfe, 0x00, 0xe6, 0x6c, 0x78, 0x7c, 0x66, 0x66, 0xe6, 0x00, + 0x82, 0xc6, 0xee, 0xfe, 0xd6, 0xd6, 0xd6, 0x00, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, + 0x30, 0x78, 0xcc, 0xcc, 0xcc, 0x78, 0x30, 0x00, 0xfc, 0x66, 0x7c, 0x60, 0x60, 0x60, 0xf0, 0x00, + 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x7c, 0xc6, 0xc0, 0xc0, 0xc6, 0xc6, 0x7c, 0x00, + 0x82, 0xc6, 0x6c, 0x38, 0x6c, 0xc6, 0x82, 0x00, 0x00, 0x00, 0xfe, 0x06, 0xfe, 0xc6, 0xfe, 0x00, + 0x00, 0x00, 0xfe, 0xc6, 0xfe, 0xc0, 0xfe, 0x00, 0x00, 0x00, 0x38, 0x6c, 0xc6, 0x6c, 0x38, 0x00, + 0x00, 0x00, 0xfc, 0xc6, 0xfc, 0xc0, 0xc0, 0x00, 0x00, 0x00, 0xfe, 0xc2, 0xc0, 0xc2, 0xfe, 0x00, + 0x00, 0x00, 0xc6, 0xee, 0x38, 0xee, 0xc6, 0x00, 0x00, 0x00, 0xf0, 0x60, 0x7c, 0x66, 0xfc, 0x00, + 0x6c, 0x00, 0xc6, 0xc6, 0x7e, 0x06, 0x7c, 0x00, 0xc6, 0xc6, 0x7c, 0x6c, 0x7c, 0xc6, 0xc6, 0x00, + 0x38, 0x6c, 0x00, 0x7c, 0xc0, 0xc0, 0x7c, 0x00, 0x7c, 0x82, 0x7c, 0xc6, 0xc0, 0xc6, 0x7c, 0x00, + 0x00, 0x30, 0x00, 0x7c, 0xc0, 0xc0, 0x7c, 0x00, 0x18, 0x00, 0x7c, 0xc6, 0xc0, 0xc6, 0x7c, 0x00, + 0x30, 0x00, 0x78, 0x30, 0x30, 0x30, 0x78, 0x00, 0x38, 0x6c, 0x00, 0x7e, 0xcc, 0x7c, 0x0c, 0xf8, + 0x7c, 0x82, 0x7c, 0xc0, 0xce, 0xc6, 0x7e, 0x00, 0x1c, 0x36, 0x00, 0x1e, 0x0c, 0x0c, 0xcc, 0x78, + 0xc6, 0x7c, 0x7c, 0xc0, 0xce, 0xc6, 0x7e, 0x00, 0x6c, 0x38, 0x00, 0x7e, 0xcc, 0x7c, 0x0c, 0xf8, + 0x3c, 0x66, 0x00, 0x66, 0x66, 0x7e, 0x66, 0x00, 0x38, 0x6c, 0x00, 0xe0, 0x7c, 0x66, 0xe6, 0x00, + 0x3c, 0x66, 0x1c, 0x0c, 0x0c, 0xcc, 0x78, 0x00, 0x00, 0x7c, 0xc0, 0x78, 0x0c, 0xf8, 0x30, 0xe0, + 0x78, 0xcc, 0x60, 0x38, 0xcc, 0x78, 0x30, 0xe0, 0x38, 0x6c, 0x7e, 0xc0, 0x7c, 0x06, 0xfc, 0x00, + 0x38, 0x6c, 0x3c, 0x60, 0x38, 0x0c, 0x78, 0x00, 0x18, 0x00, 0x7c, 0xc0, 0xce, 0xc6, 0x7e, 0x00, + 0x00, 0x18, 0x00, 0x7e, 0xcc, 0x7c, 0x0c, 0xf8, 0x66, 0xff, 0x66, 0x7e, 0x66, 0x66, 0x66, 0x00, + 0x60, 0xfe, 0x60, 0x7c, 0x66, 0x66, 0x66, 0x00, 0x6c, 0x38, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, + 0x6c, 0x38, 0x00, 0xcc, 0xcc, 0xcc, 0x76, 0x00, 0x38, 0x6c, 0x6c, 0x78, 0xe0, 0x66, 0x3c, 0x00, + 0xc0, 0x80, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x38, 0x18, 0x18, 0x18, 0x7e, 0x00, 0x00, 0x18, 0x00, 0x7e, 0x0c, 0x30, 0x7e, 0x00, + 0x30, 0x00, 0xfe, 0x8c, 0x38, 0x62, 0xfe, 0x00, 0x30, 0x00, 0xcc, 0xcc, 0xcc, 0xcc, 0x76, 0x00, + 0x08, 0x10, 0x00, 0x7c, 0xc0, 0xc0, 0x7c, 0x00, 0x38, 0x1a, 0x1c, 0x38, 0x58, 0x18, 0x7e, 0x00, + 0x24, 0x48, 0x7c, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x24, 0x48, 0x00, 0x7c, 0xc6, 0xc6, 0x7c, 0x00, + 0x08, 0x10, 0xfe, 0x8c, 0x38, 0x62, 0xfe, 0x00, 0x04, 0x08, 0x7c, 0xc6, 0xc0, 0xc6, 0x7c, 0x00, + 0x20, 0x40, 0xf0, 0x60, 0x60, 0x62, 0xfe, 0x00, 0x08, 0x10, 0x38, 0x18, 0x18, 0x18, 0x7e, 0x00, + 0x90, 0x60, 0xf0, 0x60, 0x60, 0x62, 0xfe, 0x00, 0x48, 0x30, 0x38, 0x18, 0x18, 0x18, 0x7e, 0x00, + 0x08, 0x10, 0x7e, 0xc0, 0x7c, 0x06, 0xfc, 0x00, 0x08, 0x10, 0x3c, 0x60, 0x3c, 0x06, 0x3c, 0x00, + 0x24, 0x18, 0x7e, 0x5a, 0x18, 0x18, 0x18, 0x00, 0x48, 0x30, 0x30, 0x7e, 0x30, 0x30, 0x1c, 0x00, + 0x78, 0x34, 0x38, 0x70, 0xb0, 0x32, 0x7e, 0x00, 0x48, 0x30, 0x00, 0x7c, 0xc0, 0xc0, 0x7c, 0x00, + 0x30, 0x78, 0xcc, 0xfc, 0xcc, 0xcc, 0x18, 0x0e, 0x00, 0x78, 0x0c, 0x7c, 0xcc, 0x76, 0x18, 0x0e, + 0x48, 0x30, 0xfe, 0x8c, 0x38, 0x62, 0xfe, 0x00, 0x24, 0x18, 0x00, 0x7e, 0x0c, 0x30, 0x7e, 0x00, + 0xfe, 0xc2, 0xf8, 0xc0, 0xc2, 0xfe, 0x18, 0x0e, 0x00, 0x3c, 0x66, 0x7e, 0x60, 0x3c, 0x18, 0x0e, + 0x08, 0x10, 0x00, 0x7e, 0x0c, 0x30, 0x7e, 0x00, 0x44, 0x38, 0x7c, 0xc6, 0xc0, 0xc6, 0x7c, 0x00, + 0x48, 0x30, 0xfe, 0xc2, 0xf8, 0xc2, 0xfe, 0x00, 0x48, 0x30, 0x78, 0xcc, 0xcc, 0xfc, 0xcc, 0x00, + 0x48, 0x30, 0x78, 0x0c, 0x7c, 0xcc, 0x76, 0x00, 0x48, 0x30, 0xfc, 0x66, 0x66, 0x66, 0xfc, 0x00, + 0x24, 0x18, 0x0c, 0x7c, 0xcc, 0xcc, 0x76, 0x00, 0x24, 0x18, 0xe6, 0xf6, 0xde, 0xce, 0xc6, 0x00, + 0x24, 0x18, 0x3c, 0x66, 0x7e, 0x60, 0x3c, 0x00, 0x7e, 0x5a, 0x18, 0x18, 0x18, 0x18, 0x0c, 0x38, + 0x10, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x08, 0x10, 0xe6, 0xf6, 0xde, 0xce, 0xc6, 0x00, + 0x08, 0x10, 0xdc, 0x66, 0x66, 0x66, 0x66, 0x00, 0x24, 0x18, 0xdc, 0x66, 0x66, 0x66, 0x66, 0x00, + 0x24, 0x18, 0x7e, 0xc0, 0x3c, 0x06, 0xfc, 0x00, 0x24, 0x18, 0x3c, 0x60, 0x3c, 0x06, 0x3c, 0x00, + 0x10, 0x20, 0xf8, 0xcc, 0xf8, 0xcc, 0xc6, 0x00, 0x08, 0x10, 0xdc, 0x66, 0x60, 0x60, 0xf0, 0x00, + 0x24, 0x48, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x30, 0x7e, 0x30, 0x36, 0x1c, 0x0c, 0x38, + 0x24, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x1c, + 0x24, 0x48, 0xcc, 0xcc, 0xcc, 0xcc, 0x76, 0x00, 0x90, 0x60, 0xf8, 0xcc, 0xf8, 0xcc, 0xc6, 0x00, + 0x24, 0x18, 0xdc, 0x66, 0x60, 0x60, 0xf0, 0x00, 0x00, 0x6c, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xff, 0x01, 0xff, 0x02, 0xff, 0x03, 0xff, 0x04, 0xff, 0x05, 0xff, 0x06, 0xff, 0x07, 0xff, + 0x08, 0xff, 0x09, 0xff, 0x0a, 0xff, 0x0b, 0xff, 0x0c, 0xff, 0x0d, 0xff, 0x0e, 0xff, 0x0f, 0xff, + 0x10, 0xff, 0x11, 0xff, 0x12, 0xff, 0x13, 0xff, 0x14, 0xc2, 0xb6, 0xff, 0x15, 0xc2, 0xa7, 0xff, + 0x16, 0xff, 0x17, 0xff, 0x18, 0xff, 0x19, 0xff, 0x1a, 0xff, 0x1b, 0xff, 0x1c, 0xff, 0x1d, 0xff, + 0x1e, 0xff, 0x1f, 0xff, 0x20, 0xc2, 0xa0, 0xe2, 0x80, 0x80, 0xe2, 0x80, 0x81, 0xe2, 0x80, 0x82, + 0xe2, 0x80, 0x83, 0xe2, 0x80, 0x84, 0xe2, 0x80, 0x85, 0xe2, 0x80, 0x86, 0xe2, 0x80, 0x87, 0xe2, + 0x80, 0x88, 0xe2, 0x80, 0x89, 0xe2, 0x80, 0x8a, 0xe2, 0x80, 0xaf, 0xff, 0x21, 0xff, 0x22, 0xff, + 0x23, 0xff, 0x24, 0xff, 0x25, 0xff, 0x26, 0xff, 0x27, 0xff, 0x28, 0xff, 0x29, 0xff, 0x2a, 0xff, + 0x2b, 0xff, 0x2c, 0xff, 0x2d, 0xff, 0x2e, 0xff, 0x2f, 0xff, 0x30, 0xff, 0x31, 0xff, 0x32, 0xff, + 0x33, 0xff, 0x34, 0xff, 0x35, 0xff, 0x36, 0xff, 0x37, 0xff, 0x38, 0xff, 0x39, 0xff, 0x3a, 0xff, + 0x3b, 0xff, 0x3c, 0xff, 0x3d, 0xff, 0x3e, 0xff, 0x3f, 0xff, 0x40, 0xff, 0x41, 0xff, 0x42, 0xff, + 0x43, 0xff, 0x44, 0xff, 0x45, 0xff, 0x46, 0xff, 0x47, 0xff, 0x48, 0xff, 0x49, 0xff, 0x4a, 0xff, + 0x4b, 0xff, 0x4c, 0xff, 0x4d, 0xff, 0x4e, 0xff, 0x4f, 0xff, 0x50, 0xff, 0x51, 0xff, 0x52, 0xff, + 0x53, 0xff, 0x54, 0xff, 0x55, 0xff, 0x56, 0xff, 0x57, 0xff, 0x58, 0xff, 0x59, 0xff, 0x5a, 0xff, + 0x5b, 0xff, 0x5c, 0xff, 0x5d, 0xff, 0x5e, 0xff, 0x5f, 0xff, 0x60, 0xff, 0x61, 0xff, 0x62, 0xff, + 0x63, 0xff, 0x64, 0xff, 0x65, 0xff, 0x66, 0xff, 0x67, 0xff, 0x68, 0xff, 0x69, 0xff, 0x6a, 0xff, + 0x6b, 0xff, 0x6c, 0xff, 0x6d, 0xff, 0x6e, 0xff, 0x6f, 0xff, 0x70, 0xff, 0x71, 0xff, 0x72, 0xff, + 0x73, 0xff, 0x74, 0xff, 0x75, 0xff, 0x76, 0xff, 0x77, 0xff, 0x78, 0xff, 0x79, 0xff, 0x7a, 0xff, + 0x7b, 0xff, 0x7c, 0xff, 0x7d, 0xff, 0x7e, 0xff, 0x7f, 0xff, 0xc3, 0x87, 0xff, 0xc3, 0xbc, 0xff, + 0xc3, 0xa9, 0xff, 0xc3, 0xa2, 0xff, 0xc3, 0xa4, 0xff, 0xc3, 0xa0, 0xff, 0xc3, 0xa5, 0xff, 0xc3, + 0xa7, 0xff, 0xc3, 0xaa, 0xff, 0xc3, 0xab, 0xd1, 0x91, 0xff, 0xc3, 0xa8, 0xff, 0xc3, 0xaf, 0xd1, + 0x97, 0xff, 0xc3, 0xae, 0xff, 0xc3, 0xac, 0xff, 0xc3, 0x84, 0xff, 0xc3, 0x85, 0xff, 0xc3, 0x89, + 0xff, 0xc3, 0xa6, 0xff, 0xc3, 0x86, 0xff, 0xc3, 0xb4, 0xff, 0xc3, 0xb6, 0xff, 0xc3, 0xb2, 0xff, + 0xc3, 0xbb, 0xff, 0xc3, 0xb9, 0xff, 0xc3, 0xbf, 0xff, 0xc3, 0x96, 0xff, 0xc3, 0x9c, 0xff, 0xc2, + 0xa2, 0xff, 0xc2, 0xa3, 0xff, 0xc2, 0xa5, 0xff, 0xe2, 0x82, 0xa7, 0xff, 0xc6, 0x92, 0xff, 0xc3, + 0xa1, 0xff, 0xc3, 0xad, 0xff, 0xc3, 0xb3, 0xff, 0xc3, 0xba, 0xff, 0xc3, 0xb1, 0xff, 0xc3, 0x91, + 0xff, 0xc2, 0xaa, 0xff, 0xc2, 0xba, 0xff, 0xc2, 0xbf, 0xff, 0xe2, 0x8c, 0x90, 0xff, 0xc2, 0xac, + 0xff, 0xc2, 0xbd, 0xff, 0xc2, 0xbc, 0xff, 0xc2, 0xa1, 0xff, 0xc2, 0xab, 0xff, 0xc2, 0xbb, 0xff, + 0xe2, 0x96, 0x91, 0xff, 0xe2, 0x96, 0x92, 0xff, 0xe2, 0x96, 0x93, 0xff, 0xe2, 0x94, 0x82, 0xff, + 0xe2, 0x94, 0xa4, 0xff, 0xe2, 0x95, 0xa1, 0xff, 0xe2, 0x95, 0xa2, 0xff, 0xe2, 0x95, 0x96, 0xff, + 0xe2, 0x95, 0x95, 0xff, 0xe2, 0x95, 0xa3, 0xff, 0xe2, 0x95, 0x91, 0xff, 0xe2, 0x95, 0x97, 0xff, + 0xe2, 0x95, 0x9d, 0xff, 0xe2, 0x95, 0x9c, 0xff, 0xe2, 0x95, 0x9b, 0xff, 0xe2, 0x94, 0x90, 0xff, + 0xe2, 0x94, 0x94, 0xff, 0xe2, 0x94, 0xb4, 0xff, 0xe2, 0x94, 0xac, 0xff, 0xe2, 0x94, 0x9c, 0xff, + 0xe2, 0x94, 0x80, 0xff, 0xe2, 0x94, 0xbc, 0xff, 0xe2, 0x95, 0x9e, 0xff, 0xe2, 0x95, 0x9f, 0xff, + 0xe2, 0x95, 0x9a, 0xff, 0xe2, 0x95, 0x94, 0xff, 0xe2, 0x95, 0xa9, 0xff, 0xe2, 0x95, 0xa6, 0xff, + 0xe2, 0x95, 0xa0, 0xff, 0xe2, 0x95, 0x90, 0xff, 0xe2, 0x95, 0xac, 0xff, 0xe2, 0x95, 0xa7, 0xff, + 0xe2, 0x95, 0xa8, 0xff, 0xe2, 0x95, 0xa4, 0xff, 0xe2, 0x95, 0xa5, 0xff, 0xe2, 0x95, 0x99, 0xff, + 0xe2, 0x95, 0x98, 0xff, 0xe2, 0x95, 0x92, 0xff, 0xe2, 0x95, 0x93, 0xff, 0xe2, 0x95, 0xab, 0xff, + 0xe2, 0x95, 0xaa, 0xff, 0xe2, 0x94, 0x98, 0xff, 0xe2, 0x94, 0x8c, 0xff, 0xe2, 0x96, 0x88, 0xff, + 0xe2, 0x96, 0x84, 0xff, 0xe2, 0x96, 0x8c, 0xff, 0xe2, 0x96, 0x90, 0xff, 0xe2, 0x96, 0x80, 0xff, + 0xce, 0xb1, 0xff, 0xc3, 0x9f, 0xff, 0xce, 0x93, 0xff, 0xcf, 0x80, 0xff, 0xce, 0xa3, 0xff, 0xcf, + 0x83, 0xff, 0xc2, 0xb5, 0xff, 0xcf, 0x84, 0xff, 0xce, 0xa6, 0xff, 0xce, 0x98, 0xff, 0xce, 0xa9, + 0xff, 0xce, 0xb4, 0xff, 0xe2, 0x88, 0x9e, 0xff, 0xcf, 0x86, 0xe2, 0x88, 0x85, 0xff, 0xce, 0xb5, + 0xff, 0xe2, 0x88, 0xa9, 0xff, 0xe2, 0x89, 0xa1, 0xff, 0xc2, 0xb1, 0xff, 0xe2, 0x89, 0xa5, 0xff, + 0xe2, 0x89, 0xa4, 0xff, 0xe2, 0x8c, 0xa0, 0xff, 0xe2, 0x8c, 0xa1, 0xff, 0xc3, 0xb7, 0xff, 0xe2, + 0x89, 0x88, 0xff, 0xc2, 0xb0, 0xe2, 0x88, 0x98, 0xff, 0xc2, 0xb7, 0xe2, 0x88, 0x99, 0xff, 0xc2, + 0xb7, 0xcb, 0x99, 0xe2, 0x80, 0xa2, 0xff, 0xe2, 0x88, 0x9a, 0xff, 0xe2, 0x81, 0xbf, 0xff, 0xc2, + 0xb2, 0xff, 0xe2, 0x96, 0xa0, 0xff, 0xc2, 0xa0, 0xff, 0xc3, 0xb8, 0xff, 0xc3, 0x98, 0xff, 0xc3, + 0x97, 0xff, 0xc2, 0xae, 0xff, 0xc3, 0x81, 0xff, 0xc3, 0x82, 0xff, 0xc3, 0x80, 0xff, 0xc2, 0xa9, + 0xff, 0xc3, 0xa3, 0xff, 0xc3, 0x83, 0xff, 0xc2, 0xa4, 0xc2, 0xbb, 0xff, 0xc3, 0xb0, 0xc4, 0x91, + 0xff, 0xc3, 0x90, 0xc4, 0x90, 0xff, 0xc3, 0x8a, 0xff, 0xc3, 0x8b, 0xd0, 0x81, 0xff, 0xc3, 0x88, + 0xff, 0xc4, 0xb1, 0xff, 0xc3, 0x8d, 0xff, 0xc3, 0x8e, 0xff, 0xc3, 0x8f, 0xd0, 0x87, 0xff, 0xc2, + 0xa6, 0xff, 0xc3, 0x8c, 0xc3, 0x8e, 0xff, 0xc3, 0x92, 0xc3, 0x93, 0xff, 0xc3, 0x94, 0xff, 0xc3, + 0x92, 0xc3, 0x93, 0xff, 0xc3, 0xb5, 0xff, 0xc3, 0x95, 0xff, 0xc3, 0xbe, 0xff, 0xc3, 0x9e, 0xff, + 0xc3, 0x9a, 0xff, 0xc3, 0x9b, 0xff, 0xc3, 0x99, 0xff, 0xc3, 0xbd, 0xff, 0xc3, 0x9d, 0xff, 0xc2, + 0xaf, 0xe2, 0x80, 0x94, 0xff, 0xc2, 0xb4, 0xff, 0xc2, 0xad, 0xc2, 0xaf, 0xff, 0xc3, 0xac, 0xe2, + 0x80, 0x97, 0xff, 0xc2, 0xbe, 0xff, 0xc2, 0xb8, 0xcb, 0x9b, 0xff, 0xc2, 0xa8, 0xff, 0xc2, 0xb9, + 0xff, 0xc2, 0xb3, 0xff, 0xd0, 0x91, 0xff, 0xd0, 0x93, 0xff, 0xd0, 0x94, 0xff, 0xd0, 0x96, 0xff, + 0xd0, 0x97, 0xff, 0xd0, 0x98, 0xff, 0xd0, 0x99, 0xff, 0xd0, 0x9b, 0xff, 0xd0, 0x9f, 0xff, 0xd0, + 0xa3, 0xff, 0xd0, 0xa4, 0xff, 0xd0, 0xa6, 0xff, 0xd0, 0xa7, 0xff, 0xd0, 0xa8, 0xff, 0xd0, 0xa9, + 0xff, 0xd0, 0xaa, 0xff, 0xd0, 0xab, 0xff, 0xd0, 0xac, 0xff, 0xd0, 0xad, 0xff, 0xd0, 0xae, 0xff, + 0xd0, 0xaf, 0xff, 0xd0, 0xb1, 0xff, 0xd0, 0xb2, 0xff, 0xd0, 0xb3, 0xff, 0xd0, 0xb4, 0xff, 0xd0, + 0xb6, 0xff, 0xd0, 0xb7, 0xff, 0xd0, 0xb8, 0xff, 0xd0, 0xb9, 0xff, 0xd0, 0xba, 0xff, 0xd0, 0xbb, + 0xff, 0xd0, 0xbc, 0xff, 0xd0, 0xbd, 0xff, 0xd0, 0xbf, 0xff, 0xd1, 0x82, 0xff, 0xd1, 0x83, 0xff, + 0xd1, 0x84, 0xff, 0xd1, 0x86, 0xff, 0xd1, 0x87, 0xff, 0xd1, 0x88, 0xff, 0xd1, 0x89, 0xff, 0xd1, + 0x8a, 0xff, 0xd1, 0x8b, 0xff, 0xff, 0xd1, 0x8d, 0xff, 0xd1, 0x8e, 0xff, 0xd1, 0x8f, 0xff, 0xd0, + 0x84, 0xff, 0xd1, 0x94, 0xff, 0xd0, 0x8e, 0xff, 0xe2, 0x84, 0x96, 0xff, 0xd0, 0x90, 0xff, 0xd0, + 0x92, 0xff, 0xd0, 0x95, 0xff, 0xd0, 0x9a, 0xff, 0xd0, 0x9c, 0xff, 0xd0, 0x9d, 0xff, 0xd0, 0x9e, + 0xff, 0xd0, 0xa0, 0xff, 0xd0, 0xa2, 0xff, 0xd0, 0xa1, 0xff, 0xd0, 0xa5, 0xff, 0xd0, 0xb0, 0xff, + 0xd0, 0xb5, 0xff, 0xd0, 0xbe, 0xff, 0xd1, 0x80, 0xff, 0xd1, 0x81, 0xff, 0xd1, 0x85, 0xff, 0xd1, + 0x8c, 0xff, 0xd1, 0x9e, 0xff, 0xc2, 0xa4, 0xff, 0xc4, 0x89, 0xff, 0xc4, 0x88, 0xff, 0xc4, 0x8b, + 0xff, 0xc4, 0x8a, 0xff, 0xc4, 0xb0, 0xff, 0xc4, 0x9d, 0xff, 0xc4, 0x9c, 0xff, 0xff, 0xc4, 0x9e, + 0xff, 0xc4, 0x9f, 0xff, 0xc4, 0xa4, 0xff, 0xc4, 0xa5, 0xff, 0xc4, 0xb4, 0xff, 0xc4, 0xb5, 0xc5, + 0x9f, 0xff, 0xc3, 0x97, 0xc5, 0x9e, 0xff, 0xc5, 0x9c, 0xff, 0xc5, 0x9d, 0xff, 0xc4, 0xa0, 0xff, + 0xc4, 0xa1, 0xff, 0xc4, 0xa6, 0xff, 0xc4, 0xa7, 0xff, 0xc5, 0xac, 0xff, 0xc5, 0xad, 0xff, 0xe2, + 0x84, 0x93, 0xff, 0xc5, 0x89, 0xff, 0xcb, 0x98, 0xff, 0xc3, 0xac, 0xc4, 0xb1, 0xff, 0xc5, 0xbc, + 0xff, 0xc5, 0xbb, 0xff, 0xc5, 0xaf, 0xff, 0xc4, 0x87, 0xff, 0xc5, 0x82, 0xff, 0xc5, 0x90, 0xff, + 0xc5, 0x91, 0xff, 0xc5, 0xb9, 0xff, 0xc4, 0x86, 0xff, 0xc4, 0xb9, 0xff, 0xc4, 0xba, 0xff, 0xc4, + 0xbd, 0xff, 0xc4, 0xbe, 0xff, 0xc5, 0x9a, 0xff, 0xc5, 0x9b, 0xff, 0xc5, 0xa4, 0xff, 0xc5, 0xa5, + 0xff, 0xc5, 0x81, 0xff, 0xc4, 0x8d, 0xff, 0xc4, 0x84, 0xff, 0xc4, 0x85, 0xff, 0xc5, 0xbd, 0xff, + 0xc5, 0xbe, 0xff, 0xc4, 0x98, 0xff, 0xc4, 0x99, 0xff, 0xc5, 0xba, 0xff, 0xc4, 0x8c, 0xff, 0xc4, + 0x9a, 0xff, 0xc4, 0x82, 0xff, 0xc4, 0x83, 0xff, 0xc4, 0x8e, 0xff, 0xc4, 0x8f, 0xff, 0xc5, 0x87, + 0xff, 0xc4, 0x9b, 0xff, 0xc5, 0xa2, 0xff, 0xc5, 0xae, 0xff, 0xc5, 0x83, 0xff, 0xc5, 0x84, 0xff, + 0xc5, 0x88, 0xff, 0xc5, 0xa0, 0xff, 0xc5, 0xa1, 0xff, 0xc5, 0x94, 0xff, 0xc5, 0x95, 0xff, 0xc5, + 0xb0, 0xff, 0xc5, 0xa3, 0xff, 0xcb, 0x9d, 0xff, 0xcb, 0x9b, 0xff, 0xc5, 0xb1, 0xff, 0xc5, 0x98, + 0xff, 0xc5, 0x99, 0xff, 0xcb, 0x87, 0xff +}; +static constexpr unsigned int drdos8x8_psfu_len = 4983; + +#endif /* KERNEL_SRC_GRAPHICS_FONTS_DRDOS8X8_HPP_ */ diff --git a/kernel/src/graphics/geometry.hpp b/kernel/src/graphics/geometry.hpp new file mode 100644 index 00000000..d547b152 --- /dev/null +++ b/kernel/src/graphics/geometry.hpp @@ -0,0 +1,38 @@ +#ifndef KERNEL_SRC_GRAPHICS_GEOMETRY_HPP_ +#define KERNEL_SRC_GRAPHICS_GEOMETRY_HPP_ + +#include +#include + +namespace Graphics +{ + +struct Point { + i32 x; + i32 y; +}; + +struct Rect { + i32 x; + i32 y; + i32 w; + i32 h; +}; + +struct TextCmd { + i32 x; + i32 y; + std::string_view text; + u8 scale = 1; +}; + +struct CharCmd { + i32 x; + i32 y; + char c; + u8 scale = 1; +}; + +} // namespace Graphics + +#endif // KERNEL_SRC_GRAPHICS_GEOMETRY_HPP_ diff --git a/kernel/src/graphics/painter.cpp b/kernel/src/graphics/painter.cpp index 4017039e..61817dfc 100644 --- a/kernel/src/graphics/painter.cpp +++ b/kernel/src/graphics/painter.cpp @@ -24,8 +24,10 @@ u32 Painter::PackColor(Color c) const (static_cast(c.b & b_mask) << format_.blue_pos); } -void Painter::DrawPixel(i32 x, i32 y) +void Painter::DrawPixel(Point p) { + i32 x = p.x; + i32 y = p.y; if (x < 0 || y < 0 || static_cast(x) >= target_.GetWidth() || static_cast(y) >= target_.GetHeight()) { return; @@ -41,7 +43,6 @@ void Painter::FillScanline(u32 *dest, u32 count, u32 color) (color & 0xFF) == ((color >> 24) & 0xFF)) { memset(dest, color & 0xFF, count * sizeof(u32)); } else { - // Compiler auto-vectorization usually handles this loop well for (u32 i = 0; i < count; ++i) { dest[i] = color; } @@ -55,7 +56,7 @@ void Painter::Clear(Color color) u32 width = target_.GetWidth(); u32 pitch = target_.GetPitch(); - // Optimization: if buffer is contiguous (pitch == width * 4 for 32bpp) + // If buffer is contiguous (pitch == width * 4 for 32bpp) // we can clear it in one go. if (pitch == width * sizeof(u32)) { FillScanline(target_.GetScanline(0), width * height, raw); @@ -66,8 +67,13 @@ void Painter::Clear(Color color) } } -void Painter::FillRect(i32 x, i32 y, i32 w, i32 h) +void Painter::FillRect(Rect r) { + i32 x = r.x; + i32 y = r.y; + i32 w = r.w; + i32 h = r.h; + // Clipping Logic if (x < 0) { w += x; @@ -103,16 +109,21 @@ void Painter::FillRect(i32 x, i32 y, i32 w, i32 h) } } -void Painter::DrawRect(i32 x, i32 y, i32 w, i32 h) +void Painter::DrawRect(Rect r) { + i32 x = r.x; + i32 y = r.y; + i32 w = r.w; + i32 h = r.h; + // Top - FillRect(x, y, w, 1); + FillRect({x, y, w, 1}); // Bottom - FillRect(x, y + h - 1, w, 1); + FillRect({x, y + h - 1, w, 1}); // Left - FillRect(x, y, 1, h); + FillRect({x, y, 1, h}); // Right - FillRect(x + w - 1, y, 1, h); + FillRect({x + w - 1, y, 1, h}); } } // namespace Graphics diff --git a/kernel/src/graphics/painter.hpp b/kernel/src/graphics/painter.hpp index 4265af7d..33e3ae76 100644 --- a/kernel/src/graphics/painter.hpp +++ b/kernel/src/graphics/painter.hpp @@ -1,12 +1,25 @@ #ifndef KERNEL_SRC_GRAPHICS_PAINTER_HPP_ #define KERNEL_SRC_GRAPHICS_PAINTER_HPP_ +#include +#include #include "graphics/color.hpp" +#include "graphics/font/glyph.hpp" +#include "graphics/geometry.hpp" #include "graphics/surface.hpp" namespace Graphics { +/** + * @brief Concept ensuring a type acts like a Font. + */ +template +concept FontType = requires(const T &t, char c) { + { t.GetGlyph(c) } -> std::same_as; + { t.GetHeight() } -> std::convertible_to; +}; + class Painter { public: @@ -20,10 +33,28 @@ class Painter void Clear(Color color); - // Safe drawing with clipping - void DrawPixel(i32 x, i32 y); - void DrawRect(i32 x, i32 y, i32 w, i32 h); - void FillRect(i32 x, i32 y, i32 w, i32 h); + void DrawPixel(Point p); + void DrawRect(Rect r); + void FillRect(Rect r); + + // ------------------------------------------------------------------------- + // Text Rendering + // ------------------------------------------------------------------------- + + /** + * @brief Draws a single character using the provided font. + * @tparam FontT A type satisfying the FontType concept (e.g., Psf2Font). + */ + template + void DrawChar(const CharCmd &cmd, const FontT &font); + + /** + * @brief Draws a null-terminated string. + * Handles newlines (\n) and carriage returns (\r). + * @tparam FontT A type satisfying the FontType concept. + */ + template + void DrawString(const TextCmd &cmd, const FontT &font); private: void FillScanline(u32 *dest, u32 count, u32 color); @@ -36,4 +67,6 @@ class Painter } // namespace Graphics +#include "graphics/painter.tpp" + #endif // KERNEL_SRC_GRAPHICS_PAINTER_HPP_ diff --git a/kernel/src/graphics/painter.tpp b/kernel/src/graphics/painter.tpp new file mode 100644 index 00000000..8d470abc --- /dev/null +++ b/kernel/src/graphics/painter.tpp @@ -0,0 +1,70 @@ +#ifndef KERNEL_SRC_GRAPHICS_PAINTER_TPP_ +#define KERNEL_SRC_GRAPHICS_PAINTER_TPP_ + +#include "painter.hpp" + +namespace Graphics +{ + +template +void Painter::DrawChar(const CharCmd &cmd, const FontT &font) +{ + Glyph glyph = font.GetGlyph(cmd.c); + + i32 scaled_width = static_cast(glyph.width) * cmd.scale; + i32 scaled_height = static_cast(glyph.height) * cmd.scale; + + // Skip if character is off-screen + if (cmd.x >= static_cast(target_.GetWidth()) || + cmd.y >= static_cast(target_.GetHeight()) || cmd.x + scaled_width <= 0 || + cmd.y + scaled_height <= 0) { + return; + } + + for (u32 row = 0; row < glyph.height; ++row) { + const byte *row_data = glyph.buffer + (static_cast(row * glyph.stride)); + + for (u32 col = 0; col < glyph.width; ++col) { + // Bit testing for PSF (Big Endian / MSB first) + u32 byte_idx = col >> 3; + u32 bit_idx = 7 - (col & 7); + + bool is_set = (row_data[byte_idx] & (1 << bit_idx)); + + if (is_set) { + FillRect( + {static_cast(cmd.x + (col * cmd.scale)), + static_cast(cmd.y + (row * cmd.scale)), cmd.scale, cmd.scale} + ); + } + } + } +} + +template +void Painter::DrawString(const TextCmd &cmd, const FontT &font) +{ + if (cmd.text.empty()) { + return; + } + + i32 cursor_x = cmd.x; + i32 cursor_y = cmd.y; + const u32 line_height = font.GetHeight(); + + for (char c : cmd.text) { + if (c == '\n') { + cursor_x = cmd.x; + cursor_y += static_cast(line_height * cmd.scale); + } else if (c == '\r') { + cursor_x = cmd.x; + } else { + DrawChar({.x = cursor_x, .y = cursor_y, .c = c, .scale = cmd.scale}, font); + cursor_x += static_cast(font.GetGlyph(c).advance * cmd.scale); + } + } +} + +} // namespace Graphics + +#endif // KERNEL_SRC_GRAPHICS_PAINTER_TPP_ diff --git a/kernel/src/io/register.hpp b/kernel/src/io/register.hpp index 98a2f1d4..0ff3c89a 100644 --- a/kernel/src/io/register.hpp +++ b/kernel/src/io/register.hpp @@ -11,16 +11,8 @@ namespace IO * @brief Abstraction for a specific hardware I/O register or port. * * This class encapsulates the address of a hardware register (e.g., an I/O port number - * on x86 or a physical memory address on MMIO architectures) and provides a type-safe - * interface for data access. + * on x86 or a physical memory address on MMIO architectures) * - * It acts as a wrapper around the Hardware Abstraction Layer (HAL), delegating - * specific read/write instructions (like `inb`/`outb` or volatile pointer access) - * to `hal::IoRead` and `hal::IoWrite`. This allows device drivers to remain - * architecture-agnostic. - * - * @note Operations are restricted to valid I/O types (u8, u16, u32) via the - * `hal::IoT` concept. */ class Register { diff --git a/kernel/src/kernel.cpp b/kernel/src/kernel.cpp index d5f2f473..ec2abf69 100644 --- a/kernel/src/kernel.cpp +++ b/kernel/src/kernel.cpp @@ -7,8 +7,11 @@ /* internal includes */ #include -#include "boot_args.hpp" +#include "graphics/font/psf2_font.hpp" +#include "graphics/fonts/drdos8x8.hpp" #include "graphics/painter.hpp" + +#include "boot_args.hpp" #include "hal/boot_args.hpp" #include "mem/heap.hpp" #include "modules/video.hpp" @@ -35,17 +38,39 @@ static void KernelRun() auto fmt = video.GetFormat(); Graphics::Painter p(screen, fmt); + Graphics::Psf2Font system_font(drdos8x8_psfu); + + if (!system_font.IsValid()) { + TRACE_WARN_VIDEO("System font magic invalid! Rendering might be corrupted."); + } // Animation Loop i32 x = 0; i32 y = 0; u16 speed = 1; + while (true) { p.Clear(Graphics::Color::Black()); + + // Shape drawing p.SetColor(Graphics::Color::Green()); - p.FillRect(x, 100, 50, 50); + p.FillRect({.x = x, .y = 100, .w = 50, .h = 50}); p.SetColor(Graphics::Color::Blue()); - p.FillRect(100, y, 70, 70); + p.FillRect({.x = 100, .y = y, .w = 70, .h = 70}); + + // Text drawing + p.SetColor(Graphics::Color::White()); + p.DrawString({.x = 40, .y = 20, .text = "AlkOS Kernel", .scale = 3}, system_font); + + p.SetColor(Graphics::Color::Red()); + p.DrawString( + {.x = 40, .y = 50, .text = "Graphics Subsystem Online.", .scale = 3}, system_font + ); + + // Dynamic Text Position + p.SetColor(Graphics::Color::Green()); + p.DrawString({.x = x, .y = y + 80, .text = "Moving Text!", .scale = 2}, system_font); + video.Flush(); x += (speed / 255) + 1; @@ -55,7 +80,7 @@ static void KernelRun() // crude delay for (volatile int i = 0; i < 1000000; i++); - speed = speed + 80; + speed = speed + 10; } } diff --git a/libs/libcontainers/include/data_structures/array_structures.hpp b/libs/libcontainers/include/data_structures/array_structures.hpp index 4f38bef2..99ae1506 100644 --- a/libs/libcontainers/include/data_structures/array_structures.hpp +++ b/libs/libcontainers/include/data_structures/array_structures.hpp @@ -1,10 +1,11 @@ -#ifndef ALKOS_LIBC_INCLUDE_EXTENSIONS_DATA_STRUCTURES_ARRAY_STRUCTURES_HPP_ -#define ALKOS_LIBC_INCLUDE_EXTENSIONS_DATA_STRUCTURES_ARRAY_STRUCTURES_HPP_ +#ifndef LIBS_LIBCONTAINERS_INCLUDE_DATA_STRUCTURES_ARRAY_STRUCTURES_HPP_ +#define LIBS_LIBCONTAINERS_INCLUDE_DATA_STRUCTURES_ARRAY_STRUCTURES_HPP_ #include #include #include #include +#include namespace data_structures { @@ -342,6 +343,15 @@ struct StringArray : public std::array { } NODISCARD FORCE_INLINE_F const char *GetCStr() const noexcept { return this->data(); } + + NODISCARD constexpr operator std::string_view() const noexcept + { + size_t len = 0; + while (len < kSize && (*this)[len] != '\0') { + len++; + } + return std::string_view(this->data(), len); + } }; template @@ -377,4 +387,4 @@ NODISCARD FAST_CALL constexpr StringArray IntegralToStringArray(const } // namespace data_structures -#endif // ALKOS_LIBC_INCLUDE_EXTENSIONS_DATA_STRUCTURES_ARRAY_STRUCTURES_HPP_ +#endif // LIBS_LIBCONTAINERS_INCLUDE_DATA_STRUCTURES_ARRAY_STRUCTURES_HPP_ From ba284c30cf610081da3754dab2e9b678b6af7d1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Kryczka?= <60490378+kryczkal@users.noreply.github.com> Date: Tue, 9 Dec 2025 23:42:51 +0100 Subject: [PATCH 04/56] Kryczkal/console 02 (#230) ![2025-12-0923-31-11-ezgif com-video-to-gif-converter](https://github.com/user-attachments/assets/c74fddb2-e716-49f1-ada3-1715169bd0cd) ## Problem There is no interface to communicate with the OS from a graphical perspective ## Solution Implemented a `graphics_console` and a super basic shell that uses it. --- kernel/src/demos/donut.hpp | 222 +++++++++++++++++++++++ kernel/src/drivers/video/framebuffer.cpp | 8 +- kernel/src/drivers/video/framebuffer.hpp | 3 +- kernel/src/graphics/color.hpp | 11 +- kernel/src/graphics/font/psf2_font.hpp | 42 ++++- kernel/src/graphics/geometry.hpp | 5 + kernel/src/graphics/native_pixel.hpp | 47 +++++ kernel/src/graphics/painter.cpp | 65 +++---- kernel/src/graphics/painter.hpp | 23 ++- kernel/src/graphics/surface.hpp | 32 ++-- kernel/src/kernel.cpp | 70 ++----- kernel/src/modules/video.cpp | 3 +- kernel/src/sys/graphics_console.cpp | 130 +++++++++++++ kernel/src/sys/graphics_console.hpp | 68 +++++++ kernel/src/sys/shell.cpp | 101 +++++++++++ kernel/src/sys/shell.hpp | 57 ++++++ 16 files changed, 759 insertions(+), 128 deletions(-) create mode 100644 kernel/src/demos/donut.hpp create mode 100644 kernel/src/graphics/native_pixel.hpp create mode 100644 kernel/src/sys/graphics_console.cpp create mode 100644 kernel/src/sys/graphics_console.hpp create mode 100644 kernel/src/sys/shell.cpp create mode 100644 kernel/src/sys/shell.hpp diff --git a/kernel/src/demos/donut.hpp b/kernel/src/demos/donut.hpp new file mode 100644 index 00000000..e2c75be7 --- /dev/null +++ b/kernel/src/demos/donut.hpp @@ -0,0 +1,222 @@ +#ifndef KERNEL_SRC_DEMOS_DONUT_HPP_ +#define KERNEL_SRC_DEMOS_DONUT_HPP_ + +#include +#include +#include +#include +#include +#include + +#include "graphics/color.hpp" +#include "graphics/font/psf2_font.hpp" +#include "graphics/fonts/drdos8x8.hpp" +#include "graphics/native_pixel.hpp" +#include "graphics/painter.hpp" +#include "graphics/surface.hpp" +#include "mem/heap.hpp" +#include "modules/video.hpp" +#include "trace_framework.hpp" + +namespace Demos +{ + +// -------------------------------------------------------------------------------- +// Minimal Math for donut Implementation +// -------------------------------------------------------------------------------- + +namespace Math +{ +// Simple Taylor series approximation for sin/cos to avoid libm dependency +// or massive lookup tables. Precision is "good enough" for a donut. +constexpr f32 PI = 3.14159265359f; + +f32 sin(f32 x) +{ + // Normalize to -PI to PI + while (x < -PI) { + x += 2 * PI; + } + while (x > PI) { + x -= 2 * PI; + } + + f32 res = 0; + f32 term = x; + f32 k = 1; + for (i32 i = 0; i < 5; ++i) { // 5 iterations is plenty for visual + res += term; + term *= -1 * x * x / ((k + 1) * (k + 2)); + k += 2; + } + return res; +} + +f32 cos(f32 x) { return sin(x + (PI / 2.0F)); } +} // namespace Math + +class SpinningDonut +{ + public: + void Init(u32 width, u32 height, const Graphics::PixelFormat &format) + { + width_ = width; + height_ = height; + + size_t zbuf_size = width * height * sizeof(f32); + auto alloc = Mem::KMalloc(zbuf_size); + if (alloc) { + z_buffer_ = static_cast(*alloc); + } + + // Pre-calculate luminance palette to avoid color packing in the render loop. + for (i32 i = 0; i < 13; ++i) { + f32 intensity = i / 12.0f; + u8 r = static_cast(255 * intensity); + u8 g = static_cast(150 * intensity); + u8 b = static_cast(50 * intensity); + + luminance_palette_[i] = Graphics::NativePixel::FromColor({r, g, b}, format); + } + } + + void Render(Graphics::Painter &painter) + { + if (z_buffer_ == nullptr) { + return; + } + + // Reset Z-buffer. + memset(z_buffer_, 0, width_ * height_ * sizeof(f32)); + + painter.Clear(Graphics::Color::Black()); + + // Get unsafe pointers for direct access + Graphics::Surface &screen = painter.GetTarget(); + u8 *raw_byte_buffer = reinterpret_cast(screen.GetRawBuffer()); + u32 pitch = screen.GetPitch(); + + // Precompute sines and cosines + f32 cA = Math::cos(A); + f32 sA = Math::sin(A); + f32 cB = Math::cos(B); + f32 sB = Math::sin(B); + + // Theta (cross section of torus) + for (f32 theta = 0; theta < 2 * Math::PI; theta += 0.07F) { + f32 ct = Math::cos(theta); + f32 st = Math::sin(theta); + + // Phi (center of revolution) + for (f32 phi = 0; phi < 2 * Math::PI; phi += 0.02F) { + f32 cp = Math::cos(phi); + f32 sp = Math::sin(phi); + + // 3D coordinates calculation + f32 circleX = R2 + (R1 * ct); + f32 circleY = R1 * st; + + f32 x = (circleX * (cp * cB + sp * sA * sB)) - (circleY * cA * sB); + f32 y = (circleX * (cp * sB - sp * sA * cB)) + (circleY * cA * cB); + f32 z = K2 + (circleX * sp * cA) + (circleY * sA); + + // Inverse Z (Depth) + f32 ooz = 1.0F / z; + + // Screen projection + i32 xp = (i32)((width_ / 2) + (K1 * ooz * x)); + i32 yp = (i32)((height_ / 2) - (K1 * ooz * y)); + + // Luminance + f32 L = + (cp * ct * sB) - (cA * ct * sp) - (sA * st) + (cB * (cA * st - ct * sA * sp)); + + if (L > 0 && xp >= 0 && xp < (i32)width_ && yp >= 0 && yp < (i32)height_) { + i32 idx = xp + (yp * width_); + + if (ooz > z_buffer_[idx]) { + z_buffer_[idx] = ooz; + + i32 luminance_idx = (i32)(L * 8.0F); + if (luminance_idx > 12) + luminance_idx = 12; + else if (luminance_idx < 0) + luminance_idx = 0; + + // Direct memory write: Base + (Y * Pitch) + (X * BytesPerPixel) + u32 *pixel_addr = + reinterpret_cast(raw_byte_buffer + (yp * pitch) + (xp * 4)); + *pixel_addr = luminance_palette_[luminance_idx].value; + } + } + } + } + + A += 0.04f; + B += 0.02f; + } + + private: + f32 A = 0; + f32 B = 0; + const f32 R1 = 1.0f; // Tube radius + const f32 R2 = 2.0f; // Distance from center + const f32 K2 = 5.0f; // Distance from camera + const f32 K1 = 350.0f; // Zoom/Scale + + u32 width_ = 0; + u32 height_ = 0; + f32 *z_buffer_ = nullptr; + + Graphics::NativePixel luminance_palette_[13]; +}; + +void RunDonutDemo() +{ + auto &video = VideoModule::Get(); + auto &screen = video.GetScreen(); + auto fmt = video.GetFormat(); + + Graphics::Painter p(screen, fmt); + Graphics::Psf2Font system_font(std::span(drdos8x8_psfu, drdos8x8_psfu_len)); + + if (!system_font.IsValid()) { + TRACE_WARN_VIDEO("System font magic invalid! Rendering might be corrupted."); + } + + SpinningDonut donut; + donut.Init(screen.GetWidth(), screen.GetHeight(), fmt); + + u64 frame_count = 0; + char text_buffer[100]; + while (true) { + donut.Render(p); + + p.SetColor(Graphics::Color::White()); + p.DrawString( + {.x = 10, .y = 10, .text = "AlkOS Kernel - Graphics Module ON", .scale = 2}, system_font + ); + i32 ret = snprintf(text_buffer, 100, "Frame %lli", frame_count); + (void)ret; + p.DrawString( + {.x = (static_cast( + screen.GetWidth() - system_font.MeasureString(text_buffer).width * 2 + )), + .y = 10, + .text = text_buffer, + .scale = 2}, + system_font + ); + video.Flush(); + + for (volatile i32 i = 0; i < 10000000; i++) { + ; + } + + frame_count++; + } +} + +} // namespace Demos + +#endif // KERNEL_SRC_DEMOS_DONUT_HPP_ diff --git a/kernel/src/drivers/video/framebuffer.cpp b/kernel/src/drivers/video/framebuffer.cpp index cc116da9..ce9f8a95 100644 --- a/kernel/src/drivers/video/framebuffer.cpp +++ b/kernel/src/drivers/video/framebuffer.cpp @@ -1,5 +1,6 @@ #include "drivers/video/framebuffer.hpp" #include "defines.hpp" +#include "graphics/native_pixel.hpp" #include "mem/heap.hpp" #include "trace_framework.hpp" @@ -13,7 +14,7 @@ void Framebuffer::Init(Graphics::Surface s, Graphics::PixelFormat pf, Mem::Heap // Backbuffer allocation size_t width = front_surface_.GetWidth(); size_t height = front_surface_.GetHeight(); - size_t buffer_size = width * height * sizeof(u32); + size_t buffer_size = width * height * sizeof(Graphics::NativePixel); auto alloc_res = hp.Malloc(buffer_size); if (!alloc_res) { @@ -21,8 +22,9 @@ void Framebuffer::Init(Graphics::Surface s, Graphics::PixelFormat pf, Mem::Heap return; } - backbuffer_mem_ = static_cast>(*alloc_res); - back_surface_ = Graphics::Surface(backbuffer_mem_, width, height, width * sizeof(u32)); + backbuffer_mem_ = static_cast>(*alloc_res); + back_surface_ = + Graphics::Surface(backbuffer_mem_, width, height, width * sizeof(Graphics::NativePixel)); } void Framebuffer::Flush() diff --git a/kernel/src/drivers/video/framebuffer.hpp b/kernel/src/drivers/video/framebuffer.hpp index 184fffea..6456e650 100644 --- a/kernel/src/drivers/video/framebuffer.hpp +++ b/kernel/src/drivers/video/framebuffer.hpp @@ -3,6 +3,7 @@ #include #include "graphics/color.hpp" +#include "graphics/native_pixel.hpp" #include "graphics/surface.hpp" #include "mem/heap.hpp" @@ -33,7 +34,7 @@ class Framebuffer Graphics::PixelFormat format_{}; // We own the backbuffer memory - Mem::VPtr backbuffer_mem_{nullptr}; + Mem::VPtr backbuffer_mem_{nullptr}; }; } // namespace Drivers::Video diff --git a/kernel/src/graphics/color.hpp b/kernel/src/graphics/color.hpp index 1b728229..02189de8 100644 --- a/kernel/src/graphics/color.hpp +++ b/kernel/src/graphics/color.hpp @@ -8,13 +8,10 @@ namespace Graphics { struct PACK Color { - u8 r; - u8 g; - u8 b; - u8 a; - - constexpr Color(u8 r, u8 g, u8 b, u8 a = 255) : r(r), g(g), b(b), a(a) {} - constexpr Color() : r(0), g(0), b(0), a(255) {} + u8 r = 0; + u8 g = 0; + u8 b = 0; + u8 a = 255; static constexpr Color Red() { return {255, 0, 0, 255}; } static constexpr Color Green() { return {0, 255, 0, 255}; } diff --git a/kernel/src/graphics/font/psf2_font.hpp b/kernel/src/graphics/font/psf2_font.hpp index 491ff543..13565fb9 100644 --- a/kernel/src/graphics/font/psf2_font.hpp +++ b/kernel/src/graphics/font/psf2_font.hpp @@ -4,8 +4,11 @@ #include #include #include +#include #include + #include "graphics/font/glyph.hpp" +#include "graphics/geometry.hpp" namespace Graphics { @@ -81,10 +84,47 @@ class Psf2Font .width = hdr.width, .height = hdr.height, .stride = stride, - .advance = hdr.width // PSF2 is fundamentally a fixed-width format + .advance = hdr.width }; } + /** + * @brief Calculates the bounding box size of the text if it were drawn. + * @param text The string to measure. + * @param scale The scaling factor used for drawing. + * @return The width and height in pixels. + */ + NODISCARD Size MeasureString(std::string_view text, u8 scale = 1) const + { + if (text.empty()) { + return {0, 0}; + } + + const auto &hdr = Header(); + u32 cursor_x = 0; + u32 cursor_y = 0; + u32 max_width = 0; + + u32 total_height = hdr.height * scale; + + for (char c : text) { + if (c == '\n') { + max_width = std::max(max_width, cursor_x); + cursor_x = 0; + cursor_y += hdr.height * scale; + total_height = std::max(total_height, cursor_y + (hdr.height * scale)); + } else if (c == '\r') { + cursor_x = 0; + } else { + cursor_x += hdr.width * scale; + } + } + + max_width = std::max(max_width, cursor_x); + + return {max_width, total_height}; + } + private: std::span data_; }; diff --git a/kernel/src/graphics/geometry.hpp b/kernel/src/graphics/geometry.hpp index d547b152..cd3ec15a 100644 --- a/kernel/src/graphics/geometry.hpp +++ b/kernel/src/graphics/geometry.hpp @@ -33,6 +33,11 @@ struct CharCmd { u8 scale = 1; }; +struct Size { + u32 width; + u32 height; +}; + } // namespace Graphics #endif // KERNEL_SRC_GRAPHICS_GEOMETRY_HPP_ diff --git a/kernel/src/graphics/native_pixel.hpp b/kernel/src/graphics/native_pixel.hpp new file mode 100644 index 00000000..20797f52 --- /dev/null +++ b/kernel/src/graphics/native_pixel.hpp @@ -0,0 +1,47 @@ +#ifndef KERNEL_SRC_GRAPHICS_NATIVE_PIXEL_HPP_ +#define KERNEL_SRC_GRAPHICS_NATIVE_PIXEL_HPP_ + +#include +#include +#include "graphics/color.hpp" + +namespace Graphics +{ + +/** + * @brief A wrapper around u32 that guarantees the value is formatted + * correctly for the hardware framebuffer. + */ +struct PACK NativePixel { + u32 value; + + NativePixel() = default; + + // Explicit constructor to prevent accidental integer assignment + explicit constexpr NativePixel(u32 v) : value(v) {} + + bool operator==(const NativePixel &other) const = default; + explicit constexpr operator u32() const { return value; } + + /** + * @brief Converts a logical RGBA color to a hardware-specific NativePixel. + */ + NODISCARD static constexpr NativePixel FromColor(const Color &c, const PixelFormat &pf) + { + const u32 r_mask = (1 << pf.red_mask_size) - 1; + const u32 g_mask = (1 << pf.green_mask_size) - 1; + const u32 b_mask = (1 << pf.blue_mask_size) - 1; + + u32 raw = (static_cast(c.r & r_mask) << pf.red_pos) | + (static_cast(c.g & g_mask) << pf.green_pos) | + (static_cast(c.b & b_mask) << pf.blue_pos); + + return NativePixel(raw); + } +}; + +static_assert(sizeof(NativePixel) == sizeof(u32), "NativePixel must be 32-bit"); + +} // namespace Graphics + +#endif // KERNEL_SRC_GRAPHICS_NATIVE_PIXEL_HPP_ diff --git a/kernel/src/graphics/painter.cpp b/kernel/src/graphics/painter.cpp index 61817dfc..6b2ebb6b 100644 --- a/kernel/src/graphics/painter.cpp +++ b/kernel/src/graphics/painter.cpp @@ -11,18 +11,7 @@ Painter::Painter(Surface &target, const PixelFormat &format) : target_(target), SetColor(Color::White()); } -void Painter::SetColor(Color color) { packed_color_ = PackColor(color); } - -u32 Painter::PackColor(Color c) const -{ - const u32 r_mask = (1 << format_.red_mask_size) - 1; - const u32 g_mask = (1 << format_.green_mask_size) - 1; - const u32 b_mask = (1 << format_.blue_mask_size) - 1; - - return (static_cast(c.r & r_mask) << format_.red_pos) | - (static_cast(c.g & g_mask) << format_.green_pos) | - (static_cast(c.b & b_mask) << format_.blue_pos); -} +void Painter::SetColor(Color color) { packed_color_ = NativePixel::FromColor(color, format_); } void Painter::DrawPixel(Point p) { @@ -35,34 +24,35 @@ void Painter::DrawPixel(Point p) target_.Pixel(static_cast(x), static_cast(y)) = packed_color_; } -void Painter::FillScanline(u32 *dest, u32 count, u32 color) +void Painter::FillScanline(std::span dest, NativePixel color) { - // Check for byte-repeating pattern for memset optimization - // e.g. 0x00000000, 0xFFFFFFFF, 0xABABABAB - if ((color & 0xFF) == ((color >> 8) & 0xFF) && (color & 0xFF) == ((color >> 16) & 0xFF) && - (color & 0xFF) == ((color >> 24) & 0xFF)) { - memset(dest, color & 0xFF, count * sizeof(u32)); + u32 raw = color.value; + + // Optimization: If byte pattern is repeating, use memset + if ((raw & 0xFF) == ((raw >> 8) & 0xFF) && (raw & 0xFF) == ((raw >> 16) & 0xFF) && + (raw & 0xFF) == ((raw >> 24) & 0xFF)) { + memset(dest.data(), raw & 0xFF, dest.size_bytes()); } else { - for (u32 i = 0; i < count; ++i) { - dest[i] = color; + for (auto &px : dest) { + px = color; } } } void Painter::Clear(Color color) { - u32 raw = PackColor(color); - u32 height = target_.GetHeight(); - u32 width = target_.GetWidth(); - u32 pitch = target_.GetPitch(); - - // If buffer is contiguous (pitch == width * 4 for 32bpp) - // we can clear it in one go. - if (pitch == width * sizeof(u32)) { - FillScanline(target_.GetScanline(0), width * height, raw); + NativePixel raw = NativePixel::FromColor(color, format_); + u32 height = target_.GetHeight(); + u32 width = target_.GetWidth(); + u32 pitch = target_.GetPitch(); + + if (pitch == width * sizeof(NativePixel)) { + std::span first_line = target_.GetScanline(0); + std::span full_buffer(first_line.data(), width * height); + FillScanline(full_buffer, raw); } else { for (u32 y = 0; y < height; ++y) { - FillScanline(target_.GetScanline(y), width, raw); + FillScanline(target_.GetScanline(y), raw); } } } @@ -74,7 +64,6 @@ void Painter::FillRect(Rect r) i32 w = r.w; i32 h = r.h; - // Clipping Logic if (x < 0) { w += x; x = 0; @@ -84,14 +73,10 @@ void Painter::FillRect(Rect r) y = 0; } - if (static_cast(x) >= target_.GetWidth()) { - return; - } - if (static_cast(y) >= target_.GetHeight()) { + if (static_cast(x) >= target_.GetWidth() || static_cast(y) >= target_.GetHeight()) { return; } - // Clamp width/height to edges if (static_cast(x + w) > target_.GetWidth()) { w = target_.GetWidth() - x; } @@ -104,8 +89,8 @@ void Painter::FillRect(Rect r) } for (i32 row = 0; row < h; ++row) { - u32 *line = target_.GetScanline(static_cast(y + row)); - FillScanline(line + x, static_cast(w), packed_color_); + std::span line = target_.GetScanline(static_cast(y + row)); + FillScanline(line.subspan(static_cast(x), static_cast(w)), packed_color_); } } @@ -116,13 +101,9 @@ void Painter::DrawRect(Rect r) i32 w = r.w; i32 h = r.h; - // Top FillRect({x, y, w, 1}); - // Bottom FillRect({x, y + h - 1, w, 1}); - // Left FillRect({x, y, 1, h}); - // Right FillRect({x + w - 1, y, 1, h}); } diff --git a/kernel/src/graphics/painter.hpp b/kernel/src/graphics/painter.hpp index 33e3ae76..22044062 100644 --- a/kernel/src/graphics/painter.hpp +++ b/kernel/src/graphics/painter.hpp @@ -2,10 +2,12 @@ #define KERNEL_SRC_GRAPHICS_PAINTER_HPP_ #include +#include #include #include "graphics/color.hpp" #include "graphics/font/glyph.hpp" #include "graphics/geometry.hpp" +#include "graphics/native_pixel.hpp" #include "graphics/surface.hpp" namespace Graphics @@ -41,28 +43,25 @@ class Painter // Text Rendering // ------------------------------------------------------------------------- - /** - * @brief Draws a single character using the provided font. - * @tparam FontT A type satisfying the FontType concept (e.g., Psf2Font). - */ template void DrawChar(const CharCmd &cmd, const FontT &font); - /** - * @brief Draws a null-terminated string. - * Handles newlines (\n) and carriage returns (\r). - * @tparam FontT A type satisfying the FontType concept. - */ template void DrawString(const TextCmd &cmd, const FontT &font); + // ------------------------------------------------------------------------- + // Accessors + // ------------------------------------------------------------------------- + + NODISCARD Surface &GetTarget() { return target_; } + NODISCARD const PixelFormat &GetFormat() const { return format_; } + private: - void FillScanline(u32 *dest, u32 count, u32 color); - NODISCARD FORCE_INLINE_F u32 PackColor(Color c) const; + void FillScanline(std::span dest, NativePixel color); Surface &target_; PixelFormat format_; - u32 packed_color_; + NativePixel packed_color_; }; } // namespace Graphics diff --git a/kernel/src/graphics/surface.hpp b/kernel/src/graphics/surface.hpp index 54b2dd4c..a07eaf65 100644 --- a/kernel/src/graphics/surface.hpp +++ b/kernel/src/graphics/surface.hpp @@ -3,7 +3,9 @@ #include #include +#include #include +#include "graphics/native_pixel.hpp" namespace Graphics { @@ -17,7 +19,7 @@ class Surface public: Surface() = default; - Surface(Mem::VPtr buffer, u32 width, u32 height, u32 pitch_bytes) + Surface(Mem::VPtr buffer, u32 width, u32 height, u32 pitch_bytes) : buffer_(buffer), width_(width), height_(height), pitch_bytes_(pitch_bytes) { R_ASSERT_NOT_NULL(buffer_); @@ -37,40 +39,48 @@ class Surface ASSERT_EQ(height_, src.height_); for (u32 y = 0; y < height_; ++y) { - u32 *dest_row = GetScanline(y); - const u32 *src_row = src.GetScanline(y); + std::span dest_row = GetScanline(y); + std::span src_row = src.GetScanline(y); // We use the width for the copy size, not the pitch, // to handle stride differences correctly. - memcpy(dest_row, src_row, width_ * sizeof(u32)); + memcpy(dest_row.data(), src_row.data(), width_ * sizeof(NativePixel)); } } // ------------------------------------------------------------------------- - // Fast Accessors + // Accessors // ------------------------------------------------------------------------- - NODISCARD FORCE_INLINE_F Mem::VPtr GetScanline(u32 y) + NODISCARD FORCE_INLINE_F std::span GetScanline(u32 y) { ASSERT_LT(y, height_); // Pointer arithmetic on byte level for pitch uptr addr = Mem::PtrToUptr(buffer_) + (static_cast(y) * pitch_bytes_); - return Mem::UptrToPtr(addr); + Mem::VPtr ptr = Mem::UptrToPtr(addr); + return std::span(ptr, width_); } - NODISCARD FORCE_INLINE_F const Mem::VPtr GetScanline(u32 y) const + NODISCARD FORCE_INLINE_F std::span GetScanline(u32 y) const { ASSERT_LT(y, height_); uptr addr = Mem::PtrToUptr(buffer_) + (static_cast(y) * pitch_bytes_); - return Mem::UptrToPtr(addr); + Mem::VPtr ptr = Mem::UptrToPtr(addr); + return std::span(ptr, width_); } - FORCE_INLINE_F u32 &Pixel(u32 x, u32 y) + FORCE_INLINE_F NativePixel &Pixel(u32 x, u32 y) { ASSERT_LT(x, width_); return GetScanline(y)[x]; } + /** + * @brief Gets the unsafe raw buffer pointer. + * @warning Use only for high-performance code where bounds are checked manually. + */ + NODISCARD FORCE_INLINE_F Mem::VPtr GetRawBuffer() { return buffer_; } + // ------------------------------------------------------------------------- // Properties // ------------------------------------------------------------------------- @@ -81,7 +91,7 @@ class Surface NODISCARD FORCE_INLINE_F bool IsValid() const { return buffer_ != nullptr; } private: - Mem::VPtr buffer_{nullptr}; + Mem::VPtr buffer_{nullptr}; u32 width_{0}; u32 height_{0}; u32 pitch_bytes_{0}; // Stride in bytes diff --git a/kernel/src/kernel.cpp b/kernel/src/kernel.cpp index ec2abf69..1944bf18 100644 --- a/kernel/src/kernel.cpp +++ b/kernel/src/kernel.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include "trace_framework.hpp" @@ -10,11 +11,13 @@ #include "graphics/font/psf2_font.hpp" #include "graphics/fonts/drdos8x8.hpp" #include "graphics/painter.hpp" +#include "hal/terminal.hpp" +#include "modules/video.hpp" +#include "sys/shell.hpp" #include "boot_args.hpp" #include "hal/boot_args.hpp" #include "mem/heap.hpp" -#include "modules/video.hpp" #include "todo.hpp" extern void KernelInit(const hal::RawBootArguments &); @@ -25,62 +28,29 @@ static void KernelRun() trace::DumpAllBuffersOnFailure(); TODO_MMU_MINIMAL - // static constexpr size_t kBuffSize = 256; - // char buff[kBuffSize]; - // - // const auto t = time(nullptr); - // strftime(buff, kBuffSize, "%Y-%m-%d %H:%M:%S", localtime(&t)); - // - // KernelTraceSuccess("Hello from AlkOS! Today we have: %s", buff); - - auto &video = VideoModule::Get(); - auto &screen = video.GetScreen(); - auto fmt = video.GetFormat(); + auto &video = VideoModule::Get(); + Graphics::Painter painter(video.GetScreen(), video.GetFormat()); + Graphics::Psf2Font font(drdos8x8_psfu); - Graphics::Painter p(screen, fmt); - Graphics::Psf2Font system_font(drdos8x8_psfu); - - if (!system_font.IsValid()) { - TRACE_WARN_VIDEO("System font magic invalid! Rendering might be corrupted."); + if (!font.IsValid()) { + TRACE_WARN_VIDEO("Invalid font"); } - // Animation Loop - i32 x = 0; - i32 y = 0; - u16 speed = 1; - - while (true) { - p.Clear(Graphics::Color::Black()); + System::GraphicsConsole console(painter, font); + System::Shell shell(console); - // Shape drawing - p.SetColor(Graphics::Color::Green()); - p.FillRect({.x = x, .y = 100, .w = 50, .h = 50}); - p.SetColor(Graphics::Color::Blue()); - p.FillRect({.x = 100, .y = y, .w = 70, .h = 70}); - - // Text drawing - p.SetColor(Graphics::Color::White()); - p.DrawString({.x = 40, .y = 20, .text = "AlkOS Kernel", .scale = 3}, system_font); - - p.SetColor(Graphics::Color::Red()); - p.DrawString( - {.x = 40, .y = 50, .text = "Graphics Subsystem Online.", .scale = 3}, system_font - ); - - // Dynamic Text Position - p.SetColor(Graphics::Color::Green()); - p.DrawString({.x = x, .y = y + 80, .text = "Moving Text!", .scale = 2}, system_font); + shell.Init(); + video.Flush(); + while (true) { + // Poll Serial Port for input (temporary until IRQ is hooked) + char c = hal::TerminalGetChar(); + shell.OnInput(c); video.Flush(); - x += (speed / 255) + 1; - y += (speed / 255) + 1; - x = x % screen.GetWidth(); - y = y % screen.GetHeight(); - - // crude delay - for (volatile int i = 0; i < 1000000; i++); - speed = speed + 10; + // Don't burn CPU 100% + for (volatile i32 i = 0; i < 10000; ++i) { + } } } diff --git a/kernel/src/modules/video.cpp b/kernel/src/modules/video.cpp index 49782801..56d646b3 100644 --- a/kernel/src/modules/video.cpp +++ b/kernel/src/modules/video.cpp @@ -1,4 +1,5 @@ #include "modules/video.hpp" +#include "graphics/native_pixel.hpp" #include "graphics/painter.hpp" #include "mem/types.hpp" #include "trace_framework.hpp" @@ -12,7 +13,7 @@ internal::VideoModule::VideoModule(const BootArguments &args, Heap &hp) noexcept DEBUG_INFO_GENERAL("VideoModule::VideoModule()"); const auto &fb_args = args.fb_args; - auto *fb_pptr = static_cast>(fb_args.base_address); + auto *fb_pptr = static_cast>(fb_args.base_address); R_ASSERT_NOT_NULL(fb_pptr, "VideoModule: Framebuffer is null"); diff --git a/kernel/src/sys/graphics_console.cpp b/kernel/src/sys/graphics_console.cpp new file mode 100644 index 00000000..9e44e021 --- /dev/null +++ b/kernel/src/sys/graphics_console.cpp @@ -0,0 +1,130 @@ +#include "sys/graphics_console.hpp" + +#include +#include + +namespace System +{ + +GraphicsConsole::GraphicsConsole(Graphics::Painter &painter, const Graphics::Psf2Font &font) + : painter_(painter), font_(font) +{ + // Auto-scale based on resolution + scale_ = static_cast(std::max(1u, painter_.GetTarget().GetWidth() / 400)); + + glyph_w_ = font_.GetWidth() * scale_; + glyph_h_ = font_.GetHeight() * scale_; + + // Calculate grid size + max_cols_ = painter_.GetTarget().GetWidth() / glyph_w_; + max_rows_ = painter_.GetTarget().GetHeight() / glyph_h_; + + Clear(); +} + +void GraphicsConsole::SetColors(Graphics::Color fg, Graphics::Color bg) +{ + fg_color_ = fg; + bg_color_ = bg; +} + +void GraphicsConsole::Clear() +{ + painter_.Clear(bg_color_); + cursor_x_ = 0; + cursor_y_ = 0; +} + +void GraphicsConsole::ScrollUp() +{ + // Scroll by one line height + auto &surface = painter_.GetTarget(); + u32 pitch = surface.GetPitch(); + u32 bytes_per_line = pitch * glyph_h_; + u32 total_height = surface.GetHeight(); + + u8 *raw_buf = reinterpret_cast(surface.GetRawBuffer()); + + // Move memory up + // Dest: Start of buffer + // Src: Start of buffer + one line of pixels + // Size: Total size - one line of pixels + memmove( + raw_buf, raw_buf + bytes_per_line, static_cast(pitch * (total_height - glyph_h_)) + ); + + // Clear bottom area using the painter to ensure correct color format + painter_.SetColor(bg_color_); + painter_.FillRect( + {0, static_cast(total_height - glyph_h_), static_cast(surface.GetWidth()), + static_cast(glyph_h_)} + ); +} + +void GraphicsConsole::NewLine() +{ + cursor_x_ = 0; + cursor_y_++; + + if (cursor_y_ >= max_rows_) { + cursor_y_ = max_rows_ - 1; + ScrollUp(); + } +} + +void GraphicsConsole::InternalPutChar(char c) +{ + if (c == '\n') { + NewLine(); + return; + } + + if (c == '\r') { + cursor_x_ = 0; + return; + } + + if (c == '\b') { + if (cursor_x_ > 0) { + cursor_x_--; + // Erase char + painter_.SetColor(bg_color_); + painter_.FillRect( + {static_cast(cursor_x_ * glyph_w_), static_cast(cursor_y_ * glyph_h_), + static_cast(glyph_w_), static_cast(glyph_h_)} + ); + } + return; + } + + // Draw the character + Graphics::CharCmd cmd{ + .x = static_cast(cursor_x_ * glyph_w_), + .y = static_cast(cursor_y_ * glyph_h_), + .c = c, + .scale = scale_ + }; + + // Clear background for char + painter_.SetColor(bg_color_); + painter_.FillRect({cmd.x, cmd.y, static_cast(glyph_w_), static_cast(glyph_h_)}); + + // Draw foreground + painter_.SetColor(fg_color_); + painter_.DrawChar(cmd, font_); + + cursor_x_++; + if (cursor_x_ >= max_cols_) { + NewLine(); + } +} + +IO::IoResult GraphicsConsole::Write(std::span buffer) +{ + for (const auto &b : buffer) { + InternalPutChar(static_cast(b)); + } + return buffer.size(); +} + +} // namespace System diff --git a/kernel/src/sys/graphics_console.hpp b/kernel/src/sys/graphics_console.hpp new file mode 100644 index 00000000..3a18a058 --- /dev/null +++ b/kernel/src/sys/graphics_console.hpp @@ -0,0 +1,68 @@ +#ifndef KERNEL_SRC_SYS_GRAPHICS_CONSOLE_HPP_ +#define KERNEL_SRC_SYS_GRAPHICS_CONSOLE_HPP_ + +#include +#include +#include +#include + +namespace System +{ + +// -------------------------------------------------------------------------------- +// GraphicsConsole +// -------------------------------------------------------------------------------- + +class GraphicsConsole : public IO::IWriter +{ + public: + GraphicsConsole(Graphics::Painter &painter, const Graphics::Psf2Font &font); + + // ------------------------------------------------------------------------- + // IWriter Interface + // ------------------------------------------------------------------------- + + IO::IoResult Write(std::span buffer) override; + + // ------------------------------------------------------------------------- + // Console Operations + // ------------------------------------------------------------------------- + + void Clear(); + + // ------------------------------------------------------------------------- + // Configuration + // ------------------------------------------------------------------------- + + void SetColors(Graphics::Color fg, Graphics::Color bg); + + private: + // ------------------------------------------------------------------------- + // Internal Helpers + // ------------------------------------------------------------------------- + + void InternalPutChar(char c); + void NewLine(); + void ScrollUp(); + + Graphics::Painter &painter_; + const Graphics::Psf2Font &font_; + + // Cursor State + u32 cursor_x_{0}; + u32 cursor_y_{0}; + + // Cache dimensions + u32 max_cols_{0}; + u32 max_rows_{0}; + u32 glyph_w_{0}; + u32 glyph_h_{0}; + u8 scale_{1}; + + Graphics::Color fg_color_{Graphics::Color::White()}; + Graphics::Color bg_color_{Graphics::Color::Black()}; +}; + +} // namespace System + +#endif // KERNEL_SRC_SYS_GRAPHICS_CONSOLE_HPP_ diff --git a/kernel/src/sys/shell.cpp b/kernel/src/sys/shell.cpp new file mode 100644 index 00000000..5db0ecb8 --- /dev/null +++ b/kernel/src/sys/shell.cpp @@ -0,0 +1,101 @@ +#include "sys/shell.hpp" +#include +#include +#include + +namespace System +{ + +Shell::Shell(GraphicsConsole &console) : console_(console) {} + +void Shell::Init() +{ + console_.Write( + std::span(reinterpret_cast("Welcome to AlkOS Shell!\n"), 24) + ); + PrintPrompt(); +} + +void Shell::PrintPrompt() +{ + console_.SetColors(Graphics::Color::Green(), Graphics::Color::Black()); + const char *p = "AlkOS> "; + console_.Write(std::span(reinterpret_cast(p), strlen(p))); + console_.SetColors(Graphics::Color::White(), Graphics::Color::Black()); +} + +void Shell::OnInput(char c) +{ + // Handle Special Keys + if (c == '\n' || c == '\r') { + console_.PutChar('\n'); + ProcessCommand(); + input_buffer_.Resize(0); // Clear buffer + PrintPrompt(); + return; + } + + if (c == '\b' || c == 0x7F) { // Backspace + if (input_buffer_.Size() > 0) { + input_buffer_.Pop(); + console_.PutChar('\b'); + } + return; + } + + // Normal character + if (input_buffer_.Size() < kMaxInput) { + input_buffer_.Push(c); + console_.PutChar(c); + } +} + +void Shell::ProcessCommand() +{ + if (input_buffer_.Size() == 0) { + return; + } + + std::string_view line(static_cast(input_buffer_.Data()), input_buffer_.Size()); + + // Split command and args + size_t space_pos = line.find(' '); + std::string_view cmd = (space_pos == std::string_view::npos) ? line : line.substr(0, space_pos); + std::string_view args = (space_pos == std::string_view::npos) ? "" : line.substr(space_pos + 1); + + if (cmd == "help") { + CmdHelp(); + } else if (cmd == "clear") { + CmdClear(); + } else if (cmd == "echo") { + CmdEcho(args); + } else { + console_.Write( + std::span(reinterpret_cast("Unknown command: "), 17) + ); + console_.Write( + std::span(reinterpret_cast(cmd.data()), cmd.size()) + ); + console_.PutChar('\n'); + } +} + +void Shell::CmdHelp() +{ + const char *msg = + "Available commands:\n" + " help - Show this message\n" + " clear - Clear the screen\n" + " echo - Print arguments\n"; + console_.Write(std::span(reinterpret_cast(msg), strlen(msg))); +} + +void Shell::CmdClear() { console_.Clear(); } + +void Shell::CmdEcho(std::string_view args) +{ + console_.Write(std::span(reinterpret_cast(args.data()), args.size())); + console_.PutChar('\n'); +} + +} // namespace System diff --git a/kernel/src/sys/shell.hpp b/kernel/src/sys/shell.hpp new file mode 100644 index 00000000..6b2d028a --- /dev/null +++ b/kernel/src/sys/shell.hpp @@ -0,0 +1,57 @@ +#ifndef KERNEL_SRC_SYS_SHELL_HPP_ +#define KERNEL_SRC_SYS_SHELL_HPP_ + +#include +#include +#include + +#include "sys/graphics_console.hpp" + +namespace System +{ + +// -------------------------------------------------------------------------------- +// Shell +// -------------------------------------------------------------------------------- + +class Shell +{ + public: + explicit Shell(GraphicsConsole &console); + + // ------------------------------------------------------------------------- + // Public Interface + // ------------------------------------------------------------------------- + + void Init(); + + // Call this when hardware receives a keystroke + void OnInput(char c); + + private: + // ------------------------------------------------------------------------- + // Command Processing + // ------------------------------------------------------------------------- + + void ProcessCommand(); + void PrintPrompt(); + + // ------------------------------------------------------------------------- + // Command Handlers + // ------------------------------------------------------------------------- + + void CmdHelp(); + void CmdClear(); + void CmdEcho(std::string_view args); + void CmdMem(); + + // Data + GraphicsConsole &console_; + + static constexpr size_t kMaxInput = 128; + data_structures::StaticVector input_buffer_; +}; + +} // namespace System + +#endif // KERNEL_SRC_SYS_SHELL_HPP_ From 426cf17f9740ab24052cbb09911ec48664626e8c Mon Sep 17 00:00:00 2001 From: Adam Ogieniewski Date: Thu, 11 Dec 2025 06:29:56 +0100 Subject: [PATCH 05/56] Add common data structures (#235) Introduce several new data structures and allocator wrappers: - CritBitTree: A binary crit-bit tree for NULL-terminated strings. - LinkedList: Generic single and double linked lists with custom allocators. - LruCache: A fixed-capacity LRU cache. - TaggedPointer: Stores type info in unused pointer bits. - CustomAllocatorWrapper: Enables using custom allocators with containers. --- kernel/src/interrupts/interrupt_types.hpp | 4 +- kernel/test/critbit_tree_test.cpp | 171 ++++++ kernel/test/linked_list_test.cpp | 333 ++++++++++++ kernel/test/lru_cache_tests.cpp | 78 +++ kernel/test/tagged_pointer_test.cpp | 73 +++ .../allocators/custom_allocator_wrapper.hpp | 62 +++ .../include/data_structures/critbit_tree.hpp | 370 +++++++++++++ .../include/data_structures/linked_list.hpp | 505 ++++++++++++++++++ .../include/data_structures/lru_cache.hpp | 115 ++++ .../data_structures/tagged_pointer.hpp | 214 ++++++++ libs/libtemplate/include/template/utils.hpp | 10 +- 11 files changed, 1928 insertions(+), 7 deletions(-) create mode 100644 kernel/test/critbit_tree_test.cpp create mode 100644 kernel/test/linked_list_test.cpp create mode 100644 kernel/test/lru_cache_tests.cpp create mode 100644 kernel/test/tagged_pointer_test.cpp create mode 100644 libs/libcontainers/include/allocators/custom_allocator_wrapper.hpp create mode 100644 libs/libcontainers/include/data_structures/critbit_tree.hpp create mode 100644 libs/libcontainers/include/data_structures/linked_list.hpp create mode 100644 libs/libcontainers/include/data_structures/lru_cache.hpp create mode 100644 libs/libcontainers/include/data_structures/tagged_pointer.hpp diff --git a/kernel/src/interrupts/interrupt_types.hpp b/kernel/src/interrupts/interrupt_types.hpp index 22d21066..d738c891 100644 --- a/kernel/src/interrupts/interrupt_types.hpp +++ b/kernel/src/interrupts/interrupt_types.hpp @@ -40,9 +40,7 @@ struct InterruptHandlerEntry { HandlerData handler_data{}; u16 logical_irq{}; u64 hardware_irq{}; - template_lib::OptionalField< - kInterruptType == InterruptType::kHardwareInterrupt, InterruptDriver *> - driver{}; + OPTIONAL_FIELD(kInterruptType == InterruptType::kHardwareInterrupt, InterruptDriver *) driver{}; }; template diff --git a/kernel/test/critbit_tree_test.cpp b/kernel/test/critbit_tree_test.cpp new file mode 100644 index 00000000..29530741 --- /dev/null +++ b/kernel/test/critbit_tree_test.cpp @@ -0,0 +1,171 @@ +#include + +#include +#include +#include + +using namespace data_structures; + +class CritBitTreeTest : public TestGroupBase +{ +}; + +TEST_F(CritBitTreeTest, InsertAndContains) +{ + CritBitTree tree; + + R_ASSERT_FALSE(tree.Contains("key1")); + R_ASSERT_TRUE(tree.Insert("key1", 42)); + R_ASSERT_TRUE(tree.Contains("key1")); + + R_ASSERT_FALSE(tree.Contains("key2")); + R_ASSERT_TRUE(tree.Insert("key2", 67)); + R_ASSERT_TRUE(tree.Contains("key2")); +} + +TEST_F(CritBitTreeTest, InsertOverwrite) +{ + CritBitTree tree; + + R_ASSERT_TRUE(tree.Insert("key", 42)); + R_ASSERT_TRUE(tree.Contains("key")); + + R_ASSERT_FALSE(tree.Insert("key", 67)); + R_ASSERT_TRUE(tree.Contains("key")); + + R_ASSERT_TRUE(tree.Insert("key", 67)); + R_ASSERT_TRUE(tree.Contains("key")); +} + +TEST_F(CritBitTreeTest, Get) +{ + CritBitTree tree; + + R_ASSERT_TRUE(tree.Insert("key", 42)); + + auto retrieved = tree.Get("key"); + R_ASSERT_TRUE(retrieved); + R_ASSERT_EQ(42, **retrieved); + + retrieved = tree.Get("nonexistent"); + R_ASSERT_FALSE(retrieved.has_value()); +} + +TEST_F(CritBitTreeTest, Remove) +{ + CritBitTree tree; + + R_ASSERT_TRUE(tree.Insert("key", 42)); + R_ASSERT_TRUE(tree.Contains("key")); + + R_ASSERT_TRUE(tree.Remove("key")); + R_ASSERT_FALSE(tree.Contains("key")); + + R_ASSERT_FALSE(tree.Remove("key")); +} + +TEST_F(CritBitTreeTest, ComplexOperations) +{ + CritBitTree tree; + + R_ASSERT_TRUE(tree.Insert("apple", 1)); + R_ASSERT_TRUE(tree.Insert("banana", 2)); + R_ASSERT_TRUE(tree.Insert("apricot", 3)); + R_ASSERT_TRUE(tree.Insert("blueberry", 4)); + + R_ASSERT_TRUE(tree.Contains("apple")); + R_ASSERT_TRUE(tree.Contains("banana")); + R_ASSERT_TRUE(tree.Contains("apricot")); + R_ASSERT_TRUE(tree.Contains("blueberry")); + + R_ASSERT_TRUE(tree.Remove("banana")); + R_ASSERT_FALSE(tree.Contains("banana")); + + auto retrieved = tree.Get("apricot"); + R_ASSERT_TRUE(retrieved); + R_ASSERT_EQ(3, **retrieved); +} + +TEST_F(CritBitTreeTest, EmptyTreeOperations) +{ + CritBitTree tree; + + R_ASSERT_FALSE(tree.Contains("key")); + R_ASSERT_FALSE(tree.Remove("key")); + + auto retrieved = tree.Get("key"); + R_ASSERT_FALSE(retrieved.has_value()); +} + +TEST_F(CritBitTreeTest, InsertSimilarKeys) +{ + CritBitTree tree; + + R_ASSERT_TRUE(tree.Insert("test", 10)); + R_ASSERT_TRUE(tree.Insert("testing", 20)); + R_ASSERT_TRUE(tree.Insert("tester", 30)); + + R_ASSERT_TRUE(tree.Contains("test")); + R_ASSERT_TRUE(tree.Contains("testing")); + R_ASSERT_TRUE(tree.Contains("tester")); + + auto retrieved = tree.Get("testing"); + R_ASSERT_TRUE(retrieved); + R_ASSERT_EQ(20, **retrieved); +} + +TEST_F(CritBitTreeTest, RemoveNonExistentKey) +{ + CritBitTree tree; + + R_ASSERT_FALSE(tree.Remove("nonexistent")); + + R_ASSERT_TRUE(tree.Insert("existent", 100)); + R_ASSERT_TRUE(tree.Remove("existent")); + R_ASSERT_FALSE(tree.Remove("existent")); +} + +TEST_F(CritBitTreeTest, ConstGet) +{ + CritBitTree tree; + + R_ASSERT_TRUE(tree.Insert("constKey", 55)); + + const auto &const_tree = tree; + auto retrieved = const_tree.Get("constKey"); + R_ASSERT_TRUE(retrieved); + R_ASSERT_EQ(55, **retrieved); +} + +TEST_F(CritBitTreeTest, IndexOperator) +{ + CritBitTree tree; + + R_ASSERT_TRUE(tree.Insert("indexKey", 77)); + + auto retrieved = tree["indexKey"]; + R_ASSERT_TRUE(retrieved); + R_ASSERT_EQ(77, **retrieved); + + retrieved = tree["nonexistentKey"]; + R_ASSERT_FALSE(retrieved.has_value()); +} + +TEST_F(CritBitTreeTest, ComplexFilePaths) +{ + CritBitTree tree; + + R_ASSERT_TRUE(tree.Insert("/home/user/docs", 1)); + R_ASSERT_TRUE(tree.Insert("/home/user/images", 2)); + R_ASSERT_TRUE(tree.Insert("/var/log/syslog", 3)); + R_ASSERT_TRUE(tree.Insert("/etc/config/settings", 4)); + + R_ASSERT_TRUE(tree.Contains("/home/user/docs")); + R_ASSERT_TRUE(tree.Contains("/home/user/images")); + R_ASSERT_TRUE(tree.Contains("/var/log/syslog")); + R_ASSERT_TRUE(tree.Contains("/etc/config/settings")); + + auto retrieved = tree.Get("/var/log/syslog"); + R_ASSERT_TRUE(retrieved); + R_ASSERT_EQ(3, **retrieved); +} diff --git a/kernel/test/linked_list_test.cpp b/kernel/test/linked_list_test.cpp new file mode 100644 index 00000000..fd05815d --- /dev/null +++ b/kernel/test/linked_list_test.cpp @@ -0,0 +1,333 @@ +#include +#include +#include + +// ------------------------------ +// SingleLinkedList +// ------------------------------ + +using IntSingleList = data_structures::SingleLinkedList; + +class SingleLinkedListTest : public TestGroupBase +{ + public: + IntSingleList list; +}; + +TEST_F(SingleLinkedListTest, EmptyList) +{ + EXPECT_TRUE(list.Empty()); + EXPECT_EQ(0_size, list.Size()); + EXPECT_EQ(nullptr, list.GetHead()); +} + +TEST_F(SingleLinkedListTest, PushFront) +{ + list.PushFront(1); + EXPECT_EQ(1_size, list.Size()); + EXPECT_EQ(1, list.Front()); + + list.PushFront(2); + EXPECT_EQ(2_size, list.Size()); + EXPECT_EQ(2, list.Front()); + + list.PushFront(0); + EXPECT_EQ(3_size, list.Size()); + EXPECT_EQ(0, list.Front()); +} + +TEST_F(SingleLinkedListTest, PopFront) +{ + list.PushFront(3); + list.PushFront(2); + list.PushFront(1); + + list.PopFront(); + EXPECT_EQ(2_size, list.Size()); + EXPECT_EQ(2, list.Front()); + + list.PopFront(); + EXPECT_EQ(1_size, list.Size()); + EXPECT_EQ(3, list.Front()); + + list.PopFront(); + EXPECT_TRUE(list.Empty()); +} + +TEST_F(SingleLinkedListTest, Clear) +{ + list.PushFront(1); + list.PushFront(2); + list.PushFront(3); + + EXPECT_EQ(3_size, list.Size()); + list.Clear(); + EXPECT_TRUE(list.Empty()); + EXPECT_EQ(0_size, list.Size()); +} + +TEST_F(SingleLinkedListTest, Iterator) +{ + list.PushFront(3); + list.PushFront(2); + list.PushFront(1); + + int expected = 1; + for (auto value : list) { + EXPECT_EQ(expected, value); + ++expected; + } +} + +TEST_F(SingleLinkedListTest, MoveConstructor) +{ + list.PushFront(2); + list.PushFront(1); + + IntSingleList otherList(std::move(list)); + EXPECT_TRUE(list.Empty()); + EXPECT_EQ(2_size, otherList.Size()); + EXPECT_EQ(1, otherList.Front()); +} + +TEST_F(SingleLinkedListTest, MoveAssignment) +{ + list.PushFront(2); + list.PushFront(1); + + IntSingleList otherList; + otherList.PushFront(99); + + otherList = std::move(list); + EXPECT_TRUE(list.Empty()); + EXPECT_EQ(2_size, otherList.Size()); + EXPECT_EQ(1, otherList.Front()); +} + +TEST_F(SingleLinkedListTest, CustomAllocator) +{ + data_structures::StaticSingleLinkedList list; + + list.PushFront(3); + list.PushFront(2); + list.PushFront(1); + + EXPECT_EQ(3_size, list.Size()); + EXPECT_EQ(1, list.Front()); + + list.Clear(); + EXPECT_EQ(0_size, list.Size()); +} + +TEST_F(SingleLinkedListTest, InsertAfter) +{ + auto *node1 = list.PushFront(1); + auto *node2 = list.InsertAfter(node1, 2); + list.InsertAfter(node2, 3); + + EXPECT_EQ(3_size, list.Size()); + + int expected = 1; + for (auto value : list) { + EXPECT_EQ(expected, value); + ++expected; + } +} + +TEST_F(SingleLinkedListTest, StructureSizes) +{ + EXPECT_EQ(16_size, sizeof(IntSingleList::Node)); + EXPECT_EQ(16_size, sizeof(IntSingleList)); +} + +// ------------------------------ +// DoubleLinkedList +// ------------------------------ + +using IntDoubleList = data_structures::DoubleLinkedList; + +class DoubleLinkedListTest : public TestGroupBase +{ + public: + IntDoubleList list; +}; + +TEST_F(DoubleLinkedListTest, EmptyList) +{ + EXPECT_TRUE(list.Empty()); + EXPECT_EQ(0_size, list.Size()); + EXPECT_EQ(nullptr, list.GetHead()); + EXPECT_EQ(nullptr, list.GetTail()); +} + +TEST_F(DoubleLinkedListTest, PushFrontAndBack) +{ + list.PushFront(1); + EXPECT_EQ(1_size, list.Size()); + EXPECT_EQ(1, list.Front()); + EXPECT_EQ(1, list.Back()); + + list.PushBack(2); + EXPECT_EQ(2_size, list.Size()); + EXPECT_EQ(1, list.Front()); + EXPECT_EQ(2, list.Back()); + + list.PushFront(0); + EXPECT_EQ(3_size, list.Size()); + EXPECT_EQ(0, list.Front()); + EXPECT_EQ(2, list.Back()); +} + +TEST_F(DoubleLinkedListTest, PopFrontAndBack) +{ + list.PushBack(1); + list.PushBack(2); + list.PushBack(3); + + list.PopFront(); + EXPECT_EQ(2_size, list.Size()); + EXPECT_EQ(2, list.Front()); + + list.PopBack(); + EXPECT_EQ(1_size, list.Size()); + EXPECT_EQ(2, list.Back()); + + list.PopBack(); + EXPECT_TRUE(list.Empty()); +} + +TEST_F(DoubleLinkedListTest, RemoveNode) +{ + auto *node1 = list.PushBack(1); + auto *node2 = list.PushBack(2); + auto *node3 = list.PushBack(3); + + list.Remove(node2); + EXPECT_EQ(2_size, list.Size()); + EXPECT_EQ(1, list.Front()); + EXPECT_EQ(3, list.Back()); + + list.Remove(node1); + EXPECT_EQ(1_size, list.Size()); + EXPECT_EQ(3, list.Front()); + + list.Remove(node3); + EXPECT_TRUE(list.Empty()); +} + +TEST_F(DoubleLinkedListTest, MoveToFront) +{ + auto *node1 = list.PushBack(1); + auto *node2 = list.PushBack(2); + auto *node3 = list.PushBack(3); + + list.MoveToFront(node3); + EXPECT_EQ(3, list.Front()); + EXPECT_EQ(2, list.Back()); + + list.MoveToFront(node1); + EXPECT_EQ(1, list.Front()); +} + +TEST_F(DoubleLinkedListTest, MoveToBack) +{ + auto *node1 = list.PushBack(1); + auto *node2 = list.PushBack(2); + auto *node3 = list.PushBack(3); + + list.MoveToBack(node1); + EXPECT_EQ(2, list.Front()); + EXPECT_EQ(1, list.Back()); + + list.MoveToBack(node3); + EXPECT_EQ(3, list.Back()); +} + +TEST_F(DoubleLinkedListTest, Clear) +{ + list.PushBack(1); + list.PushBack(2); + list.PushBack(3); + + EXPECT_EQ(3_size, list.Size()); + list.Clear(); + EXPECT_TRUE(list.Empty()); + EXPECT_EQ(0_size, list.Size()); +} + +TEST_F(DoubleLinkedListTest, Iterator) +{ + list.PushBack(1); + list.PushBack(2); + list.PushBack(3); + + int expected = 1; + for (auto value : list) { + EXPECT_EQ(expected, value); + ++expected; + } +} + +TEST_F(DoubleLinkedListTest, MoveConstructor) +{ + list.PushBack(1); + list.PushBack(2); + + IntDoubleList otherList(std::move(list)); + EXPECT_TRUE(list.Empty()); + EXPECT_EQ(2_size, otherList.Size()); + EXPECT_EQ(1, otherList.Front()); + EXPECT_EQ(2, otherList.Back()); +} + +TEST_F(DoubleLinkedListTest, MoveAssignment) +{ + list.PushBack(1); + list.PushBack(2); + + IntDoubleList otherList; + otherList.PushBack(99); + + otherList = std::move(list); + EXPECT_TRUE(list.Empty()); + EXPECT_EQ(2_size, otherList.Size()); + EXPECT_EQ(1, otherList.Front()); + EXPECT_EQ(2, otherList.Back()); +} + +TEST_F(DoubleLinkedListTest, CustomAllocator) +{ + data_structures::StaticDoubleLinkedList list; + + list.PushBack(1); + list.PushBack(2); + list.PushBack(3); + + EXPECT_EQ(3_size, list.Size()); + EXPECT_EQ(1, list.Front()); + EXPECT_EQ(3, list.Back()); + + list.Clear(); + EXPECT_EQ(0_size, list.Size()); +} + +TEST_F(DoubleLinkedListTest, InsertAfter) +{ + auto *node1 = list.PushBack(1); + auto *node2 = list.InsertAfter(node1, 2); + list.InsertAfter(node2, 3); + + EXPECT_EQ(3_size, list.Size()); + + int expected = 1; + for (auto value : list) { + EXPECT_EQ(expected, value); + ++expected; + } +} + +TEST_F(DoubleLinkedListTest, StructureSizes) +{ + EXPECT_EQ(24_size, sizeof(IntDoubleList::Node)); // 20 + 4 bytes padding + EXPECT_EQ(24_size, sizeof(IntDoubleList)); +} diff --git a/kernel/test/lru_cache_tests.cpp b/kernel/test/lru_cache_tests.cpp new file mode 100644 index 00000000..a1b45aad --- /dev/null +++ b/kernel/test/lru_cache_tests.cpp @@ -0,0 +1,78 @@ +#include +#include + +class LruCacheTest : public TestGroupBase +{ + public: + data_structures::LruCache cache; +}; + +TEST_F(LruCacheTest, InsertionAndLookup) +{ + cache.Put(1, 10); + cache.Put(2, 20); + cache.Put(3, 30); + + EXPECT_EQ(3_size, cache.Size()); + EXPECT_EQ(30, *cache.Get(3)); + EXPECT_EQ(20, *cache.Get(2)); + EXPECT_EQ(10, *cache.Get(1)); + EXPECT_EQ(nullptr, cache.Get(42)); +} + +TEST_F(LruCacheTest, UpdateKeepsSizeAndMovesToFront) +{ + cache.Put(1, 10); + cache.Put(2, 20); + cache.Put(3, 30); + + cache.Put(2, 200); + EXPECT_EQ(3_size, cache.Size()); + EXPECT_EQ(200, *cache.Get(2)); + + cache.Put(4, 40); + EXPECT_EQ(nullptr, cache.Get(1)); + EXPECT_NOT_NULL(cache.Get(4)); + EXPECT_NOT_NULL(cache.Get(2)); + EXPECT_NOT_NULL(cache.Get(3)); +} + +TEST_F(LruCacheTest, EvictionOfLruEntry) +{ + cache.Put(1, 10); + cache.Put(2, 20); + cache.Put(3, 30); + EXPECT_NOT_NULL(cache.Get(1)); + + cache.Put(4, 30); + EXPECT_EQ(nullptr, cache.Get(2)); + EXPECT_NOT_NULL(cache.Get(1)); + EXPECT_NOT_NULL(cache.Get(3)); + + cache.Get(1); + cache.Put(5, 40); + EXPECT_EQ(nullptr, cache.Get(4)); + EXPECT_NOT_NULL(cache.Get(3)); + EXPECT_NOT_NULL(cache.Get(1)); + EXPECT_NOT_NULL(cache.Get(5)); + EXPECT_EQ(3_size, cache.Size()); +} + +TEST_F(LruCacheTest, EraseAndClear) +{ + cache.Put(1, 10); + cache.Put(2, 20); + cache.Put(3, 30); + + EXPECT_EQ(3_size, cache.Size()); + EXPECT_TRUE(cache.Erase(2)); + EXPECT_EQ(nullptr, cache.Get(2)); + EXPECT_EQ(2_size, cache.Size()); + + EXPECT_FALSE(cache.Erase(42)); + + cache.Clear(); + EXPECT_EQ(0_size, cache.Size()); + EXPECT_TRUE(cache.Empty()); + EXPECT_EQ(nullptr, cache.Get(1)); +} diff --git a/kernel/test/tagged_pointer_test.cpp b/kernel/test/tagged_pointer_test.cpp new file mode 100644 index 00000000..e9ff57cb --- /dev/null +++ b/kernel/test/tagged_pointer_test.cpp @@ -0,0 +1,73 @@ +#include + +#include + +using namespace data_structures; + +class TaggedPointerTest : public TestGroupBase +{ +}; + +struct AlignedStruct1 { + int value; + AlignedStruct1(int v) : value(v) {} +}; + +struct AlignedStruct2 { + long long a, b, c; + + AlignedStruct2(long long x, long long y, long long z) : a(x), b(y), c(z) {} +}; + +using TagPtr = TaggedPointer; + +TEST_F(TaggedPointerTest, BasicCreateAndIs) +{ + // Test Create with tag dispatch - uses universal reference binding + auto ptr1 = TagPtr::Construct(42); + R_ASSERT_TRUE(ptr1.IsValid()); + R_ASSERT_TRUE(ptr1.Is()); + R_ASSERT_FALSE(ptr1.Is()); + + auto ptr2 = TagPtr::Construct(1LL, 2LL, 3LL); + R_ASSERT_TRUE(ptr2.IsValid()); + R_ASSERT_FALSE(ptr2.Is()); + R_ASSERT_TRUE(ptr2.Is()); +} + +TEST_F(TaggedPointerTest, AsMethod) +{ + auto ptr1 = TagPtr::Construct(100); + + auto &s1 = ptr1.As(); + R_ASSERT_EQ(100, s1.value); + + auto ptr2 = TagPtr::Construct(10LL, 20LL, 30LL); + + auto &s2 = ptr2.As(); + R_ASSERT_EQ(10LL, s2.a); + R_ASSERT_EQ(20LL, s2.b); + R_ASSERT_EQ(30LL, s2.c); +} + +TEST_F(TaggedPointerTest, MoveSemantics) +{ + auto ptr1 = TagPtr::Construct(123); + R_ASSERT_TRUE(ptr1.IsValid()); + + // Move construction + auto ptr2 = std::move(ptr1); + R_ASSERT_FALSE(ptr1.IsValid()); // moved-from object is invalid + R_ASSERT_TRUE(ptr2.IsValid()); + R_ASSERT_EQ(123, ptr2.As().value); + + // Move assignment + auto ptr3 = TagPtr::Construct(5LL, 10LL, 15LL); + R_ASSERT_TRUE(ptr3.IsValid()); + + ptr3 = std::move(ptr2); + R_ASSERT_FALSE(ptr2.IsValid()); // moved-from object is invalid + R_ASSERT_TRUE(ptr3.IsValid()); + R_ASSERT_TRUE(ptr3.Is()); + R_ASSERT_EQ(123, ptr3.As().value); +} diff --git a/libs/libcontainers/include/allocators/custom_allocator_wrapper.hpp b/libs/libcontainers/include/allocators/custom_allocator_wrapper.hpp new file mode 100644 index 00000000..020d60ae --- /dev/null +++ b/libs/libcontainers/include/allocators/custom_allocator_wrapper.hpp @@ -0,0 +1,62 @@ +#ifndef LIBS_LIBCONTAINERS_INCLUDE_ALLOCATORS_CUSTOM_ALLOCATOR_WRAPPER_HPP_ +#define LIBS_LIBCONTAINERS_INCLUDE_ALLOCATORS_CUSTOM_ALLOCATOR_WRAPPER_HPP_ + +#include +#include +#include +#include + +namespace allocators +{ + +template +class CustomAllocatorWrapper : decltype(AllocateFunc), decltype(DeallocateFunc) +{ + static constexpr bool kCondition = !std::is_void_v; + + public: + template + FORCE_INLINE_F U *Allocate(Args &&...args) + { + return decltype(AllocateFunc)::operator()(std::forward(args)...); + } + + template + FORCE_INLINE_F void Deallocate(U *ptr) + { + decltype(DeallocateFunc)::operator()(ptr); + } + + OPTIONAL_FIELD(kCondition, AllocatorT) Allocator {}; +}; + +template +using KMallocAllocator = CustomAllocatorWrapper< + [](this auto &, Args &&...args) FORCE_INLINE_L { + auto mem = Mem::KMalloc(); + if (!mem) { + return static_cast(nullptr); + } + + return new (*mem) T(std::forward(args)...); + }, + [](this auto &, U *ptr) FORCE_INLINE_L { + if (ptr) { + ptr->~U(); + Mem::KFree(ptr); + } + }>; + +template +using CyclicAllocatorWrapper = CustomAllocatorWrapper< + [](this auto &self, Args &&...args) FORCE_INLINE_L { + return self.Allocator.Allocate(std::forward(args)...); + }, + [](this auto &self, U *ptr) FORCE_INLINE_L { + self.Allocator.Free(ptr); + }, + CyclicAllocator>; + +} // namespace allocators + +#endif // LIBS_LIBCONTAINERS_INCLUDE_ALLOCATORS_CUSTOM_ALLOCATOR_WRAPPER_HPP_ diff --git a/libs/libcontainers/include/data_structures/critbit_tree.hpp b/libs/libcontainers/include/data_structures/critbit_tree.hpp new file mode 100644 index 00000000..02beffe6 --- /dev/null +++ b/libs/libcontainers/include/data_structures/critbit_tree.hpp @@ -0,0 +1,370 @@ +#ifndef LIBS_LIBCONTAINERS_INCLUDE_DATA_STRUCTURES_CRITBIT_TREE_HPP_ +#define LIBS_LIBCONTAINERS_INCLUDE_DATA_STRUCTURES_CRITBIT_TREE_HPP_ + +#include +#include +#include +#include +#include +#include +#include + +namespace data_structures +{ + +// Crit-bit tree reference: +// https://github.com/agl/critbit/blob/4bb69901a9813de05331bd3afc5085e00050f701/critbit.pdf + +/** + * @brief A binary crit-bit tree implementation for NUL-terminated strings. + * + * This implementation uses a `TaggedPointer` to distinguish between + * internal nodes and external nodes. + * + * @tparam T The type of value stored in the tree. + */ +template +class CritBitTree +{ + // Forward declarations + struct ExternalNode; + struct InternalNode; + + // Tag 0: ExternalNode + // Tag 1: InternalNode + using NodePtr = TaggedPointer; + + struct ExternalNode { + char *key; + T value; + + template + ExternalNode(const char *k, Args &&...args) : value(std::forward(args)...) + { + const size_t len = strlen(k); + key = static_cast(Mem::KMalloc(len + 1).value_or(nullptr)); + if (key) { + strncpy(key, k, len + 1); + } + } + + ~ExternalNode() + { + if (key) { + Mem::KFree(key); + } + } + }; + + struct InternalNode { + NodePtr child[2]; + u32 byte; + u8 mask; + + InternalNode(u32 b, u8 o) : byte(b), mask(o) {} + ~InternalNode() = default; + }; + + public: + using value_type = T; + using pointer = value_type *; + using const_pointer = const value_type *; + + // ----------------------------------------------------------------------------- + // Construction / Destruction + // ----------------------------------------------------------------------------- + + CritBitTree() = default; + + ~CritBitTree() { Clear(); } + + CritBitTree(const CritBitTree &) = delete; + CritBitTree &operator=(const CritBitTree &) = delete; + + CritBitTree(CritBitTree &&other) noexcept : root_(std::move(other.root_)) {} + + CritBitTree &operator=(CritBitTree &&other) noexcept + { + if (this != &other) { + root_ = std::move(other.root_); + } + return *this; + } + + // ----------------------------------------------------------------------------- + // Public API + // ----------------------------------------------------------------------------- + + void Clear() { root_ = {}; } + + /** + * @brief Checks if a key exists in the tree. + */ + NODISCARD FORCE_INLINE_F bool Contains(std::string_view key) const + { + const ExternalNode *match = GetBestMatch(key); + if (!match) { + return false; + } + + return std::string_view(match->key) == key; + } + + /** + * @brief Retrieves a pointer to the value associated with the key. + * @return An optional containing the pointer, or empty if not found. + */ + NODISCARD FORCE_INLINE_F std::optional Get(std::string_view key) + { + ExternalNode *match = GetBestMatch(key); + if (!match) { + return {}; + } + + if (std::string_view(match->key) != key) { + return {}; + } + + auto ret = std::optional(); + ret.emplace(&match->value); + + return ret; + } + + /** + * @brief Retrieves a const pointer to the value associated with the key. + */ + NODISCARD FORCE_INLINE_F std::optional Get(std::string_view key) const + { + const ExternalNode *match = GetBestMatch(key); + if (!match) { + return {}; + } + + if (std::string_view(match->key) != key) { + return {}; + } + + auto ret = std::optional(); + ret.emplace(&match->value); + + return ret; + } + + NODISCARD FORCE_INLINE_F std::optional operator[](std::string_view key) + { + return Get(key); + } + NODISCARD FORCE_INLINE_F std::optional operator[](std::string_view key) const + { + return Get(key); + } + + /** + * @brief Get Longest Prefix Match for the given key. + * @param key The key to search for. + * @return An optional containing a pointer to the value, or empty if no match found. + */ + NODISCARD FORCE_INLINE_F std::optional GetLongestPrefixMatch(std::string_view key) + { + ExternalNode *match = GetBestMatch(key); + if (!match) { + return {}; + } + + auto ret = std::optional(); + ret.emplace(&match->value); + return ret; + } + + /** + * @brief Inserts a key-value pair into the tree. + * + * @param key A NUL-terminated string. + * @param args Arguments to construct the value T. + * + * @tparam overwrite If true, overwrites the value if the key already exists. + * @return true if inserted (or overwritten), false if key exists (and overwrite is false) or + * allocation failed. + */ + template + bool Insert(const char *key, Args &&...args) + { + // 1. Handle insertion into an empty tree + if (!root_.IsValid()) { + root_ = NodePtr::template Construct(key, std::forward(args)...); + if (!root_.IsValid()) { + return false; + } + return true; + } + + // 2. Walk tree for best member + ExternalNode *best_match = GetBestMatch(key); + + // 3. Check for successful membership (exact match) + // Find the first differing byte + const char *p = best_match->key; + size_t i = 0; + while (p[i] != '\0' && key[i] == p[i]) { + i++; + } + + // If we reached the end of both strings, it's a match + if (key[i] == '\0' && p[i] == '\0') { + if constexpr (overwrite) { + // Update value in place + best_match->value.~T(); + new (&best_match->value) T(std::forward(args)...); + return true; + } + return false; + } + + // 4. Find the critical bit + u32 newbyte = static_cast(i); + u32 newmask = 0; + + // Get the differing bytes + u8 u_char = static_cast(key[newbyte]); + u8 p_char = static_cast(p[newbyte]); + + newmask = u_char ^ p_char; + + // Find the most significant differing bit. + newmask |= (newmask >> 1); + newmask |= (newmask >> 2); + newmask |= (newmask >> 4); + newmask = (newmask & ~(newmask >> 1)) ^ 255; + + u8 mask = static_cast(newmask); + + // 5. Allocate new internal node and external node + // Calculate direction for the new key at this critical bit + int dir = (1 + (mask | p_char)) >> 8; + + NodePtr new_ext_node = + NodePtr::template Construct(key, std::forward(args)...); + if (!new_ext_node.IsValid()) { + return false; + } + + NodePtr new_int_node = NodePtr::template Construct(newbyte, mask); + if (!new_int_node.IsValid()) { + return false; + } + + // Link the new external node + auto &internal_ref = new_int_node.template As(); + internal_ref.child[1 - dir] = std::move(new_ext_node); + + // 6. Insert new node into the tree + InsertInternalNode(new_int_node, newbyte, mask, dir, key); + + return true; + } + + /** + * @brief Removes a key from the tree. + */ + bool Remove(std::string_view key) + { + if (!root_.IsValid()) { + return false; + } + + NodePtr *current = &root_; + NodePtr *parent = nullptr; + InternalNode *q = nullptr; + int dir = 0; + + const char *u = key.data(); + const size_t ulen = key.size(); + + // Walk to find best match, keeping track of parent + while (current->template Is()) { + parent = current; + q = ¤t->template As(); + + const u8 c = (q->byte < ulen) ? static_cast(u[q->byte]) : 0; + dir = (1 + (q->mask | c)) >> 8; + current = &q->child[dir]; + } + + ExternalNode &p = current->template As(); + if (std::string_view(p.key) != key) { + return false; + } + + // Found it. Remove p. + // If it's the root (and it's external), the tree becomes empty. + if (!parent) { + root_ = {}; + return true; + } + + *parent = NodePtr(q->child[1 - dir].Release()); + return true; + } + + // ----------------------------------------------------------------------------- + // Private Helpers + // ----------------------------------------------------------------------------- + + private: + ExternalNode *GetBestMatch(std::string_view key) const + { + if (!root_.IsValid()) { + return nullptr; + } + + const NodePtr *p = &root_; + const char *u = key.data(); + const size_t ulen = key.size(); + + while (p->template Is()) { + const InternalNode &q = p->template As(); + const u8 c = (q.byte < ulen) ? static_cast(u[q.byte]) : 0; + int dir = (1 + (q.mask | c)) >> 8; + p = &q.child[dir]; + } + + // p is now an ExternalNode + return &const_cast(p)->template As(); + } + + void InsertInternalNode( + NodePtr &new_node_ptr, u32 newbyte, u8 newmask, int newdir, const char *key + ) + { + NodePtr *wherep = &root_; + const size_t key_len = strlen(key); + + // Walk the tree again to find the insertion point. + // We insert above the first node that checks a bit position *after* our new critical bit. + while (wherep->template Is()) { + InternalNode &q = wherep->template As(); + if (q.byte > newbyte || (q.byte == newbyte && q.mask > newmask)) { + break; + } + + // Move down + const u8 c = (q.byte < key_len) ? static_cast(key[q.byte]) : 0; + const int dir = (1 + (q.mask | c)) >> 8; + wherep = &q.child[dir]; + } + + // The other child of the new node adopts the subtree we are replacing + auto &new_node = new_node_ptr.template As(); + new_node.child[newdir] = std::move(*wherep); + + // The parent points to the new node + *wherep = std::move(new_node_ptr); + } + + NodePtr root_; +}; + +} // namespace data_structures + +#endif // LIBS_LIBCONTAINERS_INCLUDE_DATA_STRUCTURES_CRITBIT_TREE_HPP_ diff --git a/libs/libcontainers/include/data_structures/linked_list.hpp b/libs/libcontainers/include/data_structures/linked_list.hpp new file mode 100644 index 00000000..33633ece --- /dev/null +++ b/libs/libcontainers/include/data_structures/linked_list.hpp @@ -0,0 +1,505 @@ +#ifndef LIBS_LIBCONTAINERS_INCLUDE_DATA_STRUCTURES_LINKED_LIST_HPP_ +#define LIBS_LIBCONTAINERS_INCLUDE_DATA_STRUCTURES_LINKED_LIST_HPP_ + +#include +#include +#include +#include