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