Files
2026-04-21 15:08:17 -05:00

539 lines
17 KiB
C

/*
* PROJECT: ReactOS Generic Framebuffer Boot Video Driver
* LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
* or MIT (https://spdx.org/licenses/MIT)
* PURPOSE: Main file
* COPYRIGHT: Copyright 2023-2026 Hermès Bélusca-Maïto <[email protected]>
*/
#include "precomp.h"
#define NDEBUG
#include <debug.h>
/* Include the Boot-time (POST) display discovery helper functions */
#include <drivers/bootvid/framebuf.c>
/* Scaling of the bootvid 640x480 default virtual screen to the larger video framebuffer */
#define SCALING_SUPPORT
#define SCALING_PROPORTIONAL
/* Keep borders black or controlled with palette */
// #define COLORED_BORDERS
/* GLOBALS ********************************************************************/
#define BB_PIXEL(x, y) \
((PUCHAR)BackBuffer + (y) * SCREEN_WIDTH + (x))
#define FB_PIXEL(x, y) \
((PUCHAR)FrameBufferStart + (PanV + VidpYScale * (y)) * BytesPerScanLine \
+ (PanH + VidpXScale * (x)) * BytesPerPixel)
static ULONG_PTR FrameBufferStart = 0;
static ULONG FrameBufferSize;
static ULONG ScreenWidth, ScreenHeight, BytesPerScanLine;
static UCHAR BytesPerPixel;
static PUCHAR BackBuffer = NULL;
static SIZE_T BackBufferSize;
#ifdef SCALING_SUPPORT
static USHORT VidpXScale = 1;
static USHORT VidpYScale = 1;
#else
#define VidpXScale 1
#define VidpYScale 1
#endif
static ULONG PanH, PanV;
static RGBQUAD CachedPalette[BV_MAX_COLORS];
/* PRIVATE FUNCTIONS *********************************************************/
static VOID
ApplyPalette(VOID)
{
PULONG Frame = (PULONG)FrameBufferStart;
ULONG x, y;
#ifdef COLORED_BORDERS
/* Top border */
for (x = 0; x < PanV * ScreenWidth; ++x)
{
*Frame++ = CachedPalette[BV_COLOR_BLACK];
}
/* Left border */
for (y = 0; y < VidpYScale * SCREEN_HEIGHT; ++y)
{
// Frame = (PULONG)(FrameBufferStart + FB_OFFSET(-(LONG)PanH, y));
Frame = (PULONG)(FrameBufferStart + (PanV + y) * BytesPerScanLine);
for (x = 0; x < PanH; ++x)
{
*Frame++ = CachedPalette[BV_COLOR_BLACK];
}
}
#endif // COLORED_BORDERS
/* Screen redraw */
PUCHAR Back = BackBuffer;
for (y = 0; y < SCREEN_HEIGHT; ++y)
{
Frame = (PULONG)FB_PIXEL(0, y);
PULONG Pixel = Frame;
for (x = 0; x < SCREEN_WIDTH; ++x)
{
for (ULONG j = VidpXScale; j > 0; --j)
*Pixel++ = CachedPalette[*Back];
Back++;
}
Pixel = Frame;
for (ULONG i = VidpYScale-1; i > 0; --i)
{
Pixel = (PULONG)((ULONG_PTR)Pixel + BytesPerScanLine);
RtlCopyMemory(Pixel, Frame, VidpXScale * SCREEN_WIDTH * BytesPerPixel);
}
}
#ifdef COLORED_BORDERS
/* Right border */
for (y = 0; y < VidpYScale * SCREEN_HEIGHT; ++y)
{
// Frame = (PULONG)(FrameBufferStart + FB_OFFSET(SCREEN_WIDTH, y));
Frame = (PULONG)(FrameBufferStart + (PanV + y) * BytesPerScanLine + (PanH + VidpXScale * SCREEN_WIDTH) * BytesPerPixel);
for (x = 0; x < PanH; ++x)
{
*Frame++ = CachedPalette[BV_COLOR_BLACK];
}
}
/* Bottom border */
// Frame = (PULONG)(FrameBufferStart + FB_OFFSET(-(LONG)PanH, SCREEN_HEIGHT));
Frame = (PULONG)(FrameBufferStart + (PanV + VidpYScale * SCREEN_HEIGHT) * BytesPerScanLine);
for (x = 0; x < PanV * ScreenWidth; ++x)
{
*Frame++ = CachedPalette[BV_COLOR_BLACK];
}
#endif // COLORED_BORDERS
}
/* PUBLIC FUNCTIONS **********************************************************/
BOOLEAN
NTAPI
VidInitialize(
_In_ BOOLEAN SetMode)
{
PHYSICAL_ADDRESS FrameBuffer;
PHYSICAL_ADDRESS VramAddress;
ULONG VramSize;
CM_FRAMEBUF_DEVICE_DATA VideoConfigData; /* Configuration data from hardware tree */
INTERFACE_TYPE Interface;
ULONG BusNumber;
NTSTATUS Status;
/* Find boot-time framebuffer display information from the LoaderBlock */
Status = FindBootDisplay(&VramAddress,
&VramSize,
&VideoConfigData,
NULL, // MonitorConfigData
&Interface,
&BusNumber);
if (!NT_SUCCESS(Status))
{
DPRINT1("Boot framebuffer does not exist!\n");
return FALSE;
}
/* The VRAM address must be page-aligned */
if (VramAddress.QuadPart % PAGE_SIZE != 0) // DPRINTed for diagnostics on some systems
DPRINT1("** VramAddress 0x%I64X isn't PAGE_SIZE aligned\n", VramAddress.QuadPart);
ASSERT(VramAddress.QuadPart % PAGE_SIZE == 0);
if (VramSize % PAGE_SIZE != 0)
DPRINT1("** VramSize %lu (0x%lx) isn't multiple of PAGE_SIZE\n", VramSize, VramSize);
// ASSERT(VramSize % PAGE_SIZE == 0); // This assert may fail, e.g. 800x600@32bpp UEFI GOP display
/* Retrieve the framebuffer address, its visible screen dimensions, and its attributes */
FrameBuffer.QuadPart = VramAddress.QuadPart + VideoConfigData.FrameBufferOffset;
ScreenWidth = VideoConfigData.ScreenWidth;
ScreenHeight = VideoConfigData.ScreenHeight;
if (ScreenWidth < SCREEN_WIDTH || ScreenHeight < SCREEN_HEIGHT)
{
DPRINT1("Unsupported screen resolution!\n");
return FALSE;
}
BytesPerPixel = (VideoConfigData.BitsPerPixel + 7) / 8; // Round up to nearest byte.
ASSERT(BytesPerPixel >= 1 && BytesPerPixel <= 4);
if (BytesPerPixel != 4)
{
UNIMPLEMENTED;
DPRINT1("Unsupported BytesPerPixel = %u\n", BytesPerPixel);
return FALSE;
}
ASSERT(ScreenWidth <= VideoConfigData.PixelsPerScanLine);
BytesPerScanLine = VideoConfigData.PixelsPerScanLine * BytesPerPixel;
if (BytesPerScanLine < 1)
{
DPRINT1("Invalid BytesPerScanLine = %lu\n", BytesPerScanLine);
return FALSE;
}
/* Compute the visible framebuffer size */
FrameBufferSize = ScreenHeight * BytesPerScanLine;
/* Verify that the framebuffer actually fits inside the video RAM */
if (FrameBuffer.QuadPart + FrameBufferSize > VramAddress.QuadPart + VramSize)
{
DPRINT1("The framebuffer exceeds video memory bounds!\n");
return FALSE;
}
/* Translate the framebuffer from bus-relative to physical address */
PHYSICAL_ADDRESS TranslatedAddress;
ULONG AddressSpace = 0; /* MMIO space */
if (!BootTranslateBusAddress(Interface,
BusNumber,
FrameBuffer,
&AddressSpace,
&TranslatedAddress))
{
DPRINT1("Could not translate framebuffer bus address 0x%I64X\n", FrameBuffer.QuadPart);
return FALSE;
}
/* Map it into system space if necessary */
ULONG MappedSize = 0;
PVOID FrameBufferBase = NULL;
if (AddressSpace == 0)
{
/* Calculate page-aligned address and size for MmMapIoSpace() */
FrameBuffer.HighPart = TranslatedAddress.HighPart;
FrameBuffer.LowPart = ALIGN_DOWN_BY(TranslatedAddress.LowPart, PAGE_SIZE);
MappedSize = FrameBufferSize;
MappedSize += (ULONG)(TranslatedAddress.QuadPart - FrameBuffer.QuadPart); // BYTE_OFFSET()
MappedSize = ROUND_TO_PAGES(MappedSize);
/* Essentially MmMapVideoDisplay() */
FrameBufferBase = MmMapIoSpace(FrameBuffer, MappedSize, MmFrameBufferCached);
if (!FrameBufferBase)
FrameBufferBase = MmMapIoSpace(FrameBuffer, MappedSize, MmNonCached);
if (!FrameBufferBase)
{
DPRINT1("Could not map framebuffer 0x%I64X (%lu bytes)\n",
FrameBuffer.QuadPart, MappedSize);
goto Failure;
}
FrameBufferStart = (ULONG_PTR)FrameBufferBase;
FrameBufferStart += (TranslatedAddress.QuadPart - FrameBuffer.QuadPart); // BYTE_OFFSET()
}
else
{
/* The base is the translated address, no need to map */
FrameBufferStart = (ULONG_PTR)TranslatedAddress.QuadPart;
}
/*
* Reserve off-screen area for the backbuffer that contains
* 8-bit indexed color screen image, plus preserved row data.
*/
BackBufferSize = SCREEN_WIDTH * (SCREEN_HEIGHT + (BOOTCHAR_HEIGHT + 1));
/* If there is enough video memory in the physical framebuffer,
* place the backbuffer in the hidden part of the framebuffer,
* otherwise allocate a zone for the backbuffer. */
if (VideoConfigData.FrameBufferOffset + FrameBufferSize + BackBufferSize
<= ((AddressSpace == 0) ? MappedSize : VramSize))
{
/* Backbuffer placed following the framebuffer in the hidden part */
BackBuffer = (PUCHAR)(FrameBufferStart + FrameBufferSize);
// BackBuffer = (PUCHAR)(VramAddress + VramSize - BackBufferSize); // Or at the end of VRAM.
}
else
{
/* Allocate the backbuffer */
PHYSICAL_ADDRESS NullAddress = {{0, 0}};
PHYSICAL_ADDRESS HighestAddress = {{-1, -1}};
BackBuffer = MmAllocateContiguousMemorySpecifyCache(
BackBufferSize, NullAddress, HighestAddress,
NullAddress, MmNonCached);
if (!BackBuffer)
{
DPRINT1("Could not allocate backbuffer (size: %lu)\n", (ULONG)BackBufferSize);
goto Failure;
}
}
#ifdef SCALING_SUPPORT
/* Compute autoscaling; only integer (not fractional) scaling is supported */
VidpXScale = ScreenWidth / SCREEN_WIDTH;
VidpYScale = ScreenHeight / SCREEN_HEIGHT;
ASSERT(VidpXScale >= 1);
ASSERT(VidpYScale >= 1);
#ifdef SCALING_PROPORTIONAL
VidpXScale = min(VidpXScale, VidpYScale);
VidpYScale = VidpXScale;
#endif
DPRINT1("Scaling X = %hu, Y = %hu\n", VidpXScale, VidpYScale);
#endif // SCALING_SUPPORT
/* Calculate left/right and top/bottom border values
* to keep the displayed area centered on the screen */
PanH = (ScreenWidth - VidpXScale * SCREEN_WIDTH) / 2;
PanV = (ScreenHeight - VidpYScale * SCREEN_HEIGHT) / 2;
DPRINT1("Borders X = %lu, Y = %lu\n", PanH, PanV);
/* Reset the video mode if requested */
if (SetMode)
VidResetDisplay(TRUE);
return TRUE;
Failure:
/* We failed somewhere; unmap the framebuffer if we mapped it */
if (FrameBufferBase && (AddressSpace == 0))
MmUnmapIoSpace(FrameBufferBase, MappedSize);
return FALSE;
}
VOID
NTAPI
VidCleanUp(VOID)
{
/* Just fill the screen black */
VidSolidColorFill(0, 0, SCREEN_WIDTH - 1, SCREEN_HEIGHT - 1, BV_COLOR_BLACK);
}
VOID
ResetDisplay(
_In_ BOOLEAN SetMode)
{
RtlZeroMemory(BackBuffer, BackBufferSize);
RtlZeroMemory((PVOID)FrameBufferStart, FrameBufferSize);
/* Re-initialize the palette and fill the screen black */
InitializePalette();
VidSolidColorFill(0, 0, SCREEN_WIDTH - 1, SCREEN_HEIGHT - 1, BV_COLOR_BLACK);
}
VOID
InitPaletteWithTable(
_In_reads_(Count) const ULONG* Table,
_In_ ULONG Count)
{
const ULONG* Entry = Table;
ULONG i;
BOOLEAN HasChanged = FALSE;
for (i = 0; i < Count; i++, Entry++)
{
HasChanged |= !!((CachedPalette[i] ^ *Entry) & 0x00FFFFFF);
CachedPalette[i] = *Entry | 0xFF000000;
}
/* Re-apply the palette if it has changed */
if (HasChanged)
ApplyPalette();
}
VOID
SetPixel(
_In_ ULONG Left,
_In_ ULONG Top,
_In_ UCHAR Color)
{
PUCHAR Back = BB_PIXEL(Left, Top);
PULONG Frame = (PULONG)FB_PIXEL(Left, Top);
*Back = Color;
for (ULONG i = VidpYScale; i > 0; --i)
{
PULONG Pixel = Frame;
for (ULONG j = VidpXScale; j > 0; --j)
*Pixel++ = CachedPalette[Color];
Frame = (PULONG)((ULONG_PTR)Frame + BytesPerScanLine);
}
}
VOID
PreserveRow(
_In_ ULONG CurrentTop,
_In_ ULONG Height,
_In_ BOOLEAN Restore)
{
PUCHAR NewPosition, OldPosition;
/* Calculate the position in memory for the row */
if (Restore)
{
/* Restore the row by copying back the contents saved off-screen */
NewPosition = BB_PIXEL(0, CurrentTop);
OldPosition = BB_PIXEL(0, SCREEN_HEIGHT);
}
else
{
/* Preserve the row by saving its contents off-screen */
NewPosition = BB_PIXEL(0, SCREEN_HEIGHT);
OldPosition = BB_PIXEL(0, CurrentTop);
}
/* Set the count and copy the pixel data back to the other position in the backbuffer */
ULONG Count = Height * SCREEN_WIDTH;
RtlCopyMemory(NewPosition, OldPosition, Count);
/* On restore, mirror the backbuffer changes to the framebuffer */
if (Restore)
{
NewPosition = BB_PIXEL(0, CurrentTop);
for (ULONG y = 0; y < Height; ++y)
{
PULONG Frame = (PULONG)FB_PIXEL(0, CurrentTop + y);
PULONG Pixel = Frame;
for (Count = 0; Count < SCREEN_WIDTH; ++Count)
{
for (ULONG j = VidpXScale; j > 0; --j)
*Pixel++ = CachedPalette[*NewPosition];
NewPosition++;
}
Pixel = Frame;
for (ULONG i = VidpYScale-1; i > 0; --i)
{
Pixel = (PULONG)((ULONG_PTR)Pixel + BytesPerScanLine);
RtlCopyMemory(Pixel, Frame, VidpXScale * SCREEN_WIDTH * BytesPerPixel);
}
}
}
}
VOID
DoScroll(
_In_ ULONG Scroll)
{
ULONG RowSize = VidpScrollRegion.Right - VidpScrollRegion.Left + 1;
/* Calculate the position in memory for the row */
PUCHAR OldPosition = BB_PIXEL(VidpScrollRegion.Left, VidpScrollRegion.Top + Scroll);
PUCHAR NewPosition = BB_PIXEL(VidpScrollRegion.Left, VidpScrollRegion.Top);
/* Start loop */
for (ULONG Top = VidpScrollRegion.Top; Top <= VidpScrollRegion.Bottom; ++Top)
{
/* Scroll the row */
RtlCopyMemory(NewPosition, OldPosition, RowSize);
PULONG Frame = (PULONG)FB_PIXEL(VidpScrollRegion.Left, Top);
PULONG Pixel = Frame;
for (ULONG Count = 0; Count < RowSize; ++Count)
{
for (ULONG j = VidpXScale; j > 0; --j)
*Pixel++ = CachedPalette[NewPosition[Count]];
}
Pixel = Frame;
for (ULONG i = VidpYScale-1; i > 0; --i)
{
Pixel = (PULONG)((ULONG_PTR)Pixel + BytesPerScanLine);
RtlCopyMemory(Pixel, Frame, VidpXScale * RowSize * BytesPerPixel);
}
OldPosition += SCREEN_WIDTH;
NewPosition += SCREEN_WIDTH;
}
}
VOID
DisplayCharacter(
_In_ CHAR Character,
_In_ ULONG Left,
_In_ ULONG Top,
_In_ ULONG TextColor,
_In_ ULONG BackColor)
{
/* Get the font line for this character */
const UCHAR* FontChar = GetFontPtr(Character);
/* Loop each pixel height */
for (ULONG y = Top; y < Top + BOOTCHAR_HEIGHT; ++y, FontChar += FONT_PTR_DELTA)
{
/* Loop each pixel width */
ULONG x = Left;
for (UCHAR bit = 1 << (BOOTCHAR_WIDTH - 1); bit > 0; bit >>= 1, ++x)
{
/* If we should draw this pixel, use the text color. Otherwise
* this is a background pixel, draw it unless it's transparent. */
if (*FontChar & bit)
SetPixel(x, y, (UCHAR)TextColor);
else if (BackColor < BV_COLOR_NONE)
SetPixel(x, y, (UCHAR)BackColor);
}
}
}
VOID
NTAPI
VidSolidColorFill(
_In_ ULONG Left,
_In_ ULONG Top,
_In_ ULONG Right,
_In_ ULONG Bottom,
_In_ UCHAR Color)
{
for (; Top <= Bottom; ++Top)
{
PUCHAR Back = BB_PIXEL(Left, Top);
// NOTE: Assumes 32bpp
PULONG Frame = (PULONG)FB_PIXEL(Left, Top);
PULONG Pixel = Frame;
for (ULONG L = Left; L <= Right; ++L)
{
*Back++ = Color;
for (ULONG j = VidpXScale; j > 0; --j)
*Pixel++ = CachedPalette[Color];
}
Pixel = Frame;
for (ULONG i = VidpYScale-1; i > 0; --i)
{
Pixel = (PULONG)((ULONG_PTR)Pixel + BytesPerScanLine);
RtlCopyMemory(Pixel, Frame, VidpXScale * (Right - Left + 1) * BytesPerPixel);
}
}
}
VOID
NTAPI
VidScreenToBufferBlt(
_Out_writes_bytes_all_(Height * Stride) PUCHAR Buffer,
_In_ ULONG Left,
_In_ ULONG Top,
_In_ ULONG Width,
_In_ ULONG Height,
_In_ ULONG Stride)
{
ULONG x, y;
/* Clear the destination buffer */
RtlZeroMemory(Buffer, Height * Stride);
/* Start the outer Y height loop */
for (y = 0; y < Height; ++y)
{
/* Set current scanline */
PUCHAR Back = BB_PIXEL(Left, Top + y);
PUCHAR Buf = Buffer + y * Stride;
/* Start the X inner loop */
for (x = 0; x < Width; x += sizeof(USHORT))
{
/* Read the current value */
*Buf = (*Back++ & 0xF) << 4;
*Buf |= *Back++ & 0xF;
Buf++;
}
}
}
/* EOF */