Reactos
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
34static ULONG_PTR FrameBufferStart = 0;
35static ULONG FrameBufferSize;
36static ULONG ScreenWidth, ScreenHeight, BytesPerScanLine;
37static UCHAR BytesPerPixel;
38static PUCHAR BackBuffer = NULL;
39static SIZE_T BackBufferSize;
40
41#ifdef SCALING_SUPPORT
42static USHORT VidpXScale = 1;
43static USHORT VidpYScale = 1;
44#else
45#define VidpXScale 1
46#define VidpYScale 1
47#endif
48static ULONG PanH, PanV;
49
50static RGBQUAD CachedPalette[BV_MAX_COLORS];
51
52
53/* PRIVATE FUNCTIONS *********************************************************/
54
55static VOID
56ApplyPalette(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
124BOOLEAN
125NTAPI
126VidInitialize(
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
295Failure:
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
303VOID
304NTAPI
305VidCleanUp(VOID)
306{
307 /* Just fill the screen black */
308 VidSolidColorFill(0, 0, SCREEN_WIDTH - 1, SCREEN_HEIGHT - 1, BV_COLOR_BLACK);
309}
310
311VOID
312ResetDisplay(
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
323VOID
324InitPaletteWithTable(
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
343VOID
344SetPixel(
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
362VOID
363PreserveRow(
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
412VOID
413DoScroll(
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
447VOID
448DisplayCharacter(
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
475VOID
476NTAPI
477VidSolidColorFill(
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
505VOID
506NTAPI
507VidScreenToBufferBlt(
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 */