Displaying Images on Screen Using UEFI Graphics Output Protocol (GOP)
In our previous exploration of UEFI application development, we examined how to call specific protocols within UEFI applications. This article advances that foundation by demonstrating how to utilize the Graphics Output Protocol (GOP) to display custom graphics directly on the screen before the operating system loads.
Project Structure Overview
Our project follows a standard EDK II (Extensible Firmware Interface Development Kit) structure:
MyPkg/
├── Application/
│ └── GopDrawApp/
│ ├── GopDrawApp.c # Main source code
│ └── GopDrawApp.inf # Module information file
├── MyPkg.dec # Package declaration file
└── MyPkg.dsc # Platform description fileThis organized structure separates concerns clearly: application code, module metadata, package definitions, and platform configuration each have their dedicated files.
Understanding the Graphics Output Protocol (GOP)
The Graphics Output Protocol represents one of the core interfaces in the UEFI specification, designed to take control of the graphics card and display graphical interfaces before the operating system boots. Let's explore GOP in two parts: first, we'll explain the display control principles in accessible language, then we'll analyze the actual GOP source code definitions from the UEFI specification.
How Screen Display Works
Every image you see on a screen consists of a finite grid of specific pixel points. A common resolution like 1920×1080 contains over two million individual pixels. Each pixel displays a specific color through different intensity combinations of the three primary colors: red, green, and blue (RGB).
Within the graphics memory (VRAM), there exists a dedicated region for storing current frame pixel color information, called the FrameBuffer (frame buffer). The screen display controller continuously reads data from this FrameBuffer and converts it into visible images on your monitor.
GOP's Role in This Process
GOP serves as a standardized interface that allows software running before the operating system (such as BootLoaders and UEFI applications) to access the FrameBuffer. Through GOP, we can:
- Obtain the FrameBuffer address – Know where in memory to write pixel data
- Query resolution information – Understand the screen dimensions
- Determine pixel format – Know how color data is structured
- Write directly to memory – Display custom images by writing pixel data
GOP Protocol Definition in UEFI
The official UEFI specification defines GOP as follows:
struct _EFI_GRAPHICS_OUTPUT_PROTOCOL {
EFI_GRAPHICS_OUTPUT_PROTOCOL_QUERY_MODE QueryMode;
EFI_GRAPHICS_OUTPUT_PROTOCOL_SET_MODE SetMode;
EFI_GRAPHICS_OUTPUT_PROTOCOL_BLT Blt;
EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE *Mode;
};Let's examine each component in detail:
QueryMode Function
Purpose: Enumerate all display modes supported by the graphics card.
Usage: Before setting a resolution, you must first call this function to discover available modes. You pass a ModeNumber (starting from 0), and it returns specific information for that mode (resolution, refresh rate, pixel format, etc.).
Why it matters: Different graphics cards support different resolutions. QueryMode ensures you only select modes your hardware actually supports.
SetMode Function
Purpose: Switch the display device to your specified mode.
Usage: After calling successfully, the framebuffer's resolution, pixel format, and other parameters change accordingly. You must pass a ModeNumber that was obtained through QueryMode—using an invalid mode number will cause the function to fail.
Important: Always validate mode numbers through QueryMode before calling SetMode.
Blt Function (Block Transfer)
Purpose: This is a highly efficient graphics drawing function.
Capabilities: It can copy, fill, or transform rectangular pixel blocks:
- Within the framebuffer itself
- Between the framebuffer and memory buffers
- With format conversion
Performance: Blt operations are typically hardware-accelerated, making them significantly faster than direct framebuffer writes for certain operations.
Mode Structure (Read-Only Status Information)
The Mode member provides current display state information that you can query at any time:
typedef struct {
UINT32 MaxMode; // Maximum number of modes supported by QueryMode
UINT32 Mode; // Currently active mode number
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info; // Pointer to current mode detailed information
UINTN SizeOfInfo; // Size of the Info structure
EFI_PHYSICAL_ADDRESS FrameBufferBase; // Framebuffer base address (physical address in VRAM)
UINTN FrameBufferSize; // Framebuffer size in bytes
} EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE;FrameBufferBase: This is the physical memory address where you write pixel data to display images on screen.
FrameBufferSize: Total size of the framebuffer in bytes, useful for bounds checking.
Typical GOP Usage Workflow
A standard GOP implementation follows these steps:
- Enumeration: Call
Gop->QueryMode()to retrieve all supported resolutions - Selection: Call
Gop->SetMode()to switch to your desired resolution (e.g., 1920×1080) - Information Retrieval: Access
Gop->Mode->FrameBufferBaseandGop->Mode->Info->PixelFormatto know where to write data and in what format - Drawing: Write pixel data directly to FrameBufferBase, or call
Gop->Blt()for efficient graphics operations
Complete Source Code Example
The following comprehensive example demonstrates the entire GOP workflow from protocol discovery to actual screen drawing.
Main Application Code (GopDrawApp.c)
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/UefiApplicationEntryPoint.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Protocol/GraphicsOutput.h>
#define DRAW
EFI_STATUS
EFIAPI
UefiMain (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop;
UINT32 MaxMode;
UINT32 ModeNumber;
UINTN SizeOfInfo;
UINT32 SelectedMode = 0;
UINT32 Horizontal = 0;
UINT32 Vertical = 0;
UINTN Index;
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *ModeInfo = NULL;
// ========================================
// Step 1: Obtain GOP Protocol Pointer
// ========================================
Status = gBS->LocateProtocol(
&gEfiGraphicsOutputProtocolGuid,
NULL,
(VOID**)&Gop
);
if (EFI_ERROR(Status)) {
Print(L"Failed to locate GOP protocol\n");
return Status;
}
Print(L"GOP protocol found successfully!\n");
// ========================================
// Step 2: Enumerate All Display Modes
// ========================================
Print(L"=== Enumerating All Display Modes ===\n");
Print(L"MaxMode = %d\n\n", Gop->Mode->MaxMode);
MaxMode = Gop->Mode->MaxMode;
for (ModeNumber = 0; ModeNumber < MaxMode; ModeNumber++) {
Status = Gop->QueryMode(Gop, ModeNumber, &SizeOfInfo, &ModeInfo);
if (EFI_ERROR(Status)) {
Print(L"QueryMode failed for mode %d: %r\n", ModeNumber, Status);
continue;
}
Print(L"Mode %2d: %4d x %4d, PixelFormat = %d\n",
ModeNumber,
ModeInfo->HorizontalResolution,
ModeInfo->VerticalResolution,
ModeInfo->PixelFormat
);
// Note: ModeInfo memory is allocated internally by GOP, no manual freeing required
}
// ========================================
// Step 3: Set Display Mode
// ========================================
Status = Gop->SetMode(Gop, SelectedMode);
if (EFI_ERROR(Status)) {
Print(L"Failed to set mode %d: %r\n", SelectedMode, Status);
return Status;
}
Horizontal = Gop->Mode->Info->HorizontalResolution;
Vertical = Gop->Mode->Info->VerticalResolution;
Print(L"Selected mode %d with resolution %d x %d\n", SelectedMode, Horizontal, Vertical);
Print(L"Mode %d set successfully!\n", SelectedMode);
// ========================================
// Step 4: Display Current Mode Information
// ========================================
Print(L"\n=== Current Mode Information ===\n");
Print(L"Current Mode Number: %d\n", Gop->Mode->Mode);
Print(L"FrameBuffer Base: 0x%016lx\n", Gop->Mode->FrameBufferBase);
Print(L"FrameBuffer Size: %d bytes (%.2f MB)\n",
Gop->Mode->FrameBufferSize,
(double)Gop->Mode->FrameBufferSize / (1024 * 1024));
Print(L"Horizontal Resolution: %d\n", Gop->Mode->Info->HorizontalResolution);
Print(L"Vertical Resolution: %d\n", Gop->Mode->Info->VerticalResolution);
Print(L"Pixel Format: %d\n", Gop->Mode->Info->PixelFormat);
// Explain pixel format
switch (Gop->Mode->Info->PixelFormat) {
case PixelRedGreenBlueReserved8BitPerColor:
Print(L" -> Pixel Format: RGB (8:8:8, with reserved byte)\n");
break;
case PixelBlueGreenRedReserved8BitPerColor:
Print(L" -> Pixel Format: BGR (most common on PC)\n");
break;
case PixelBitMask:
Print(L" -> Pixel Format: Custom bitmask\n");
break;
case PixelBltOnly:
Print(L" -> Pixel Format: Blt only (no direct framebuffer access)\n");
break;
default:
Print(L" -> Pixel Format: Unknown\n");
}
Print(L"\nPress any key to continue...\n");
SystemTable->ConIn->Reset(SystemTable->ConIn, FALSE);
// Wait for user to press a key
SystemTable->BootServices->WaitForEvent(1, &SystemTable->ConIn->WaitForKey, &Index);
#ifdef DRAW
// ========================================
// Step 5: Direct Framebuffer Drawing
// ========================================
UINT32 *FrameBuffer = (UINT32*)Gop->Mode->FrameBufferBase;
UINT32 PixelsPerScanLine = Gop->Mode->Info->PixelsPerScanLine;
UINT32 x, y;
// Draw gradient background
for (y = 0; y < Vertical; y++) {
for (x = 0; x < Horizontal; x++) {
UINT8 r = (UINT8)(x * 255 / Horizontal);
UINT8 g = (UINT8)(y * 255 / Vertical);
UINT8 b = 0x80;
UINT32 color = (r << 16) | (g << 8) | b;
FrameBuffer[y * PixelsPerScanLine + x] = color;
}
}
// Draw center cross
UINT32 cx = Horizontal / 2;
UINT32 cy = Vertical / 2;
// Horizontal line (white)
for (x = 0; x < Horizontal; x++) {
FrameBuffer[cy * PixelsPerScanLine + x] = 0xFFFFFF;
}
// Vertical line (white)
for (y = 0; y < Vertical; y++) {
FrameBuffer[y * PixelsPerScanLine + cx] = 0xFFFFFF;
}
#endif // DRAW
// Keep display visible (infinite loop)
while(1);
return EFI_SUCCESS;
}Understanding the Drawing Code
The drawing section creates a visually appealing demonstration:
Gradient Background: The color of each pixel varies based on its position:
- Red component increases from left to right (0 to 255)
- Green component increases from top to bottom (0 to 255)
- Blue component remains constant at 0x80 (medium blue)
This creates a smooth color gradient across the entire screen.
Center Cross: Two white lines intersect at the screen center:
- Horizontal line spans the entire width at vertical center
- Vertical line spans the entire height at horizontal center
This provides a clear visual reference point.
Module Information File (GopDrawApp.inf)
[Defines]
INF_VERSION = 0x00010006
BASE_NAME = GopDrawApp
FILE_GUID = 4e397097-665f-4745-88c3-6305ac8623aa
MODULE_TYPE = UEFI_APPLICATION
VERSION_STRING = 1.0
ENTRY_POINT = UefiMain
[Sources]
GopDrawApp.c
[Packages]
MdePkg/MdePkg.dec
[LibraryClasses]
UefiApplicationEntryPoint
UefiLib
UefiBootServicesTableLibPackage Declaration File (MyPkg.dec)
[Defines]
DEC_SPECIFICATION = 0x00010005
PACKAGE_NAME = MyPkg
PACKAGE_GUID = a2ab400d-c171-43ec-a0cc-582527a93887
PACKAGE_VERSION = 1.0Platform Description File (MyPkg.dsc)
[Defines]
PLATFORM_NAME = MyPkg
PLATFORM_GUID = 87654321-4321-4321-4321-CBA987654321
PLATFORM_VERSION = 1.0
DSC_SPECIFICATION = 0x00010005
OUTPUT_DIRECTORY = Build/MyPkg
SUPPORTED_ARCHITECTURES = X64
BUILD_TARGETS = DEBUG|RELEASE
[LibraryClasses]
UefiLib|MdePkg/Library/UefiLib/UefiLib.inf
UefiApplicationEntryPoint|MdePkg/Library/UefiApplicationEntryPoint/UefiApplicationEntryPoint.inf
PrintLib|MdePkg/Library/BasePrintLib/BasePrintLib.inf
PcdLib|MdePkg/Library/BasePcdLibNull/BasePcdLibNull.inf
MemoryAllocationLib|MdePkg/Library/UefiMemoryAllocationLib/UefiMemoryAllocationLib.inf
DebugLib|MdePkg/Library/UefiDebugLibConOut/UefiDebugLibConOut.inf
BaseMemoryLib|MdePkg/Library/BaseMemoryLib/BaseMemoryLib.inf
BaseLib|MdePkg/Library/BaseLib/BaseLib.inf
UefiBootServicesTableLib|MdePkg/Library/UefiBootServicesTableLib/UefiBootServicesTableLib.inf
DevicePathLib|MdePkg/Library/UefiDevicePathLib/UefiDevicePathLib.inf
UefiRuntimeServicesTableLib|MdePkg/Library/UefiRuntimeServicesTableLib/UefiRuntimeServicesTableLib.inf
RegisterFilterLib|MdePkg/Library/RegisterFilterLibNull/RegisterFilterLibNull.inf
DebugPrintErrorLevelLib|MdePkg/Library/BaseDebugPrintErrorLevelLib/BaseDebugPrintErrorLevelLib.inf
[Components]
MyPkg/Application/GopDrawApp/GopDrawApp.infBuilding and Testing
Build Commands
# Navigate to EDK II base directory
cd /path/to/edk2
# Source the build environment
source edksetup.sh
# Build the package
build -p MyPkg/MyPkg.dsc -a X64 -t GCC5 -b RELEASEExpected Output
When the application runs successfully, you should see:
- Console output displaying all supported display modes
- Current mode information including framebuffer address and size
- Visual output showing the gradient background with white cross
- Supported graphics modes listed with their resolutions
Typical supported modes might include:
- Mode 0: 640 × 480
- Mode 1: 800 × 600
- Mode 2: 1024 × 768
- Mode 3: 1280 × 1024
- Mode 4: 1920 × 1080
Advanced Considerations
Pixel Format Handling
Different systems may use different pixel formats. The most common are:
PixelRedGreenBlueReserved8BitPerColor: Each pixel uses 32 bits with 8 bits each for red, green, blue, and a reserved byte.
PixelBlueGreenRedReserved8BitPerColor: Similar but with BGR order (common on PC systems).
PixelBitMask: Custom bitmask definition where you must read the specific mask values.
PixelBltOnly: No direct framebuffer access; you must use the Blt function for all drawing operations.
Performance Optimization
For better performance in production applications:
- Use Blt for bulk operations: Hardware-accelerated block transfers are faster than pixel-by-pixel writes
- Minimize mode switches: Each SetMode call may involve hardware reinitialization
- Cache mode information: Query once at startup, reuse throughout application lifetime
- Consider double buffering: For animations, render to off-screen buffer, then Blt to framebuffer
Error Handling Best Practices
Always check return statuses:
Status = Gop->QueryMode(Gop, ModeNumber, &SizeOfInfo, &ModeInfo);
if (EFI_ERROR(Status)) {
// Handle error appropriately
Print(L"Error querying mode: %r\n", Status);
continue;
}Common error codes:
EFI_INVALID_PARAMETER: Invalid mode number or null pointerEFI_UNSUPPORTED: Mode not supported by hardwareEFI_OUT_OF_RESOURCES: Insufficient memory
Conclusion
The Graphics Output Protocol provides a powerful, standardized interface for pre-boot graphics programming. By understanding GOP's structure and following the patterns demonstrated in this guide, you can create sophisticated UEFI applications with custom graphical interfaces.
The key concepts to remember:
- Always query modes before setting them
- Understand your pixel format for correct color rendering
- Use the framebuffer address for direct pixel manipulation
- Leverage Blt for efficient bulk operations
- Handle errors gracefully with proper status checks
With these foundations, you're ready to explore more advanced UEFI graphics programming, including image loading, font rendering, and interactive user interfaces—all before the operating system even begins to load.
The journey into low-level system programming continues. Steady progress!