UEFI图形编程实战:手把手教你用GOP协议在屏幕上画矩形(附完整代码)

news2026/3/13 17:25:08
UEFI图形编程实战手把手教你用GOP协议在屏幕上画矩形附完整代码如果你曾经在UEFI环境下尝试过图形编程可能会觉得这像是一场与底层硬件的直接对话——没有操作系统提供的图形库没有现成的窗口管理器只有最原始的帧缓冲区和像素操作。这正是UEFI图形编程的魅力所在也是它的挑战所在。今天我将带你深入UEFI的图形输出协议Graphics Output Protocol简称GOP通过一个完整的实战项目让你掌握在UEFI环境下绘制基本图形的核心技能。这篇文章面向的是有一定UEFI开发基础但希望在图形编程方面有所突破的开发者。无论你是想为自定义引导加载程序添加图形界面还是想深入了解UEFI的图形子系统这里的内容都将为你提供实用的指导。我们将从GOP协议的基本原理讲起逐步深入到具体的代码实现最终完成一个能够在屏幕上绘制矩形的完整UEFI应用程序。过程中我会分享一些实际开发中容易遇到的“坑”以及对应的解决方案这些经验大多来自我自己的项目实践。1. 理解UEFI图形输出的核心GOP协议在传统的BIOS时代图形输出主要依赖VGAVideo Graphics Array模式这种方式虽然简单但功能有限且效率不高。UEFI引入的GOP协议彻底改变了这一局面它提供了一套标准化的接口让开发者能够以更现代、更高效的方式操作图形硬件。1.1 GOP协议的基本架构GOP协议本质上是一个结构体包含了几个关键的函数指针和一个模式信息指针。要使用它首先需要通过UEFI的引导服务Boot Services来定位并获取这个协议的实例。这个过程通常使用LocateProtocol函数通过GUID全局唯一标识符来查找。EFI_STATUS Status; EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput; EFI_GUID gopGuid EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; Status gBS-LocateProtocol(gopGuid, NULL, (VOID **)GraphicsOutput); if (EFI_ERROR(Status)) { // 处理错误GOP协议不可用 return Status; }成功获取GOP实例后你就可以通过它来操作图形输出了。GOP协议的核心成员包括QueryMode查询系统支持的显示模式SetMode设置当前的显示模式Blt执行块传输操作这是我们绘制图形的关键Mode指向当前显示模式信息的指针1.2 显示模式与帧缓冲区GOP的Mode成员提供了当前图形输出的详细信息这些信息对于任何图形操作都至关重要struct EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE { UINT32 MaxMode; // 支持的最大模式数 UINT32 Mode; // 当前模式索引 EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info; // 模式详细信息 UINTN SizeOfInfo; // 信息结构体大小 EFI_PHYSICAL_ADDRESS FrameBufferBase; // 帧缓冲区基地址 UINTN FrameBufferSize; // 帧缓冲区大小 };其中Info指向的结构体包含了分辨率、像素格式等关键信息struct EFI_GRAPHICS_OUTPUT_MODE_INFORMATION { UINT32 Version; UINT32 HorizontalResolution; // 水平分辨率像素 UINT32 VerticalResolution; // 垂直分辨率像素 EFI_GRAPHICS_PIXEL_FORMAT PixelFormat; // 像素格式 // ... 其他字段 };理解像素格式特别重要因为它决定了你如何向帧缓冲区写入数据。UEFI定义了以下几种主要的像素格式枚举值描述典型内存布局PixelRedGreenBlueReserved8BitPerColorRGB格式每个通道8位R, G, B, ReservedPixelBlueGreenRedReserved8BitPerColorBGR格式每个通道8位B, G, R, ReservedPixelBitMask位掩码格式依赖具体的掩码PixelBltOnly仅支持Blt操作不直接访问帧缓冲区在实际开发中我遇到的大多数平台包括QEMU虚拟机和常见的PC硬件都使用PixelBlueGreenRedReserved8BitPerColor格式也就是BGR顺序。这意味着在内存中每个像素的四个字节依次是蓝色、绿色、红色和保留字节。注意不同硬件平台的像素格式可能不同在编写图形代码时一定要先检查PixelFormat字段确保你的代码能够正确处理不同的像素格式。忽略这一点是很多UEFI图形程序无法跨平台工作的主要原因。1.3 帧缓冲区的内存布局帧缓冲区是一块连续的物理内存区域显卡会定期读取这块内存的内容并显示到屏幕上。理解它的内存布局是进行高效图形编程的基础。假设我们有一个分辨率为1024×768的显示器使用32位BGR格式每个像素4字节那么帧缓冲区总大小 1024 × 768 × 4 3,145,728字节约3MB每行像素 1024个像素 × 4字节/像素 4096字节像素(0,0)位于帧缓冲区的起始位置像素(x,y)的偏移量 y × 每行字节数 x × 每像素字节数这种线性布局使得我们可以通过简单的指针运算来访问任意位置的像素但也意味着我们需要小心处理边界情况避免写入超出缓冲区范围的内存。2. 深入解析Blt函数UEFI的图形操作核心BltBlock Transfer函数是GOP协议中最重要的函数它提供了四种基本的图形操作。理解这四种操作模式你就掌握了UEFI图形编程的核心工具。2.1 Blt函数的参数详解让我们先看看Blt函数的完整原型typedef EFI_STATUS (EFIAPI *EFI_GRAPHICS_OUTPUT_PROTOCOL_BLT)( IN EFI_GRAPHICS_OUTPUT_PROTOCOL *This, IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BltBuffer, OPTIONAL IN EFI_GRAPHICS_OUTPUT_BLT_OPERATION BltOperation, IN UINTN SourceX, IN UINTN SourceY, IN UINTN DestinationX, IN UINTN DestinationY, IN UINTN Width, IN UINTN Height, IN UINTN Delta OPTIONAL );每个参数的具体含义如下This指向GOP协议实例的指针通常就是我们通过LocateProtocol获取的那个指针BltBuffer图像缓冲区指针对于某些操作可以为NULLBltOperation要执行的操作类型枚举值SourceX/Y源区域的起始坐标DestinationX/Y目标区域的起始坐标Width/Height操作区域的宽度和高度Delta图像缓冲区每行的字节数可选0表示自动计算2.2 四种Blt操作模式Blt函数支持四种不同的操作模式每种模式都有特定的用途1. EfiBltVideoFill - 填充屏幕区域这是最简单的操作模式用单一颜色填充屏幕的指定矩形区域。BltBuffer参数在这里指向一个包含单个像素颜色的缓冲区。EFI_GRAPHICS_OUTPUT_BLT_PIXEL FillColor {0, 0, 255, 0}; // 纯蓝色 Status GraphicsOutput-Blt( GraphicsOutput, FillColor, EfiBltVideoFill, 0, 0, // 源坐标忽略 100, 100, // 目标坐标 200, 150, // 宽度和高度 0 // Delta自动计算 );这个操作特别适合清屏或者绘制纯色背景。在实际项目中我经常用它来初始化屏幕状态。2. EfiBltBufferToVideo - 从缓冲区复制到屏幕这是最常用的操作模式用于将内存中的图像数据显示到屏幕上。你需要提前准备好一个包含像素数据的缓冲区。// 假设我们有一个200x150的图像缓冲区 UINTN BufferSize 200 * 150 * sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL); EFI_GRAPHICS_OUTPUT_BLT_PIXEL *ImageBuffer; // 分配内存并填充图像数据 Status gBS-AllocatePool(EfiLoaderData, BufferSize, (VOID **)ImageBuffer); // ... 填充ImageBuffer ... // 将图像显示到屏幕的(100, 100)位置 Status GraphicsOutput-Blt( GraphicsOutput, ImageBuffer, EfiBltBufferToVideo, 0, 0, // 源缓冲区中的起始位置 100, 100, // 屏幕上的目标位置 200, 150, // 图像的宽度和高度 0 // Delta通常为0表示连续内存 );3. EfiBltVideoToBltBuffer - 从屏幕复制到缓冲区这个操作与上一个相反用于捕获屏幕内容。这在实现截图功能或者需要读取当前屏幕状态时非常有用。// 分配足够大的缓冲区来存储捕获的图像 UINTN CaptureWidth 400; UINTN CaptureHeight 300; UINTN CaptureSize CaptureWidth * CaptureHeight * sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL); EFI_GRAPHICS_OUTPUT_BLT_PIXEL *CaptureBuffer; Status gBS-AllocatePool(EfiLoaderData, CaptureSize, (VOID **)CaptureBuffer); // 捕获屏幕(50, 50)位置开始的400x300区域 Status GraphicsOutput-Blt( GraphicsOutput, CaptureBuffer, EfiBltVideoToBltBuffer, 50, 50, // 屏幕上的源位置 0, 0, // 缓冲区中的目标位置 CaptureWidth, CaptureHeight, 0 );4. EfiBltVideoToVideo - 屏幕内复制这个操作在屏幕内部复制像素数据可以实现简单的动画效果比如移动窗口或者实现双缓冲。// 将屏幕(100, 100)位置的200x150区域复制到(300, 200)位置 Status GraphicsOutput-Blt( GraphicsOutput, NULL, // 对于VideoToVideo操作BltBuffer必须为NULL EfiBltVideoToVideo, 100, 100, // 源位置 300, 200, // 目标位置 200, 150, // 要复制的区域大小 0 // Delta忽略 );提示在实际使用中我发现EfiBltVideoToVideo操作在某些硬件上可能不被支持。如果你的代码需要跨平台兼容最好在使用前检查这个功能是否可用或者准备一个备用方案比如通过缓冲区中转。2.3 Delta参数的实际意义Delta参数经常让初学者感到困惑。简单来说它定义了图像缓冲区中每行的字节数。当你的图像数据在内存中不是紧密打包时比如每行后面有填充字节就需要指定这个值。大多数情况下你可以将Delta设置为0让系统自动计算为Width * sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL)。但在处理某些特定格式的图像数据时可能需要手动设置。// 假设我们有一个宽度为200像素的图像但每行实际存储了256字节 // 每像素4字节所以有效数据是200*4800字节但每行分配了256字节 UINTN ActualRowBytes 256; // 每行实际分配的字节数 UINTN ImageWidth 200; UINTN ImageHeight 150; Status GraphicsOutput-Blt( GraphicsOutput, ImageBuffer, EfiBltBufferToVideo, 0, 0, 100, 100, ImageWidth, ImageHeight, ActualRowBytes // 指定实际的行字节数 );3. 实战构建矩形绘制函数理解了GOP和Blt函数的基本原理后我们现在可以开始编写实际的图形代码了。这一节将带你一步步构建一个完整的矩形绘制函数并讨论其中的关键实现细节。3.1 基础矩形绘制实现最简单的矩形绘制就是填充一个矩形区域。我们可以直接使用EfiBltVideoFill操作EFI_STATUS DrawSolidRect( IN EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput, IN UINTN X, IN UINTN Y, IN UINTN Width, IN UINTN Height, IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL Color ) { if (GraphicsOutput NULL) { return EFI_INVALID_PARAMETER; } // 检查矩形是否在屏幕范围内 UINTN ScreenWidth GraphicsOutput-Mode-Info-HorizontalResolution; UINTN ScreenHeight GraphicsOutput-Mode-Info-VerticalResolution; if (X ScreenWidth || Y ScreenHeight) { return EFI_INVALID_PARAMETER; } // 调整宽度和高度确保不超出屏幕边界 if (X Width ScreenWidth) { Width ScreenWidth - X; } if (Y Height ScreenHeight) { Height ScreenHeight - Y; } // 使用Blt函数填充矩形 return GraphicsOutput-Blt( GraphicsOutput, Color, EfiBltVideoFill, 0, 0, // 源坐标对于填充操作忽略 X, Y, // 目标坐标 Width, Height, 0 // Delta ); }这个基础版本虽然简单但已经可以满足很多需求。不过它有一个明显的限制只能绘制实心矩形。如果我们想绘制空心矩形只有边框或者有特殊填充模式的矩形就需要更复杂的实现。3.2 高级矩形绘制支持边框和填充在实际的图形界面中我们经常需要绘制不同样式的矩形。下面是一个更完整的实现支持边框、填充色和不同的边框宽度typedef struct { EFI_GRAPHICS_OUTPUT_BLT_PIXEL FillColor; // 填充颜色 EFI_GRAPHICS_OUTPUT_BLT_PIXEL BorderColor; // 边框颜色 UINTN BorderWidth; // 边框宽度0表示无边框 BOOLEAN RoundedCorners; // 是否圆角 } RECT_STYLE; EFI_STATUS DrawStyledRect( IN EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput, IN UINTN X, IN UINTN Y, IN UINTN Width, IN UINTN Height, IN RECT_STYLE *Style ) { EFI_STATUS Status; if (GraphicsOutput NULL || Style NULL) { return EFI_INVALID_PARAMETER; } // 边界检查 UINTN ScreenWidth GraphicsOutput-Mode-Info-HorizontalResolution; UINTN ScreenHeight GraphicsOutput-Mode-Info-VerticalResolution; if (X ScreenWidth || Y ScreenHeight) { return EFI_INVALID_PARAMETER; } // 调整尺寸 UINTN ActualWidth Width; UINTN ActualHeight Height; if (X ActualWidth ScreenWidth) { ActualWidth ScreenWidth - X; } if (Y ActualHeight ScreenHeight) { ActualHeight ScreenHeight - Y; } // 如果有填充色先填充整个矩形 if (Style-FillColor.Reserved ! 0xFF) { // 特殊值表示透明 Status GraphicsOutput-Blt( GraphicsOutput, Style-FillColor, EfiBltVideoFill, 0, 0, X, Y, ActualWidth, ActualHeight, 0 ); if (EFI_ERROR(Status)) { return Status; } } // 绘制边框 if (Style-BorderWidth 0) { // 上边框 Status GraphicsOutput-Blt( GraphicsOutput, Style-BorderColor, EfiBltVideoFill, 0, 0, X, Y, ActualWidth, Style-BorderWidth, 0 ); if (EFI_ERROR(Status)) return Status; // 下边框 Status GraphicsOutput-Blt( GraphicsOutput, Style-BorderColor, EfiBltVideoFill, 0, 0, X, Y ActualHeight - Style-BorderWidth, ActualWidth, Style-BorderWidth, 0 ); if (EFI_ERROR(Status)) return Status; // 左边框 Status GraphicsOutput-Blt( GraphicsOutput, Style-BorderColor, EfiBltVideoFill, 0, 0, X, Y Style-BorderWidth, Style-BorderWidth, ActualHeight - 2 * Style-BorderWidth, 0 ); if (EFI_ERROR(Status)) return Status; // 右边框 Status GraphicsOutput-Blt( GraphicsOutput, Style-BorderColor, EfiBltVideoFill, 0, 0, X ActualWidth - Style-BorderWidth, Y Style-BorderWidth, Style-BorderWidth, ActualHeight - 2 * Style-BorderWidth, 0 ); if (EFI_ERROR(Status)) return Status; } return EFI_SUCCESS; }这个实现虽然代码量增加了但提供了更大的灵活性。在实际的UEFI应用程序中这样的矩形绘制函数可以作为构建更复杂图形界面的基础。3.3 性能优化技巧在UEFI环境下图形操作的性能往往是个问题特别是在资源受限的引导环境中。以下是一些我在实际项目中总结的优化技巧1. 批量操作尽量避免频繁调用Blt函数绘制小区域而是尽量一次性绘制更大的区域。每次Blt调用都有一定的开销合并操作可以显著提高性能。// 不推荐多次小范围绘制 for (UINTN i 0; i 100; i) { DrawSolidRect(GraphicsOutput, i*10, 100, 5, 5, RedColor); } // 推荐一次性绘制 EFI_GRAPHICS_OUTPUT_BLT_PIXEL *PatternBuffer; // ... 创建包含整个图案的缓冲区 ... GraphicsOutput-Blt(GraphicsOutput, PatternBuffer, EfiBltBufferToVideo, ...);2. 内存对齐确保图像缓冲区按照合适的边界对齐。大多数现代CPU对未对齐的内存访问有性能惩罚。在UEFI中通常建议使用64位对齐。// 分配对齐的内存 EFI_GRAPHICS_OUTPUT_BLT_PIXEL *AlignedBuffer; UINTN BufferSize Width * Height * sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL); UINTN Alignment 64; // 64字节对齐 Status gBS-AllocatePool(EfiLoaderData, BufferSize Alignment, (VOID **)RawBuffer); if (!EFI_ERROR(Status)) { AlignedBuffer (EFI_GRAPHICS_OUTPUT_BLT_PIXEL *) (((UINTN)RawBuffer Alignment - 1) ~(Alignment - 1)); }3. 避免不必要的颜色转换如果你需要频繁绘制相同颜色的图形可以预先计算好像素值避免在绘制循环中进行颜色转换。// 预计算常用颜色 EFI_GRAPHICS_OUTPUT_BLT_PIXEL PrecomputedColors[] { {0, 0, 0, 0}, // 黑色 {255, 255, 255, 0}, // 白色 {0, 0, 255, 0}, // 蓝色 {0, 255, 0, 0}, // 绿色 {255, 0, 0, 0}, // 红色 // ... 更多颜色 };4. 完整示例构建一个简单的图形测试程序现在让我们把所有知识整合起来创建一个完整的UEFI应用程序它将在屏幕上绘制多个不同样式的矩形并演示GOP协议的基本用法。4.1 项目结构与头文件首先我们定义项目的基本结构。创建一个名为RectDemo的目录包含以下文件RectDemo/ ├── RectDemo.inf # EDK II模块定义文件 ├── RectDemo.c # 主源文件 └── RectDemo.h # 头文件RectDemo.h内容如下#ifndef _RECT_DEMO_H_ #define _RECT_DEMO_H_ #include Uefi.h #include Library/UefiLib.h #include Library/UefiBootServicesTableLib.h #include Library/MemoryAllocationLib.h #include Protocol/GraphicsOutput.h // 矩形样式结构体 typedef struct { EFI_GRAPHICS_OUTPUT_BLT_PIXEL FillColor; EFI_GRAPHICS_OUTPUT_BLT_PIXEL BorderColor; UINTN BorderWidth; BOOLEAN RoundedCorners; } RECT_STYLE; // 函数声明 EFI_STATUS EFIAPI UefiMain ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ); EFI_STATUS InitializeGraphics ( OUT EFI_GRAPHICS_OUTPUT_PROTOCOL **GraphicsOutput ); EFI_STATUS DrawSolidRect ( IN EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput, IN UINTN X, IN UINTN Y, IN UINTN Width, IN UINTN Height, IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL Color ); EFI_STATUS DrawStyledRect ( IN EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput, IN UINTN X, IN UINTN Y, IN UINTN Width, IN UINTN Height, IN RECT_STYLE *Style ); VOID PrintScreenInfo ( IN EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput ); #endif // _RECT_DEMO_H_4.2 主程序实现RectDemo.c是程序的核心包含了所有功能的实现#include RectDemo.h // 全局颜色定义 STATIC EFI_GRAPHICS_OUTPUT_BLT_PIXEL mColors[] { { 0, 0, 0, 0}, // 黑色 {255, 255, 255, 0}, // 白色 { 0, 0, 255, 0}, // 蓝色 { 0, 255, 0, 0}, // 绿色 {255, 0, 0, 0}, // 红色 {255, 255, 0, 0}, // 黄色 {255, 0, 255, 0}, // 紫色 { 0, 255, 255, 0}, // 青色 }; EFI_STATUS InitializeGraphics ( OUT EFI_GRAPHICS_OUTPUT_PROTOCOL **GraphicsOutput ) { EFI_STATUS Status; // 定位Graphics Output Protocol Status gBS-LocateProtocol( gEfiGraphicsOutputProtocolGuid, NULL, (VOID **)GraphicsOutput ); if (EFI_ERROR(Status)) { Print(L错误无法定位Graphics Output Protocol\n); Print(L状态码%r\n, Status); return Status; } // 检查当前图形模式 if ((*GraphicsOutput)-Mode NULL || (*GraphicsOutput)-Mode-Info NULL) { Print(L错误图形模式信息不可用\n); return EFI_UNSUPPORTED; } return EFI_SUCCESS; } VOID PrintScreenInfo ( IN EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput ) { EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info; if (GraphicsOutput NULL || GraphicsOutput-Mode NULL) { Print(L错误图形输出协议未初始化\n); return; } Info GraphicsOutput-Mode-Info; Print(L\n 屏幕信息 \n); Print(L分辨率%dx%d\n, Info-HorizontalResolution, Info-VerticalResolution); Print(L当前模式%d共%d种模式\n, GraphicsOutput-Mode-Mode, GraphicsOutput-Mode-MaxMode); // 像素格式 switch (Info-PixelFormat) { case PixelRedGreenBlueReserved8BitPerColor: Print(L像素格式RGB每个通道8位\n); break; case PixelBlueGreenRedReserved8BitPerColor: Print(L像素格式BGR每个通道8位\n); break; case PixelBitMask: Print(L像素格式位掩码\n); break; case PixelBltOnly: Print(L像素格式仅支持Blt操作\n); break; default: Print(L像素格式未知%d\n, Info-PixelFormat); break; } Print(L帧缓冲区基地址0x%016lx\n, GraphicsOutput-Mode-FrameBufferBase); Print(L帧缓冲区大小%lu字节约%.2f MB\n, GraphicsOutput-Mode-FrameBufferSize, GraphicsOutput-Mode-FrameBufferSize / (1024.0 * 1024.0)); Print(L\n\n); } EFI_STATUS DrawSolidRect ( IN EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput, IN UINTN X, IN UINTN Y, IN UINTN Width, IN UINTN Height, IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL Color ) { EFI_STATUS Status; UINTN ScreenWidth, ScreenHeight; if (GraphicsOutput NULL) { return EFI_INVALID_PARAMETER; } ScreenWidth GraphicsOutput-Mode-Info-HorizontalResolution; ScreenHeight GraphicsOutput-Mode-Info-VerticalResolution; // 边界检查 if (X ScreenWidth || Y ScreenHeight) { return EFI_INVALID_PARAMETER; } // 调整尺寸确保不超出屏幕 if (X Width ScreenWidth) { Width ScreenWidth - X; } if (Y Height ScreenHeight) { Height ScreenHeight - Y; } if (Width 0 || Height 0) { return EFI_SUCCESS; // 没有需要绘制的区域 } Status GraphicsOutput-Blt( GraphicsOutput, Color, EfiBltVideoFill, 0, 0, X, Y, Width, Height, 0 ); return Status; } EFI_STATUS DrawStyledRect ( IN EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput, IN UINTN X, IN UINTN Y, IN UINTN Width, IN UINTN Height, IN RECT_STYLE *Style ) { EFI_STATUS Status; UINTN ScreenWidth, ScreenHeight; if (GraphicsOutput NULL || Style NULL) { return EFI_INVALID_PARAMETER; } ScreenWidth GraphicsOutput-Mode-Info-HorizontalResolution; ScreenHeight GraphicsOutput-Mode-Info-VerticalResolution; // 边界检查 if (X ScreenWidth || Y ScreenHeight) { return EFI_INVALID_PARAMETER; } // 调整尺寸 UINTN ActualWidth Width; UINTN ActualHeight Height; if (X ActualWidth ScreenWidth) { ActualWidth ScreenWidth - X; } if (Y ActualHeight ScreenHeight) { ActualHeight ScreenHeight - Y; } if (ActualWidth 0 || ActualHeight 0) { return EFI_SUCCESS; } // 绘制填充如果不是透明色 if (Style-FillColor.Reserved ! 0xFF) { Status DrawSolidRect( GraphicsOutput, X Style-BorderWidth, Y Style-BorderWidth, ActualWidth - 2 * Style-BorderWidth, ActualHeight - 2 * Style-BorderWidth, Style-FillColor ); if (EFI_ERROR(Status)) { return Status; } } // 绘制边框 if (Style-BorderWidth 0) { // 上边框 Status DrawSolidRect( GraphicsOutput, X, Y, ActualWidth, Style-BorderWidth, Style-BorderColor ); if (EFI_ERROR(Status)) return Status; // 下边框 Status DrawSolidRect( GraphicsOutput, X, Y ActualHeight - Style-BorderWidth, ActualWidth, Style-BorderWidth, Style-BorderColor ); if (EFI_ERROR(Status)) return Status; // 左边框 Status DrawSolidRect( GraphicsOutput, X, Y Style-BorderWidth, Style-BorderWidth, ActualHeight - 2 * Style-BorderWidth, Style-BorderColor ); if (EFI_ERROR(Status)) return Status; // 右边框 Status DrawSolidRect( GraphicsOutput, X ActualWidth - Style-BorderWidth, Y Style-BorderWidth, Style-BorderWidth, ActualHeight - 2 * Style-BorderWidth, Style-BorderColor ); if (EFI_ERROR(Status)) return Status; } return EFI_SUCCESS; } EFI_STATUS EFIAPI UefiMain ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { EFI_STATUS Status; EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput; UINTN ScreenWidth, ScreenHeight; UINTN i; // 初始化图形输出 Status InitializeGraphics(GraphicsOutput); if (EFI_ERROR(Status)) { return Status; } // 打印屏幕信息 PrintScreenInfo(GraphicsOutput); // 获取屏幕尺寸 ScreenWidth GraphicsOutput-Mode-Info-HorizontalResolution; ScreenHeight GraphicsOutput-Mode-Info-VerticalResolution; // 清屏填充为深灰色 EFI_GRAPHICS_OUTPUT_BLT_PIXEL Background {40, 40, 40, 0}; Status DrawSolidRect(GraphicsOutput, 0, 0, ScreenWidth, ScreenHeight, Background); if (EFI_ERROR(Status)) { Print(L清屏失败%r\n, Status); return Status; } // 示例1绘制一系列彩色矩形 Print(L绘制彩色矩形...\n); for (i 0; i 8; i) { UINTN RectWidth 80; UINTN RectHeight 60; UINTN X 50 i * 100; UINTN Y 50; if (X RectWidth ScreenWidth) { break; } Status DrawSolidRect(GraphicsOutput, X, Y, RectWidth, RectHeight, mColors[i]); if (EFI_ERROR(Status)) { Print(L绘制矩形%d失败%r\n, i, Status); } } // 示例2绘制带边框的矩形 Print(L绘制带边框的矩形...\n); RECT_STYLE BorderedRect { .FillColor {200, 200, 255, 0}, // 浅蓝色填充 .BorderColor {0, 0, 180, 0}, // 深蓝色边框 .BorderWidth 4, .RoundedCorners FALSE }; Status DrawStyledRect(GraphicsOutput, 100, 200, 300, 150, BorderedRect); if (EFI_ERROR(Status)) { Print(L绘制带边框矩形失败%r\n, Status); } // 示例3绘制只有边框的矩形空心矩形 Print(L绘制空心矩形...\n); RECT_STYLE HollowRect { .FillColor {0, 0, 0, 0xFF}, // 透明填充Reserved0xFF表示透明 .BorderColor {255, 200, 0, 0}, // 橙色边框 .BorderWidth 3, .RoundedCorners FALSE }; Status DrawStyledRect(GraphicsOutput, 450, 200, 250, 120, HollowRect); if (EFI_ERROR(Status)) { Print(L绘制空心矩形失败%r\n, Status); } // 示例4绘制嵌套矩形 Print(L绘制嵌套矩形...\n); for (i 0; i 5; i) { UINTN Size 200 - i * 30; UINTN X (ScreenWidth - Size) / 2; UINTN Y 400 i * 20; EFI_GRAPHICS_OUTPUT_BLT_PIXEL Color { (UINT8)(50 i * 40), (UINT8)(100 i * 30), (UINT8)(150 - i * 30), 0 }; Status DrawSolidRect(GraphicsOutput, X, Y, Size, 20, Color); if (EFI_ERROR(Status)) { Print(L绘制嵌套矩形%d失败%r\n, i, Status); break; } } // 等待用户按键 Print(L\n所有矩形绘制完成\n); Print(L按任意键退出...\n); EFI_INPUT_KEY Key; SystemTable-ConIn-Reset(SystemTable-ConIn, FALSE); while (SystemTable-ConIn-ReadKeyStroke(SystemTable-ConIn, Key) EFI_NOT_READY) { gBS-Stall(10000); // 等待10ms } return EFI_SUCCESS; }4.3 模块定义文件RectDemo.inf文件定义了EDK II构建系统如何编译这个模块[Defines] INF_VERSION 0x00010006 BASE_NAME RectDemo FILE_GUID 12345678-1234-1234-1234-123456789ABC MODULE_TYPE UEFI_APPLICATION VERSION_STRING 1.0 ENTRY_POINT UefiMain [Sources] RectDemo.c RectDemo.h [Packages] MdePkg/MdePkg.dec [LibraryClasses] UefiApplicationEntryPoint UefiLib MemoryAllocationLib [Protocols] gEfiGraphicsOutputProtocolGuid4.4 构建与运行要构建这个应用程序你需要将其集成到EDK II构建环境中。假设你已经设置了EDK II开发环境可以按照以下步骤操作将RectDemo目录复制到你的工作区例如MyWorkspace/AppPkg/Applications/在AppPkg.dsc文件中添加RectDemo模块使用以下命令构建# 设置环境变量 export WORKSPACE/path/to/your/edk2 export PACKAGES_PATH$WORKSPACE/MyWorkspace # 构建 build -a X64 -p AppPkg/AppPkg.dsc -m AppPkg/Applications/RectDemo/RectDemo.inf构建成功后你会在Build/AppPkg/DEBUG_GCC5/X64/目录下找到RectDemo.efi文件。你可以将其复制到UEFI可访问的磁盘如FAT格式的USB驱动器然后在UEFI Shell中运行fs0: RectDemo.efi程序运行后你应该能看到屏幕上绘制了多种不同样式和颜色的矩形同时控制台会输出当前的屏幕信息。5. 调试技巧与常见问题解决在UEFI图形编程过程中你可能会遇到各种问题。这一节分享一些我在实际开发中积累的调试技巧和常见问题的解决方案。5.1 调试输出与错误处理由于UEFI环境没有成熟的调试器打印调试信息是最常用的调试手段。但要注意在图形模式下控制台输出可能会与图形显示冲突。// 安全的调试输出函数 VOID DebugPrint ( IN CONST CHAR16 *Format, ... ) { VA_LIST Args; CHAR16 Buffer[256]; VA_START(Args, Format); UnicodeVSPrint(Buffer, sizeof(Buffer), Format, Args); VA_END(Args); // 只在文本模式下输出避免干扰图形显示 if (gST-ConOut-Mode-Attribute ! 0) { gST-ConOut-OutputString(gST-ConOut, Buffer); } } // 检查Blt操作的状态 EFI_STATUS SafeBltCall ( IN EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput, IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BltBuffer, IN EFI_GRAPHICS_OUTPUT_BLT_OPERATION BltOperation, IN UINTN SourceX, IN UINTN SourceY, IN UINTN DestinationX, IN UINTN DestinationY, IN UINTN Width, IN UINTN Height, IN UINTN Delta ) { EFI_STATUS Status; Status GraphicsOutput-Blt( GraphicsOutput, BltBuffer, BltOperation, SourceX, SourceY, DestinationX, DestinationY, Width, Height, Delta ); if (EFI_ERROR(Status)) { DebugPrint(LBlt操作失败%r (操作%d, 位置%dx%d, 大小%dx%d)\n, Status, BltOperation, DestinationX, DestinationY, Width, Height); } return Status; }5.2 常见问题与解决方案问题1Blt函数返回EFI_INVALID_PARAMETER这通常意味着参数有问题。检查以下几点坐标和尺寸是否在屏幕范围内对于EfiBltVideoToVideo操作BltBuffer必须为NULLDelta参数是否正确特别是当图像数据有填充时确保没有传递NULL指针给必需的参数问题2图形显示异常或颜色错误这通常与像素格式不匹配有关// 检查并适配像素格式 VOID AdaptPixelFormat ( IN EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput, IN OUT EFI_GRAPHICS_OUTPUT_BLT_PIXEL *Pixel ) { EFI_GRAPHICS_PIXEL_FORMAT Format; if (GraphicsOutput NULL || GraphicsOutput-Mode NULL || GraphicsOutput-Mode-Info NULL) { return; } Format GraphicsOutput-Mode-Info-PixelFormat; // 如果是RGB格式但我们的数据是BGR格式需要交换R和B if (Format PixelRedGreenBlueReserved8BitPerColor) { UINT8 Temp Pixel-Red; Pixel-Red Pixel-Blue; Pixel-Blue Temp; } // 其他格式可能需要不同的转换 }问题3性能问题如果图形操作太慢可以尝试以下优化减少Blt调用次数合并多个小操作使用合适的缓冲区大小避免频繁的内存分配和释放检查内存对齐确保缓冲区正确对齐考虑使用双缓冲减少屏幕闪烁// 简单的双缓冲实现示例 EFI_STATUS DoubleBufferDraw ( IN EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput, IN UINTN Width, IN UINTN Height ) { EFI_STATUS Status; UINTN BufferSize; EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BackBuffer; // 分配后台缓冲区 BufferSize Width * Height * sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL); Status gBS-AllocatePool(EfiLoaderData, BufferSize, (VOID **)BackBuffer); if (EFI_ERROR(Status)) { return Status; } // 在后台缓冲区中绘制所有内容 // ... 绘制操作 ... // 一次性将整个后台缓冲区复制到屏幕 Status GraphicsOutput-Blt( GraphicsOutput, BackBuffer, EfiBltBufferToVideo, 0, 0, // 源位置 0, 0, // 目标位置 Width, Height, 0 ); // 清理 gBS-FreePool(BackBuffer); return Status; }问题4内存泄漏UEFI应用程序退出时不会自动释放分配的内存必须手动管理// 资源管理结构体 typedef struct { EFI_GRAPHICS_OUTPUT_BLT_PIXEL *Buffers[10]; UINTN BufferCount; // 其他需要管理的资源... } GRAPHICS_CONTEXT; // 初始化时清零 GRAPHICS_CONTEXT Context; ZeroMem(Context, sizeof(Context)); // 分配内存时记录 Status gBS-AllocatePool(EfiLoaderData, Size, (VOID **)Buffer); if (!EFI_ERROR(Status) Context.BufferCount 10) { Context.Buffers[Context.BufferCount] Buffer; } // 退出时释放所有资源 VOID CleanupGraphicsContext ( IN GRAPHICS_CONTEXT *Context ) { UINTN i; for (i 0; i Context-BufferCount; i) { if (Context-Buffers[i] ! NULL) { gBS-FreePool(Context-Buffers[i]); Context-Buffers[i] NULL; } } Context-BufferCount 0; }5.3 硬件兼容性问题不同的硬件平台可能有不同的GOP实现以下是一些兼容性问题的解决方法检查GOP功能支持BOOLEAN CheckBltOperationSupport ( IN EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput, IN EFI_GRAPHICS_OUTPUT_BLT_OPERATION Operation ) { EFI_STATUS Status; EFI_GRAPHICS_OUTPUT_BLT_PIXEL TestPixel {0, 0, 0, 0}; // 尝试一个微小的测试操作 Status GraphicsOutput-Blt( GraphicsOutput, TestPixel, Operation, 0, 0, 0, 0, 1, 1, // 1x1像素 0 ); // 有些实现可能不支持某些操作 if (Status EFI_UNSUPPORTED) { return FALSE; } // 重置可能的状态 if (!EFI_ERROR(Status)) { // 恢复原状 GraphicsOutput-Blt( GraphicsOutput, TestPixel, EfiBltVideoToVideo, 0, 0, 0, 0, 1, 1, 0 ); } return TRUE; }处理不同的像素格式// 通用的像素处理函数 VOID SetPixelColor ( IN EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput, IN OUT EFI_GRAPHICS_OUTPUT_BLT_PIXEL *Pixel, IN UINT8 Red, IN UINT8 Green, IN UINT8 Blue, IN UINT8 Alpha ) { EFI_GRAPHICS_PIXEL_FORMAT Format; Format GraphicsOutput-Mode-Info-PixelFormat; switch (Format) { case PixelRedGreenBlueReserved8BitPerColor: Pixel-Red Red; Pixel-Green Green; Pixel-Blue Blue; Pixel-Reserved Alpha; break; case PixelBlueGreenRedReserved8BitPerColor: Pixel-Blue Blue; Pixel-Green Green; Pixel-Red Red; Pixel-Reserved Alpha; break; case PixelBitMask: // 需要根据具体的位掩码处理 // 这里简化处理实际使用时需要查询具体的掩码 break; default: // 使用默认的BGR顺序 Pixel-Blue Blue; Pixel-Green Green; Pixel-Red Red; Pixel-Reserved Alpha; break; } }通过本文的实战教程你应该已经掌握了UEFI环境下使用GOP协议进行图形编程的基本技能。从理解GOP协议的基本原理到使用Blt函数进行各种图形操作再到实现完整的矩形绘制功能我们覆盖了UEFI图形编程的核心内容。在实际项目中我建议先从简单的图形开始逐步增加复杂度。记得总是检查函数返回值处理好边界情况并考虑不同硬件平台的兼容性。UEFI图形编程虽然有一定门槛但一旦掌握你就能为引导加载程序、系统工具甚至简单的游戏创建丰富的图形界面。如果你在实现过程中遇到问题可以回顾本文中的代码示例或者参考UEFI规范中关于Graphics Output Protocol的详细说明。最重要的是多实践通过实际编写和调试代码来加深理解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2408299.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…