GOP: Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content added Content deleted
mNo edit summary
 
(12 intermediate revisions by 9 users not shown)
Line 1: Line 1:
This is the new standard for UEFI that superseded [[VESA]] (BIOS) and UGA (EFI 1.0).
This is the new standard for UEFI that superseded [[VESA]] (BIOS) and UGA (EFI 1.0).


== Graphic Output Protocol ==
== Graphics Output Protocol ==
It has basicly the same functions as VESA, you can query the modes, set the modes. You also have an efficient BitBlitter function, which you can't use from your OS unfortunately. GOP is an EFI Boot Time Service, meaning you can't access it after you call ExitBootServices().
It has basically the same functions as VESA, you can query the modes, set the modes. It also provides an efficient BitBlitter function, which you can't use from your OS unfortunately. GOP is an EFI Boot Time Service, meaning you can't access it after you call ExitBootServices(). However, the framebuffer provided by GOP persists, so you can continue to use it for graphics output in your OS.


NOTE: UEFI uses it's own ABI. You can either configure your build environment to use that globally, or you must use a wrapper function. These examples use the latter for compatibility reasons. Omit uefi_call_wrapper if you have configured your build system for the former.
NOTE: UEFI uses its own ABI. You can either configure your build environment to use that globally, or you must use a wrapper function. These examples use the latter for compatibility reasons. Omit uefi_call_wrapper if you have configured your build system for the former. See [[GNU-EFI]] for more information.


=== Detecting GOP ===
=== Detecting GOP ===
As with other UEFI protocols, you have to locate a structure with the function pointers first using the protocol's GUID.
As with other UEFI protocols, you have to locate a structure with the function pointers first using the protocol's GUID.
<source lang="c">
<syntaxhighlight lang="c">
EFI_GUID gopGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
EFI_GUID gopGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
EFI_GRAPHICS_OUTPUT_PROTOCOL *gop;
EFI_GRAPHICS_OUTPUT_PROTOCOL *gop;
Line 15: Line 15:
if(EFI_ERROR(status))
if(EFI_ERROR(status))
PrintLn(L"Unable to locate GOP");
PrintLn(L"Unable to locate GOP");
</syntaxhighlight>
</source>
GOP is the default protocol, so you should be able to locate it on all UEFI firmware. It can probably only fail if you're on an old EFI (pre-UEFI) machine, like an Itanium-based computer or a Mac released before 2009.


If your kernel uses GRUB, you need to insert a module called "all_video" before loading the kernel to add UEFI GOP compatibility. Not doing so will display a message saying "WARNING: no console will be available to OS".
=== Get the Current Mode ===
<syntaxhighlight lang="c">
In order to get the mode code for the current video mode, you must set the mode first to circumvent some buggy UEFI firmware. This is done using the QueryMode function, and then gop->Mode->Mode will contain the code (this is a perfect example how badly designed UEFI is. Look: gop->Mode is a struct, while gop->Mode->Mode is a UINTN). See the QueryMode example below.
insmod all_video


menuentry "Example OS" {
=== Query Available Video Modes ===
multiboot2 /boot/kernel.bin
Similarly to VESA, there's no standard mode codes, rather you have a function to query the available modes with video card specific mode codes.
boot
<source lang="c">
}
</syntaxhighlight>

=== Get the Current Mode ===
In order to get the mode code for the current video mode, you must set the mode as well to circumvent some buggy UEFI firmware. Otherwise this is done using the QueryMode function, and then gop->Mode->Mode will contain the code.
<syntaxhighlight lang="c">
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info;
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info;
UINTN SizeOfInfo, numModes, nativeMode;
UINTN SizeOfInfo, numModes, nativeMode;
Line 32: Line 40:
if(EFI_ERROR(status)) {
if(EFI_ERROR(status)) {
PrintLn(L"Unable to get native mode");
PrintLn(L"Unable to get native mode");
} else {
return status;
nativeMode = gop->Mode->Mode;
numModes = gop->Mode->MaxMode;
}
}
</syntaxhighlight>
nativeMode = gop->Mode->Mode;

numModes = gop->Mode->MaxMode;
=== Query Available Video Modes ===
</source>
Now you know how many modes there are, and which one is currently set. You can iterate on the modes and query the information structure for each:
Similarly to VESA, there's no standard mode codes, rather you have a function to query the available modes. Now you know how many modes there are (numModes above), and which one is currently set (nativeMode). You can iterate on the modes and query the information structure for each:
<source lang="c">
<syntaxhighlight lang="c">
for (i = 0; i < numModes; i++) {
for (i = 0; i < numModes; i++) {
status = uefi_call_wrapper(gop->QueryMode, 4, gop, i, &SizeOfInfo, &info);
status = uefi_call_wrapper(gop->QueryMode, 4, gop, i, &SizeOfInfo, &info);
Line 49: Line 59:
);
);
}
}
</syntaxhighlight>
</source>


=== Set Video Mode and Get the Framebuffer ===
=== Set Video Mode and Get the Framebuffer ===
This is pretty easy. The mode argument is between 0 and numModes.
This is pretty easy. The mode argument is between 0 and numModes.
<source lang="c">
<syntaxhighlight lang="c">
status = uefi_call_wrapper(gop->SetMode, 2, gop, mode);
status = uefi_call_wrapper(gop->SetMode, 2, gop, mode);
if(EFI_ERROR(status)) {
if(EFI_ERROR(status)) {
PrintLn(L"Unable to set mode %03d", mode);
PrintLn(L"Unable to set mode %03d", mode);
} else {
return status;
// get framebuffer
PrintLn(L"Framebuffer address %x size %d, width %d height %d pixelsperline %d",
gop->Mode->FrameBufferBase,
gop->Mode->FrameBufferSize,
gop->Mode->Info->HorizontalResolution,
gop->Mode->Info->VerticalResolution,
gop->Mode->Info->PixelsPerScanLine
);
}
}
</syntaxhighlight>
// get framebuffer
To get the same value as scanline in VESA (also commonly called pitch in many graphics libraries), you have to multiply PixelsPerScanLine by the number of bytes per pixel. That can be detected by examining the gop->Mode->Info->PixelFormat field. For example with 32 bit packed pixel formats,
PrintLn(L"Framebuffer address %x size %d, width %d height %d pixelperline %d",
<syntaxhighlight lang="c">
gop->Mode->FrameBufferBase,
gop->Mode->FrameBufferSize,
gop->Mode->Info->HorizontalResolution,
gop->Mode->Info->VerticalResolution,
gop->Mode->Info->PixelsPerScanLine
);
</source>
To get the same value as scanline in VESA (also called commonly pitch in many graphics libraries), you have to multiply PixelsPerScanLine by the number of bytes per pixel. That can be detected by examining the gop->Mode->Info->PixelFormat field. For example with 32 bit packed pixel formats,
<source lang="c">
pitch = 4 * gop->Mode->Info->PixelsPerScanLine;
pitch = 4 * gop->Mode->Info->PixelsPerScanLine;
</syntaxhighlight>
</source>


=== Plotting Pixels ===
=== Plotting Pixels ===
{{Main|Drawing In Protected Mode}}
{{Main|Drawing In Protected Mode}}
Now you can use the returned framebuffer exactly the same way as you would with VESA, there's no difference.
Now you can use the returned framebuffer exactly the same way as you would with VESA, there's absolutely no difference.
To calculate the offset for an (X,Y) coordinate, do pitch*Y+pixelbytes*X. For example for 32 bit true-color:
To calculate the offset for an (X,Y) coordinate on screen, do pitch*Y+pixelbytes*X. For example for 32 bit true-color (where pixelbytes is 4):
<source lang="c">
<syntaxhighlight lang="c">
static inline void PlotPixel_32bpp(int x, int y, uint32_t pixel)
static inline void PlotPixel_32bpp(int x, int y, uint32_t pixel)
{
{
*((uint32_t*)(gop->Mode->FrameBufferBase + 4 * gop->Mode->Info->PixelsPerScanLine * y + 4 * x)) = pixel;
*((uint32_t*)(gop->Mode->FrameBufferBase + 4 * gop->Mode->Info->PixelsPerScanLine * y + 4 * x)) = pixel;
}
}
</syntaxhighlight>
</source>
For drawing characters, you can use the same method described in [[VGA Fonts]].


=== Don't Read From Video Memory ===
=== Don't Read From Video Memory ===

Reading from the video memory is slooow! Use [[Double Buffering|double buffering]] instead.
Reading from the video memory is slooow! Use [[Double Buffering|double buffering]] instead.


Line 95: Line 105:
* GOP at [https://en.wikipedia.org/wiki/Graphics_Output_Protocol Wikipedia]
* GOP at [https://en.wikipedia.org/wiki/Graphics_Output_Protocol Wikipedia]
* UEFI Graphic Features at [https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface#Graphics_features Wikipedia]
* UEFI Graphic Features at [https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface#Graphics_features Wikipedia]
* [https://edk2-docs.gitbook.io/edk-ii-uefi-driver-writer-s-guide/23_graphics_driver_design_guidelines/232_graphics_output_protocol_implementation EDK2 on implementing GOP]
* [https://tianocore-docs.github.io/edk2-UefiDriverWritersGuide/draft/23_graphics_driver_design_guidelines/232_graphics_output_protocol_implementation EDK2 on implementing GOP]
* [https://www.intel.com/content/dam/doc/guide/uefi-driver-graphics-controller-guide.pdf Intel documentation on GOP]
* [https://www.intel.com/content/dam/doc/guide/uefi-driver-graphics-controller-guide.pdf Intel documentation on GOP]


[[Category:UEFI]]
[[Category:Video]]
[[Category:Video]]

Latest revision as of 16:40, 25 June 2024

This is the new standard for UEFI that superseded VESA (BIOS) and UGA (EFI 1.0).

Graphics Output Protocol

It has basically the same functions as VESA, you can query the modes, set the modes. It also provides an efficient BitBlitter function, which you can't use from your OS unfortunately. GOP is an EFI Boot Time Service, meaning you can't access it after you call ExitBootServices(). However, the framebuffer provided by GOP persists, so you can continue to use it for graphics output in your OS.

NOTE: UEFI uses its own ABI. You can either configure your build environment to use that globally, or you must use a wrapper function. These examples use the latter for compatibility reasons. Omit uefi_call_wrapper if you have configured your build system for the former. See GNU-EFI for more information.

Detecting GOP

As with other UEFI protocols, you have to locate a structure with the function pointers first using the protocol's GUID.

  EFI_GUID gopGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
  EFI_GRAPHICS_OUTPUT_PROTOCOL *gop;

  status = uefi_call_wrapper(BS->LocateProtocol, 3, &gopGuid, NULL, (void**)&gop);
  if(EFI_ERROR(status))
    PrintLn(L"Unable to locate GOP");

GOP is the default protocol, so you should be able to locate it on all UEFI firmware. It can probably only fail if you're on an old EFI (pre-UEFI) machine, like an Itanium-based computer or a Mac released before 2009.

If your kernel uses GRUB, you need to insert a module called "all_video" before loading the kernel to add UEFI GOP compatibility. Not doing so will display a message saying "WARNING: no console will be available to OS".

insmod all_video

menuentry "Example OS" {
  multiboot2  /boot/kernel.bin
  boot
}

Get the Current Mode

In order to get the mode code for the current video mode, you must set the mode as well to circumvent some buggy UEFI firmware. Otherwise this is done using the QueryMode function, and then gop->Mode->Mode will contain the code.

  EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info;
  UINTN SizeOfInfo, numModes, nativeMode;

  status = uefi_call_wrapper(gop->QueryMode, 4, gop, gop->Mode==NULL?0:gop->Mode->Mode, &SizeOfInfo, &info);
  // this is needed to get the current video mode
  if (status == EFI_NOT_STARTED)
    status = uefi_call_wrapper(gop->SetMode, 2, gop, 0);
  if(EFI_ERROR(status)) {
    PrintLn(L"Unable to get native mode");
  } else {
    nativeMode = gop->Mode->Mode;
    numModes = gop->Mode->MaxMode;
  }

Query Available Video Modes

Similarly to VESA, there's no standard mode codes, rather you have a function to query the available modes. Now you know how many modes there are (numModes above), and which one is currently set (nativeMode). You can iterate on the modes and query the information structure for each:

for (i = 0; i < numModes; i++) {
  status = uefi_call_wrapper(gop->QueryMode, 4, gop, i, &SizeOfInfo, &info);
  PrintLn(L"mode %03d width %d height %d format %x%s",
    i,
    info->HorizontalResolution,
    info->VerticalResolution,
    info->PixelFormat,
    i == nativeMode ? "(current)" : ""
  );
}

Set Video Mode and Get the Framebuffer

This is pretty easy. The mode argument is between 0 and numModes.

  status = uefi_call_wrapper(gop->SetMode, 2, gop, mode);
  if(EFI_ERROR(status)) {
    PrintLn(L"Unable to set mode %03d", mode);
  } else {
    // get framebuffer
    PrintLn(L"Framebuffer address %x size %d, width %d height %d pixelsperline %d",
      gop->Mode->FrameBufferBase,
      gop->Mode->FrameBufferSize,
      gop->Mode->Info->HorizontalResolution,
      gop->Mode->Info->VerticalResolution,
      gop->Mode->Info->PixelsPerScanLine
    );
  }

To get the same value as scanline in VESA (also commonly called pitch in many graphics libraries), you have to multiply PixelsPerScanLine by the number of bytes per pixel. That can be detected by examining the gop->Mode->Info->PixelFormat field. For example with 32 bit packed pixel formats,

  pitch = 4 * gop->Mode->Info->PixelsPerScanLine;

Plotting Pixels

Main article: Drawing In Protected Mode

Now you can use the returned framebuffer exactly the same way as you would with VESA, there's absolutely no difference. To calculate the offset for an (X,Y) coordinate on screen, do pitch*Y+pixelbytes*X. For example for 32 bit true-color (where pixelbytes is 4):

static inline void PlotPixel_32bpp(int x, int y, uint32_t pixel)
{
   *((uint32_t*)(gop->Mode->FrameBufferBase + 4 * gop->Mode->Info->PixelsPerScanLine * y + 4 * x)) = pixel;
}

For drawing characters, you can use the same method described in VGA Fonts.

Don't Read From Video Memory

Reading from the video memory is slooow! Use double buffering instead.

See Also

Articles

  • VESA - the former video standard

External Links