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 file

This 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:

  1. Obtain the FrameBuffer address – Know where in memory to write pixel data
  2. Query resolution information – Understand the screen dimensions
  3. Determine pixel format – Know how color data is structured
  4. 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:

  1. Enumeration: Call Gop->QueryMode() to retrieve all supported resolutions
  2. Selection: Call Gop->SetMode() to switch to your desired resolution (e.g., 1920×1080)
  3. Information Retrieval: Access Gop->Mode->FrameBufferBase and Gop->Mode->Info->PixelFormat to know where to write data and in what format
  4. 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
    UefiBootServicesTableLib

Package Declaration File (MyPkg.dec)

[Defines]
    DEC_SPECIFICATION = 0x00010005
    PACKAGE_NAME = MyPkg
    PACKAGE_GUID = a2ab400d-c171-43ec-a0cc-582527a93887
    PACKAGE_VERSION = 1.0

Platform 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.inf

Building 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 RELEASE

Expected Output

When the application runs successfully, you should see:

  1. Console output displaying all supported display modes
  2. Current mode information including framebuffer address and size
  3. Visual output showing the gradient background with white cross
  4. 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:

  1. Use Blt for bulk operations: Hardware-accelerated block transfers are faster than pixel-by-pixel writes
  2. Minimize mode switches: Each SetMode call may involve hardware reinitialization
  3. Cache mode information: Query once at startup, reuse throughout application lifetime
  4. 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 pointer
  • EFI_UNSUPPORTED: Mode not supported by hardware
  • EFI_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!