diff --git a/drivers/base/bootvid/CMakeLists.txt b/drivers/base/bootvid/CMakeLists.txt index 82c284b8298..bea2843645d 100644 --- a/drivers/base/bootvid/CMakeLists.txt +++ b/drivers/base/bootvid/CMakeLists.txt @@ -68,3 +68,4 @@ if((ARCH STREQUAL "i386") OR (ARCH STREQUAL "amd64")) elseif(ARCH STREQUAL "arm") add_bootvid(bootvid arm/armbvid.cmake) endif() +add_bootvid(lfbbvid framebuf/lfbbvid.cmake) diff --git a/drivers/base/bootvid/framebuf/bootvid.c b/drivers/base/bootvid/framebuf/bootvid.c new file mode 100644 index 00000000000..90fce23d4d0 --- /dev/null +++ b/drivers/base/bootvid/framebuf/bootvid.c @@ -0,0 +1,538 @@ +/* + * 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 + */ + +#include "precomp.h" + +#define NDEBUG +#include + +/* Include the Boot-time (POST) display discovery helper functions */ +#include + +/* 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 TopDelta, + _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 = TopDelta * 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 < TopDelta; ++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_(Delta * Height) PUCHAR Buffer, + _In_ ULONG Left, + _In_ ULONG Top, + _In_ ULONG Width, + _In_ ULONG Height, + _In_ ULONG Delta) +{ + ULONG x, y; + + /* Clear the destination buffer */ + RtlZeroMemory(Buffer, Delta * Height); + + /* 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 * Delta; + + /* 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 */ diff --git a/drivers/base/bootvid/framebuf/framebuf.h b/drivers/base/bootvid/framebuf/framebuf.h new file mode 100644 index 00000000000..03aa2d57d61 --- /dev/null +++ b/drivers/base/bootvid/framebuf/framebuf.h @@ -0,0 +1,22 @@ +/* + * 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: Arch-specific header file + * COPYRIGHT: Copyright 2023-2026 Hermès Bélusca-Maïto + */ + +#pragma once + +VOID +InitPaletteWithTable( + _In_reads_(Count) const ULONG* Table, + _In_ ULONG Count); + +#define PrepareForSetPixel() + +VOID +SetPixel( + _In_ ULONG Left, + _In_ ULONG Top, + _In_ UCHAR Color); diff --git a/drivers/base/bootvid/framebuf/lfbbvid.cmake b/drivers/base/bootvid/framebuf/lfbbvid.cmake new file mode 100644 index 00000000000..aa4ac48ef83 --- /dev/null +++ b/drivers/base/bootvid/framebuf/lfbbvid.cmake @@ -0,0 +1,7 @@ + +set(MODULE_HEADER ${CMAKE_CURRENT_LIST_DIR}/framebuf.h) # For precomp.h +list(APPEND SOURCE + ${MODULE_HEADER} + ${CMAKE_CURRENT_LIST_DIR}/bootvid.c) + +set(REACTOS_STR_FILE_DESCRIPTION "Generic Linear FrameBuffer Boot Driver")