Reactos

[BOOTVID] Implement a generic linear framebuffer boot video driver (#8618)

Its purpose is to offer an out-of-the-box generic framebuffer video
(a video miniport driver for win32k will be introduced in a future PR)
to ease ReactOS porting to other possibly non-PC-compatible systems,
where no VGA-compatible video is present and only linear framebuffers
are available; for example: XBOX, UEFI with GOP only, AppleTV, etc.

Of course, once ReactOS is ported, one can then (and should) write,
or use existing video drivers tailored to the system of interest.

Together with our FreeLoader, this driver could also be employed for
of porting/modding Windows 2000/XP/2003 attempts to other platforms,
as this has been done by external contributors.

Current limitations:
- Only supports 32 bits-per-pixel ARGB format. This limitation will be
removed in subsequent PR(s).
- May be slow during rendering (region color filling and scrolling);
I will try to improve this as time goes.

This driver's code is loosely based upon preliminary code by Justin Miller
and on the existing XBOX bootvid implementation.
Tested by Justin Miller (@TheDarkFire) for UEFI, and by Sylas Hollander
(@DistroHopper39B) with his AppleTV port.
It has also been tested with VESA linear modes on PC by myself.

+568
+1
drivers/base/bootvid/CMakeLists.txt
··· 68 68 elseif(ARCH STREQUAL "arm") 69 69 add_bootvid(bootvid arm/armbvid.cmake) 70 70 endif() 71 + add_bootvid(lfbbvid framebuf/lfbbvid.cmake)
+538
drivers/base/bootvid/framebuf/bootvid.c
··· 1 + /* 2 + * PROJECT: ReactOS Generic Framebuffer Boot Video Driver 3 + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) 4 + * or MIT (https://spdx.org/licenses/MIT) 5 + * PURPOSE: Main file 6 + * COPYRIGHT: Copyright 2023-2026 Hermès Bélusca-Maïto <hermes.belusca-maito@reactos.org> 7 + */ 8 + 9 + #include "precomp.h" 10 + 11 + #define NDEBUG 12 + #include <debug.h> 13 + 14 + /* Include the Boot-time (POST) display discovery helper functions */ 15 + #include <drivers/bootvid/framebuf.c> 16 + 17 + /* Scaling of the bootvid 640x480 default virtual screen to the larger video framebuffer */ 18 + #define SCALING_SUPPORT 19 + #define SCALING_PROPORTIONAL 20 + 21 + /* Keep borders black or controlled with palette */ 22 + // #define COLORED_BORDERS 23 + 24 + 25 + /* GLOBALS ********************************************************************/ 26 + 27 + #define BB_PIXEL(x, y) \ 28 + ((PUCHAR)BackBuffer + (y) * SCREEN_WIDTH + (x)) 29 + 30 + #define FB_PIXEL(x, y) \ 31 + ((PUCHAR)FrameBufferStart + (PanV + VidpYScale * (y)) * BytesPerScanLine \ 32 + + (PanH + VidpXScale * (x)) * BytesPerPixel) 33 + 34 + static ULONG_PTR FrameBufferStart = 0; 35 + static ULONG FrameBufferSize; 36 + static ULONG ScreenWidth, ScreenHeight, BytesPerScanLine; 37 + static UCHAR BytesPerPixel; 38 + static PUCHAR BackBuffer = NULL; 39 + static SIZE_T BackBufferSize; 40 + 41 + #ifdef SCALING_SUPPORT 42 + static USHORT VidpXScale = 1; 43 + static USHORT VidpYScale = 1; 44 + #else 45 + #define VidpXScale 1 46 + #define VidpYScale 1 47 + #endif 48 + static ULONG PanH, PanV; 49 + 50 + static RGBQUAD CachedPalette[BV_MAX_COLORS]; 51 + 52 + 53 + /* PRIVATE FUNCTIONS *********************************************************/ 54 + 55 + static VOID 56 + ApplyPalette(VOID) 57 + { 58 + PULONG Frame = (PULONG)FrameBufferStart; 59 + ULONG x, y; 60 + 61 + #ifdef COLORED_BORDERS 62 + /* Top border */ 63 + for (x = 0; x < PanV * ScreenWidth; ++x) 64 + { 65 + *Frame++ = CachedPalette[BV_COLOR_BLACK]; 66 + } 67 + 68 + /* Left border */ 69 + for (y = 0; y < VidpYScale * SCREEN_HEIGHT; ++y) 70 + { 71 + // Frame = (PULONG)(FrameBufferStart + FB_OFFSET(-(LONG)PanH, y)); 72 + Frame = (PULONG)(FrameBufferStart + (PanV + y) * BytesPerScanLine); 73 + for (x = 0; x < PanH; ++x) 74 + { 75 + *Frame++ = CachedPalette[BV_COLOR_BLACK]; 76 + } 77 + } 78 + #endif // COLORED_BORDERS 79 + 80 + /* Screen redraw */ 81 + PUCHAR Back = BackBuffer; 82 + for (y = 0; y < SCREEN_HEIGHT; ++y) 83 + { 84 + Frame = (PULONG)FB_PIXEL(0, y); 85 + PULONG Pixel = Frame; 86 + for (x = 0; x < SCREEN_WIDTH; ++x) 87 + { 88 + for (ULONG j = VidpXScale; j > 0; --j) 89 + *Pixel++ = CachedPalette[*Back]; 90 + Back++; 91 + } 92 + Pixel = Frame; 93 + for (ULONG i = VidpYScale-1; i > 0; --i) 94 + { 95 + Pixel = (PULONG)((ULONG_PTR)Pixel + BytesPerScanLine); 96 + RtlCopyMemory(Pixel, Frame, VidpXScale * SCREEN_WIDTH * BytesPerPixel); 97 + } 98 + } 99 + 100 + #ifdef COLORED_BORDERS 101 + /* Right border */ 102 + for (y = 0; y < VidpYScale * SCREEN_HEIGHT; ++y) 103 + { 104 + // Frame = (PULONG)(FrameBufferStart + FB_OFFSET(SCREEN_WIDTH, y)); 105 + Frame = (PULONG)(FrameBufferStart + (PanV + y) * BytesPerScanLine + (PanH + VidpXScale * SCREEN_WIDTH) * BytesPerPixel); 106 + for (x = 0; x < PanH; ++x) 107 + { 108 + *Frame++ = CachedPalette[BV_COLOR_BLACK]; 109 + } 110 + } 111 + 112 + /* Bottom border */ 113 + // Frame = (PULONG)(FrameBufferStart + FB_OFFSET(-(LONG)PanH, SCREEN_HEIGHT)); 114 + Frame = (PULONG)(FrameBufferStart + (PanV + VidpYScale * SCREEN_HEIGHT) * BytesPerScanLine); 115 + for (x = 0; x < PanV * ScreenWidth; ++x) 116 + { 117 + *Frame++ = CachedPalette[BV_COLOR_BLACK]; 118 + } 119 + #endif // COLORED_BORDERS 120 + } 121 + 122 + /* PUBLIC FUNCTIONS **********************************************************/ 123 + 124 + BOOLEAN 125 + NTAPI 126 + VidInitialize( 127 + _In_ BOOLEAN SetMode) 128 + { 129 + PHYSICAL_ADDRESS FrameBuffer; 130 + PHYSICAL_ADDRESS VramAddress; 131 + ULONG VramSize; 132 + CM_FRAMEBUF_DEVICE_DATA VideoConfigData; /* Configuration data from hardware tree */ 133 + INTERFACE_TYPE Interface; 134 + ULONG BusNumber; 135 + NTSTATUS Status; 136 + 137 + /* Find boot-time framebuffer display information from the LoaderBlock */ 138 + Status = FindBootDisplay(&VramAddress, 139 + &VramSize, 140 + &VideoConfigData, 141 + NULL, // MonitorConfigData 142 + &Interface, 143 + &BusNumber); 144 + if (!NT_SUCCESS(Status)) 145 + { 146 + DPRINT1("Boot framebuffer does not exist!\n"); 147 + return FALSE; 148 + } 149 + 150 + /* The VRAM address must be page-aligned */ 151 + if (VramAddress.QuadPart % PAGE_SIZE != 0) // DPRINTed for diagnostics on some systems 152 + DPRINT1("** VramAddress 0x%I64X isn't PAGE_SIZE aligned\n", VramAddress.QuadPart); 153 + ASSERT(VramAddress.QuadPart % PAGE_SIZE == 0); 154 + if (VramSize % PAGE_SIZE != 0) 155 + DPRINT1("** VramSize %lu (0x%lx) isn't multiple of PAGE_SIZE\n", VramSize, VramSize); 156 + // ASSERT(VramSize % PAGE_SIZE == 0); // This assert may fail, e.g. 800x600@32bpp UEFI GOP display 157 + 158 + /* Retrieve the framebuffer address, its visible screen dimensions, and its attributes */ 159 + FrameBuffer.QuadPart = VramAddress.QuadPart + VideoConfigData.FrameBufferOffset; 160 + ScreenWidth = VideoConfigData.ScreenWidth; 161 + ScreenHeight = VideoConfigData.ScreenHeight; 162 + if (ScreenWidth < SCREEN_WIDTH || ScreenHeight < SCREEN_HEIGHT) 163 + { 164 + DPRINT1("Unsupported screen resolution!\n"); 165 + return FALSE; 166 + } 167 + 168 + BytesPerPixel = (VideoConfigData.BitsPerPixel + 7) / 8; // Round up to nearest byte. 169 + ASSERT(BytesPerPixel >= 1 && BytesPerPixel <= 4); 170 + if (BytesPerPixel != 4) 171 + { 172 + UNIMPLEMENTED; 173 + DPRINT1("Unsupported BytesPerPixel = %u\n", BytesPerPixel); 174 + return FALSE; 175 + } 176 + 177 + ASSERT(ScreenWidth <= VideoConfigData.PixelsPerScanLine); 178 + BytesPerScanLine = VideoConfigData.PixelsPerScanLine * BytesPerPixel; 179 + if (BytesPerScanLine < 1) 180 + { 181 + DPRINT1("Invalid BytesPerScanLine = %lu\n", BytesPerScanLine); 182 + return FALSE; 183 + } 184 + 185 + /* Compute the visible framebuffer size */ 186 + FrameBufferSize = ScreenHeight * BytesPerScanLine; 187 + 188 + /* Verify that the framebuffer actually fits inside the video RAM */ 189 + if (FrameBuffer.QuadPart + FrameBufferSize > VramAddress.QuadPart + VramSize) 190 + { 191 + DPRINT1("The framebuffer exceeds video memory bounds!\n"); 192 + return FALSE; 193 + } 194 + 195 + /* Translate the framebuffer from bus-relative to physical address */ 196 + PHYSICAL_ADDRESS TranslatedAddress; 197 + ULONG AddressSpace = 0; /* MMIO space */ 198 + if (!BootTranslateBusAddress(Interface, 199 + BusNumber, 200 + FrameBuffer, 201 + &AddressSpace, 202 + &TranslatedAddress)) 203 + { 204 + DPRINT1("Could not translate framebuffer bus address 0x%I64X\n", FrameBuffer.QuadPart); 205 + return FALSE; 206 + } 207 + 208 + /* Map it into system space if necessary */ 209 + ULONG MappedSize = 0; 210 + PVOID FrameBufferBase = NULL; 211 + if (AddressSpace == 0) 212 + { 213 + /* Calculate page-aligned address and size for MmMapIoSpace() */ 214 + FrameBuffer.HighPart = TranslatedAddress.HighPart; 215 + FrameBuffer.LowPart = ALIGN_DOWN_BY(TranslatedAddress.LowPart, PAGE_SIZE); 216 + MappedSize = FrameBufferSize; 217 + MappedSize += (ULONG)(TranslatedAddress.QuadPart - FrameBuffer.QuadPart); // BYTE_OFFSET() 218 + MappedSize = ROUND_TO_PAGES(MappedSize); 219 + /* Essentially MmMapVideoDisplay() */ 220 + FrameBufferBase = MmMapIoSpace(FrameBuffer, MappedSize, MmFrameBufferCached); 221 + if (!FrameBufferBase) 222 + FrameBufferBase = MmMapIoSpace(FrameBuffer, MappedSize, MmNonCached); 223 + if (!FrameBufferBase) 224 + { 225 + DPRINT1("Could not map framebuffer 0x%I64X (%lu bytes)\n", 226 + FrameBuffer.QuadPart, MappedSize); 227 + goto Failure; 228 + } 229 + FrameBufferStart = (ULONG_PTR)FrameBufferBase; 230 + FrameBufferStart += (TranslatedAddress.QuadPart - FrameBuffer.QuadPart); // BYTE_OFFSET() 231 + } 232 + else 233 + { 234 + /* The base is the translated address, no need to map */ 235 + FrameBufferStart = (ULONG_PTR)TranslatedAddress.QuadPart; 236 + } 237 + 238 + 239 + /* 240 + * Reserve off-screen area for the backbuffer that contains 241 + * 8-bit indexed color screen image, plus preserved row data. 242 + */ 243 + BackBufferSize = SCREEN_WIDTH * (SCREEN_HEIGHT + (BOOTCHAR_HEIGHT + 1)); 244 + 245 + /* If there is enough video memory in the physical framebuffer, 246 + * place the backbuffer in the hidden part of the framebuffer, 247 + * otherwise allocate a zone for the backbuffer. */ 248 + if (VideoConfigData.FrameBufferOffset + FrameBufferSize + BackBufferSize 249 + <= ((AddressSpace == 0) ? MappedSize : VramSize)) 250 + { 251 + /* Backbuffer placed following the framebuffer in the hidden part */ 252 + BackBuffer = (PUCHAR)(FrameBufferStart + FrameBufferSize); 253 + // BackBuffer = (PUCHAR)(VramAddress + VramSize - BackBufferSize); // Or at the end of VRAM. 254 + } 255 + else 256 + { 257 + /* Allocate the backbuffer */ 258 + PHYSICAL_ADDRESS NullAddress = {{0, 0}}; 259 + PHYSICAL_ADDRESS HighestAddress = {{-1, -1}}; 260 + BackBuffer = MmAllocateContiguousMemorySpecifyCache( 261 + BackBufferSize, NullAddress, HighestAddress, 262 + NullAddress, MmNonCached); 263 + if (!BackBuffer) 264 + { 265 + DPRINT1("Could not allocate backbuffer (size: %lu)\n", (ULONG)BackBufferSize); 266 + goto Failure; 267 + } 268 + } 269 + 270 + #ifdef SCALING_SUPPORT 271 + /* Compute autoscaling; only integer (not fractional) scaling is supported */ 272 + VidpXScale = ScreenWidth / SCREEN_WIDTH; 273 + VidpYScale = ScreenHeight / SCREEN_HEIGHT; 274 + ASSERT(VidpXScale >= 1); 275 + ASSERT(VidpYScale >= 1); 276 + #ifdef SCALING_PROPORTIONAL 277 + VidpXScale = min(VidpXScale, VidpYScale); 278 + VidpYScale = VidpXScale; 279 + #endif 280 + DPRINT1("Scaling X = %hu, Y = %hu\n", VidpXScale, VidpYScale); 281 + #endif // SCALING_SUPPORT 282 + 283 + /* Calculate left/right and top/bottom border values 284 + * to keep the displayed area centered on the screen */ 285 + PanH = (ScreenWidth - VidpXScale * SCREEN_WIDTH) / 2; 286 + PanV = (ScreenHeight - VidpYScale * SCREEN_HEIGHT) / 2; 287 + DPRINT1("Borders X = %lu, Y = %lu\n", PanH, PanV); 288 + 289 + /* Reset the video mode if requested */ 290 + if (SetMode) 291 + VidResetDisplay(TRUE); 292 + 293 + return TRUE; 294 + 295 + Failure: 296 + /* We failed somewhere; unmap the framebuffer if we mapped it */ 297 + if (FrameBufferBase && (AddressSpace == 0)) 298 + MmUnmapIoSpace(FrameBufferBase, MappedSize); 299 + 300 + return FALSE; 301 + } 302 + 303 + VOID 304 + NTAPI 305 + VidCleanUp(VOID) 306 + { 307 + /* Just fill the screen black */ 308 + VidSolidColorFill(0, 0, SCREEN_WIDTH - 1, SCREEN_HEIGHT - 1, BV_COLOR_BLACK); 309 + } 310 + 311 + VOID 312 + ResetDisplay( 313 + _In_ BOOLEAN SetMode) 314 + { 315 + RtlZeroMemory(BackBuffer, BackBufferSize); 316 + RtlZeroMemory((PVOID)FrameBufferStart, FrameBufferSize); 317 + 318 + /* Re-initialize the palette and fill the screen black */ 319 + InitializePalette(); 320 + VidSolidColorFill(0, 0, SCREEN_WIDTH - 1, SCREEN_HEIGHT - 1, BV_COLOR_BLACK); 321 + } 322 + 323 + VOID 324 + InitPaletteWithTable( 325 + _In_reads_(Count) const ULONG* Table, 326 + _In_ ULONG Count) 327 + { 328 + const ULONG* Entry = Table; 329 + ULONG i; 330 + BOOLEAN HasChanged = FALSE; 331 + 332 + for (i = 0; i < Count; i++, Entry++) 333 + { 334 + HasChanged |= !!((CachedPalette[i] ^ *Entry) & 0x00FFFFFF); 335 + CachedPalette[i] = *Entry | 0xFF000000; 336 + } 337 + 338 + /* Re-apply the palette if it has changed */ 339 + if (HasChanged) 340 + ApplyPalette(); 341 + } 342 + 343 + VOID 344 + SetPixel( 345 + _In_ ULONG Left, 346 + _In_ ULONG Top, 347 + _In_ UCHAR Color) 348 + { 349 + PUCHAR Back = BB_PIXEL(Left, Top); 350 + PULONG Frame = (PULONG)FB_PIXEL(Left, Top); 351 + 352 + *Back = Color; 353 + for (ULONG i = VidpYScale; i > 0; --i) 354 + { 355 + PULONG Pixel = Frame; 356 + for (ULONG j = VidpXScale; j > 0; --j) 357 + *Pixel++ = CachedPalette[Color]; 358 + Frame = (PULONG)((ULONG_PTR)Frame + BytesPerScanLine); 359 + } 360 + } 361 + 362 + VOID 363 + PreserveRow( 364 + _In_ ULONG CurrentTop, 365 + _In_ ULONG TopDelta, 366 + _In_ BOOLEAN Restore) 367 + { 368 + PUCHAR NewPosition, OldPosition; 369 + 370 + /* Calculate the position in memory for the row */ 371 + if (Restore) 372 + { 373 + /* Restore the row by copying back the contents saved off-screen */ 374 + NewPosition = BB_PIXEL(0, CurrentTop); 375 + OldPosition = BB_PIXEL(0, SCREEN_HEIGHT); 376 + } 377 + else 378 + { 379 + /* Preserve the row by saving its contents off-screen */ 380 + NewPosition = BB_PIXEL(0, SCREEN_HEIGHT); 381 + OldPosition = BB_PIXEL(0, CurrentTop); 382 + } 383 + 384 + /* Set the count and copy the pixel data back to the other position in the backbuffer */ 385 + ULONG Count = TopDelta * SCREEN_WIDTH; 386 + RtlCopyMemory(NewPosition, OldPosition, Count); 387 + 388 + /* On restore, mirror the backbuffer changes to the framebuffer */ 389 + if (Restore) 390 + { 391 + NewPosition = BB_PIXEL(0, CurrentTop); 392 + for (ULONG y = 0; y < TopDelta; ++y) 393 + { 394 + PULONG Frame = (PULONG)FB_PIXEL(0, CurrentTop + y); 395 + PULONG Pixel = Frame; 396 + for (Count = 0; Count < SCREEN_WIDTH; ++Count) 397 + { 398 + for (ULONG j = VidpXScale; j > 0; --j) 399 + *Pixel++ = CachedPalette[*NewPosition]; 400 + NewPosition++; 401 + } 402 + Pixel = Frame; 403 + for (ULONG i = VidpYScale-1; i > 0; --i) 404 + { 405 + Pixel = (PULONG)((ULONG_PTR)Pixel + BytesPerScanLine); 406 + RtlCopyMemory(Pixel, Frame, VidpXScale * SCREEN_WIDTH * BytesPerPixel); 407 + } 408 + } 409 + } 410 + } 411 + 412 + VOID 413 + DoScroll( 414 + _In_ ULONG Scroll) 415 + { 416 + ULONG RowSize = VidpScrollRegion.Right - VidpScrollRegion.Left + 1; 417 + 418 + /* Calculate the position in memory for the row */ 419 + PUCHAR OldPosition = BB_PIXEL(VidpScrollRegion.Left, VidpScrollRegion.Top + Scroll); 420 + PUCHAR NewPosition = BB_PIXEL(VidpScrollRegion.Left, VidpScrollRegion.Top); 421 + 422 + /* Start loop */ 423 + for (ULONG Top = VidpScrollRegion.Top; Top <= VidpScrollRegion.Bottom; ++Top) 424 + { 425 + /* Scroll the row */ 426 + RtlCopyMemory(NewPosition, OldPosition, RowSize); 427 + 428 + PULONG Frame = (PULONG)FB_PIXEL(VidpScrollRegion.Left, Top); 429 + PULONG Pixel = Frame; 430 + for (ULONG Count = 0; Count < RowSize; ++Count) 431 + { 432 + for (ULONG j = VidpXScale; j > 0; --j) 433 + *Pixel++ = CachedPalette[NewPosition[Count]]; 434 + } 435 + Pixel = Frame; 436 + for (ULONG i = VidpYScale-1; i > 0; --i) 437 + { 438 + Pixel = (PULONG)((ULONG_PTR)Pixel + BytesPerScanLine); 439 + RtlCopyMemory(Pixel, Frame, VidpXScale * RowSize * BytesPerPixel); 440 + } 441 + 442 + OldPosition += SCREEN_WIDTH; 443 + NewPosition += SCREEN_WIDTH; 444 + } 445 + } 446 + 447 + VOID 448 + DisplayCharacter( 449 + _In_ CHAR Character, 450 + _In_ ULONG Left, 451 + _In_ ULONG Top, 452 + _In_ ULONG TextColor, 453 + _In_ ULONG BackColor) 454 + { 455 + /* Get the font line for this character */ 456 + const UCHAR* FontChar = GetFontPtr(Character); 457 + 458 + /* Loop each pixel height */ 459 + for (ULONG y = Top; y < Top + BOOTCHAR_HEIGHT; ++y, FontChar += FONT_PTR_DELTA) 460 + { 461 + /* Loop each pixel width */ 462 + ULONG x = Left; 463 + for (UCHAR bit = 1 << (BOOTCHAR_WIDTH - 1); bit > 0; bit >>= 1, ++x) 464 + { 465 + /* If we should draw this pixel, use the text color. Otherwise 466 + * this is a background pixel, draw it unless it's transparent. */ 467 + if (*FontChar & bit) 468 + SetPixel(x, y, (UCHAR)TextColor); 469 + else if (BackColor < BV_COLOR_NONE) 470 + SetPixel(x, y, (UCHAR)BackColor); 471 + } 472 + } 473 + } 474 + 475 + VOID 476 + NTAPI 477 + VidSolidColorFill( 478 + _In_ ULONG Left, 479 + _In_ ULONG Top, 480 + _In_ ULONG Right, 481 + _In_ ULONG Bottom, 482 + _In_ UCHAR Color) 483 + { 484 + for (; Top <= Bottom; ++Top) 485 + { 486 + PUCHAR Back = BB_PIXEL(Left, Top); 487 + // NOTE: Assumes 32bpp 488 + PULONG Frame = (PULONG)FB_PIXEL(Left, Top); 489 + PULONG Pixel = Frame; 490 + for (ULONG L = Left; L <= Right; ++L) 491 + { 492 + *Back++ = Color; 493 + for (ULONG j = VidpXScale; j > 0; --j) 494 + *Pixel++ = CachedPalette[Color]; 495 + } 496 + Pixel = Frame; 497 + for (ULONG i = VidpYScale-1; i > 0; --i) 498 + { 499 + Pixel = (PULONG)((ULONG_PTR)Pixel + BytesPerScanLine); 500 + RtlCopyMemory(Pixel, Frame, VidpXScale * (Right - Left + 1) * BytesPerPixel); 501 + } 502 + } 503 + } 504 + 505 + VOID 506 + NTAPI 507 + VidScreenToBufferBlt( 508 + _Out_writes_bytes_all_(Delta * Height) PUCHAR Buffer, 509 + _In_ ULONG Left, 510 + _In_ ULONG Top, 511 + _In_ ULONG Width, 512 + _In_ ULONG Height, 513 + _In_ ULONG Delta) 514 + { 515 + ULONG x, y; 516 + 517 + /* Clear the destination buffer */ 518 + RtlZeroMemory(Buffer, Delta * Height); 519 + 520 + /* Start the outer Y height loop */ 521 + for (y = 0; y < Height; ++y) 522 + { 523 + /* Set current scanline */ 524 + PUCHAR Back = BB_PIXEL(Left, Top + y); 525 + PUCHAR Buf = Buffer + y * Delta; 526 + 527 + /* Start the X inner loop */ 528 + for (x = 0; x < Width; x += sizeof(USHORT)) 529 + { 530 + /* Read the current value */ 531 + *Buf = (*Back++ & 0xF) << 4; 532 + *Buf |= *Back++ & 0xF; 533 + Buf++; 534 + } 535 + } 536 + } 537 + 538 + /* EOF */
+22
drivers/base/bootvid/framebuf/framebuf.h
··· 1 + /* 2 + * PROJECT: ReactOS Generic Framebuffer Boot Video Driver 3 + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) 4 + * or MIT (https://spdx.org/licenses/MIT) 5 + * PURPOSE: Arch-specific header file 6 + * COPYRIGHT: Copyright 2023-2026 Hermès Bélusca-Maïto <hermes.belusca-maito@reactos.org> 7 + */ 8 + 9 + #pragma once 10 + 11 + VOID 12 + InitPaletteWithTable( 13 + _In_reads_(Count) const ULONG* Table, 14 + _In_ ULONG Count); 15 + 16 + #define PrepareForSetPixel() 17 + 18 + VOID 19 + SetPixel( 20 + _In_ ULONG Left, 21 + _In_ ULONG Top, 22 + _In_ UCHAR Color);
+7
drivers/base/bootvid/framebuf/lfbbvid.cmake
··· 1 + 2 + set(MODULE_HEADER ${CMAKE_CURRENT_LIST_DIR}/framebuf.h) # For precomp.h 3 + list(APPEND SOURCE 4 + ${MODULE_HEADER} 5 + ${CMAKE_CURRENT_LIST_DIR}/bootvid.c) 6 + 7 + set(REACTOS_STR_FILE_DESCRIPTION "Generic Linear FrameBuffer Boot Driver")