Atlas - SDL_dosframebuffer.c

Home / ext / SDL / src / video / dos Lines: 1 | Size: 40567 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2026 Sam Lantinga <[email protected]> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20*/ 21#include "SDL_internal.h" 22 23#ifdef SDL_VIDEO_DRIVER_DOSVESA 24 25#include "../../SDL_properties_c.h" 26#include "../../events/SDL_mouse_c.h" 27#include "../SDL_sysvideo.h" 28#include "SDL_dosframebuffer_c.h" 29#include "SDL_dosmodes.h" 30#include "SDL_dosmouse.h" 31#include "SDL_dosvideo.h" 32 33#include <pc.h> // for inportb, outportb 34#include <sys/movedata.h> // for dosmemput (banked framebuffer writes) 35 36// note that DOS_SURFACE's value is the same string that the dummy driver uses. 37#define DOS_SURFACE "SDL.internal.window.surface" 38 39// Consolidated framebuffer state (DOS has only one window) 40typedef struct DOSFramebufferState 41{ 42 SDL_Surface *surface; // system-RAM surface (app renders here) 43 SDL_Surface *lfb_surface; // LFB surface (pixels in VRAM), NULL for direct-FB path 44 bool direct_fb; // true when the fast direct-FB hint path is active 45 bool use_dosmemput; // true = dosmemput (banked), false = nearptr (LFB) 46 int vram_pitch; 47 int vram_w; 48 int vram_h; 49 int bpp; // bytes per pixel 50 51 // dosmemput path state (use_dosmemput) 52 Uint32 vram_phys; // base address for dosmemput (e.g. 0xA0000) 53 bool banked_multibank; // true if fb doesn't fit in one bank window 54 Uint32 win_gran_bytes; // bank granularity in bytes 55 Uint32 win_size_bytes; // bank window size in bytes 56 Uint32 win_base; // window base address (segment << 4) 57 Uint32 win_func_ptr; // real-mode far pointer to bank-switch function 58 59 // nearptr path state (use_dosmemput == false) 60 Uint8 *vram_ptr; // nearptr into VRAM (LFB) 61 62 // Cached per-frame values (set at create time, avoid repeated lookups) 63 Uint8 *pixels; // == surface->pixels (stable after create) 64 int src_pitch; // == surface->pitch 65 Uint32 fb_size; // total framebuffer bytes (pitch * h) for full-surface fast path 66 bool pitches_match; // src_pitch == vram_pitch (enables single-copy fast path) 67} DOSFramebufferState; 68 69static DOSFramebufferState fb_state; 70 71// Convert cursor to the current format. 72static SDL_Surface *GetConvertedCursorSurface(SDL_CursorData *curdata, SDL_Surface *dst) 73{ 74 SDL_Palette *pal = SDL_GetSurfacePalette(dst); 75 if (!pal) { 76 return NULL; 77 } 78 79 Uint32 pal_version = pal ? pal->version : 0; 80 81 if (curdata->converted_surface && 82 curdata->converted_surface->format == dst->format && 83 curdata->converted_palette_version == pal_version) { 84 return curdata->converted_surface; 85 } 86 87 SDL_DestroySurface(curdata->converted_surface); 88 curdata->converted_surface = NULL; 89 90 SDL_Surface *src = curdata->surface; 91 SDL_assert(src->format == SDL_PIXELFORMAT_ARGB8888); 92 93 int w = src->w; 94 int h = src->h; 95 SDL_Surface *conv = SDL_CreateSurface(w, h, dst->format); 96 if (!conv) { 97 return NULL; 98 } 99 100 // Copy the destination palette. 101 SDL_Palette *conv_pal = SDL_CreateSurfacePalette(conv); 102 if (conv_pal) { 103 SDL_SetPaletteColors(conv_pal, pal->colors, 0, pal->ncolors); 104 } 105 106 // Track which palette indices are used by opaque pixels. 107 bool used[256]; 108 SDL_memset(used, 0, sizeof(used)); 109 110 // First pass: blit with BLENDMODE_NONE to get raw color-matched indices. 111 SDL_SetSurfaceBlendMode(src, SDL_BLENDMODE_NONE); 112 SDL_BlitSurface(src, NULL, conv, NULL); 113 SDL_SetSurfaceBlendMode(src, SDL_BLENDMODE_BLEND); 114 115 // Mark which indices are used by non-transparent source pixels. 116 for (int y = 0; y < h; y++) { 117 const Uint32 *srcrow = (const Uint32 *)((const Uint8 *)src->pixels + y * src->pitch); 118 const Uint8 *convrow = (const Uint8 *)conv->pixels + y * conv->pitch; 119 for (int x = 0; x < w; x++) { 120 Uint8 srcA = (Uint8)(srcrow[x] >> 24); 121 if (srcA > 0) { 122 used[convrow[x]] = true; 123 } 124 } 125 } 126 127 // Find an unused index for the colorkey. 128 Uint32 colorkey = 0; 129 for (int i = 0; i < 256; i++) { 130 if (!used[i]) { 131 colorkey = (Uint32)i; 132 break; 133 } 134 } 135 136 // Second pass: set transparent pixels to the colorkey index. 137 for (int y = 0; y < h; y++) { 138 const Uint32 *srcrow = (const Uint32 *)((const Uint8 *)src->pixels + y * src->pitch); 139 Uint8 *convrow = (Uint8 *)conv->pixels + y * conv->pitch; 140 for (int x = 0; x < w; x++) { 141 Uint8 srcA = (Uint8)(srcrow[x] >> 24); 142 if (srcA == 0) { 143 convrow[x] = (Uint8)colorkey; 144 } 145 } 146 } 147 148 SDL_SetSurfaceColorKey(conv, true, colorkey); 149 SDL_SetSurfaceBlendMode(conv, SDL_BLENDMODE_NONE); 150 151 curdata->converted_surface = conv; 152 curdata->converted_palette_version = pal_version; 153 return conv; 154} 155 156// Invalidation (called from SetDisplayMode before freeing DPMI mapping) 157void DOSVESA_InvalidateCachedFramebuffer(void) 158{ 159 fb_state.direct_fb = false; 160 fb_state.vram_ptr = NULL; 161 fb_state.vram_phys = 0; 162 fb_state.use_dosmemput = false; 163 // Clear the LFB surface pointer so UpdateWindowFramebuffer won't try 164 // to blit into VRAM after the DPMI mapping has been freed. 165 fb_state.lfb_surface = NULL; 166} 167 168// Create a system-RAM surface (with a blank palette if INDEX8 and update the VGA DAC). 169static SDL_Surface *CreateSystemSurface(SDL_VideoData *data, int w, int h, SDL_PixelFormat surface_format) 170{ 171 SDL_Surface *surface = SDL_CreateSurface(w, h, surface_format); 172 if (!surface) { 173 return NULL; 174 } 175 176 // For 8-bit indexed modes, both surfaces need palettes. 177 // Share the same palette object so palette updates propagate to both. 178 if (surface_format == SDL_PIXELFORMAT_INDEX8) { 179 SDL_Palette *palette = SDL_CreateSurfacePalette(surface); 180 if (palette) { 181 // Initialize palette to all-black so that transitions start 182 // from black instead of flashing uninitialized (white) colors. 183 SDL_Color black[256]; 184 SDL_memset(black, 0, sizeof(black)); 185 for (int i = 0; i < 256; i++) { 186 black[i].a = SDL_ALPHA_OPAQUE; 187 } 188 SDL_SetPaletteColors(palette, black, 0, 256); 189 } 190 data->palette_version = 0; // force DAC update on first present 191 192 // Also program the VGA DAC to all-black right now, so no flash 193 // of stale/white palette colors before the first present. 194 outportb(VGA_DAC_WRITE_INDEX, 0); 195 for (int i = 0; i < 256; i++) { 196 outportb(VGA_DAC_DATA, 0); 197 outportb(VGA_DAC_DATA, 0); 198 outportb(VGA_DAC_DATA, 0); 199 } 200 } 201 202 return surface; 203} 204 205// Normal (non-direct-FB) path: system-RAM surface with optional LFB blit, 206// cursor compositing, palette sync, vsync, and page-flipping. 207static bool CreateNormalFramebuffer(SDL_VideoDevice *device, SDL_Window *window, 208 SDL_PixelFormat *format, void **pixels, int *pitch) 209{ 210 SDL_VideoData *data = device->internal; 211 const SDL_DisplayMode *mode = &data->current_mode; 212 const SDL_DisplayModeData *mdata = mode->internal; 213 const SDL_PixelFormat surface_format = mode->format; 214 int w, h; 215 216 SDL_GetWindowSizeInPixels(window, &w, &h); 217 218 SDL_Surface *surface = CreateSystemSurface(data, w, h, surface_format); 219 if (!surface) { 220 return false; 221 } 222 223 SDL_Surface *lfb_surface = NULL; 224 225 if (!data->banked_mode) { 226 // LFB path: Make a surface that uses video memory directly, ot let SDL do the blitting for us. 227 // Point the LFB surface at the back page for tear-free double-buffering. 228 int back_page = data->page_flip_available ? (1 - data->current_page) : 0; 229 void *lfb_pixels = (Uint8 *)DOS_PhysicalToLinear(data->mapping.address) + data->page_offset[back_page]; 230 lfb_surface = SDL_CreateSurfaceFrom(mode->w, mode->h, surface_format, lfb_pixels, mdata->pitch); 231 if (!lfb_surface) { 232 SDL_DestroySurface(surface); 233 return false; 234 } 235 fb_state.lfb_surface = lfb_surface; 236 237 // Share the palette so updates propagate to both surfaces. 238 SDL_Palette *src_palette = SDL_GetSurfacePalette(surface); 239 if (src_palette) { 240 SDL_SetSurfacePalette(lfb_surface, src_palette); 241 } 242 } 243 244 // clear the framebuffer completely, in case another window at a larger size was using this before us. 245 if (lfb_surface) { 246 SDL_ClearSurface(lfb_surface, 0.0f, 0.0f, 0.0f, 0.0f); 247 } 248 // (For banked mode, the framebuffer was already zeroed in DOSVESA_SetDisplayMode.) 249 250 // Save the info and return! 251 fb_state.surface = surface; 252 SDL_SetSurfaceProperty(SDL_GetWindowProperties(window), DOS_SURFACE, surface); 253 254 *format = surface_format; 255 *pixels = surface->pixels; 256 *pitch = surface->pitch; 257 return true; 258} 259 260bool DOSVESA_CreateWindowFramebuffer(SDL_VideoDevice *device, SDL_Window *window, SDL_PixelFormat *format, void **pixels, int *pitch) 261{ 262 SDL_zero(fb_state); 263 264 SDL_VideoData *data = device->internal; 265 const SDL_DisplayMode *mode = &data->current_mode; 266 const SDL_DisplayModeData *mdata = mode->internal; 267 const SDL_PixelFormat surface_format = mode->format; 268 int w, h; 269 270 // writing to video RAM shows up as the screen refreshes, done or not, and it might have a weird pitch, so give the app a buffer of system RAM. 271 SDL_GetWindowSizeInPixels(window, &w, &h); 272 273 // Try to set up fast path where UpdateWindowFramebuffer copies system-RAM directly to VRAM. 274 if (SDL_GetHintBoolean(SDL_HINT_DOS_ALLOW_DIRECT_FRAMEBUFFER, false)) { 275 int vram_pitch = mdata->pitch; 276 277 // Check if we have any usable VRAM access path (banked or LFB). 278 bool have_vram = false; 279 280 if (mdata->win_a_segment && mdata->win_size > 0 && 281 (mdata->win_a_attributes & VBE_WINATTR_USABLE) == VBE_WINATTR_USABLE) { 282 // Banked window available. Use dosmemput path (preferred). 283 have_vram = true; 284 } else if (!data->banked_mode && data->mapping.size) { 285 // LFB only, use nearptr fallback. 286 have_vram = true; 287 } 288 289 if (have_vram) { 290 SDL_Surface *surface = CreateSystemSurface(data, w, h, surface_format); 291 if (!surface) { 292 return false; 293 } 294 295 SDL_SetSurfaceProperty(SDL_GetWindowProperties(window), DOS_SURFACE, surface); 296 297 fb_state.surface = surface; 298 fb_state.direct_fb = true; 299 fb_state.vram_pitch = vram_pitch; 300 fb_state.vram_w = mdata->w; 301 fb_state.vram_h = mdata->h; 302 fb_state.bpp = SDL_BYTESPERPIXEL(surface_format); 303 fb_state.pixels = (Uint8 *)surface->pixels; 304 fb_state.src_pitch = surface->pitch; 305 fb_state.fb_size = (Uint32)vram_pitch * mdata->h; 306 fb_state.pitches_match = (surface->pitch == vram_pitch); 307 308 // Prefer dosmemput via the banked VGA window (0xA0000). Real 309 // hardware testing shows dosmemput is significantly faster 310 // than nearptr writes to DPMI-mapped LFB, even with bank 311 // switching overhead at higher resolutions. 312 // 313 // The banked window always maps to page 0, so page flipping is 314 // not possible through this path. We accept tearing: the app 315 // opted into the direct-FB hint for maximum throughput, and 316 // games like Quake never used page flipping for their software 317 // renderers anyway. 318 if (mdata->win_a_segment && mdata->win_size > 0 && 319 (mdata->win_a_attributes & VBE_WINATTR_USABLE) == VBE_WINATTR_USABLE) { 320 Uint32 win_bytes = (Uint32)mdata->win_size * 1024; 321 fb_state.use_dosmemput = true; 322 fb_state.vram_ptr = NULL; 323 fb_state.vram_phys = (Uint32)mdata->win_a_segment << 4; 324 fb_state.win_base = fb_state.vram_phys; 325 fb_state.win_gran_bytes = (Uint32)mdata->win_granularity * 1024; 326 fb_state.win_size_bytes = win_bytes; 327 fb_state.win_func_ptr = mdata->win_func_ptr; 328 fb_state.banked_multibank = (fb_state.fb_size > win_bytes); 329 } else if (!data->banked_mode && data->mapping.size) { 330 // Fallback: nearptr to LFB. 331 int back_page = data->page_flip_available ? (1 - data->current_page) : 0; 332 fb_state.vram_ptr = (Uint8 *)DOS_PhysicalToLinear(data->mapping.address) + data->page_offset[back_page]; 333 fb_state.vram_phys = 0; 334 fb_state.use_dosmemput = false; 335 fb_state.banked_multibank = false; 336 } else { 337 // No usable VRAM path. Shouldn't happen, but bail out. 338 SDL_DestroySurface(surface); 339 return CreateNormalFramebuffer(device, window, format, pixels, pitch); 340 } 341 *format = surface_format; 342 *pixels = surface->pixels; 343 *pitch = surface->pitch; 344 return true; 345 } 346 // else: couldn't get a direct pointer, fall through to normal path. 347 } 348 349 return CreateNormalFramebuffer(device, window, format, pixels, pitch); 350} 351 352bool DOSVESA_SetWindowFramebufferVSync(SDL_VideoDevice *device, SDL_Window *window, int vsync) 353{ 354 if (vsync < 0) { 355 return SDL_SetError("Unsupported vsync type"); 356 } 357 SDL_WindowData *data = window->internal; 358 data->framebuffer_vsync = vsync; 359 return true; 360} 361 362bool DOSVESA_GetWindowFramebufferVSync(SDL_VideoDevice *device, SDL_Window *window, int *vsync) 363{ 364 if (vsync) { 365 SDL_WindowData *data = window->internal; 366 *vsync = data->framebuffer_vsync; 367 } 368 return true; 369} 370 371// Switch the VGA bank window if needed. Returns without doing anything 372// if the requested bank is already active. 373SDL_FORCE_INLINE void SwitchBank(int bank, int *current_bank, Uint32 win_func_ptr) 374{ 375 if (bank == *current_bank) { 376 return; 377 } 378 __dpmi_regs regs; 379 SDL_zero(regs); 380 regs.x.bx = 0; // Window A 381 regs.x.dx = (Uint16)bank; 382 if (win_func_ptr) { 383 regs.x.cs = (Uint16)(win_func_ptr >> 16); 384 regs.x.ip = (Uint16)(win_func_ptr & 0xFFFF); 385 __dpmi_simulate_real_mode_procedure_retf(&regs); 386 } else { 387 regs.x.ax = 0x4F05; 388 __dpmi_int(0x10, &regs); 389 } 390 *current_bank = bank; 391} 392 393// Copy a contiguous byte range from system RAM to VRAM through the banked 394// window, switching banks as needed. 395SDL_FORCE_INLINE void BankedDosmemput(const Uint8 *src, Uint32 total_bytes, Uint32 dst_offset, 396 Uint32 win_gran_bytes, Uint32 win_size_bytes, 397 Uint32 win_base, Uint32 win_func_ptr, 398 int *current_bank) 399{ 400 Uint32 src_off = 0; 401 while (total_bytes > 0) { 402 int bank = (int)(dst_offset / win_gran_bytes); 403 Uint32 off_in_win = dst_offset % win_gran_bytes; 404 Uint32 avail = win_size_bytes - off_in_win; 405 Uint32 n = total_bytes; 406 if (n > avail) { 407 n = avail; 408 } 409 SwitchBank(bank, current_bank, win_func_ptr); 410 dosmemput(src + src_off, n, win_base + off_in_win); 411 src_off += n; 412 dst_offset += n; 413 total_bytes -= n; 414 } 415} 416 417// Bank-switched copy of a rectangular region from a system RAM surface to the 418// VGA banked window. `src_rect` is in source-surface coordinates; `dst_x` and 419// `dst_y` give the top-left of the *surface's* position on screen (the 420// centering offset). The routine handles bank boundaries correctly even when a 421// scanline spans two banks. 422// 423// `current_bank` is an in/out parameter so that consecutive calls (one per dirty 424// rect) can avoid redundant bank switches when rects happen to fall in the same 425// bank. Initialise it to -1 before the first call. 426static void BankedFramebufferCopyRect(const SDL_DisplayModeData *mdata, 427 const SDL_Surface *src, 428 const SDL_Rect *src_rect, 429 int dst_x, int dst_y, 430 Uint32 win_gran_bytes, 431 Uint32 win_size_bytes, 432 Uint32 win_base, 433 int *current_bank, 434 Uint32 win_func_ptr) 435{ 436 const Uint16 dst_pitch = mdata->pitch; 437 const int bytes_per_pixel = SDL_BYTESPERPIXEL(src->format); 438 const int row_bytes = src_rect->w * bytes_per_pixel; 439 440 // Fast path: if the source row width matches src pitch AND the destination 441 // row width matches dst pitch, the data is contiguous in both source and 442 // destination — we can copy it as one flat block, minimizing dosmemput calls. 443 if (row_bytes == src->pitch && row_bytes == dst_pitch) { 444 const Uint8 *src_data = (const Uint8 *)src->pixels + src_rect->y * src->pitch + src_rect->x * bytes_per_pixel; 445 Uint32 dst_offset = (Uint32)(dst_y + src_rect->y) * dst_pitch + (Uint32)(dst_x + src_rect->x) * bytes_per_pixel; 446 BankedDosmemput(src_data, (Uint32)(row_bytes * src_rect->h), dst_offset, 447 win_gran_bytes, win_size_bytes, win_base, win_func_ptr, current_bank); 448 return; 449 } 450 451 for (int y = 0; y < src_rect->h; y++) { 452 const Uint8 *src_row = (const Uint8 *)src->pixels + (src_rect->y + y) * src->pitch + src_rect->x * bytes_per_pixel; 453 Uint32 dst_offset = (Uint32)(dst_y + src_rect->y + y) * dst_pitch + (Uint32)(dst_x + src_rect->x) * bytes_per_pixel; 454 BankedDosmemput(src_row, (Uint32)row_bytes, dst_offset, 455 win_gran_bytes, win_size_bytes, win_base, win_func_ptr, current_bank); 456 } 457} 458 459static void WaitForVBlank(void) 460{ 461 while (inportb(VGA_STATUS_PORT) & VGA_STATUS_VBLANK) { 462 SDL_CPUPauseInstruction(); 463 } 464 while (!(inportb(VGA_STATUS_PORT) & VGA_STATUS_VBLANK)) { 465 SDL_CPUPauseInstruction(); 466 } 467} 468 469static void ProgramVGADAC(SDL_Palette *palette) 470{ 471 outportb(VGA_DAC_WRITE_INDEX, 0); 472 for (int i = 0; i < palette->ncolors && i < 256; i++) { 473 outportb(VGA_DAC_DATA, palette->colors[i].r >> 2); 474 outportb(VGA_DAC_DATA, palette->colors[i].g >> 2); 475 outportb(VGA_DAC_DATA, palette->colors[i].b >> 2); 476 } 477} 478 479bool DOSVESA_UpdateWindowFramebuffer(SDL_VideoDevice *device, SDL_Window *window, const SDL_Rect *rects, int numrects) 480{ 481 SDL_VideoData *vdata = device->internal; 482 483 // =================================================================== 484 // Direct-FB fast path: copy system-RAM to VRAM, program palette, 485 // optionally page-flip. No cursor compositing, no SDL_BlitSurface. 486 // 487 // This path prefers banked dosmemput (through the VGA window at 488 // 0xA0000) over nearptr writes to the DPMI-mapped LFB. Real 489 // hardware testing on multiple NVIDIA cards showed dosmemput is 490 // significantly faster, even with bank-switching overhead at 491 // higher resolutions. The nearptr LFB path is only used as a 492 // fallback if no usable banked window is available. 493 // =================================================================== 494 if (fb_state.direct_fb && (fb_state.vram_ptr || fb_state.vram_phys)) { 495 496 // Palette update. 497 if (window->surface) { 498 SDL_Palette *pal = window->surface->palette; 499 if (pal && pal->version != vdata->palette_version) { 500 vdata->palette_version = pal->version; 501 ProgramVGADAC(pal); 502 } 503 } 504 505 if (fb_state.use_dosmemput) { 506 // dosmemput path (banked window, possibly multi-bank) 507 const int src_pitch = fb_state.src_pitch; 508 const int dst_pitch = fb_state.vram_pitch; 509 const Uint8 *pixels = fb_state.pixels; 510 511 if (!fb_state.banked_multibank) { 512 // Single-bank: entire FB fits in one window (e.g. mode 13h). 513 // Full-surface fast path when pitches match. 514 if (fb_state.pitches_match) { 515 dosmemput(pixels, fb_state.fb_size, fb_state.vram_phys); 516 } else { 517 for (int i = 0; i < numrects; i++) { 518 int rx = rects[i].x, ry = rects[i].y; 519 int rw = rects[i].w, rh = rects[i].h; 520 if (rx < 0) { 521 rw += rx; 522 rx = 0; 523 } 524 if (ry < 0) { 525 rh += ry; 526 ry = 0; 527 } 528 if (rx + rw > fb_state.vram_w) { 529 rw = fb_state.vram_w - rx; 530 } 531 if (ry + rh > fb_state.vram_h) { 532 rh = fb_state.vram_h - ry; 533 } 534 if (rw <= 0 || rh <= 0) { 535 continue; 536 } 537 const int bx = rx * fb_state.bpp; 538 const int bw = rw * fb_state.bpp; 539 const Uint8 *sp = pixels + ry * src_pitch + bx; 540 Uint32 dst_off = fb_state.vram_phys + ry * dst_pitch + bx; 541 for (int row = 0; row < rh; row++) { 542 dosmemput(sp, bw, dst_off); 543 sp += src_pitch; 544 dst_off += dst_pitch; 545 } 546 } 547 } 548 } else { 549 // Multi-bank: bank-switch as needed. 550 const Uint32 gran = fb_state.win_gran_bytes; 551 const Uint32 wsize = fb_state.win_size_bytes; 552 const Uint32 wbase = fb_state.win_base; 553 const Uint32 wfunc = fb_state.win_func_ptr; 554 int current_bank = -1; 555 556 if (fb_state.pitches_match) { 557 // Contiguous: stream the entire framebuffer through banks. 558 BankedDosmemput(pixels, fb_state.fb_size, 0, 559 gran, wsize, wbase, wfunc, &current_bank); 560 } else { 561 // Per-rect with bank switching. 562 for (int i = 0; i < numrects; i++) { 563 int rx = rects[i].x, ry = rects[i].y; 564 int rw = rects[i].w, rh = rects[i].h; 565 if (rx < 0) { 566 rw += rx; 567 rx = 0; 568 } 569 if (ry < 0) { 570 rh += ry; 571 ry = 0; 572 } 573 if (rx + rw > fb_state.vram_w) { 574 rw = fb_state.vram_w - rx; 575 } 576 if (ry + rh > fb_state.vram_h) { 577 rh = fb_state.vram_h - ry; 578 } 579 if (rw <= 0 || rh <= 0) { 580 continue; 581 } 582 const int bx = rx * fb_state.bpp; 583 const int bw = rw * fb_state.bpp; 584 585 for (int row = 0; row < rh; row++) { 586 const Uint8 *sp = pixels + (ry + row) * src_pitch + bx; 587 Uint32 dst_offset = (Uint32)(ry + row) * dst_pitch + bx; 588 BankedDosmemput(sp, (Uint32)bw, dst_offset, 589 gran, wsize, wbase, wfunc, &current_bank); 590 } 591 } 592 } 593 } 594 } else { 595 // nearptr path (LFB fallback) 596 if (fb_state.pitches_match) { 597 SDL_memcpy(fb_state.vram_ptr, fb_state.pixels, fb_state.fb_size); 598 } else { 599 const int bpp = fb_state.bpp; 600 const int src_pitch = fb_state.src_pitch; 601 const int dst_pitch = fb_state.vram_pitch; 602 const Uint8 *pixels = fb_state.pixels; 603 604 for (int i = 0; i < numrects; i++) { 605 int rx = rects[i].x, ry = rects[i].y; 606 int rw = rects[i].w, rh = rects[i].h; 607 if (rx < 0) { 608 rw += rx; 609 rx = 0; 610 } 611 if (ry < 0) { 612 rh += ry; 613 ry = 0; 614 } 615 if (rx + rw > fb_state.vram_w) { 616 rw = fb_state.vram_w - rx; 617 } 618 if (ry + rh > fb_state.vram_h) { 619 rh = fb_state.vram_h - ry; 620 } 621 if (rw <= 0 || rh <= 0) { 622 continue; 623 } 624 const int bw = rw * bpp; 625 const Uint8 *sp = pixels + ry * src_pitch + rx * bpp; 626 Uint8 *dp = fb_state.vram_ptr + ry * dst_pitch + rx * bpp; 627 for (int row = 0; row < rh; row++) { 628 SDL_memcpy(dp, sp, bw); 629 sp += src_pitch; 630 dp += dst_pitch; 631 } 632 } 633 } 634 } 635 636 // Page flipping is only used with the nearptr LFB path. The banked 637 // dosmemput path always writes to page 0 (the visible page) and 638 // accepts tearing, avoiding a BIOS call. 639 if (!fb_state.use_dosmemput && vdata->page_flip_available) { 640 const SDL_DisplayModeData *mdata = vdata->current_mode.internal; 641 int back_page = 1 - vdata->current_page; 642 Uint16 first_scanline = (Uint16)(vdata->page_offset[back_page] / mdata->pitch); 643 644 __dpmi_regs regs; 645 SDL_zero(regs); 646 regs.x.ax = 0x4F07; 647 regs.x.bx = 0x0080; 648 regs.x.cx = 0; 649 regs.x.dx = first_scanline; 650 __dpmi_int(0x10, &regs); 651 652 vdata->current_page = back_page; 653 654 int new_back = 1 - vdata->current_page; 655 fb_state.vram_ptr = (Uint8 *)DOS_PhysicalToLinear(vdata->mapping.address) + vdata->page_offset[new_back]; 656 } 657 658 return true; 659 } 660 661 // =================================================================== 662 // Normal path: system-RAM to LFB surface (or banked copy) 663 // with cursor compositing, palette sync, vsync, and page-flipping. 664 // =================================================================== 665 666 SDL_Surface *src = fb_state.surface; 667 if (!src) { 668 src = (SDL_Surface *)SDL_GetPointerProperty(SDL_GetWindowProperties(window), DOS_SURFACE, NULL); 669 } 670 if (!src) { 671 return SDL_SetError("Couldn't find DOS surface for window"); 672 } 673 674 const SDL_DisplayModeData *mdata = vdata->current_mode.internal; 675 SDL_WindowData *windata = window->internal; 676 677 // For 8-bit indexed modes, sync palette data between surfaces so the 678 // blit uses the correct color mapping. The actual VGA DAC programming 679 // is deferred until vertical blanking to avoid visible palette flicker. 680 SDL_Palette *dac_palette = NULL; 681 bool dac_needs_update = false; 682 683 if (src->format == SDL_PIXELFORMAT_INDEX8) { 684 SDL_Palette *win_palette = window->surface ? SDL_GetSurfacePalette(window->surface) : NULL; 685 SDL_Palette *src_palette = SDL_GetSurfacePalette(src); 686 687 // Sync: if the app set colors on the window surface palette, 688 // copy them to the internal surface so the blit works correctly. 689 if (win_palette && src_palette && win_palette != src_palette && 690 win_palette->version != src_palette->version) { 691 SDL_SetPaletteColors(src_palette, win_palette->colors, 0, 692 SDL_min(win_palette->ncolors, src_palette->ncolors)); 693 694 if (!vdata->banked_mode && fb_state.lfb_surface) { 695 // Also update the LFB surface palette for correct blitting 696 SDL_Palette *dst_palette = SDL_GetSurfacePalette(fb_state.lfb_surface); 697 if (dst_palette && dst_palette != src_palette) { 698 SDL_SetPaletteColors(dst_palette, win_palette->colors, 0, 699 SDL_min(win_palette->ncolors, dst_palette->ncolors)); 700 } 701 } 702 } 703 704 // Determine whether the VGA DAC needs reprogramming. 705 dac_palette = win_palette ? win_palette : src_palette; 706 if (dac_palette && dac_palette->version != vdata->palette_version) { 707 dac_needs_update = true; 708 } 709 } 710 711 if (vdata->banked_mode) { 712 // --- Banked framebuffer path (dirty-rect aware) --- 713 // We composite the cursor onto the source surface (in system RAM), 714 // then bank-copy only the dirty rectangles to the VGA window. We 715 // need to undo the cursor composite afterwards so the app's surface 716 // isn't permanently modified. 717 718 const int dst_x = ((int)mdata->w - src->w) / 2; 719 const int dst_y = ((int)mdata->h - src->h) / 2; 720 721 SDL_Mouse *mouse = SDL_GetMouse(); 722 SDL_Surface *cursor = NULL; 723 SDL_Rect cursorrect; 724 SDL_Rect cursor_clipped; // cursorrect clipped to src bounds 725 SDL_Surface *cursor_save = NULL; 726 bool have_cursor_rect = false; 727 728 SDL_Cursor *cur = mouse ? mouse->cur_cursor : NULL; 729 if (cur && cur->animation) { 730 cur = cur->animation->frames[cur->animation->current_frame]; 731 } 732 if (mouse && mouse->internal && !mouse->relative_mode && mouse->cursor_visible && cur && cur->internal) { 733 cursor = cur->internal->surface; 734 if (cursor) { 735 cursorrect.x = SDL_clamp((int)mouse->x, 0, window->w) - cur->internal->hot_x; 736 cursorrect.y = SDL_clamp((int)mouse->y, 0, window->h) - cur->internal->hot_y; 737 cursorrect.w = cursor->w; 738 cursorrect.h = cursor->h; 739 740 // Clip cursor rect to src bounds for save/restore. 741 cursor_clipped = cursorrect; 742 if (cursor_clipped.x < 0) { 743 cursor_clipped.w += cursor_clipped.x; 744 cursor_clipped.x = 0; 745 } 746 if (cursor_clipped.y < 0) { 747 cursor_clipped.h += cursor_clipped.y; 748 cursor_clipped.y = 0; 749 } 750 if (cursor_clipped.x + cursor_clipped.w > src->w) { 751 cursor_clipped.w = src->w - cursor_clipped.x; 752 } 753 if (cursor_clipped.y + cursor_clipped.h > src->h) { 754 cursor_clipped.h = src->h - cursor_clipped.y; 755 } 756 757 if (cursor_clipped.w > 0 && cursor_clipped.h > 0) { 758 have_cursor_rect = true; 759 760 // Save the pixels under the cursor so we can restore them after the copy. 761 cursor_save = SDL_CreateSurface(cursor_clipped.w, cursor_clipped.h, src->format); 762 if (cursor_save) { 763 if (src->format == SDL_PIXELFORMAT_INDEX8) { 764 SDL_Palette *sp = SDL_GetSurfacePalette(src); 765 if (sp) { 766 SDL_SetSurfacePalette(cursor_save, sp); 767 } 768 } 769 SDL_BlitSurface(src, &cursor_clipped, cursor_save, NULL); 770 } 771 } 772 773 // Composite cursor onto the source surface. 774 { 775 SDL_Surface *blit_cursor = GetConvertedCursorSurface(cur->internal, src); 776 SDL_BlitSurface(blit_cursor ? blit_cursor : cursor, NULL, src, &cursorrect); 777 } 778 } 779 } 780 781 // Wait for vsync before the copy to reduce tearing. 782 const int vsync_interval = windata->framebuffer_vsync; 783 if (vsync_interval > 0 || dac_needs_update) { 784 WaitForVBlank(); 785 } 786 787 if (dac_needs_update) { 788 vdata->palette_version = dac_palette->version; 789 ProgramVGADAC(dac_palette); 790 } 791 792 // Bank-switched copy of only the dirty rectangles. 793 // Pre-compute constants shared across all rect copies. 794 const Uint32 win_gran_bytes = (Uint32)mdata->win_granularity * 1024; 795 const Uint32 win_size_bytes = (Uint32)mdata->win_size * 1024; 796 const Uint32 win_base = (Uint32)mdata->win_a_segment << 4; 797 int current_bank = -1; 798 799 // Track whether the cursor region was already covered by a dirty rect 800 // so we don't copy it twice. 801 bool cursor_covered = false; 802 803 for (int r = 0; r < numrects; r++) { 804 // Clip the dirty rect to the source surface bounds. 805 SDL_Rect rect = rects[r]; 806 if (rect.x < 0) { 807 rect.w += rect.x; 808 rect.x = 0; 809 } 810 if (rect.y < 0) { 811 rect.h += rect.y; 812 rect.y = 0; 813 } 814 if (rect.x + rect.w > src->w) { 815 rect.w = src->w - rect.x; 816 } 817 if (rect.y + rect.h > src->h) { 818 rect.h = src->h - rect.y; 819 } 820 if (rect.w <= 0 || rect.h <= 0) { 821 continue; 822 } 823 824 // If the cursor is visible, check whether this rect fully covers it. 825 if (have_cursor_rect && !cursor_covered) { 826 if (rect.x <= cursor_clipped.x && 827 rect.y <= cursor_clipped.y && 828 rect.x + rect.w >= cursor_clipped.x + cursor_clipped.w && 829 rect.y + rect.h >= cursor_clipped.y + cursor_clipped.h) { 830 cursor_covered = true; 831 } 832 } 833 834 BankedFramebufferCopyRect(mdata, src, &rect, dst_x, dst_y, 835 win_gran_bytes, win_size_bytes, win_base, 836 &current_bank, mdata->win_func_ptr); 837 } 838 839 // If no dirty rect covered the cursor, copy the cursor region separately. 840 if (have_cursor_rect && !cursor_covered) { 841 BankedFramebufferCopyRect(mdata, src, &cursor_clipped, dst_x, dst_y, 842 win_gran_bytes, win_size_bytes, win_base, 843 &current_bank, mdata->win_func_ptr); 844 } 845 846 // Restore the source surface pixels under the cursor. 847 if (cursor_save) { 848 SDL_Rect restore_rect = cursor_clipped; 849 SDL_BlitSurface(cursor_save, NULL, src, &restore_rect); 850 SDL_DestroySurface(cursor_save); 851 } 852 853 } else { 854 // --- LFB path --- 855 SDL_Surface *dst = fb_state.lfb_surface; 856 if (!dst) { 857 return SDL_SetError("Couldn't find VESA linear framebuffer surface for window"); 858 } 859 860 const SDL_Rect dstrect = { (dst->w - src->w) / 2, (dst->h - src->h) / 2, src->w, src->h }; 861 SDL_Mouse *mouse = SDL_GetMouse(); 862 SDL_Surface *cursor = NULL; 863 SDL_Rect cursorrect; 864 865 SDL_Cursor *cur = mouse ? mouse->cur_cursor : NULL; 866 if (cur && cur->animation) { 867 cur = cur->animation->frames[cur->animation->current_frame]; 868 } 869 if (mouse && mouse->internal && !mouse->relative_mode && mouse->cursor_visible && cur && cur->internal) { 870 cursor = cur->internal->surface; 871 if (cursor) { 872 cursorrect.x = dstrect.x + SDL_clamp((int)mouse->x, 0, window->w) - cur->internal->hot_x; 873 cursorrect.y = dstrect.y + SDL_clamp((int)mouse->y, 0, window->h) - cur->internal->hot_y; 874 } 875 } 876 877 // If both surfaces are INDEX8 and same size, skip the SDL blit 878 // machinery (which does per-pixel palette remapping even for 879 // identical palettes), copy directly to the LFB surface using 880 // SDL_memcpy. 881 if (src->format == SDL_PIXELFORMAT_INDEX8 && 882 dst->format == SDL_PIXELFORMAT_INDEX8 && 883 src->w == dstrect.w && src->h == dstrect.h) { 884 const Uint8 *sp = (const Uint8 *)src->pixels; 885 Uint8 *dp = (Uint8 *)dst->pixels + dstrect.y * dst->pitch + dstrect.x; 886 if (src->pitch == dstrect.w && dst->pitch == dstrect.w) { 887 SDL_memcpy(dp, sp, (size_t)dstrect.w * dstrect.h); 888 } else { 889 for (int row = 0; row < dstrect.h; row++) { 890 SDL_memcpy(dp, sp, dstrect.w); 891 sp += src->pitch; 892 dp += dst->pitch; 893 } 894 } 895 } else { 896 // Blit to the back page (or the only page, if no page-flipping) 897 if (!SDL_BlitSurface(src, NULL, dst, &dstrect)) { 898 return false; 899 } 900 } 901 902 if (cursor) { 903 SDL_Surface *blit_cursor = GetConvertedCursorSurface(cur->internal, dst); 904 if (!SDL_BlitSurface(blit_cursor ? blit_cursor : cursor, NULL, dst, &cursorrect)) { 905 return false; 906 } 907 } 908 909 if (vdata->page_flip_available) { 910 // Page-flip with optional vsync. 911 const int vsync_interval = windata->framebuffer_vsync; 912 int back_page = 1 - vdata->current_page; 913 Uint16 first_scanline = (Uint16)(vdata->page_offset[back_page] / mdata->pitch); 914 915 if (vsync_interval > 0 || dac_needs_update) { 916 // Wait for vblank so the flip and DAC update appear together. 917 WaitForVBlank(); 918 } 919 920 if (dac_needs_update) { 921 vdata->palette_version = dac_palette->version; 922 ProgramVGADAC(dac_palette); 923 } 924 925 // Flip: make the back page (which we just drew to) the visible page. 926 // Always use subfunction 0x0080 (set display start, don't wait) — 927 // vsync is controlled by our manual vblank wait above. 928 __dpmi_regs regs; 929 SDL_zero(regs); 930 regs.x.ax = 0x4F07; 931 regs.x.bx = 0x0080; 932 regs.x.cx = 0; // first pixel in scan line 933 regs.x.dx = first_scanline; 934 __dpmi_int(0x10, &regs); 935 936 vdata->current_page = back_page; 937 938 // Update LFB surface to point at the new back page (the old front page) 939 int new_back = 1 - vdata->current_page; 940 dst->pixels = (Uint8 *)DOS_PhysicalToLinear(vdata->mapping.address) + vdata->page_offset[new_back]; 941 } else { 942 // No page-flipping: wait for vsync, then update DAC atomically 943 const int vsync_interval = windata->framebuffer_vsync; 944 if (vsync_interval > 0 || dac_needs_update) { 945 WaitForVBlank(); 946 } 947 948 if (dac_needs_update) { 949 vdata->palette_version = dac_palette->version; 950 ProgramVGADAC(dac_palette); 951 } 952 } 953 } 954 955 return true; 956} 957 958void DOSVESA_DestroyWindowFramebuffer(SDL_VideoDevice *device, SDL_Window *window) 959{ 960 // Note: we intentionally do NOT call SDL_ClearSurface on the LFB surface 961 // here. SetDisplayMode already blanks the framebuffer (both pages) when 962 // setting a new mode, and the LFB surface's pixels pointer may be stale 963 // if the DPMI mapping was freed before this function is called. 964 (void)device; 965 SDL_zero(fb_state); 966 SDL_ClearProperty(SDL_GetWindowProperties(window), DOS_SURFACE); 967} 968 969#endif // SDL_VIDEO_DRIVER_DOSVESA 970
[FILE END]
(C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.