luajitos

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

vesa.c (63280B)


      1 #include "vesa.h"
      2 #include "decoder.h"
      3 #include <lauxlib.h>
      4 #include <string.h>
      5 #include <stdint.h>
      6 #include <stdlib.h>
      7 
      8 /* External function to initialize screen buffer from kernel.c */
      9 extern void init_screen_buffer(void);
     10 extern uint8_t* get_screen_buffer_ptr(void);
     11 
     12 /* Forward declarations */
     13 void vesa_draw_string(int x, int y, const char* str, uint32_t fg_color, uint32_t bg_color);
     14 
     15 /* Window buffer management */
     16 typedef struct {
     17     uint32_t* pixels;
     18     int width;
     19     int height;
     20 } window_buffer_t;
     21 
     22 /* Global VESA state */
     23 vesa_state_t vesa_state = {0};
     24 
     25 /* Double buffering control - when true, draw to screen buffer instead of framebuffer */
     26 static int use_double_buffering = 1;
     27 
     28 /* Render target - if NULL, render to screen buffer/framebuffer. Otherwise render to this window buffer */
     29 /* Made non-static so other modules can check if rendering to a buffer */
     30 window_buffer_t* current_render_target = NULL;
     31 
     32 /* Simple 8x8 bitmap font (basic ASCII characters 32-127) */
     33 static const uint8_t font_8x8[96][8] = {
     34     {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // Space
     35     {0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00}, // !
     36     {0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // "
     37     {0x36, 0x36, 0x7F, 0x36, 0x7F, 0x36, 0x36, 0x00}, // #
     38     {0x0C, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x0C, 0x00}, // $
     39     {0x00, 0x63, 0x33, 0x18, 0x0C, 0x66, 0x63, 0x00}, // %
     40     {0x1C, 0x36, 0x1C, 0x6E, 0x3B, 0x33, 0x6E, 0x00}, // &
     41     {0x06, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}, // '
     42     {0x18, 0x0C, 0x06, 0x06, 0x06, 0x0C, 0x18, 0x00}, // (
     43     {0x06, 0x0C, 0x18, 0x18, 0x18, 0x0C, 0x06, 0x00}, // )
     44     {0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00}, // *
     45     {0x00, 0x0C, 0x0C, 0x3F, 0x0C, 0x0C, 0x00, 0x00}, // +
     46     {0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // ,
     47     {0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00}, // -
     48     {0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // .
     49     {0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00}, // /
     50     {0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00}, // 0
     51     {0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00}, // 1
     52     {0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00}, // 2
     53     {0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00}, // 3
     54     {0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00}, // 4
     55     {0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00}, // 5
     56     {0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00}, // 6
     57     {0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00}, // 7
     58     {0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00}, // 8
     59     {0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00}, // 9
     60     {0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // :
     61     {0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // ;
     62     {0x18, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x18, 0x00}, // <
     63     {0x00, 0x00, 0x3F, 0x00, 0x00, 0x3F, 0x00, 0x00}, // =
     64     {0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06, 0x00}, // >
     65     {0x1E, 0x33, 0x30, 0x18, 0x0C, 0x00, 0x0C, 0x00}, // ?
     66     {0x3E, 0x63, 0x7B, 0x7B, 0x7B, 0x03, 0x1E, 0x00}, // @
     67     {0x0C, 0x1E, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x00}, // A
     68     {0x3F, 0x66, 0x66, 0x3E, 0x66, 0x66, 0x3F, 0x00}, // B
     69     {0x3C, 0x66, 0x03, 0x03, 0x03, 0x66, 0x3C, 0x00}, // C
     70     {0x1F, 0x36, 0x66, 0x66, 0x66, 0x36, 0x1F, 0x00}, // D
     71     {0x7F, 0x46, 0x16, 0x1E, 0x16, 0x46, 0x7F, 0x00}, // E
     72     {0x7F, 0x46, 0x16, 0x1E, 0x16, 0x06, 0x0F, 0x00}, // F
     73     {0x3C, 0x66, 0x03, 0x03, 0x73, 0x66, 0x7C, 0x00}, // G
     74     {0x33, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x33, 0x00}, // H
     75     {0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // I
     76     {0x78, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E, 0x00}, // J
     77     {0x67, 0x66, 0x36, 0x1E, 0x36, 0x66, 0x67, 0x00}, // K
     78     {0x0F, 0x06, 0x06, 0x06, 0x46, 0x66, 0x7F, 0x00}, // L
     79     {0x63, 0x77, 0x7F, 0x7F, 0x6B, 0x63, 0x63, 0x00}, // M
     80     {0x63, 0x67, 0x6F, 0x7B, 0x73, 0x63, 0x63, 0x00}, // N
     81     {0x1C, 0x36, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x00}, // O
     82     {0x3F, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x0F, 0x00}, // P
     83     {0x1E, 0x33, 0x33, 0x33, 0x3B, 0x1E, 0x38, 0x00}, // Q
     84     {0x3F, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x67, 0x00}, // R
     85     {0x1E, 0x33, 0x07, 0x0E, 0x38, 0x33, 0x1E, 0x00}, // S
     86     {0x3F, 0x2D, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // T
     87     {0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0x00}, // U
     88     {0x33, 0x33, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // V
     89     {0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00}, // W
     90     {0x63, 0x63, 0x36, 0x1C, 0x1C, 0x36, 0x63, 0x00}, // X
     91     {0x33, 0x33, 0x33, 0x1E, 0x0C, 0x0C, 0x1E, 0x00}, // Y
     92     {0x7F, 0x63, 0x31, 0x18, 0x4C, 0x66, 0x7F, 0x00}, // Z
     93     {0x1E, 0x06, 0x06, 0x06, 0x06, 0x06, 0x1E, 0x00}, // [
     94     {0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00}, // backslash
     95     {0x1E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1E, 0x00}, // ]
     96     {0x08, 0x1C, 0x36, 0x63, 0x00, 0x00, 0x00, 0x00}, // ^
     97     {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF}, // _
     98     {0x0C, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00}, // `
     99     {0x00, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x6E, 0x00}, // a
    100     {0x07, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3B, 0x00}, // b
    101     {0x00, 0x00, 0x1E, 0x33, 0x03, 0x33, 0x1E, 0x00}, // c
    102     {0x38, 0x30, 0x30, 0x3e, 0x33, 0x33, 0x6E, 0x00}, // d
    103     {0x00, 0x00, 0x1E, 0x33, 0x3f, 0x03, 0x1E, 0x00}, // e
    104     {0x1C, 0x36, 0x06, 0x0f, 0x06, 0x06, 0x0F, 0x00}, // f
    105     {0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // g
    106     {0x07, 0x06, 0x36, 0x6E, 0x66, 0x66, 0x67, 0x00}, // h
    107     {0x0C, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // i
    108     {0x30, 0x00, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E}, // j
    109     {0x07, 0x06, 0x66, 0x36, 0x1E, 0x36, 0x67, 0x00}, // k
    110     {0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // l
    111     {0x00, 0x00, 0x33, 0x7F, 0x7F, 0x6B, 0x63, 0x00}, // m
    112     {0x00, 0x00, 0x1F, 0x33, 0x33, 0x33, 0x33, 0x00}, // n
    113     {0x00, 0x00, 0x1E, 0x33, 0x33, 0x33, 0x1E, 0x00}, // o
    114     {0x00, 0x00, 0x3B, 0x66, 0x66, 0x3E, 0x06, 0x0F}, // p
    115     {0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x78}, // q
    116     {0x00, 0x00, 0x3B, 0x6E, 0x66, 0x06, 0x0F, 0x00}, // r
    117     {0x00, 0x00, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x00}, // s
    118     {0x08, 0x0C, 0x3E, 0x0C, 0x0C, 0x2C, 0x18, 0x00}, // t
    119     {0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x6E, 0x00}, // u
    120     {0x00, 0x00, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // v
    121     {0x00, 0x00, 0x63, 0x6B, 0x7F, 0x7F, 0x36, 0x00}, // w
    122     {0x00, 0x00, 0x63, 0x36, 0x1C, 0x36, 0x63, 0x00}, // x
    123     {0x00, 0x00, 0x33, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // y
    124     {0x00, 0x00, 0x3F, 0x19, 0x0C, 0x26, 0x3F, 0x00}, // z
    125     {0x38, 0x0C, 0x0C, 0x07, 0x0C, 0x0C, 0x38, 0x00}, // {
    126     {0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00}, // |
    127     {0x07, 0x0C, 0x0C, 0x38, 0x0C, 0x0C, 0x07, 0x00}, // }
    128     {0x6E, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // ~
    129     {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}  // DEL
    130 };
    131 
    132 /* Multiboot info structure (minimal - just what we need) */
    133 typedef struct {
    134     uint32_t flags;
    135     uint32_t mem_lower;
    136     uint32_t mem_upper;
    137     uint32_t boot_device;
    138     uint32_t cmdline;
    139     uint32_t mods_count;
    140     uint32_t mods_addr;
    141     uint32_t syms[4];
    142     uint32_t mmap_length;
    143     uint32_t mmap_addr;
    144     uint32_t drives_length;
    145     uint32_t drives_addr;
    146     uint32_t config_table;
    147     uint32_t boot_loader_name;
    148     uint32_t apm_table;
    149     uint32_t vbe_control_info;
    150     uint32_t vbe_mode_info;
    151     uint16_t vbe_mode;
    152     uint16_t vbe_interface_seg;
    153     uint16_t vbe_interface_off;
    154     uint16_t vbe_interface_len;
    155     uint64_t framebuffer_addr;
    156     uint32_t framebuffer_pitch;
    157     uint32_t framebuffer_width;
    158     uint32_t framebuffer_height;
    159     uint8_t  framebuffer_bpp;
    160     uint8_t  framebuffer_type;
    161     uint8_t  color_info[6];
    162 } __attribute__((packed)) multiboot_info_t;
    163 
    164 /* External multiboot info pointer set in boot.s */
    165 extern uint32_t* multiboot_info_ptr;
    166 
    167 /* Note: VESA VBE requires real mode BIOS calls (INT 0x10).
    168  * In protected mode, we need to either:
    169  * 1. Switch back to real mode temporarily (complex)
    170  * 2. Use V86 mode (complex)
    171  * 3. Use GRUB's multiboot video mode setup (easier)
    172  *
    173  * For now, we'll implement GRUB multiboot approach where GRUB sets up
    174  * the video mode for us before the kernel starts.
    175  */
    176 
    177 /* Multiboot framebuffer info (passed by GRUB) */
    178 typedef struct {
    179     uint32_t framebuffer_addr_low;
    180     uint32_t framebuffer_addr_high;
    181     uint32_t framebuffer_pitch;
    182     uint32_t framebuffer_width;
    183     uint32_t framebuffer_height;
    184     uint8_t framebuffer_bpp;
    185     uint8_t framebuffer_type;
    186     uint8_t color_info[6];
    187 } __attribute__((packed)) multiboot_framebuffer_t;
    188 
    189 /* External multiboot info (would be set in boot.s) */
    190 extern multiboot_framebuffer_t* multiboot_fb_info;
    191 
    192 /* Forward declare terminal_writestring */
    193 extern void terminal_writestring(const char* str);
    194 
    195 /* Initialize VESA using multiboot framebuffer info */
    196 int vesa_init(void) {
    197     /* Check if we have multiboot info */
    198     if (!multiboot_info_ptr) {
    199         terminal_writestring("VESA: multiboot_info_ptr is NULL\n");
    200         vesa_state.initialized = 0;
    201         vesa_state.active = 0;
    202         return -1;
    203     }
    204 
    205     multiboot_info_t* mb_info = (multiboot_info_t*)multiboot_info_ptr;
    206 
    207     /* Check if framebuffer info is available (bit 12 of flags) */
    208     if (!(mb_info->flags & (1 << 12))) {
    209         /* No framebuffer info available */
    210         terminal_writestring("VESA: No framebuffer info in multiboot (bit 12 not set)\n");
    211         vesa_state.initialized = 0;
    212         vesa_state.active = 0;
    213         return -1;
    214     }
    215 
    216     /* Extract framebuffer information */
    217     vesa_state.current_mode.framebuffer = (uint32_t)mb_info->framebuffer_addr;
    218     vesa_state.current_mode.width = mb_info->framebuffer_width;
    219     vesa_state.current_mode.height = mb_info->framebuffer_height;
    220     vesa_state.current_mode.bpp = mb_info->framebuffer_bpp;
    221     vesa_state.current_mode.pitch = mb_info->framebuffer_pitch;
    222     vesa_state.current_mode.memory_model = VBE_MEMORY_MODEL_DIRECT_COLOR;
    223 
    224     /* Set up color masks for RGB mode */
    225     if (mb_info->framebuffer_bpp == 32) {
    226         vesa_state.current_mode.red_mask_size = 8;
    227         vesa_state.current_mode.red_field_position = 16;
    228         vesa_state.current_mode.green_mask_size = 8;
    229         vesa_state.current_mode.green_field_position = 8;
    230         vesa_state.current_mode.blue_mask_size = 8;
    231         vesa_state.current_mode.blue_field_position = 0;
    232     } else if (mb_info->framebuffer_bpp == 24) {
    233         vesa_state.current_mode.red_mask_size = 8;
    234         vesa_state.current_mode.red_field_position = 16;
    235         vesa_state.current_mode.green_mask_size = 8;
    236         vesa_state.current_mode.green_field_position = 8;
    237         vesa_state.current_mode.blue_mask_size = 8;
    238         vesa_state.current_mode.blue_field_position = 0;
    239     }
    240 
    241     vesa_state.framebuffer = (uint8_t*)vesa_state.current_mode.framebuffer;
    242     vesa_state.active = 1;
    243     vesa_state.initialized = 1;
    244 
    245     /* Debug output */
    246     terminal_writestring("VESA: Framebuffer at 0x");
    247     char hex[16];
    248     for (int i = 7; i >= 0; i--) {
    249         int nibble = (vesa_state.current_mode.framebuffer >> (i * 4)) & 0xF;
    250         hex[7-i] = (nibble < 10) ? ('0' + nibble) : ('A' + nibble - 10);
    251     }
    252     hex[8] = '\0';
    253     terminal_writestring(hex);
    254     terminal_writestring("\n");
    255 
    256     return 0;
    257 }
    258 
    259 /* Set VESA mode (validates against GRUB's pre-set mode) */
    260 int vesa_set_mode(uint16_t width, uint16_t height, uint8_t bpp) {
    261     /* VESA mode setting in protected mode requires going back to real mode
    262      * which is complex. Instead, we verify the requested mode matches what
    263      * GRUB set up for us, or is compatible. */
    264 
    265     if (!vesa_state.initialized) {
    266         /* Try to initialize first */
    267         if (vesa_init() != 0) {
    268             return -1;
    269         }
    270     }
    271 
    272     /* Check if the requested mode matches what we have */
    273     if (vesa_state.current_mode.width == width &&
    274         vesa_state.current_mode.height == height &&
    275         vesa_state.current_mode.bpp == bpp) {
    276         /* Perfect match */
    277         return 0;
    278     }
    279 
    280     /* Mode doesn't match - for now just use what we have */
    281     /* In a real implementation, we'd either:
    282      * 1. Request mode change via GRUB config
    283      * 2. Implement VM86 mode to call BIOS
    284      * 3. Use VBE/PM interface if available */
    285 
    286     /* Return success if framebuffer is available, error otherwise */
    287     return vesa_state.active ? 0 : -1;
    288 }
    289 
    290 /* Color conversion: RGB to packed color */
    291 uint32_t vesa_rgb_to_color(uint8_t r, uint8_t g, uint8_t b) {
    292     if (!vesa_state.active) return 0;
    293 
    294     uint8_t bpp = vesa_state.current_mode.bpp;
    295 
    296     if (bpp == 32) {
    297         /* RGBA8888 - set alpha to 255 (fully opaque) */
    298         return (0xFF << 24) | (r << 16) | (g << 8) | b;
    299     } else if (bpp == 24) {
    300         /* RGB888 - no alpha channel */
    301         return (r << 16) | (g << 8) | b;
    302     } else if (bpp == 16) {
    303         /* RGB565 */
    304         uint16_t r5 = (r >> 3) & 0x1F;
    305         uint16_t g6 = (g >> 2) & 0x3F;
    306         uint16_t b5 = (b >> 3) & 0x1F;
    307         return (r5 << 11) | (g6 << 5) | b5;
    308     }
    309 
    310     return 0;
    311 }
    312 
    313 /* Color conversion: packed color to RGB */
    314 void vesa_color_to_rgb(uint32_t color, uint8_t* r, uint8_t* g, uint8_t* b) {
    315     if (!vesa_state.active) {
    316         *r = *g = *b = 0;
    317         return;
    318     }
    319 
    320     uint8_t bpp = vesa_state.current_mode.bpp;
    321 
    322     if (bpp == 32 || bpp == 24) {
    323         *r = (color >> 16) & 0xFF;
    324         *g = (color >> 8) & 0xFF;
    325         *b = color & 0xFF;
    326     } else if (bpp == 16) {
    327         /* RGB565 */
    328         *r = ((color >> 11) & 0x1F) << 3;
    329         *g = ((color >> 5) & 0x3F) << 2;
    330         *b = (color & 0x1F) << 3;
    331     }
    332 }
    333 
    334 /* Draw a single pixel */
    335 void vesa_draw_pixel(int x, int y, uint32_t color) {
    336     if (!vesa_state.active) return;
    337 
    338     /* If rendering to a window buffer, use that instead */
    339     if (current_render_target) {
    340         if (x < 0 || x >= current_render_target->width || y < 0 || y >= current_render_target->height) return;
    341         current_render_target->pixels[y * current_render_target->width + x] = color;
    342         return;
    343     }
    344 
    345     int width = vesa_state.current_mode.width;
    346     int height = vesa_state.current_mode.height;
    347     int bpp = vesa_state.current_mode.bpp;
    348     int pitch = vesa_state.current_mode.pitch;
    349 
    350     if (x < 0 || x >= width || y < 0 || y >= height) return;
    351 
    352     /* Use screen buffer if double buffering is enabled, otherwise use framebuffer */
    353     uint8_t* fb = use_double_buffering ? get_screen_buffer_ptr() : vesa_state.framebuffer;
    354     int bytes_per_pixel = bpp / 8;
    355     uint8_t* pixel = fb + y * pitch + x * bytes_per_pixel;
    356 
    357     if (bpp == 32) {
    358         *(uint32_t*)pixel = color;
    359     } else if (bpp == 24) {
    360         pixel[0] = color & 0xFF;
    361         pixel[1] = (color >> 8) & 0xFF;
    362         pixel[2] = (color >> 16) & 0xFF;
    363     } else if (bpp == 16) {
    364         *(uint16_t*)pixel = (uint16_t)color;
    365     }
    366 }
    367 
    368 /* Draw a filled rectangle */
    369 void vesa_draw_rect_fill(int x, int y, int width, int height, uint32_t color) {
    370     for (int dy = 0; dy < height; dy++) {
    371         for (int dx = 0; dx < width; dx++) {
    372             vesa_draw_pixel(x + dx, y + dy, color);
    373         }
    374     }
    375 }
    376 
    377 /* Draw an outlined rectangle */
    378 void vesa_draw_rect_outline(int x, int y, int width, int height, uint32_t color, int thickness) {
    379     /* Top and bottom edges */
    380     for (int i = 0; i < thickness; i++) {
    381         for (int dx = 0; dx < width; dx++) {
    382             vesa_draw_pixel(x + dx, y + i, color);
    383             vesa_draw_pixel(x + dx, y + height - 1 - i, color);
    384         }
    385     }
    386 
    387     /* Left and right edges */
    388     for (int i = 0; i < thickness; i++) {
    389         for (int dy = 0; dy < height; dy++) {
    390             vesa_draw_pixel(x + i, y + dy, color);
    391             vesa_draw_pixel(x + width - 1 - i, y + dy, color);
    392         }
    393     }
    394 }
    395 
    396 /* Draw a line using Bresenham's algorithm */
    397 void vesa_draw_line(int x0, int y0, int x1, int y1, uint32_t color, int thickness) {
    398     int dx = x1 - x0;
    399     int dy = y1 - y0;
    400 
    401     /* Handle vertical and horizontal lines specially for thickness */
    402     if (dx == 0) {
    403         int startY = y0 < y1 ? y0 : y1;
    404         int endY = y0 < y1 ? y1 : y0;
    405         for (int ty = startY; ty <= endY; ty++) {
    406             for (int tx = 0; tx < thickness; tx++) {
    407                 vesa_draw_pixel(x0 + tx, ty, color);
    408             }
    409         }
    410         return;
    411     }
    412 
    413     if (dy == 0) {
    414         int startX = x0 < x1 ? x0 : x1;
    415         int endX = x0 < x1 ? x1 : x0;
    416         for (int tx = startX; tx <= endX; tx++) {
    417             for (int ty = 0; ty < thickness; ty++) {
    418                 vesa_draw_pixel(tx, y0 + ty, color);
    419             }
    420         }
    421         return;
    422     }
    423 
    424     /* Bresenham's line algorithm */
    425     int dx_abs = dx < 0 ? -dx : dx;
    426     int dy_abs = dy < 0 ? -dy : dy;
    427     int sx = dx > 0 ? 1 : -1;
    428     int sy = dy > 0 ? 1 : -1;
    429     int err = dx_abs - dy_abs;
    430 
    431     int x = x0;
    432     int y = y0;
    433 
    434     while (1) {
    435         /* Draw with thickness */
    436         for (int ty = 0; ty < thickness; ty++) {
    437             for (int tx = 0; tx < thickness; tx++) {
    438                 vesa_draw_pixel(x + tx, y + ty, color);
    439             }
    440         }
    441 
    442         if (x == x1 && y == y1) break;
    443 
    444         int e2 = 2 * err;
    445         if (e2 > -dy_abs) {
    446             err = err - dy_abs;
    447             x = x + sx;
    448         }
    449         if (e2 < dx_abs) {
    450             err = err + dx_abs;
    451             y = y + sy;
    452         }
    453     }
    454 }
    455 
    456 /* Draw a filled circle */
    457 void vesa_draw_circle_fill(int cx, int cy, int radius, uint32_t color) {
    458     int x = radius;
    459     int y = 0;
    460     int err = 0;
    461 
    462     while (x >= y) {
    463         /* Draw horizontal lines for each octant */
    464         for (int dx = -x; dx <= x; dx++) {
    465             vesa_draw_pixel(cx + dx, cy + y, color);
    466             vesa_draw_pixel(cx + dx, cy - y, color);
    467         }
    468         for (int dx = -y; dx <= y; dx++) {
    469             vesa_draw_pixel(cx + dx, cy + x, color);
    470             vesa_draw_pixel(cx + dx, cy - x, color);
    471         }
    472 
    473         y++;
    474         err = err + 1 + 2 * y;
    475         if (2 * (err - x) + 1 > 0) {
    476             x--;
    477             err = err + 1 - 2 * x;
    478         }
    479     }
    480 }
    481 
    482 /* Draw an outlined circle */
    483 void vesa_draw_circle_outline(int cx, int cy, int radius, uint32_t color, int thickness) {
    484     for (int t = 0; t < thickness; t++) {
    485         int r = radius - t;
    486         if (r < 0) break;
    487 
    488         int x = r;
    489         int y = 0;
    490         int err = 0;
    491 
    492         while (x >= y) {
    493             /* Draw 8 symmetric points */
    494             vesa_draw_pixel(cx + x, cy + y, color);
    495             vesa_draw_pixel(cx + y, cy + x, color);
    496             vesa_draw_pixel(cx - y, cy + x, color);
    497             vesa_draw_pixel(cx - x, cy + y, color);
    498             vesa_draw_pixel(cx - x, cy - y, color);
    499             vesa_draw_pixel(cx - y, cy - x, color);
    500             vesa_draw_pixel(cx + y, cy - x, color);
    501             vesa_draw_pixel(cx + x, cy - y, color);
    502 
    503             y++;
    504             err = err + 1 + 2 * y;
    505             if (2 * (err - x) + 1 > 0) {
    506                 x--;
    507                 err = err + 1 - 2 * x;
    508             }
    509         }
    510     }
    511 }
    512 
    513 /* Draw a filled triangle */
    514 void vesa_draw_triangle_fill(int x0, int y0, int x1, int y1, int x2, int y2, uint32_t color) {
    515     /* Sort vertices by y-coordinate (y0 <= y1 <= y2) */
    516     if (y0 > y1) {
    517         int tmp;
    518         tmp = x0; x0 = x1; x1 = tmp;
    519         tmp = y0; y0 = y1; y1 = tmp;
    520     }
    521     if (y0 > y2) {
    522         int tmp;
    523         tmp = x0; x0 = x2; x2 = tmp;
    524         tmp = y0; y0 = y2; y2 = tmp;
    525     }
    526     if (y1 > y2) {
    527         int tmp;
    528         tmp = x1; x1 = x2; x2 = tmp;
    529         tmp = y1; y1 = y2; y2 = tmp;
    530     }
    531 
    532     /* Draw flat-bottom triangle (y0 to y1) */
    533     if (y1 - y0 > 0) {
    534         float invslope1 = (float)(x1 - x0) / (y1 - y0);
    535         float invslope2 = (float)(x2 - x0) / (y2 - y0);
    536         float curx1 = (float)x0;
    537         float curx2 = (float)x0;
    538 
    539         for (int scanlineY = y0; scanlineY <= y1; scanlineY++) {
    540             int startX = (int)(curx1 < curx2 ? curx1 : curx2);
    541             int endX = (int)(curx1 < curx2 ? curx2 : curx1);
    542 
    543             for (int dx = 0; dx <= endX - startX; dx++) {
    544                 vesa_draw_pixel(startX + dx, scanlineY, color);
    545             }
    546             curx1 += invslope1;
    547             curx2 += invslope2;
    548         }
    549     }
    550 
    551     /* Draw flat-top triangle (y1 to y2) */
    552     if (y2 - y1 > 0) {
    553         float invslope1 = (float)(x2 - x1) / (y2 - y1);
    554         float invslope2 = (float)(x2 - x0) / (y2 - y0);
    555         float curx1 = (float)x1;
    556         float curx2 = (float)x0 + (y1 - y0) * ((float)(x2 - x0) / (y2 - y0));
    557 
    558         for (int scanlineY = y1; scanlineY <= y2; scanlineY++) {
    559             int startX = (int)(curx1 < curx2 ? curx1 : curx2);
    560             int endX = (int)(curx1 < curx2 ? curx2 : curx1);
    561 
    562             for (int dx = 0; dx <= endX - startX; dx++) {
    563                 vesa_draw_pixel(startX + dx, scanlineY, color);
    564             }
    565             curx1 += invslope1;
    566             curx2 += invslope2;
    567         }
    568     }
    569 }
    570 
    571 /* Lua binding: Initialize VESA */
    572 int lua_vesa_init(lua_State* L) {
    573     int result = vesa_init();
    574     lua_pushboolean(L, result == 0);
    575     return 1;
    576 }
    577 
    578 /* Lua binding: Set VESA mode */
    579 int lua_vesa_set_mode(lua_State* L) {
    580     int width = luaL_checkinteger(L, 1);
    581     int height = luaL_checkinteger(L, 2);
    582     int bpp = luaL_optinteger(L, 3, 32);  /* Default to 32-bit */
    583 
    584     int result = vesa_set_mode(width, height, bpp);
    585 
    586     /* Initialize screen buffer after mode is set */
    587     if (result == 0) {
    588         init_screen_buffer();
    589     }
    590 
    591     lua_pushboolean(L, result == 0);
    592     return 1;
    593 }
    594 
    595 /* Lua binding: Get current mode info */
    596 int lua_vesa_get_mode_info(lua_State* L) {
    597     if (!vesa_state.active) {
    598         lua_pushnil(L);
    599         return 1;
    600     }
    601 
    602     lua_newtable(L);
    603 
    604     lua_pushinteger(L, vesa_state.current_mode.width);
    605     lua_setfield(L, -2, "width");
    606 
    607     lua_pushinteger(L, vesa_state.current_mode.height);
    608     lua_setfield(L, -2, "height");
    609 
    610     lua_pushinteger(L, vesa_state.current_mode.bpp);
    611     lua_setfield(L, -2, "bpp");
    612 
    613     lua_pushinteger(L, vesa_state.current_mode.pitch);
    614     lua_setfield(L, -2, "pitch");
    615 
    616     return 1;
    617 }
    618 
    619 /* Lua binding: List available modes */
    620 int lua_vesa_list_modes(lua_State* L) {
    621     /* TODO: Implement mode enumeration via BIOS calls */
    622     lua_newtable(L);
    623     return 1;
    624 }
    625 
    626 /* Lua binding: Set pixel */
    627 int lua_vesa_set_pixel(lua_State* L) {
    628     int x = luaL_checkinteger(L, 1);
    629     int y = luaL_checkinteger(L, 2);
    630     int r = luaL_checkinteger(L, 3);
    631     int g = luaL_checkinteger(L, 4);
    632     int b = luaL_checkinteger(L, 5);
    633 
    634     uint32_t color = vesa_rgb_to_color(r, g, b);
    635     vesa_draw_pixel(x, y, color);
    636 
    637     return 0;
    638 }
    639 
    640 /* Lua binding: Draw filled rectangle */
    641 int lua_vesa_draw_rect(lua_State* L) {
    642     int x = luaL_checkinteger(L, 1);
    643     int y = luaL_checkinteger(L, 2);
    644     int width = luaL_checkinteger(L, 3);
    645     int height = luaL_checkinteger(L, 4);
    646     int r = luaL_checkinteger(L, 5);
    647     int g = luaL_checkinteger(L, 6);
    648     int b = luaL_checkinteger(L, 7);
    649 
    650     uint32_t color = vesa_rgb_to_color(r, g, b);
    651     vesa_draw_rect_fill(x, y, width, height, color);
    652 
    653     return 0;
    654 }
    655 
    656 /* Lua binding: Clear screen */
    657 int lua_vesa_clear_screen(lua_State* L) {
    658     int r = luaL_optinteger(L, 1, 0);
    659     int g = luaL_optinteger(L, 2, 0);
    660     int b = luaL_optinteger(L, 3, 0);
    661 
    662     if (!vesa_state.active) return 0;
    663 
    664     uint32_t color = vesa_rgb_to_color(r, g, b);
    665     int width = vesa_state.current_mode.width;
    666     int height = vesa_state.current_mode.height;
    667 
    668     vesa_draw_rect_fill(0, 0, width, height, color);
    669 
    670     return 0;
    671 }
    672 
    673 /* Forward declarations for scaled text drawing */
    674 void vesa_draw_char_scaled(int x, int y, char c, uint32_t fg_color, uint32_t bg_color, int scale);
    675 void vesa_draw_string_scaled(int x, int y, const char* str, uint32_t fg_color, uint32_t bg_color, int scale);
    676 
    677 /* Process buffered draw operations for VESA */
    678 int lua_vesa_process_buffered_draw_ops(lua_State* L) {
    679     /* Same operation types as VGA */
    680     #define OP_PIXEL         0
    681     #define OP_RECT          1
    682     #define OP_RECT_FILL     2
    683     #define OP_CIRCLE        3
    684     #define OP_CIRCLE_FILL   4
    685     #define OP_TRIANGLE      5
    686     #define OP_TRIANGLE_FILL 6
    687     #define OP_POLYGON       7
    688     #define OP_POLYGON_FILL  8
    689     #define OP_LINE          9
    690     #define OP_TEXT          10
    691     #define OP_IMAGE         11
    692     #define OP_BUFFER        12
    693 
    694     if (!vesa_state.active) return 0;
    695 
    696     /* Expect a table of operations as the first argument */
    697     luaL_checktype(L, 1, LUA_TTABLE);
    698 
    699     /* Get the number of operations */
    700     int num_ops = lua_objlen(L, 1);
    701 
    702     /* Process each operation */
    703     for (int i = 1; i <= num_ops; i++) {
    704         lua_rawgeti(L, 1, i);  /* Get operation table */
    705 
    706         if (!lua_istable(L, -1)) {
    707             lua_pop(L, 1);
    708             continue;
    709         }
    710 
    711         /* Get operation type */
    712         lua_rawgeti(L, -1, 1);
    713         int op_type = lua_tointeger(L, -1);
    714         lua_pop(L, 1);
    715 
    716         /* Process based on operation type */
    717         switch (op_type) {
    718             case OP_PIXEL: {
    719                 /* {PIXEL, x, y, r, g, b} */
    720                 lua_rawgeti(L, -1, 2); int x = lua_tointeger(L, -1); lua_pop(L, 1);
    721                 lua_rawgeti(L, -1, 3); int y = lua_tointeger(L, -1); lua_pop(L, 1);
    722                 lua_rawgeti(L, -1, 4); int r = lua_tointeger(L, -1); lua_pop(L, 1);
    723                 lua_rawgeti(L, -1, 5); int g = lua_tointeger(L, -1); lua_pop(L, 1);
    724                 lua_rawgeti(L, -1, 6); int b = lua_tointeger(L, -1); lua_pop(L, 1);
    725 
    726                 uint32_t color = vesa_rgb_to_color(r, g, b);
    727                 vesa_draw_pixel(x, y, color);
    728                 break;
    729             }
    730 
    731             case OP_LINE: {
    732                 /* {LINE, x0, y0, x1, y1, r, g, b, thickness} */
    733                 lua_rawgeti(L, -1, 2); int x0 = lua_tointeger(L, -1); lua_pop(L, 1);
    734                 lua_rawgeti(L, -1, 3); int y0 = lua_tointeger(L, -1); lua_pop(L, 1);
    735                 lua_rawgeti(L, -1, 4); int x1 = lua_tointeger(L, -1); lua_pop(L, 1);
    736                 lua_rawgeti(L, -1, 5); int y1 = lua_tointeger(L, -1); lua_pop(L, 1);
    737                 lua_rawgeti(L, -1, 6); int r = lua_tointeger(L, -1); lua_pop(L, 1);
    738                 lua_rawgeti(L, -1, 7); int g = lua_tointeger(L, -1); lua_pop(L, 1);
    739                 lua_rawgeti(L, -1, 8); int b = lua_tointeger(L, -1); lua_pop(L, 1);
    740                 lua_rawgeti(L, -1, 9); int thickness = lua_tointeger(L, -1); lua_pop(L, 1);
    741 
    742                 uint32_t color = vesa_rgb_to_color(r, g, b);
    743                 vesa_draw_line(x0, y0, x1, y1, color, thickness);
    744                 break;
    745             }
    746 
    747             case OP_RECT: {
    748                 /* {RECT, x, y, width, height, r, g, b, thickness} */
    749                 lua_rawgeti(L, -1, 2); int x = lua_tointeger(L, -1); lua_pop(L, 1);
    750                 lua_rawgeti(L, -1, 3); int y = lua_tointeger(L, -1); lua_pop(L, 1);
    751                 lua_rawgeti(L, -1, 4); int width = lua_tointeger(L, -1); lua_pop(L, 1);
    752                 lua_rawgeti(L, -1, 5); int height = lua_tointeger(L, -1); lua_pop(L, 1);
    753                 lua_rawgeti(L, -1, 6); int r = lua_tointeger(L, -1); lua_pop(L, 1);
    754                 lua_rawgeti(L, -1, 7); int g = lua_tointeger(L, -1); lua_pop(L, 1);
    755                 lua_rawgeti(L, -1, 8); int b = lua_tointeger(L, -1); lua_pop(L, 1);
    756                 lua_rawgeti(L, -1, 9); int thickness = lua_tointeger(L, -1); lua_pop(L, 1);
    757 
    758                 uint32_t color = vesa_rgb_to_color(r, g, b);
    759                 vesa_draw_rect_outline(x, y, width, height, color, thickness);
    760                 break;
    761             }
    762 
    763             case OP_RECT_FILL: {
    764                 /* {RECT_FILL, x, y, width, height, r, g, b} */
    765                 lua_rawgeti(L, -1, 2); int x = lua_tointeger(L, -1); lua_pop(L, 1);
    766                 lua_rawgeti(L, -1, 3); int y = lua_tointeger(L, -1); lua_pop(L, 1);
    767                 lua_rawgeti(L, -1, 4); int width = lua_tointeger(L, -1); lua_pop(L, 1);
    768                 lua_rawgeti(L, -1, 5); int height = lua_tointeger(L, -1); lua_pop(L, 1);
    769                 lua_rawgeti(L, -1, 6); int r = lua_tointeger(L, -1); lua_pop(L, 1);
    770                 lua_rawgeti(L, -1, 7); int g = lua_tointeger(L, -1); lua_pop(L, 1);
    771                 lua_rawgeti(L, -1, 8); int b = lua_tointeger(L, -1); lua_pop(L, 1);
    772 
    773                 uint32_t color = vesa_rgb_to_color(r, g, b);
    774                 vesa_draw_rect_fill(x, y, width, height, color);
    775                 break;
    776             }
    777 
    778             case OP_CIRCLE: {
    779                 /* {CIRCLE, cx, cy, radius, r, g, b, thickness} */
    780                 lua_rawgeti(L, -1, 2); int cx = lua_tointeger(L, -1); lua_pop(L, 1);
    781                 lua_rawgeti(L, -1, 3); int cy = lua_tointeger(L, -1); lua_pop(L, 1);
    782                 lua_rawgeti(L, -1, 4); int radius = lua_tointeger(L, -1); lua_pop(L, 1);
    783                 lua_rawgeti(L, -1, 5); int r = lua_tointeger(L, -1); lua_pop(L, 1);
    784                 lua_rawgeti(L, -1, 6); int g = lua_tointeger(L, -1); lua_pop(L, 1);
    785                 lua_rawgeti(L, -1, 7); int b = lua_tointeger(L, -1); lua_pop(L, 1);
    786                 lua_rawgeti(L, -1, 8); int thickness = lua_tointeger(L, -1); lua_pop(L, 1);
    787 
    788                 uint32_t color = vesa_rgb_to_color(r, g, b);
    789                 vesa_draw_circle_outline(cx, cy, radius, color, thickness);
    790                 break;
    791             }
    792 
    793             case OP_CIRCLE_FILL: {
    794                 /* {CIRCLE_FILL, cx, cy, radius, r, g, b} */
    795                 lua_rawgeti(L, -1, 2); int cx = lua_tointeger(L, -1); lua_pop(L, 1);
    796                 lua_rawgeti(L, -1, 3); int cy = lua_tointeger(L, -1); lua_pop(L, 1);
    797                 lua_rawgeti(L, -1, 4); int radius = lua_tointeger(L, -1); lua_pop(L, 1);
    798                 lua_rawgeti(L, -1, 5); int r = lua_tointeger(L, -1); lua_pop(L, 1);
    799                 lua_rawgeti(L, -1, 6); int g = lua_tointeger(L, -1); lua_pop(L, 1);
    800                 lua_rawgeti(L, -1, 7); int b = lua_tointeger(L, -1); lua_pop(L, 1);
    801 
    802                 uint32_t color = vesa_rgb_to_color(r, g, b);
    803                 vesa_draw_circle_fill(cx, cy, radius, color);
    804                 break;
    805             }
    806 
    807             case OP_TRIANGLE: {
    808                 /* {TRIANGLE, x0, y0, x1, y1, x2, y2, r, g, b, thickness} */
    809                 lua_rawgeti(L, -1, 2); int x0 = lua_tointeger(L, -1); lua_pop(L, 1);
    810                 lua_rawgeti(L, -1, 3); int y0 = lua_tointeger(L, -1); lua_pop(L, 1);
    811                 lua_rawgeti(L, -1, 4); int x1 = lua_tointeger(L, -1); lua_pop(L, 1);
    812                 lua_rawgeti(L, -1, 5); int y1 = lua_tointeger(L, -1); lua_pop(L, 1);
    813                 lua_rawgeti(L, -1, 6); int x2 = lua_tointeger(L, -1); lua_pop(L, 1);
    814                 lua_rawgeti(L, -1, 7); int y2 = lua_tointeger(L, -1); lua_pop(L, 1);
    815                 lua_rawgeti(L, -1, 8); int r = lua_tointeger(L, -1); lua_pop(L, 1);
    816                 lua_rawgeti(L, -1, 9); int g = lua_tointeger(L, -1); lua_pop(L, 1);
    817                 lua_rawgeti(L, -1, 10); int b = lua_tointeger(L, -1); lua_pop(L, 1);
    818                 lua_rawgeti(L, -1, 11); int thickness = lua_tointeger(L, -1); lua_pop(L, 1);
    819 
    820                 uint32_t color = vesa_rgb_to_color(r, g, b);
    821                 /* Draw triangle outline using lines */
    822                 vesa_draw_line(x0, y0, x1, y1, color, thickness);
    823                 vesa_draw_line(x1, y1, x2, y2, color, thickness);
    824                 vesa_draw_line(x2, y2, x0, y0, color, thickness);
    825                 break;
    826             }
    827 
    828             case OP_TRIANGLE_FILL: {
    829                 /* {TRIANGLE_FILL, x0, y0, x1, y1, x2, y2, r, g, b} */
    830                 lua_rawgeti(L, -1, 2); int x0 = lua_tointeger(L, -1); lua_pop(L, 1);
    831                 lua_rawgeti(L, -1, 3); int y0 = lua_tointeger(L, -1); lua_pop(L, 1);
    832                 lua_rawgeti(L, -1, 4); int x1 = lua_tointeger(L, -1); lua_pop(L, 1);
    833                 lua_rawgeti(L, -1, 5); int y1 = lua_tointeger(L, -1); lua_pop(L, 1);
    834                 lua_rawgeti(L, -1, 6); int x2 = lua_tointeger(L, -1); lua_pop(L, 1);
    835                 lua_rawgeti(L, -1, 7); int y2 = lua_tointeger(L, -1); lua_pop(L, 1);
    836                 lua_rawgeti(L, -1, 8); int r = lua_tointeger(L, -1); lua_pop(L, 1);
    837                 lua_rawgeti(L, -1, 9); int g = lua_tointeger(L, -1); lua_pop(L, 1);
    838                 lua_rawgeti(L, -1, 10); int b = lua_tointeger(L, -1); lua_pop(L, 1);
    839 
    840                 uint32_t color = vesa_rgb_to_color(r, g, b);
    841                 vesa_draw_triangle_fill(x0, y0, x1, y1, x2, y2, color);
    842                 break;
    843             }
    844 
    845             case OP_TEXT: {
    846                 /* {TEXT, x, y, text, r, g, b, scale} - scale is optional (default 1) */
    847                 lua_rawgeti(L, -1, 2); int x = lua_tointeger(L, -1); lua_pop(L, 1);
    848                 lua_rawgeti(L, -1, 3); int y = lua_tointeger(L, -1); lua_pop(L, 1);
    849                 lua_rawgeti(L, -1, 4); const char* text = lua_tostring(L, -1); lua_pop(L, 1);
    850                 lua_rawgeti(L, -1, 5); int r = lua_tointeger(L, -1); lua_pop(L, 1);
    851                 lua_rawgeti(L, -1, 6); int g = lua_tointeger(L, -1); lua_pop(L, 1);
    852                 lua_rawgeti(L, -1, 7); int b = lua_tointeger(L, -1); lua_pop(L, 1);
    853                 lua_rawgeti(L, -1, 8); int scale = lua_tointeger(L, -1); lua_pop(L, 1);
    854                 if (scale < 1) scale = 1;
    855 
    856                 uint32_t fg_color = vesa_rgb_to_color(r, g, b);
    857                 uint32_t bg_color = 0xFFFFFFFF;  /* Transparent background */
    858                 vesa_draw_string_scaled(x, y, text, fg_color, bg_color, scale);
    859                 break;
    860             }
    861 
    862             case OP_POLYGON:
    863             case OP_POLYGON_FILL: {
    864                 /* TODO: Implement polygon rendering if needed */
    865                 break;
    866             }
    867 
    868             case OP_IMAGE: {
    869                 /* {IMAGE, image_userdata, x, y, width, height, scale_mode} */
    870                 lua_rawgeti(L, -1, 2);
    871                 image_t* img = (image_t*)lua_touserdata(L, -1);
    872                 lua_pop(L, 1);
    873 
    874                 lua_rawgeti(L, -1, 3); int x = lua_tointeger(L, -1); lua_pop(L, 1);
    875                 lua_rawgeti(L, -1, 4); int y = lua_tointeger(L, -1); lua_pop(L, 1);
    876                 lua_rawgeti(L, -1, 5); int width = lua_tointeger(L, -1); lua_pop(L, 1);
    877                 lua_rawgeti(L, -1, 6); int height = lua_tointeger(L, -1); lua_pop(L, 1);
    878                 lua_rawgeti(L, -1, 7); int mode = lua_tointeger(L, -1); lua_pop(L, 1);
    879 
    880                 if (img) {
    881                     if (width > 0 && height > 0) {
    882                         image_draw_scaled(img, x, y, width, height, (scale_mode_t)mode);
    883                     } else {
    884                         image_draw(img, x, y);
    885                     }
    886                 }
    887                 break;
    888             }
    889 
    890             case OP_BUFFER: {
    891                 /* {BUFFER, buffer_string, x, y, width, height, src_x, src_y, src_w, src_h} */
    892                 /* Draws a raw BGRA buffer (from Lua Image) directly to the render target */
    893                 /* If src_x/y/w/h are provided, draws only that region of the buffer */
    894                 lua_rawgeti(L, -1, 2);
    895                 size_t buf_len;
    896                 const char* buf = lua_tolstring(L, -1, &buf_len);
    897                 lua_pop(L, 1);
    898 
    899                 lua_rawgeti(L, -1, 3); int dst_x = lua_tointeger(L, -1); lua_pop(L, 1);
    900                 lua_rawgeti(L, -1, 4); int dst_y = lua_tointeger(L, -1); lua_pop(L, 1);
    901                 lua_rawgeti(L, -1, 5); int buf_width = lua_tointeger(L, -1); lua_pop(L, 1);
    902                 lua_rawgeti(L, -1, 6); int buf_height = lua_tointeger(L, -1); lua_pop(L, 1);
    903                 lua_rawgeti(L, -1, 7); int src_x = lua_tointeger(L, -1); lua_pop(L, 1);
    904                 lua_rawgeti(L, -1, 8); int src_y = lua_tointeger(L, -1); lua_pop(L, 1);
    905                 lua_rawgeti(L, -1, 9); int src_w = lua_tointeger(L, -1); lua_pop(L, 1);
    906                 lua_rawgeti(L, -1, 10); int src_h = lua_tointeger(L, -1); lua_pop(L, 1);
    907 
    908                 if (!buf || buf_width <= 0 || buf_height <= 0) break;
    909 
    910                 /* Default to full buffer if src region not specified */
    911                 if (src_w <= 0) src_w = buf_width;
    912                 if (src_h <= 0) src_h = buf_height;
    913 
    914                 /* Calculate expected buffer size (4 bytes per pixel - BGRA) */
    915                 size_t expected_size = (size_t)buf_width * (size_t)buf_height * 4;
    916                 if (buf_len < expected_size) break;
    917 
    918                 /* Must be rendering to a window buffer (not screen) */
    919                 if (!current_render_target) break;
    920 
    921                 int target_width = current_render_target->width;
    922                 int target_height = current_render_target->height;
    923                 uint32_t* target_pixels = current_render_target->pixels;
    924 
    925                 /* Copy pixels from buffer to render target */
    926                 const uint8_t* src = (const uint8_t*)buf;
    927 
    928                 for (int row = 0; row < src_h; row++) {
    929                     int screen_y = dst_y + row;
    930                     int buf_row = src_y + row;
    931 
    932                     if (screen_y < 0 || screen_y >= target_height) continue;
    933                     if (buf_row < 0 || buf_row >= buf_height) continue;
    934 
    935                     for (int col = 0; col < src_w; col++) {
    936                         int screen_x = dst_x + col;
    937                         int buf_col = src_x + col;
    938 
    939                         if (screen_x < 0 || screen_x >= target_width) continue;
    940                         if (buf_col < 0 || buf_col >= buf_width) continue;
    941 
    942                         /* Get pixel from buffer (BGRA format) */
    943                         size_t buf_offset = ((size_t)buf_row * buf_width + buf_col) * 4;
    944                         uint8_t b = src[buf_offset];
    945                         uint8_t g = src[buf_offset + 1];
    946                         uint8_t r = src[buf_offset + 2];
    947                         /* uint8_t a = src[buf_offset + 3]; - alpha ignored for now */
    948 
    949                         /* Write to render target (ARGB format) */
    950                         uint32_t color = (0xFF << 24) | (r << 16) | (g << 8) | b;
    951                         target_pixels[screen_y * target_width + screen_x] = color;
    952                     }
    953                 }
    954                 break;
    955             }
    956         }
    957 
    958         lua_pop(L, 1);  /* Pop operation table */
    959     }
    960 
    961     return 0;
    962 }
    963 
    964 /* Draw a single character at position (x, y) with optional scaling */
    965 /* If bg_color is 0xFFFFFFFF, the background is transparent (only foreground pixels are drawn) */
    966 /* scale: 1 = 8x8, 2 = 16x16, 3 = 24x24, etc. */
    967 void vesa_draw_char_scaled(int x, int y, char c, uint32_t fg_color, uint32_t bg_color, int scale) {
    968     if (!vesa_state.active) return;
    969     if (scale < 1) scale = 1;
    970     if (scale > 8) scale = 8;  /* Limit max scale */
    971 
    972     /* Only support printable ASCII characters */
    973     if (c < 32 || c > 126) c = ' ';
    974 
    975     const uint8_t* glyph = font_8x8[c - 32];
    976     int transparent_bg = (bg_color == 0xFFFFFFFF);
    977 
    978     for (int row = 0; row < 8; row++) {
    979         uint8_t bits = glyph[row];
    980         for (int col = 0; col < 8; col++) {
    981             int is_fg = bits & (1 << col);
    982             if (is_fg || !transparent_bg) {
    983                 uint32_t color = is_fg ? fg_color : bg_color;
    984                 /* Draw scaled pixel (scale x scale block) */
    985                 for (int sy = 0; sy < scale; sy++) {
    986                     for (int sx = 0; sx < scale; sx++) {
    987                         vesa_draw_pixel(x + col * scale + sx, y + row * scale + sy, color);
    988                     }
    989                 }
    990             }
    991         }
    992     }
    993 }
    994 
    995 /* Draw a single character at position (x, y) - unscaled version */
    996 /* If bg_color is 0xFFFFFFFF, the background is transparent (only foreground pixels are drawn) */
    997 void vesa_draw_char(int x, int y, char c, uint32_t fg_color, uint32_t bg_color) {
    998     vesa_draw_char_scaled(x, y, c, fg_color, bg_color, 1);
    999 }
   1000 
   1001 /* Draw a string at position (x, y) with optional scaling */
   1002 void vesa_draw_string_scaled(int x, int y, const char* str, uint32_t fg_color, uint32_t bg_color, int scale) {
   1003     if (!vesa_state.active || !str) return;
   1004     if (scale < 1) scale = 1;
   1005 
   1006     int cur_x = x;
   1007     int cur_y = y;
   1008     int char_width = 8 * scale;
   1009     int char_height = 8 * scale;
   1010 
   1011     while (*str) {
   1012         if (*str == '\n') {
   1013             cur_x = x;
   1014             cur_y += char_height;
   1015         } else if (*str == '\t') {
   1016             cur_x += char_width * 4;  /* Tab = 4 spaces */
   1017         } else {
   1018             vesa_draw_char_scaled(cur_x, cur_y, *str, fg_color, bg_color, scale);
   1019             cur_x += char_width;
   1020         }
   1021         str++;
   1022     }
   1023 }
   1024 
   1025 /* Draw a string at position (x, y) - unscaled version */
   1026 void vesa_draw_string(int x, int y, const char* str, uint32_t fg_color, uint32_t bg_color) {
   1027     vesa_draw_string_scaled(x, y, str, fg_color, bg_color, 1);
   1028 }
   1029 
   1030 /* Lua binding: Draw text */
   1031 int lua_vesa_draw_text(lua_State* L) {
   1032     int x = luaL_checkinteger(L, 1);
   1033     int y = luaL_checkinteger(L, 2);
   1034     const char* text = luaL_checkstring(L, 3);
   1035     int r = luaL_optinteger(L, 4, 255);  /* Default white */
   1036     int g = luaL_optinteger(L, 5, 255);
   1037     int b = luaL_optinteger(L, 6, 255);
   1038     int bg_r = luaL_optinteger(L, 7, 0);  /* Default black background */
   1039     int bg_g = luaL_optinteger(L, 8, 0);
   1040     int bg_b = luaL_optinteger(L, 9, 0);
   1041 
   1042     uint32_t fg_color = vesa_rgb_to_color(r, g, b);
   1043     uint32_t bg_color = vesa_rgb_to_color(bg_r, bg_g, bg_b);
   1044 
   1045     vesa_draw_string(x, y, text, fg_color, bg_color);
   1046 
   1047     return 0;
   1048 }
   1049 
   1050 /* Set double buffering mode - if 0, draws directly to framebuffer */
   1051 int lua_vesa_set_double_buffer_mode(lua_State* L) {
   1052     int enabled = luaL_checkinteger(L, 1);
   1053     use_double_buffering = enabled ? 1 : 0;
   1054     return 0;
   1055 }
   1056 
   1057 /* Move a region in the screen buffer by dx, dy offset */
   1058 int lua_vesa_move_region(lua_State* L) {
   1059     int src_x = luaL_checkinteger(L, 1);
   1060     int src_y = luaL_checkinteger(L, 2);
   1061     int width = luaL_checkinteger(L, 3);
   1062     int height = luaL_checkinteger(L, 4);
   1063     int dx = luaL_checkinteger(L, 5);
   1064     int dy = luaL_checkinteger(L, 6);
   1065 
   1066     if (!vesa_state.framebuffer || vesa_state.current_mode.bpp != 32) {
   1067         return 0;
   1068     }
   1069 
   1070     uint8_t* buffer = use_double_buffering ? get_screen_buffer_ptr() : vesa_state.framebuffer;
   1071 
   1072     int dst_x = src_x + dx;
   1073     int dst_y = src_y + dy;
   1074 
   1075     /* Clamp to screen bounds */
   1076     if (src_x < 0 || src_y < 0 || width <= 0 || height <= 0) return 0;
   1077     if (src_x + width > (int)vesa_state.current_mode.width) width = vesa_state.current_mode.width - src_x;
   1078     if (src_y + height > (int)vesa_state.current_mode.height) height = vesa_state.current_mode.height - src_y;
   1079     if (dst_x < 0 || dst_y < 0) return 0;
   1080     if (dst_x + width > (int)vesa_state.current_mode.width) width = vesa_state.current_mode.width - dst_x;
   1081     if (dst_y + height > (int)vesa_state.current_mode.height) height = vesa_state.current_mode.height - dst_y;
   1082 
   1083     /* Use memmove for each scanline to avoid needing a temporary buffer */
   1084     /* Process from bottom to top if moving down, top to bottom if moving up */
   1085     int screen_width = vesa_state.current_mode.width;
   1086 
   1087     if (dy > 0) {
   1088         /* Moving down - copy from bottom to top to avoid overwriting */
   1089         for (int y = height - 1; y >= 0; y--) {
   1090             uint32_t src_offset = ((src_y + y) * screen_width + src_x) * 4;
   1091             uint32_t dst_offset = ((dst_y + y) * screen_width + dst_x) * 4;
   1092             memmove(buffer + dst_offset, buffer + src_offset, width * 4);
   1093         }
   1094     } else {
   1095         /* Moving up or sideways - copy from top to bottom */
   1096         for (int y = 0; y < height; y++) {
   1097             uint32_t src_offset = ((src_y + y) * screen_width + src_x) * 4;
   1098             uint32_t dst_offset = ((dst_y + y) * screen_width + dst_x) * 4;
   1099             memmove(buffer + dst_offset, buffer + src_offset, width * 4);
   1100         }
   1101     }
   1102 
   1103     return 0;
   1104 }
   1105 
   1106 
   1107 /* Create a window buffer */
   1108 int lua_vesa_create_window_buffer(lua_State* L) {
   1109     int width = luaL_checkinteger(L, 1);
   1110     int height = luaL_checkinteger(L, 2);
   1111 
   1112     if (width <= 0 || height <= 0) {
   1113         return luaL_error(L, "Invalid buffer dimensions");
   1114     }
   1115 
   1116     window_buffer_t* buffer = (window_buffer_t*)malloc(sizeof(window_buffer_t));
   1117     if (!buffer) {
   1118         return luaL_error(L, "Failed to allocate buffer structure");
   1119     }
   1120 
   1121     size_t pixel_bytes = width * height * sizeof(uint32_t);
   1122     buffer->pixels = (uint32_t*)malloc(pixel_bytes);
   1123     if (!buffer->pixels) {
   1124         free(buffer);
   1125         return luaL_error(L, "Failed to allocate pixel buffer");
   1126     }
   1127 
   1128     buffer->width = width;
   1129     buffer->height = height;
   1130     memset(buffer->pixels, 0, pixel_bytes);
   1131 
   1132     lua_pushlightuserdata(L, buffer);
   1133     return 1;
   1134 }
   1135 
   1136 /* Free a window buffer */
   1137 int lua_vesa_free_window_buffer(lua_State* L) {
   1138     window_buffer_t* buffer = (window_buffer_t*)lua_topointer(L, 1);
   1139     if (buffer) {
   1140         if (buffer->pixels) free(buffer->pixels);
   1141         free(buffer);
   1142     }
   1143     return 0;
   1144 }
   1145 
   1146 /* Blit window buffer to screen */
   1147 int lua_vesa_blit_window_buffer(lua_State* L) {
   1148     window_buffer_t* buffer = (window_buffer_t*)lua_topointer(L, 1);
   1149     int dst_x = luaL_checkinteger(L, 2);
   1150     int dst_y = luaL_checkinteger(L, 3);
   1151 
   1152     if (!buffer || !buffer->pixels || !vesa_state.active) return 0;
   1153 
   1154     uint8_t* screen_buf = use_double_buffering ? get_screen_buffer_ptr() : vesa_state.framebuffer;
   1155     if (!screen_buf) return 0;
   1156 
   1157     int screen_width = vesa_state.current_mode.width;
   1158     int screen_height = vesa_state.current_mode.height;
   1159 
   1160     int src_x = 0, src_y = 0;
   1161     int width = buffer->width;
   1162     int height = buffer->height;
   1163 
   1164     if (dst_x < 0) { src_x = -dst_x; width += dst_x; dst_x = 0; }
   1165     if (dst_y < 0) { src_y = -dst_y; height += dst_y; dst_y = 0; }
   1166     if (dst_x + width > screen_width) width = screen_width - dst_x;
   1167     if (dst_y + height > screen_height) height = screen_height - dst_y;
   1168 
   1169     if (width <= 0 || height <= 0) return 0;
   1170 
   1171     for (int y = 0; y < height; y++) {
   1172         uint32_t* src = buffer->pixels + (src_y + y) * buffer->width + src_x;
   1173         uint32_t* dst = (uint32_t*)(screen_buf + ((dst_y + y) * screen_width + dst_x) * 4);
   1174         memcpy(dst, src, width * sizeof(uint32_t));
   1175     }
   1176 
   1177     return 0;
   1178 }
   1179 
   1180 /* Blit a region of the window buffer to the screen buffer
   1181  * Args: buffer, dst_x, dst_y, region_x, region_y, region_w, region_h
   1182  * Only blits the specified region from the buffer to the screen
   1183  */
   1184 int lua_vesa_blit_window_buffer_region(lua_State* L) {
   1185     window_buffer_t* buffer = (window_buffer_t*)lua_topointer(L, 1);
   1186     int dst_x = luaL_checkinteger(L, 2);
   1187     int dst_y = luaL_checkinteger(L, 3);
   1188     int region_x = luaL_checkinteger(L, 4);
   1189     int region_y = luaL_checkinteger(L, 5);
   1190     int region_w = luaL_checkinteger(L, 6);
   1191     int region_h = luaL_checkinteger(L, 7);
   1192 
   1193     if (!buffer || !buffer->pixels || !vesa_state.active) return 0;
   1194 
   1195     uint8_t* screen_buf = use_double_buffering ? get_screen_buffer_ptr() : vesa_state.framebuffer;
   1196     if (!screen_buf) return 0;
   1197 
   1198     int screen_width = vesa_state.current_mode.width;
   1199     int screen_height = vesa_state.current_mode.height;
   1200 
   1201     /* Clip region to buffer bounds */
   1202     if (region_x < 0) { region_w += region_x; dst_x -= region_x; region_x = 0; }
   1203     if (region_y < 0) { region_h += region_y; dst_y -= region_y; region_y = 0; }
   1204     if (region_x + region_w > buffer->width) region_w = buffer->width - region_x;
   1205     if (region_y + region_h > buffer->height) region_h = buffer->height - region_y;
   1206 
   1207     /* Clip to screen bounds */
   1208     if (dst_x < 0) { region_x -= dst_x; region_w += dst_x; dst_x = 0; }
   1209     if (dst_y < 0) { region_y -= dst_y; region_h += dst_y; dst_y = 0; }
   1210     if (dst_x + region_w > screen_width) region_w = screen_width - dst_x;
   1211     if (dst_y + region_h > screen_height) region_h = screen_height - dst_y;
   1212 
   1213     if (region_w <= 0 || region_h <= 0) return 0;
   1214 
   1215     for (int y = 0; y < region_h; y++) {
   1216         uint32_t* src = buffer->pixels + (region_y + y) * buffer->width + region_x;
   1217         uint32_t* dst = (uint32_t*)(screen_buf + ((dst_y + y) * screen_width + dst_x) * 4);
   1218         memcpy(dst, src, region_w * sizeof(uint32_t));
   1219     }
   1220 
   1221     return 0;
   1222 }
   1223 
   1224 /* Blit cursor from Lua table buffer to screen with transparency
   1225  * Args: cursor_table (2D array of {r,g,b}), dst_x, dst_y, width, height
   1226  * Green (0,255,0) is treated as transparent
   1227  */
   1228 int lua_vesa_blit_cursor(lua_State* L) {
   1229     if (!lua_istable(L, 1)) return 0;
   1230 
   1231     int dst_x = luaL_checkinteger(L, 2);
   1232     int dst_y = luaL_checkinteger(L, 3);
   1233     int width = luaL_checkinteger(L, 4);
   1234     int height = luaL_checkinteger(L, 5);
   1235 
   1236     if (!vesa_state.active) return 0;
   1237 
   1238     /* Always draw directly to framebuffer for cursor (after buffer swap) */
   1239     uint8_t* screen_buf = vesa_state.framebuffer;
   1240     if (!screen_buf) return 0;
   1241 
   1242     int screen_width = vesa_state.current_mode.width;
   1243     int screen_height = vesa_state.current_mode.height;
   1244 
   1245     /* Process each row */
   1246     for (int y = 0; y < height; y++) {
   1247         int screen_y = dst_y + y;
   1248         if (screen_y < 0 || screen_y >= screen_height) continue;
   1249 
   1250         /* Get row table from cursor_table[y+1] */
   1251         lua_pushinteger(L, y + 1);
   1252         lua_gettable(L, 1);
   1253 
   1254         if (!lua_istable(L, -1)) {
   1255             lua_pop(L, 1);
   1256             continue;
   1257         }
   1258 
   1259         /* Process each pixel in this row */
   1260         for (int x = 0; x < width; x++) {
   1261             int screen_x = dst_x + x;
   1262             if (screen_x < 0 || screen_x >= screen_width) continue;
   1263 
   1264             /* Get pixel table from row[x+1] */
   1265             lua_pushinteger(L, x + 1);
   1266             lua_gettable(L, -2);
   1267 
   1268             if (lua_istable(L, -1)) {
   1269                 /* Get r, g, b from pixel table */
   1270                 lua_rawgeti(L, -1, 1);
   1271                 lua_rawgeti(L, -2, 2);
   1272                 lua_rawgeti(L, -3, 3);
   1273 
   1274                 int r = lua_tointeger(L, -3);
   1275                 int g = lua_tointeger(L, -2);
   1276                 int b = lua_tointeger(L, -1);
   1277 
   1278                 lua_pop(L, 3);  /* Pop r, g, b */
   1279 
   1280                 /* Skip transparent pixels (green = 0,255,0) */
   1281                 if (!(r == 0 && g == 255 && b == 0)) {
   1282                     uint32_t* dst = (uint32_t*)(screen_buf + (screen_y * screen_width + screen_x) * 4);
   1283                     *dst = (r << 16) | (g << 8) | b;
   1284                 }
   1285             }
   1286 
   1287             lua_pop(L, 1);  /* Pop pixel table */
   1288         }
   1289 
   1290         lua_pop(L, 1);  /* Pop row table */
   1291     }
   1292 
   1293     return 0;
   1294 }
   1295 
   1296 /* Cursor background save/restore system for flicker-free cursor */
   1297 #define CURSOR_MAX_SIZE 32
   1298 static uint32_t cursor_background[CURSOR_MAX_SIZE * CURSOR_MAX_SIZE];
   1299 static int cursor_bg_x = -1;
   1300 static int cursor_bg_y = -1;
   1301 static int cursor_bg_w = 0;
   1302 static int cursor_bg_h = 0;
   1303 static int cursor_bg_valid = 0;
   1304 
   1305 /* Cached cursor image (converted from Lua table once, used many times) */
   1306 static uint32_t cursor_image[CURSOR_MAX_SIZE * CURSOR_MAX_SIZE];
   1307 static int cursor_image_w = 0;
   1308 static int cursor_image_h = 0;
   1309 static int cursor_image_valid = 0;
   1310 #define CURSOR_TRANSPARENT 0x00FF00  /* Green = transparent */
   1311 
   1312 /* Save the area under where the cursor will be drawn */
   1313 static void save_cursor_background(int x, int y, int w, int h) {
   1314     if (!vesa_state.active || !vesa_state.framebuffer) return;
   1315     if (w > CURSOR_MAX_SIZE) w = CURSOR_MAX_SIZE;
   1316     if (h > CURSOR_MAX_SIZE) h = CURSOR_MAX_SIZE;
   1317 
   1318     int screen_width = vesa_state.current_mode.width;
   1319     int screen_height = vesa_state.current_mode.height;
   1320     uint8_t* fb = vesa_state.framebuffer;
   1321 
   1322     cursor_bg_x = x;
   1323     cursor_bg_y = y;
   1324     cursor_bg_w = w;
   1325     cursor_bg_h = h;
   1326 
   1327     for (int row = 0; row < h; row++) {
   1328         int sy = y + row;
   1329         if (sy < 0 || sy >= screen_height) continue;
   1330 
   1331         for (int col = 0; col < w; col++) {
   1332             int sx = x + col;
   1333             if (sx < 0 || sx >= screen_width) {
   1334                 cursor_background[row * CURSOR_MAX_SIZE + col] = 0;
   1335                 continue;
   1336             }
   1337 
   1338             uint32_t* src = (uint32_t*)(fb + (sy * screen_width + sx) * 4);
   1339             cursor_background[row * CURSOR_MAX_SIZE + col] = *src;
   1340         }
   1341     }
   1342     cursor_bg_valid = 1;
   1343 }
   1344 
   1345 /* Helper: restore a rectangular region from the saved background buffer */
   1346 static void restore_rect_from_bg(int rect_x, int rect_y, int rect_w, int rect_h) {
   1347     if (rect_w <= 0 || rect_h <= 0) return;
   1348 
   1349     int screen_width = vesa_state.current_mode.width;
   1350     int screen_height = vesa_state.current_mode.height;
   1351     uint8_t* fb = vesa_state.framebuffer;
   1352 
   1353     for (int row = 0; row < rect_h; row++) {
   1354         int sy = rect_y + row;
   1355         if (sy < 0 || sy >= screen_height) continue;
   1356 
   1357         /* Calculate offset into our saved background buffer */
   1358         int bg_row = sy - cursor_bg_y;
   1359         int bg_col_start = rect_x - cursor_bg_x;
   1360         int row_width = rect_w;
   1361 
   1362         if (bg_row < 0 || bg_row >= cursor_bg_h) continue;
   1363 
   1364         /* Clamp to background buffer bounds */
   1365         int bg_col = bg_col_start;
   1366         int sx = rect_x;
   1367         if (bg_col < 0) {
   1368             row_width += bg_col;
   1369             sx -= bg_col;
   1370             bg_col = 0;
   1371         }
   1372         if (bg_col + row_width > cursor_bg_w) {
   1373             row_width = cursor_bg_w - bg_col;
   1374         }
   1375         if (row_width <= 0) continue;
   1376 
   1377         /* Clamp to screen bounds */
   1378         if (sx < 0) {
   1379             bg_col -= sx;
   1380             row_width += sx;
   1381             sx = 0;
   1382         }
   1383         if (sx + row_width > screen_width) {
   1384             row_width = screen_width - sx;
   1385         }
   1386         if (row_width <= 0) continue;
   1387 
   1388         /* Copy the row */
   1389         uint32_t* src = &cursor_background[bg_row * CURSOR_MAX_SIZE + bg_col];
   1390         uint32_t* dst = (uint32_t*)(fb + (sy * screen_width + sx) * 4);
   1391         memcpy(dst, src, row_width * sizeof(uint32_t));
   1392     }
   1393 }
   1394 
   1395 /* Restore the area under the old cursor position, but only regions not covered by new cursor.
   1396  * When cursor moves from old to new position, we restore up to 2 rectangular strips:
   1397  * - Horizontal strip (rows not covered by new position)
   1398  * - Vertical strip (columns not covered by new position, excluding already restored rows)
   1399  */
   1400 static void restore_cursor_background_partial(int new_x, int new_y, int new_w, int new_h) {
   1401     if (!cursor_bg_valid || !vesa_state.active || !vesa_state.framebuffer) return;
   1402 
   1403     /* Old cursor bounds */
   1404     int old_x1 = cursor_bg_x;
   1405     int old_y1 = cursor_bg_y;
   1406     int old_x2 = cursor_bg_x + cursor_bg_w;
   1407     int old_y2 = cursor_bg_y + cursor_bg_h;
   1408 
   1409     /* New cursor bounds */
   1410     int new_x1 = new_x;
   1411     int new_y1 = new_y;
   1412     int new_x2 = new_x + new_w;
   1413     int new_y2 = new_y + new_h;
   1414 
   1415     /* Check if rectangles overlap at all */
   1416     int overlap = !(new_x2 <= old_x1 || new_x1 >= old_x2 || new_y2 <= old_y1 || new_y1 >= old_y2);
   1417 
   1418     if (!overlap) {
   1419         /* No overlap - restore entire old region */
   1420         restore_rect_from_bg(old_x1, old_y1, cursor_bg_w, cursor_bg_h);
   1421     } else {
   1422         /* There's overlap - restore the L-shaped exposed region as 2 rectangles */
   1423 
   1424         /* Horizontal strip: rows that are completely outside the new cursor Y range */
   1425         /* Top strip: old rows above new cursor */
   1426         if (old_y1 < new_y1) {
   1427             restore_rect_from_bg(old_x1, old_y1, cursor_bg_w, new_y1 - old_y1);
   1428         }
   1429         /* Bottom strip: old rows below new cursor */
   1430         if (old_y2 > new_y2) {
   1431             restore_rect_from_bg(old_x1, new_y2, cursor_bg_w, old_y2 - new_y2);
   1432         }
   1433 
   1434         /* Vertical strip: columns outside new cursor X range, but only for overlapping Y rows */
   1435         int overlap_y1 = (old_y1 > new_y1) ? old_y1 : new_y1;
   1436         int overlap_y2 = (old_y2 < new_y2) ? old_y2 : new_y2;
   1437 
   1438         if (overlap_y1 < overlap_y2) {
   1439             /* Left strip: old columns to the left of new cursor */
   1440             if (old_x1 < new_x1) {
   1441                 restore_rect_from_bg(old_x1, overlap_y1, new_x1 - old_x1, overlap_y2 - overlap_y1);
   1442             }
   1443             /* Right strip: old columns to the right of new cursor */
   1444             if (old_x2 > new_x2) {
   1445                 restore_rect_from_bg(new_x2, overlap_y1, old_x2 - new_x2, overlap_y2 - overlap_y1);
   1446             }
   1447         }
   1448     }
   1449 
   1450     cursor_bg_valid = 0;
   1451 }
   1452 
   1453 /* Restore entire cursor background (when cursor hides) */
   1454 static void restore_cursor_background_full(void) {
   1455     if (!cursor_bg_valid || !vesa_state.active || !vesa_state.framebuffer) return;
   1456 
   1457     int screen_width = vesa_state.current_mode.width;
   1458     int screen_height = vesa_state.current_mode.height;
   1459     uint8_t* fb = vesa_state.framebuffer;
   1460 
   1461     for (int row = 0; row < cursor_bg_h; row++) {
   1462         int sy = cursor_bg_y + row;
   1463         if (sy < 0 || sy >= screen_height) continue;
   1464 
   1465         for (int col = 0; col < cursor_bg_w; col++) {
   1466             int sx = cursor_bg_x + col;
   1467             if (sx < 0 || sx >= screen_width) continue;
   1468 
   1469             uint32_t* dst = (uint32_t*)(fb + (sy * screen_width + sx) * 4);
   1470             *dst = cursor_background[row * CURSOR_MAX_SIZE + col];
   1471         }
   1472     }
   1473 
   1474     cursor_bg_valid = 0;
   1475 }
   1476 
   1477 /* Cache the cursor image from a Lua table (call when cursor changes) */
   1478 int lua_vesa_cache_cursor(lua_State* L) {
   1479     if (!lua_istable(L, 1)) return 0;
   1480 
   1481     int width = luaL_checkinteger(L, 2);
   1482     int height = luaL_checkinteger(L, 3);
   1483 
   1484     if (width > CURSOR_MAX_SIZE) width = CURSOR_MAX_SIZE;
   1485     if (height > CURSOR_MAX_SIZE) height = CURSOR_MAX_SIZE;
   1486 
   1487     cursor_image_w = width;
   1488     cursor_image_h = height;
   1489 
   1490     /* Convert Lua table to flat pixel array */
   1491     for (int y = 0; y < height; y++) {
   1492         lua_pushinteger(L, y + 1);
   1493         lua_gettable(L, 1);
   1494 
   1495         if (!lua_istable(L, -1)) {
   1496             /* Fill row with transparent */
   1497             for (int x = 0; x < width; x++) {
   1498                 cursor_image[y * CURSOR_MAX_SIZE + x] = CURSOR_TRANSPARENT;
   1499             }
   1500             lua_pop(L, 1);
   1501             continue;
   1502         }
   1503 
   1504         for (int x = 0; x < width; x++) {
   1505             lua_pushinteger(L, x + 1);
   1506             lua_gettable(L, -2);
   1507 
   1508             if (lua_istable(L, -1)) {
   1509                 lua_rawgeti(L, -1, 1);
   1510                 lua_rawgeti(L, -2, 2);
   1511                 lua_rawgeti(L, -3, 3);
   1512 
   1513                 int r = lua_tointeger(L, -3);
   1514                 int g = lua_tointeger(L, -2);
   1515                 int b = lua_tointeger(L, -1);
   1516 
   1517                 lua_pop(L, 3);
   1518 
   1519                 cursor_image[y * CURSOR_MAX_SIZE + x] = (r << 16) | (g << 8) | b;
   1520             } else {
   1521                 cursor_image[y * CURSOR_MAX_SIZE + x] = CURSOR_TRANSPARENT;
   1522             }
   1523 
   1524             lua_pop(L, 1);
   1525         }
   1526 
   1527         lua_pop(L, 1);
   1528     }
   1529 
   1530     cursor_image_valid = 1;
   1531     return 0;
   1532 }
   1533 
   1534 /* Draw cursor using cached image - much faster than reading Lua table every frame */
   1535 static void draw_cursor_from_cache(int dst_x, int dst_y) {
   1536     if (!cursor_image_valid || !vesa_state.active || !vesa_state.framebuffer) return;
   1537 
   1538     uint8_t* fb = vesa_state.framebuffer;
   1539     int screen_width = vesa_state.current_mode.width;
   1540     int screen_height = vesa_state.current_mode.height;
   1541 
   1542     for (int y = 0; y < cursor_image_h; y++) {
   1543         int screen_y = dst_y + y;
   1544         if (screen_y < 0 || screen_y >= screen_height) continue;
   1545 
   1546         for (int x = 0; x < cursor_image_w; x++) {
   1547             int screen_x = dst_x + x;
   1548             if (screen_x < 0 || screen_x >= screen_width) continue;
   1549 
   1550             uint32_t pixel = cursor_image[y * CURSOR_MAX_SIZE + x];
   1551 
   1552             /* Skip transparent pixels */
   1553             if (pixel != CURSOR_TRANSPARENT) {
   1554                 uint32_t* dst = (uint32_t*)(fb + (screen_y * screen_width + screen_x) * 4);
   1555                 *dst = pixel;
   1556             }
   1557         }
   1558     }
   1559 }
   1560 
   1561 /* Move cursor: save new background, restore L-shaped region, draw at new position
   1562  * Args: old_x, old_y, new_x, new_y
   1563  * The cursor size comes from the cached cursor image
   1564  *
   1565  * IMPORTANT: Order of operations matters!
   1566  * 1. Save what's currently at the NEW position (this includes the old cursor if overlapping)
   1567  * 2. Restore the L-shaped exposed region from OLD saved background
   1568  * 3. Re-save the NEW position (now with restored background in overlap area)
   1569  * 4. Draw cursor at NEW position
   1570  */
   1571 int lua_vesa_move_cursor(lua_State* L) {
   1572     int old_x = luaL_checkinteger(L, 1);
   1573     int old_y = luaL_checkinteger(L, 2);
   1574     int new_x = luaL_checkinteger(L, 3);
   1575     int new_y = luaL_checkinteger(L, 4);
   1576 
   1577     if (!vesa_state.active || !cursor_image_valid) return 0;
   1578 
   1579     uint8_t* fb = vesa_state.framebuffer;
   1580     if (!fb) return 0;
   1581 
   1582     int screen_width = vesa_state.current_mode.width;
   1583     int screen_height = vesa_state.current_mode.height;
   1584     int w = cursor_image_w;
   1585     int h = cursor_image_h;
   1586 
   1587     /* Old and new cursor bounds */
   1588     int old_x1 = old_x, old_y1 = old_y;
   1589     int old_x2 = old_x + w, old_y2 = old_y + h;
   1590     int new_x1 = new_x, new_y1 = new_y;
   1591     int new_x2 = new_x + w, new_y2 = new_y + h;
   1592 
   1593     (void)new_x1; (void)new_y1; (void)new_x2; (void)new_y2; /* Suppress unused warnings */
   1594 
   1595     /* Step 1: Restore ENTIRE old cursor area from SCREEN BUFFER
   1596      * The buffer copy skipped the NEW cursor region, so the old cursor area
   1597      * (except where it overlaps with new) still has old cursor pixels.
   1598      * We restore the whole old area, then draw new cursor on top. */
   1599     extern uint8_t* get_screen_buffer_ptr(void);
   1600     uint8_t* screen_buf = get_screen_buffer_ptr();
   1601 
   1602     if (screen_buf) {
   1603         /* Restore entire old cursor region from screen buffer */
   1604         for (int sy = old_y1; sy < old_y2; sy++) {
   1605             if (sy < 0 || sy >= screen_height) continue;
   1606             for (int sx = old_x1; sx < old_x2; sx++) {
   1607                 if (sx < 0 || sx >= screen_width) continue;
   1608                 uint32_t* src = (uint32_t*)(screen_buf + (sy * screen_width + sx) * 4);
   1609                 uint32_t* dst = (uint32_t*)(fb + (sy * screen_width + sx) * 4);
   1610                 *dst = *src;
   1611             }
   1612         }
   1613     }
   1614 
   1615     /* Step 2: Save background at new position (after L-shape restore) */
   1616     save_cursor_background(new_x, new_y, w, h);
   1617 
   1618     /* Step 3: Draw cursor at new position */
   1619     draw_cursor_from_cache(new_x, new_y);
   1620 
   1621     return 0;
   1622 }
   1623 
   1624 /* Draw cursor at position (for initial draw, no old position to restore) */
   1625 int lua_vesa_draw_cursor(lua_State* L) {
   1626     int dst_x = luaL_checkinteger(L, 1);
   1627     int dst_y = luaL_checkinteger(L, 2);
   1628 
   1629     if (!vesa_state.active || !cursor_image_valid) return 0;
   1630 
   1631     /* Save background and draw */
   1632     save_cursor_background(dst_x, dst_y, cursor_image_w, cursor_image_h);
   1633     draw_cursor_from_cache(dst_x, dst_y);
   1634 
   1635     return 0;
   1636 }
   1637 
   1638 /* Restore cursor background fully (for hiding cursor) */
   1639 int lua_vesa_restore_cursor(lua_State* L) {
   1640     (void)L;
   1641     restore_cursor_background_full();
   1642     return 0;
   1643 }
   1644 
   1645 /* Set render target for drawing operations */
   1646 int lua_vesa_set_render_target(lua_State* L) {
   1647     if (lua_isnil(L, 1)) {
   1648         /* nil = render to screen */
   1649         current_render_target = NULL;
   1650     } else {
   1651         /* lightuserdata = render to window buffer */
   1652         /* Use lua_topointer for lightuserdata (lua_touserdata doesn't work) */
   1653         current_render_target = (window_buffer_t*)lua_topointer(L, 1);
   1654     }
   1655     return 0;
   1656 }
   1657 
   1658 /* Inspect window buffer pixels (for debugging) */
   1659 int lua_vesa_inspect_buffer(lua_State* L) {
   1660     window_buffer_t* buffer = (window_buffer_t*)lua_topointer(L, 1);
   1661     int x = luaL_checkinteger(L, 2);
   1662     int y = luaL_checkinteger(L, 3);
   1663 
   1664     if (!buffer || !buffer->pixels) {
   1665         lua_pushnil(L);
   1666         return 1;
   1667     }
   1668 
   1669     if (x < 0 || x >= buffer->width || y < 0 || y >= buffer->height) {
   1670         lua_pushnil(L);
   1671         return 1;
   1672     }
   1673 
   1674     uint32_t pixel = buffer->pixels[y * buffer->width + x];
   1675 
   1676     /* Return RGBA values */
   1677     lua_pushinteger(L, (pixel >> 16) & 0xFF);  /* R */
   1678     lua_pushinteger(L, (pixel >> 8) & 0xFF);   /* G */
   1679     lua_pushinteger(L, pixel & 0xFF);          /* B */
   1680     lua_pushinteger(L, (pixel >> 24) & 0xFF);  /* A */
   1681 
   1682     return 4;
   1683 }
   1684