Chapter 14: Graphics Output (GOP)
Access the framebuffer, draw pixels, and perform block transfers using the Graphics Output Protocol.
14.1 What Is GOP?
The Graphics Output Protocol (GOP) is UEFI’s primary graphics interface. It replaces the legacy VGA BIOS INT 10h interface and provides:
- Enumeration and selection of video modes (resolution, pixel format)
- Direct framebuffer access for pixel-level rendering
- Block Transfer (BLT) operations for efficient rectangle copying and filling
graph LR
A[UEFI Application] --> B[EFI_GRAPHICS_OUTPUT_PROTOCOL]
B --> C[GOP Driver]
C --> D[GPU Hardware / Framebuffer]
B --> E[Mode Info: Resolution, Pixel Format]
B --> F[BLT Operations]
B --> G[Direct Framebuffer Access]
GOP is available during Boot Services. After ExitBootServices(), the framebuffer pointer remains valid, but the protocol itself can no longer be called. Operating system boot loaders typically query GOP, store the framebuffer address, and pass it to the OS kernel.
14.2 Locating the GOP Protocol
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Protocol/GraphicsOutput.h>
EFI_STATUS
GetGop(
OUT EFI_GRAPHICS_OUTPUT_PROTOCOL **Gop
)
{
return gBS->LocateProtocol(
&gEfiGraphicsOutputProtocolGuid,
NULL,
(VOID **)Gop
);
}
Not all systems provide GOP. Headless servers, serial-only debug boards, and some virtual machines may lack a graphics device. Always check the return status.
14.3 Querying Video Modes
14.3.1 The Mode Information Structure
typedef struct {
UINT32 MaxMode; // Number of available modes
UINT32 Mode; // Current mode number
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info; // Current mode details
UINTN SizeOfInfo;
EFI_PHYSICAL_ADDRESS FrameBufferBase; // Physical address
UINTN FrameBufferSize;
} EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE;
typedef struct {
UINT32 Version;
UINT32 HorizontalResolution;
UINT32 VerticalResolution;
EFI_GRAPHICS_PIXEL_FORMAT PixelFormat;
EFI_PIXEL_BITMASK PixelInformation; // Only for PixelBitMask format
UINT32 PixelsPerScanLine; // May differ from HorizontalResolution
} EFI_GRAPHICS_OUTPUT_MODE_INFORMATION;
14.3.2 Enumerating All Modes
EFI_STATUS
ListVideoModes(
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop
)
{
EFI_STATUS Status;
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info;
UINTN InfoSize;
Print(L"Available video modes (%d total):\n\n", Gop->Mode->MaxMode);
Print(L" Mode Resolution Pixel Format Pixels/Scanline\n");
Print(L" ---- ---------- ------------ ---------------\n");
for (UINT32 Mode = 0; Mode < Gop->Mode->MaxMode; Mode++) {
Status = Gop->QueryMode(Gop, Mode, &InfoSize, &Info);
if (EFI_ERROR(Status)) {
continue;
}
CHAR16 *FormatStr;
switch (Info->PixelFormat) {
case PixelRedGreenBlueReserved8BitPerColor:
FormatStr = L"RGBx (32-bit)";
break;
case PixelBlueGreenRedReserved8BitPerColor:
FormatStr = L"BGRx (32-bit)";
break;
case PixelBitMask:
FormatStr = L"BitMask";
break;
case PixelBltOnly:
FormatStr = L"BLT Only (no FB)";
break;
default:
FormatStr = L"Unknown";
break;
}
Print(L" [%2d] %4d x %-4d %-20s %d\n",
Mode,
Info->HorizontalResolution,
Info->VerticalResolution,
FormatStr,
Info->PixelsPerScanLine);
gBS->FreePool(Info);
}
Print(L"\nCurrent mode: %d\n", Gop->Mode->Mode);
Print(L"Framebuffer at: 0x%lx (%d bytes)\n",
Gop->Mode->FrameBufferBase,
Gop->Mode->FrameBufferSize);
return EFI_SUCCESS;
}
14.3.3 Setting a Video Mode
EFI_STATUS
SetVideoMode(
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop,
IN UINT32 DesiredWidth,
IN UINT32 DesiredHeight
)
{
EFI_STATUS Status;
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info;
UINTN InfoSize;
for (UINT32 Mode = 0; Mode < Gop->Mode->MaxMode; Mode++) {
Status = Gop->QueryMode(Gop, Mode, &InfoSize, &Info);
if (EFI_ERROR(Status)) {
continue;
}
if (Info->HorizontalResolution == DesiredWidth &&
Info->VerticalResolution == DesiredHeight &&
Info->PixelFormat != PixelBltOnly)
{
gBS->FreePool(Info);
return Gop->SetMode(Gop, Mode);
}
gBS->FreePool(Info);
}
return EFI_NOT_FOUND;
}
14.4 Pixel Formats
UEFI defines four pixel formats. Understanding them is critical for correct framebuffer access.
graph TB
subgraph "PixelBlueGreenRedReserved8BitPerColor (BGRx)"
B1["Byte 0: Blue"] --- B2["Byte 1: Green"] --- B3["Byte 2: Red"] --- B4["Byte 3: Reserved"]
end
subgraph "PixelRedGreenBlueReserved8BitPerColor (RGBx)"
R1["Byte 0: Red"] --- R2["Byte 1: Green"] --- R3["Byte 2: Blue"] --- R4["Byte 3: Reserved"]
end
| Format | Byte Order | Notes |
|---|---|---|
PixelBlueGreenRedReserved8BitPerColor |
B, G, R, x | Most common (matches Windows BMP order) |
PixelRedGreenBlueReserved8BitPerColor |
R, G, B, x | Standard RGB order |
PixelBitMask |
Custom bitmask | Use PixelInformation to decode |
PixelBltOnly |
N/A | No framebuffer; only BLT operations work |
The
EFI_GRAPHICS_OUTPUT_BLT_PIXELstructure always uses BGRx order (Blue, Green, Red, Reserved), regardless of the framebuffer’s native pixel format. The BLT engine handles conversion automatically.
typedef struct {
UINT8 Blue;
UINT8 Green;
UINT8 Red;
UINT8 Reserved;
} EFI_GRAPHICS_OUTPUT_BLT_PIXEL;
14.5 Direct Framebuffer Access
For maximum performance, you can write directly to the framebuffer. This bypasses the BLT engine but requires you to handle pixel format conversion yourself.
14.5.1 Computing Pixel Addresses
/**
Calculate the address of a pixel in the framebuffer.
@param[in] Gop The GOP protocol instance.
@param[in] X Horizontal pixel coordinate.
@param[in] Y Vertical pixel coordinate.
@return Pointer to the pixel's first byte in the framebuffer.
**/
UINT32 *
GetPixelAddress(
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop,
IN UINT32 X,
IN UINT32 Y
)
{
UINT32 *FrameBuffer = (UINT32 *)(UINTN)Gop->Mode->FrameBufferBase;
UINT32 PixelsPerScanLine = Gop->Mode->Info->PixelsPerScanLine;
//
// PixelsPerScanLine may be larger than HorizontalResolution due to
// alignment padding. Always use PixelsPerScanLine for stride calculation.
//
return &FrameBuffer[Y * PixelsPerScanLine + X];
}
14.5.2 Drawing a Single Pixel
VOID
PutPixel(
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop,
IN UINT32 X,
IN UINT32 Y,
IN UINT8 Red,
IN UINT8 Green,
IN UINT8 Blue
)
{
if (X >= Gop->Mode->Info->HorizontalResolution ||
Y >= Gop->Mode->Info->VerticalResolution)
{
return; // Clip out-of-bounds pixels
}
UINT32 *Pixel = GetPixelAddress(Gop, X, Y);
switch (Gop->Mode->Info->PixelFormat) {
case PixelBlueGreenRedReserved8BitPerColor:
*Pixel = (UINT32)Blue | ((UINT32)Green << 8) | ((UINT32)Red << 16);
break;
case PixelRedGreenBlueReserved8BitPerColor:
*Pixel = (UINT32)Red | ((UINT32)Green << 8) | ((UINT32)Blue << 16);
break;
default:
break; // PixelBitMask and PixelBltOnly need special handling
}
}
14.6 Drawing Primitives
14.6.1 Horizontal and Vertical Lines
VOID
DrawHorizontalLine(
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop,
IN UINT32 X1,
IN UINT32 X2,
IN UINT32 Y,
IN UINT8 Red,
IN UINT8 Green,
IN UINT8 Blue
)
{
if (X1 > X2) {
UINT32 Tmp = X1; X1 = X2; X2 = Tmp;
}
for (UINT32 X = X1; X <= X2; X++) {
PutPixel(Gop, X, Y, Red, Green, Blue);
}
}
VOID
DrawVerticalLine(
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop,
IN UINT32 X,
IN UINT32 Y1,
IN UINT32 Y2,
IN UINT8 Red,
IN UINT8 Green,
IN UINT8 Blue
)
{
if (Y1 > Y2) {
UINT32 Tmp = Y1; Y1 = Y2; Y2 = Tmp;
}
for (UINT32 Y = Y1; Y <= Y2; Y++) {
PutPixel(Gop, X, Y, Red, Green, Blue);
}
}
14.6.2 Bresenham’s Line Algorithm
For arbitrary lines between any two points:
VOID
DrawLine(
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop,
IN INT32 X0,
IN INT32 Y0,
IN INT32 X1,
IN INT32 Y1,
IN UINT8 Red,
IN UINT8 Green,
IN UINT8 Blue
)
{
INT32 dx = (X1 > X0) ? (X1 - X0) : (X0 - X1);
INT32 dy = (Y1 > Y0) ? (Y1 - Y0) : (Y0 - Y1);
INT32 sx = (X0 < X1) ? 1 : -1;
INT32 sy = (Y0 < Y1) ? 1 : -1;
INT32 err = dx - dy;
while (TRUE) {
PutPixel(Gop, (UINT32)X0, (UINT32)Y0, Red, Green, Blue);
if (X0 == X1 && Y0 == Y1) {
break;
}
INT32 e2 = 2 * err;
if (e2 > -dy) {
err -= dy;
X0 += sx;
}
if (e2 < dx) {
err += dx;
Y0 += sy;
}
}
}
14.6.3 Filled Rectangle
VOID
FillRect(
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop,
IN UINT32 X,
IN UINT32 Y,
IN UINT32 Width,
IN UINT32 Height,
IN UINT8 Red,
IN UINT8 Green,
IN UINT8 Blue
)
{
for (UINT32 Row = Y; Row < Y + Height; Row++) {
for (UINT32 Col = X; Col < X + Width; Col++) {
PutPixel(Gop, Col, Row, Red, Green, Blue);
}
}
}
14.7 BLT Operations
The BLT (Block Transfer) interface is more efficient than per-pixel framebuffer writes and handles pixel format conversion automatically. It operates on EFI_GRAPHICS_OUTPUT_BLT_PIXEL arrays (always BGRx order).
14.7.1 BLT Operation Types
typedef enum {
EfiBltVideoFill, // Fill a rectangle with a single color
EfiBltVideoToBltBuffer, // Copy from framebuffer to memory buffer
EfiBltBufferToVideo, // Copy from memory buffer to framebuffer
EfiBltVideoToVideo, // Copy one framebuffer region to another
} EFI_GRAPHICS_OUTPUT_BLT_OPERATION;
graph LR
subgraph "BLT Operations"
A["EfiBltVideoFill\n(Fill rect with color)"]
B["EfiBltVideoToBltBuffer\n(Screen -> Memory)"]
C["EfiBltBufferToVideo\n(Memory -> Screen)"]
D["EfiBltVideoToVideo\n(Screen -> Screen)"]
end
14.7.2 Filling a Rectangle with BLT
EFI_STATUS
BltFillRect(
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop,
IN UINT32 X,
IN UINT32 Y,
IN UINT32 Width,
IN UINT32 Height,
IN UINT8 Red,
IN UINT8 Green,
IN UINT8 Blue
)
{
EFI_GRAPHICS_OUTPUT_BLT_PIXEL FillColor;
FillColor.Red = Red;
FillColor.Green = Green;
FillColor.Blue = Blue;
FillColor.Reserved = 0;
//
// For EfiBltVideoFill, the BltBuffer points to a single pixel
// that defines the fill color. SourceX/SourceY are ignored.
//
return Gop->Blt(
Gop,
&FillColor, // BltBuffer
EfiBltVideoFill, // BltOperation
0, 0, // SourceX, SourceY (ignored for Fill)
X, Y, // DestinationX, DestinationY
Width, Height, // Width, Height
0 // Delta (ignored for Fill)
);
}
14.7.3 Clearing the Screen to a Color
EFI_STATUS
ClearScreen(
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop,
IN UINT8 Red,
IN UINT8 Green,
IN UINT8 Blue
)
{
return BltFillRect(
Gop, 0, 0,
Gop->Mode->Info->HorizontalResolution,
Gop->Mode->Info->VerticalResolution,
Red, Green, Blue
);
}
14.7.4 Drawing an Image from a Pixel Buffer
EFI_STATUS
DrawBitmap(
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop,
IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL *PixelBuffer,
IN UINT32 ImageWidth,
IN UINT32 ImageHeight,
IN UINT32 DestX,
IN UINT32 DestY
)
{
//
// Delta = the byte stride of the source buffer (width * sizeof(pixel)).
// This tells the BLT engine how to advance rows in the source buffer.
//
return Gop->Blt(
Gop,
PixelBuffer,
EfiBltBufferToVideo,
0, 0, // SourceX, SourceY
DestX, DestY, // DestinationX, DestinationY
ImageWidth, ImageHeight,
ImageWidth * sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL) // Delta
);
}
14.7.5 Capturing the Screen
EFI_STATUS
CaptureScreen(
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop,
OUT EFI_GRAPHICS_OUTPUT_BLT_PIXEL **PixelBuffer,
OUT UINT32 *Width,
OUT UINT32 *Height
)
{
EFI_STATUS Status;
*Width = Gop->Mode->Info->HorizontalResolution;
*Height = Gop->Mode->Info->VerticalResolution;
UINTN BufferSize = (*Width) * (*Height) * sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL);
Status = gBS->AllocatePool(EfiBootServicesData, BufferSize, (VOID **)PixelBuffer);
if (EFI_ERROR(Status)) {
return Status;
}
return Gop->Blt(
Gop,
*PixelBuffer,
EfiBltVideoToBltBuffer,
0, 0, // SourceX, SourceY (from framebuffer)
0, 0, // DestinationX, DestinationY (in buffer)
*Width, *Height,
0 // Delta (0 means width == buffer stride)
);
}
14.8 Double Buffering
Writing directly to the framebuffer during rendering causes visible tearing and flicker. Double buffering solves this by drawing to an off-screen buffer, then copying the completed frame to the screen in a single BLT operation.
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>
typedef struct {
EFI_GRAPHICS_OUTPUT_BLT_PIXEL *Buffer;
UINT32 Width;
UINT32 Height;
UINTN BufferSize;
} BACK_BUFFER;
EFI_STATUS
CreateBackBuffer(
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop,
OUT BACK_BUFFER *BackBuffer
)
{
BackBuffer->Width = Gop->Mode->Info->HorizontalResolution;
BackBuffer->Height = Gop->Mode->Info->VerticalResolution;
BackBuffer->BufferSize = BackBuffer->Width * BackBuffer->Height
* sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL);
BackBuffer->Buffer = AllocateZeroPool(BackBuffer->BufferSize);
if (BackBuffer->Buffer == NULL) {
return EFI_OUT_OF_RESOURCES;
}
return EFI_SUCCESS;
}
VOID
BackBufferPutPixel(
IN BACK_BUFFER *BackBuffer,
IN UINT32 X,
IN UINT32 Y,
IN UINT8 Red,
IN UINT8 Green,
IN UINT8 Blue
)
{
if (X >= BackBuffer->Width || Y >= BackBuffer->Height) {
return;
}
EFI_GRAPHICS_OUTPUT_BLT_PIXEL *Pixel =
&BackBuffer->Buffer[Y * BackBuffer->Width + X];
Pixel->Red = Red;
Pixel->Green = Green;
Pixel->Blue = Blue;
}
VOID
BackBufferClear(
IN BACK_BUFFER *BackBuffer,
IN UINT8 Red,
IN UINT8 Green,
IN UINT8 Blue
)
{
for (UINTN i = 0; i < (UINTN)(BackBuffer->Width * BackBuffer->Height); i++) {
BackBuffer->Buffer[i].Red = Red;
BackBuffer->Buffer[i].Green = Green;
BackBuffer->Buffer[i].Blue = Blue;
}
}
EFI_STATUS
BackBufferFlip(
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop,
IN BACK_BUFFER *BackBuffer
)
{
return Gop->Blt(
Gop,
BackBuffer->Buffer,
EfiBltBufferToVideo,
0, 0,
0, 0,
BackBuffer->Width,
BackBuffer->Height,
BackBuffer->Width * sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL)
);
}
VOID
DestroyBackBuffer(
IN BACK_BUFFER *BackBuffer
)
{
if (BackBuffer->Buffer != NULL) {
FreePool(BackBuffer->Buffer);
BackBuffer->Buffer = NULL;
}
}
14.9 Complete Example: Animated Color Bars
This example draws animated color gradient bars using double buffering:
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Protocol/GraphicsOutput.h>
// Include the BackBuffer functions defined above
EFI_STATUS
EFIAPI
UefiMain(
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop;
BACK_BUFFER BackBuf;
EFI_INPUT_KEY Key;
UINT32 Frame = 0;
Status = gBS->LocateProtocol(
&gEfiGraphicsOutputProtocolGuid,
NULL,
(VOID **)&Gop
);
if (EFI_ERROR(Status)) {
Print(L"GOP not available: %r\n", Status);
return Status;
}
Status = CreateBackBuffer(Gop, &BackBuf);
if (EFI_ERROR(Status)) {
Print(L"Failed to create back buffer: %r\n", Status);
return Status;
}
//
// Animation loop -- runs until a key is pressed.
//
while (TRUE) {
//
// Check for keypress (non-blocking).
//
Status = gST->ConIn->ReadKeyStroke(gST->ConIn, &Key);
if (Status == EFI_SUCCESS) {
break; // Any key exits
}
//
// Draw color gradient bars that scroll vertically.
//
for (UINT32 Y = 0; Y < BackBuf.Height; Y++) {
UINT8 ColorPhase = (UINT8)((Y + Frame) % 256);
for (UINT32 X = 0; X < BackBuf.Width; X++) {
UINT8 R = (UINT8)((X * 255) / BackBuf.Width);
UINT8 G = ColorPhase;
UINT8 B = (UINT8)(255 - R);
BackBufferPutPixel(&BackBuf, X, Y, R, G, B);
}
}
//
// Present the frame.
//
BackBufferFlip(Gop, &BackBuf);
Frame += 2;
}
DestroyBackBuffer(&BackBuf);
return EFI_SUCCESS;
}
14.10 Practical Considerations
PixelsPerScanLine vs. HorizontalResolution
The framebuffer may have padding at the end of each scan line for alignment. Always use PixelsPerScanLine (not HorizontalResolution) when calculating byte offsets in the framebuffer. The BLT engine does not require this – its Delta parameter handles stride explicitly.
GOP After ExitBootServices
The framebuffer memory remains mapped after ExitBootServices(), so an OS boot loader can continue to use it for early display. However:
- You must not call any GOP protocol functions after
ExitBootServices(). - You must record
FrameBufferBase,FrameBufferSize,HorizontalResolution,VerticalResolution,PixelsPerScanLine, andPixelFormatbefore exiting boot services.
Multiple GOP Instances
Systems with multiple displays may expose multiple GOP instances. Use LocateHandleBuffer to find all of them:
EFI_STATUS
FindAllGopHandles(
OUT EFI_HANDLE **Handles,
OUT UINTN *HandleCount
)
{
return gBS->LocateHandleBuffer(
ByProtocol,
&gEfiGraphicsOutputProtocolGuid,
NULL,
HandleCount,
Handles
);
}
Performance Tips
| Technique | Speed |
|---|---|
| BLT operations | Fast (driver-optimized, handles format conversion) |
| Direct framebuffer (large writes) | Fast (bypasses protocol overhead) |
| Per-pixel framebuffer writes | Slow (each write crosses the bus) |
For best performance:
- Build your frame in a memory buffer (back buffer).
- Use
EfiBltBufferToVideoto copy the completed frame to the screen. - Avoid reading from the framebuffer (
EfiBltVideoToBltBuffer) when possible – MMIO reads are slow.
Complete source code: The full working example for this chapter is available at
examples/UefiMuGuidePkg/GopExample/.
Summary
| Concept | Key Points |
|---|---|
| GOP Protocol | Primary UEFI graphics interface; replaces VGA BIOS |
| Video Modes | Query with QueryMode, set with SetMode |
| Pixel Formats | BGRx is most common; BLT always uses BGRx internally |
| Framebuffer | Direct access via FrameBufferBase; use PixelsPerScanLine for stride |
| BLT Operations | Fill, screen-to-buffer, buffer-to-screen, screen-to-screen |
| Double Buffering | Draw off-screen, flip with single BLT for flicker-free rendering |
In the next chapter, we leave graphics behind and explore how to read and write files through the UEFI file system protocols.