diff --git a/OvmfPkg/VirtioGpuDxe/VirtioGpu.inf b/OvmfPkg/VirtioGpuDxe/VirtioGpu.inf
index 7a6269eded51..04bc2964c223 100644
--- a/OvmfPkg/VirtioGpuDxe/VirtioGpu.inf
+++ b/OvmfPkg/VirtioGpuDxe/VirtioGpu.inf
@@ -23,12 +23,13 @@ [Defines]
   VERSION_STRING                 = 1.0
   ENTRY_POINT                    = VirtioGpuEntryPoint
 
 [Sources]
   Commands.c
   DriverBinding.c
+  Gop.c
   VirtioGpu.h
 
 [Packages]
   MdePkg/MdePkg.dec
   OvmfPkg/OvmfPkg.dec
 
@@ -42,8 +43,9 @@ [LibraryClasses]
   UefiDriverEntryPoint
   UefiLib
   VirtioLib
 
 [Protocols]
   gEfiDevicePathProtocolGuid     ## TO_START ## BY_START
+  gEfiGraphicsOutputProtocolGuid ## BY_START
   gEfiPciIoProtocolGuid          ## TO_START
   gVirtioDeviceProtocolGuid      ## TO_START
diff --git a/OvmfPkg/VirtioGpuDxe/VirtioGpu.h b/OvmfPkg/VirtioGpuDxe/VirtioGpu.h
index f8839922487c..078b7d44d83e 100644
--- a/OvmfPkg/VirtioGpuDxe/VirtioGpu.h
+++ b/OvmfPkg/VirtioGpuDxe/VirtioGpu.h
@@ -17,12 +17,13 @@
 #ifndef _VIRTIO_GPU_DXE_H_
 #define _VIRTIO_GPU_DXE_H_
 
 #include <IndustryStandard/VirtioGpu.h>
 #include <Library/DebugLib.h>
 #include <Library/UefiLib.h>
+#include <Protocol/GraphicsOutput.h>
 #include <Protocol/VirtioDevice.h>
 
 //
 // Forward declaration of VGPU_GOP.
 //
 typedef struct VGPU_GOP_STRUCT VGPU_GOP;
@@ -111,15 +112,40 @@ struct VGPU_GOP_STRUCT {
   EFI_DEVICE_PATH_PROTOCOL             *GopDevicePath;
 
   //
   // The Gop field is installed on the child handle as Graphics Output Protocol
   // interface.
   //
-  // For now it is just a placeholder.
+  EFI_GRAPHICS_OUTPUT_PROTOCOL         Gop;
+
   //
-  UINT8                                Gop;
+  // Referenced by Gop.Mode, GopMode provides a summary about the supported
+  // graphics modes, and the current mode.
+  //
+  EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE    GopMode;
+
+  //
+  // Referenced by GopMode.Info, GopModeInfo provides detailed information
+  // about the current mode.
+  //
+  EFI_GRAPHICS_OUTPUT_MODE_INFORMATION GopModeInfo;
+
+  //
+  // Identifier of the 2D host resource that is in use by this head (scanout)
+  // of the VirtIo GPU device. Zero until the first successful -- internal --
+  // Gop.SetMode() call, never zero afterwards.
+  //
+  UINT32                               ResourceId;
+
+  //
+  // A number of whole pages providing the backing store for the 2D host
+  // resource identified by ResourceId above. NULL until the first successful
+  // -- internal -- Gop.SetMode() call, never NULL afterwards.
+  //
+  UINT32                               *BackingStore;
+  UINTN                                NumberOfPages;
 };
 
 //
 // VirtIo GPU initialization, and commands (primitives) for the GPU device.
 //
 /**
@@ -261,7 +287,41 @@ VirtioGpuResourceFlush (
   IN     UINT32   Y,
   IN     UINT32   Width,
   IN     UINT32   Height,
   IN     UINT32   ResourceId
   );
 
+/**
+  Release guest-side and host-side resources that are related to an initialized
+  VGPU_GOP.Gop.
+
+  param[in,out] VgpuGop  The VGPU_GOP object to release resources for.
+
+                         On input, the caller is responsible for having called
+                         VgpuGop->Gop.SetMode() at least once successfully.
+                         (This is equivalent to the requirement that
+                         VgpuGop->BackingStore be non-NULL. It is also
+                         equivalent to the requirement that VgpuGop->ResourceId
+                         be nonzero.)
+
+                         On output, resources will be released, and
+                         VgpuGop->BackingStore and VgpuGop->ResourceId will be
+                         nulled.
+
+  param[in] DisableHead  Whether this head (scanout) currently references the
+                         resource identified by VgpuGop->ResourceId. Only pass
+                         FALSE when VgpuGop->Gop.SetMode() calls this function
+                         while switching between modes, and set it to TRUE
+                         every other time.
+**/
+VOID
+ReleaseGopResources (
+  IN OUT VGPU_GOP *VgpuGop,
+  IN     BOOLEAN  DisableHead
+  );
+
+//
+// Template for initializing VGPU_GOP.Gop.
+//
+extern CONST EFI_GRAPHICS_OUTPUT_PROTOCOL mGopTemplate;
+
 #endif // _VIRTIO_GPU_DXE_H_
diff --git a/OvmfPkg/VirtioGpuDxe/DriverBinding.c b/OvmfPkg/VirtioGpuDxe/DriverBinding.c
index bdea55ef7dbf..33c1ad3b3110 100644
--- a/OvmfPkg/VirtioGpuDxe/DriverBinding.c
+++ b/OvmfPkg/VirtioGpuDxe/DriverBinding.c
@@ -26,22 +26,12 @@
 #include <Protocol/DriverBinding.h>
 #include <Protocol/PciIo.h>
 
 #include "VirtioGpu.h"
 
 //
-// Dummy Graphics Output Protocol GUID: a temporary placeholder for the EFI
-// counterpart. It will be replaced with the real thing as soon as we implement
-// the EFI GOP. Refer to VGPU_GOP.Gop.
-//
-STATIC EFI_GUID mDummyGraphicsOutputProtocolGuid = {
-  0x4983f8dc, 0x2782, 0x415b,
-  { 0x91, 0xf5, 0x2c, 0xeb, 0x48, 0x4a, 0x0f, 0xe9 }
-};
-
-//
 // The device path node that describes the Video Output Device Attributes for
 // the single head (UEFI child handle) that we support.
 //
 // The ACPI_DISPLAY_ADR() macro corresponds to Table B-2, section "B.4.2 _DOD"
 // in the ACPI 3.0b spec, or more recently, to Table B-379, section "B.3.2
 // _DOD" in the ACPI 6.0 spec.
@@ -353,24 +343,26 @@ InitVgpuGop (
   }
   ASSERT (ParentVirtIo == ParentBus->VirtIo);
 
   //
   // Initialize our Graphics Output Protocol.
   //
-  // This means "nothing" for now.
+  // Fill in the function members of VgpuGop->Gop from the template, then set
+  // up the rest of the GOP infrastructure by calling SetMode() right now.
   //
-  Status = EFI_SUCCESS;
+  CopyMem (&VgpuGop->Gop, &mGopTemplate, sizeof mGopTemplate);
+  Status = VgpuGop->Gop.SetMode (&VgpuGop->Gop, 0);
   if (EFI_ERROR (Status)) {
     goto CloseVirtIoByChild;
   }
 
   //
   // Install the Graphics Output Protocol on the child handle.
   //
   Status = gBS->InstallProtocolInterface (&VgpuGop->GopHandle,
-                  &mDummyGraphicsOutputProtocolGuid, EFI_NATIVE_INTERFACE,
+                  &gEfiGraphicsOutputProtocolGuid, EFI_NATIVE_INTERFACE,
                   &VgpuGop->Gop);
   if (EFI_ERROR (Status)) {
     goto UninitGop;
   }
 
   //
@@ -378,15 +370,13 @@ InitVgpuGop (
   //
   gBS->RestoreTPL (OldTpl);
   ParentBus->Child = VgpuGop;
   return EFI_SUCCESS;
 
 UninitGop:
-  //
-  // Nothing, for now.
-  //
+  ReleaseGopResources (VgpuGop, TRUE /* DisableHead */);
 
 CloseVirtIoByChild:
   gBS->CloseProtocol (ParentBusController, &gVirtioDeviceProtocolGuid,
     DriverBindingHandle, VgpuGop->GopHandle);
 
 UninstallDevicePath:
@@ -436,22 +426,19 @@ UninitVgpuGop (
 {
   VGPU_GOP   *VgpuGop;
   EFI_STATUS Status;
 
   VgpuGop = ParentBus->Child;
   Status = gBS->UninstallProtocolInterface (VgpuGop->GopHandle,
-                  &mDummyGraphicsOutputProtocolGuid, &VgpuGop->Gop);
+                  &gEfiGraphicsOutputProtocolGuid, &VgpuGop->Gop);
   ASSERT_EFI_ERROR (Status);
 
   //
   // Uninitialize VgpuGop->Gop.
   //
-  // Nothing, for now.
-  //
-  Status = EFI_SUCCESS;
-  ASSERT_EFI_ERROR (Status);
+  ReleaseGopResources (VgpuGop, TRUE /* DisableHead */);
 
   Status = gBS->CloseProtocol (ParentBusController, &gVirtioDeviceProtocolGuid,
                   DriverBindingHandle, VgpuGop->GopHandle);
   ASSERT_EFI_ERROR (Status);
 
   Status = gBS->UninstallProtocolInterface (VgpuGop->GopHandle,
diff --git a/OvmfPkg/VirtioGpuDxe/Gop.c b/OvmfPkg/VirtioGpuDxe/Gop.c
new file mode 100644
index 000000000000..c6ff9ed57461
--- /dev/null
+++ b/OvmfPkg/VirtioGpuDxe/Gop.c
@@ -0,0 +1,647 @@
+/** @file
+
+  EFI_GRAPHICS_OUTPUT_PROTOCOL member functions for the VirtIo GPU driver.
+
+  Copyright (C) 2016, Red Hat, Inc.
+
+  This program and the accompanying materials are licensed and made available
+  under the terms and conditions of the BSD License which accompanies this
+  distribution. The full text of the license may be found at
+  http://opensource.org/licenses/bsd-license.php
+
+  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, WITHOUT
+  WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
+
+**/
+
+#include <Library/BaseMemoryLib.h>
+#include <Library/MemoryAllocationLib.h>
+
+#include "VirtioGpu.h"
+
+/**
+  Release guest-side and host-side resources that are related to an initialized
+  VGPU_GOP.Gop.
+
+  param[in,out] VgpuGop  The VGPU_GOP object to release resources for.
+
+                         On input, the caller is responsible for having called
+                         VgpuGop->Gop.SetMode() at least once successfully.
+                         (This is equivalent to the requirement that
+                         VgpuGop->BackingStore be non-NULL. It is also
+                         equivalent to the requirement that VgpuGop->ResourceId
+                         be nonzero.)
+
+                         On output, resources will be released, and
+                         VgpuGop->BackingStore and VgpuGop->ResourceId will be
+                         nulled.
+
+  param[in] DisableHead  Whether this head (scanout) currently references the
+                         resource identified by VgpuGop->ResourceId. Only pass
+                         FALSE when VgpuGop->Gop.SetMode() calls this function
+                         while switching between modes, and set it to TRUE
+                         every other time.
+**/
+VOID
+ReleaseGopResources (
+  IN OUT VGPU_GOP *VgpuGop,
+  IN     BOOLEAN  DisableHead
+  )
+{
+  EFI_STATUS Status;
+
+  ASSERT (VgpuGop->ResourceId != 0);
+  ASSERT (VgpuGop->BackingStore != NULL);
+
+  //
+  // If any of the following host-side destruction steps fail, we can't get out
+  // of an inconsistent state, so we'll hang. In general errors in object
+  // destruction can hardly be recovered from.
+  //
+  if (DisableHead) {
+    //
+    // Dissociate head (scanout) #0 from the currently used 2D host resource,
+    // by setting ResourceId=0 for it.
+    //
+    Status = VirtioGpuSetScanout (
+               VgpuGop->ParentBus, // VgpuDev
+               0, 0, 0, 0,         // X, Y, Width, Height
+               0,                  // ScanoutId
+               0                   // ResourceId
+               );
+    //
+    // HACK BEGINS HERE
+    //
+    // According to the GPU Device section of the VirtIo specification, the
+    // above operation is valid:
+    //
+    // "The driver can use resource_id = 0 to disable a scanout."
+    //
+    // However, in practice QEMU does not allow us to disable head (scanout) #0
+    // -- it rejects the command with response code 0x1202
+    // (VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID). Looking at the QEMU source
+    // code, function virtio_gpu_set_scanout() in "hw/display/virtio-gpu.c",
+    // this appears fully intentional, despite not being documented in the
+    // spec.
+    //
+    // Surprisingly, ignoring the error here, and proceeding to release
+    // host-side resources that presumably underlie head (scanout) #0, work
+    // without any problems -- the driver survives repeated "disconnect" /
+    // "connect -r" commands in the UEFI shell.
+    //
+    // So, for now, let's just suppress the error.
+    //
+    Status = EFI_SUCCESS;
+    //
+    // HACK ENDS HERE
+    //
+
+    ASSERT_EFI_ERROR (Status);
+    if (EFI_ERROR (Status)) {
+      CpuDeadLoop ();
+    }
+  }
+
+  //
+  // Detach backing pages from the currently used 2D host resource.
+  //
+  Status = VirtioGpuResourceDetachBacking (
+             VgpuGop->ParentBus, // VgpuDev
+             VgpuGop->ResourceId // ResourceId
+             );
+  ASSERT_EFI_ERROR (Status);
+  if (EFI_ERROR (Status)) {
+    CpuDeadLoop ();
+  }
+
+  //
+  // Release backing pages.
+  //
+  FreePages (VgpuGop->BackingStore, VgpuGop->NumberOfPages);
+  VgpuGop->BackingStore  = NULL;
+  VgpuGop->NumberOfPages = 0;
+
+  //
+  // Destroy the currently used 2D host resource.
+  //
+  Status = VirtioGpuResourceUnref (
+             VgpuGop->ParentBus, // VgpuDev
+             VgpuGop->ResourceId // ResourceId
+             );
+  ASSERT_EFI_ERROR (Status);
+  if (EFI_ERROR (Status)) {
+    CpuDeadLoop ();
+  }
+  VgpuGop->ResourceId = 0;
+}
+
+//
+// The resolutions supported by this driver.
+//
+typedef struct {
+  UINT32 Width;
+  UINT32 Height;
+} GOP_RESOLUTION;
+
+STATIC CONST GOP_RESOLUTION mGopResolutions[] = {
+  {  640,  480 },
+  {  800,  480 },
+  {  800,  600 },
+  {  832,  624 },
+  {  960,  640 },
+  { 1024,  600 },
+  { 1024,  768 },
+  { 1152,  864 },
+  { 1152,  870 },
+  { 1280,  720 },
+  { 1280,  760 },
+  { 1280,  768 },
+  { 1280,  800 },
+  { 1280,  960 },
+  { 1280, 1024 },
+  { 1360,  768 },
+  { 1366,  768 },
+  { 1400, 1050 },
+  { 1440,  900 },
+  { 1600,  900 },
+  { 1600, 1200 },
+  { 1680, 1050 },
+  { 1920, 1080 },
+  { 1920, 1200 },
+  { 1920, 1440 },
+  { 2000, 2000 },
+  { 2048, 1536 },
+  { 2048, 2048 },
+  { 2560, 1440 },
+  { 2560, 1600 },
+  { 2560, 2048 },
+  { 2800, 2100 },
+  { 3200, 2400 },
+  { 3840, 2160 },
+  { 4096, 2160 },
+  { 7680, 4320 },
+  { 8192, 4320 },
+};
+
+//
+// Macro for casting VGPU_GOP.Gop to VGPU_GOP.
+//
+#define VGPU_GOP_FROM_GOP(GopPointer) \
+          CR (GopPointer, VGPU_GOP, Gop, VGPU_GOP_SIG)
+
+//
+// EFI_GRAPHICS_OUTPUT_PROTOCOL member functions.
+//
+STATIC
+EFI_STATUS
+EFIAPI
+GopQueryMode (
+  IN  EFI_GRAPHICS_OUTPUT_PROTOCOL         *This,
+  IN  UINT32                               ModeNumber,
+  OUT UINTN                                *SizeOfInfo,
+  OUT EFI_GRAPHICS_OUTPUT_MODE_INFORMATION **Info
+  )
+{
+  EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *GopModeInfo;
+
+  if (ModeNumber >= sizeof mGopResolutions / sizeof mGopResolutions[0]) {
+    return EFI_INVALID_PARAMETER;
+  }
+
+  GopModeInfo = AllocateZeroPool (sizeof *GopModeInfo);
+  if (GopModeInfo == NULL) {
+    return EFI_OUT_OF_RESOURCES;
+  }
+
+  GopModeInfo->HorizontalResolution = mGopResolutions[ModeNumber].Width;
+  GopModeInfo->VerticalResolution   = mGopResolutions[ModeNumber].Height;
+  GopModeInfo->PixelFormat          = PixelBltOnly;
+  GopModeInfo->PixelsPerScanLine    = mGopResolutions[ModeNumber].Width;
+
+  *SizeOfInfo = sizeof *GopModeInfo;
+  *Info = GopModeInfo;
+  return EFI_SUCCESS;
+}
+
+STATIC
+EFI_STATUS
+EFIAPI
+GopSetMode (
+  IN  EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
+  IN  UINT32                       ModeNumber
+  )
+{
+  VGPU_GOP   *VgpuGop;
+  UINT32     NewResourceId;
+  UINTN      NewNumberOfBytes;
+  UINTN      NewNumberOfPages;
+  VOID       *NewBackingStore;
+  EFI_STATUS Status;
+  EFI_STATUS Status2;
+
+  if (ModeNumber >= sizeof mGopResolutions / sizeof mGopResolutions[0]) {
+    return EFI_UNSUPPORTED;
+  }
+
+  VgpuGop = VGPU_GOP_FROM_GOP (This);
+
+  //
+  // Distinguish the first (internal) call from the other (protocol consumer)
+  // calls.
+  //
+  if (VgpuGop->ResourceId == 0) {
+    //
+    // Set up the Gop -> GopMode -> GopModeInfo pointer chain, and the other
+    // (nonzero) constant fields.
+    //
+    // No direct framebuffer access is supported, only Blt() is.
+    //
+    VgpuGop->Gop.Mode = &VgpuGop->GopMode;
+
+    VgpuGop->GopMode.MaxMode         = (UINT32)(sizeof mGopResolutions /
+                                                sizeof mGopResolutions[0]);
+    VgpuGop->GopMode.Info            = &VgpuGop->GopModeInfo;
+    VgpuGop->GopMode.SizeOfInfo      = sizeof VgpuGop->GopModeInfo;
+
+    VgpuGop->GopModeInfo.PixelFormat = PixelBltOnly;
+
+    //
+    // This is the first time we create a host side resource.
+    //
+    NewResourceId = 1;
+  } else {
+    //
+    // We already have an active host side resource. Create the new one without
+    // interfering with the current one, so that we can cleanly bail out on
+    // error, without disturbing the current graphics mode.
+    //
+    // The formula below will alternate between IDs 1 and 2.
+    //
+    NewResourceId = 3 - VgpuGop->ResourceId;
+  }
+
+  //
+  // Create the 2D host resource.
+  //
+  Status = VirtioGpuResourceCreate2d (
+             VgpuGop->ParentBus,                // VgpuDev
+             NewResourceId,                     // ResourceId
+             VirtioGpuFormatB8G8R8X8Unorm,      // Format
+             mGopResolutions[ModeNumber].Width, // Width
+             mGopResolutions[ModeNumber].Height // Height
+             );
+  if (EFI_ERROR (Status)) {
+    return Status;
+  }
+
+  //
+  // Allocate guest backing store.
+  //
+  NewNumberOfBytes = mGopResolutions[ModeNumber].Width *
+                     mGopResolutions[ModeNumber].Height * sizeof (UINT32);
+  NewNumberOfPages = EFI_SIZE_TO_PAGES (NewNumberOfBytes);
+  NewBackingStore = AllocatePages (NewNumberOfPages);
+  if (NewBackingStore == NULL) {
+    Status = EFI_OUT_OF_RESOURCES;
+    goto DestroyHostResource;
+  }
+  //
+  // Fill visible part of backing store with black.
+  //
+  ZeroMem (NewBackingStore, NewNumberOfBytes);
+
+  //
+  // Attach backing store to the host resource.
+  //
+  Status = VirtioGpuResourceAttachBacking (
+             VgpuGop->ParentBus, // VgpuDev
+             NewResourceId,      // ResourceId
+             NewBackingStore,    // FirstBackingPage
+             NewNumberOfPages    // NumberOfPages
+             );
+  if (EFI_ERROR (Status)) {
+    goto FreeBackingStore;
+  }
+
+  //
+  // Point head (scanout) #0 to the host resource.
+  //
+  Status = VirtioGpuSetScanout (
+             VgpuGop->ParentBus,                 // VgpuDev
+             0,                                  // X
+             0,                                  // Y
+             mGopResolutions[ModeNumber].Width,  // Width
+             mGopResolutions[ModeNumber].Height, // Height
+             0,                                  // ScanoutId
+             NewResourceId                       // ResourceId
+             );
+  if (EFI_ERROR (Status)) {
+    goto DetachBackingStore;
+  }
+
+  //
+  // If this is not the first (i.e., internal) call, then we have to (a) flush
+  // the new resource to head (scanout) #0, after having flipped the latter to
+  // the former above, plus (b) release the old resources.
+  //
+  if (VgpuGop->ResourceId != 0) {
+    Status = VirtioGpuResourceFlush (
+               VgpuGop->ParentBus,                 // VgpuDev
+               0,                                  // X
+               0,                                  // Y
+               mGopResolutions[ModeNumber].Width,  // Width
+               mGopResolutions[ModeNumber].Height, // Height
+               NewResourceId                       // ResourceId
+               );
+    if (EFI_ERROR (Status)) {
+      //
+      // Flip head (scanout) #0 back to the current resource. If this fails, we
+      // cannot continue, as this error occurs on the error path and is
+      // therefore non-recoverable.
+      //
+      Status2 = VirtioGpuSetScanout (
+                  VgpuGop->ParentBus,                       // VgpuDev
+                  0,                                        // X
+                  0,                                        // Y
+                  mGopResolutions[This->Mode->Mode].Width,  // Width
+                  mGopResolutions[This->Mode->Mode].Height, // Height
+                  0,                                        // ScanoutId
+                  VgpuGop->ResourceId                       // ResourceId
+                  );
+      ASSERT_EFI_ERROR (Status2);
+      if (EFI_ERROR (Status2)) {
+        CpuDeadLoop ();
+      }
+      goto DetachBackingStore;
+    }
+
+    //
+    // Flush successful; release the old resources (without disabling head
+    // (scanout) #0).
+    //
+    ReleaseGopResources (VgpuGop, FALSE /* DisableHead */);
+  }
+
+  //
+  // This is either the first (internal) call when we have no old resources
+  // yet, or we've changed the mode successfully and released the old
+  // resources.
+  //
+  ASSERT (VgpuGop->ResourceId == 0);
+  ASSERT (VgpuGop->BackingStore == NULL);
+
+  VgpuGop->ResourceId = NewResourceId;
+  VgpuGop->BackingStore = NewBackingStore;
+  VgpuGop->NumberOfPages = NewNumberOfPages;
+
+  //
+  // Populate Mode and ModeInfo (mutable fields only).
+  //
+  VgpuGop->GopMode.Mode = ModeNumber;
+  VgpuGop->GopModeInfo.HorizontalResolution =
+                                             mGopResolutions[ModeNumber].Width;
+  VgpuGop->GopModeInfo.VerticalResolution = mGopResolutions[ModeNumber].Height;
+  VgpuGop->GopModeInfo.PixelsPerScanLine = mGopResolutions[ModeNumber].Width;
+  return EFI_SUCCESS;
+
+DetachBackingStore:
+  Status2 = VirtioGpuResourceDetachBacking (VgpuGop->ParentBus, NewResourceId);
+  ASSERT_EFI_ERROR (Status2);
+  if (EFI_ERROR (Status2)) {
+    CpuDeadLoop ();
+  }
+
+FreeBackingStore:
+  FreePages (NewBackingStore, NewNumberOfPages);
+
+DestroyHostResource:
+  Status2 = VirtioGpuResourceUnref (VgpuGop->ParentBus, NewResourceId);
+  ASSERT_EFI_ERROR (Status2);
+  if (EFI_ERROR (Status2)) {
+    CpuDeadLoop ();
+  }
+
+  return Status;
+}
+
+STATIC
+EFI_STATUS
+EFIAPI
+GopBlt (
+  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
+  )
+{
+  VGPU_GOP   *VgpuGop;
+  UINT32     CurrentHorizontal;
+  UINT32     CurrentVertical;
+  UINTN      SegmentSize;
+  UINTN      Y;
+  UINTN      ResourceOffset;
+  EFI_STATUS Status;
+
+  VgpuGop = VGPU_GOP_FROM_GOP (This);
+  CurrentHorizontal = VgpuGop->GopModeInfo.HorizontalResolution;
+  CurrentVertical   = VgpuGop->GopModeInfo.VerticalResolution;
+
+  //
+  // We can avoid pixel format conversion in the guest because the internal
+  // representation of EFI_GRAPHICS_OUTPUT_BLT_PIXEL and that of
+  // VirtioGpuFormatB8G8R8X8Unorm are identical.
+  //
+  SegmentSize = Width * sizeof (UINT32);
+
+  //
+  // Delta is relevant for operations that read a rectangle from, or write a
+  // rectangle to, BltBuffer.
+  //
+  // In these cases, Delta is the stride of BltBuffer, in bytes. If Delta is
+  // zero, then Width is the entire width of BltBuffer, and the stride is
+  // supposed to be calculated from Width.
+  //
+  if (BltOperation == EfiBltVideoToBltBuffer ||
+      BltOperation == EfiBltBufferToVideo) {
+    if (Delta == 0) {
+      Delta = SegmentSize;
+    }
+  }
+
+  //
+  // For operations that write to the display, check if the destination fits
+  // onto the display.
+  //
+  if (BltOperation == EfiBltVideoFill ||
+      BltOperation == EfiBltBufferToVideo ||
+      BltOperation == EfiBltVideoToVideo) {
+    if (DestinationX > CurrentHorizontal ||
+        Width > CurrentHorizontal - DestinationX ||
+        DestinationY > CurrentVertical ||
+        Height > CurrentVertical - DestinationY) {
+      return EFI_INVALID_PARAMETER;
+    }
+  }
+
+  //
+  // For operations that read from the display, check if the source fits onto
+  // the display.
+  //
+  if (BltOperation == EfiBltVideoToBltBuffer ||
+      BltOperation == EfiBltVideoToVideo) {
+    if (SourceX > CurrentHorizontal ||
+        Width > CurrentHorizontal - SourceX ||
+        SourceY > CurrentVertical ||
+        Height > CurrentVertical - SourceY) {
+      return EFI_INVALID_PARAMETER;
+    }
+  }
+
+  //
+  // Render the request. For requests that do not modify the display, there
+  // won't be further steps.
+  //
+  switch (BltOperation) {
+  case EfiBltVideoFill:
+    //
+    // Write data from the BltBuffer pixel (0, 0) directly to every pixel of
+    // the video display rectangle (DestinationX, DestinationY) (DestinationX +
+    // Width, DestinationY + Height). Only one pixel will be used from the
+    // BltBuffer. Delta is NOT used.
+    //
+    for (Y = 0; Y < Height; ++Y) {
+      SetMem32 (
+        VgpuGop->BackingStore +
+          (DestinationY + Y) * CurrentHorizontal + DestinationX,
+        SegmentSize,
+        *(UINT32 *)BltBuffer
+        );
+    }
+    break;
+
+  case EfiBltVideoToBltBuffer:
+    //
+    // Read data from the video display rectangle (SourceX, SourceY) (SourceX +
+    // Width, SourceY + Height) and place it in the BltBuffer rectangle
+    // (DestinationX, DestinationY ) (DestinationX + Width, DestinationY +
+    // Height). If DestinationX or DestinationY is not zero then Delta must be
+    // set to the length in bytes of a row in the BltBuffer.
+    //
+    for (Y = 0; Y < Height; ++Y) {
+      CopyMem (
+        (UINT8 *)BltBuffer +
+          (DestinationY + Y) * Delta + DestinationX * sizeof *BltBuffer,
+        VgpuGop->BackingStore +
+          (SourceY + Y) * CurrentHorizontal + SourceX,
+        SegmentSize
+        );
+    }
+    return EFI_SUCCESS;
+
+  case EfiBltBufferToVideo:
+    //
+    // Write data from the BltBuffer rectangle (SourceX, SourceY) (SourceX +
+    // Width, SourceY + Height) directly to the video display rectangle
+    // (DestinationX, DestinationY) (DestinationX + Width, DestinationY +
+    // Height). If SourceX or SourceY is not zero then Delta must be set to the
+    // length in bytes of a row in the BltBuffer.
+    //
+    for (Y = 0; Y < Height; ++Y) {
+      CopyMem (
+        VgpuGop->BackingStore +
+          (DestinationY + Y) * CurrentHorizontal + DestinationX,
+        (UINT8 *)BltBuffer +
+          (SourceY + Y) * Delta + SourceX * sizeof *BltBuffer,
+        SegmentSize
+        );
+    }
+    break;
+
+  case EfiBltVideoToVideo:
+    //
+    // Copy from the video display rectangle (SourceX, SourceY) (SourceX +
+    // Width, SourceY + Height) to the video display rectangle (DestinationX,
+    // DestinationY) (DestinationX + Width, DestinationY + Height). The
+    // BltBuffer and Delta are not used in this mode.
+    //
+    // A single invocation of CopyMem() handles overlap between source and
+    // destination (that is, within a single line), but for multiple
+    // invocations, we must handle overlaps.
+    //
+    if (SourceY < DestinationY) {
+      Y = Height;
+      while (Y > 0) {
+        --Y;
+        CopyMem (
+          VgpuGop->BackingStore +
+            (DestinationY + Y) * CurrentHorizontal + DestinationX,
+          VgpuGop->BackingStore +
+            (SourceY + Y) * CurrentHorizontal + SourceX,
+          SegmentSize
+          );
+      }
+    } else {
+      for (Y = 0; Y < Height; ++Y) {
+        CopyMem (
+          VgpuGop->BackingStore +
+            (DestinationY + Y) * CurrentHorizontal + DestinationX,
+          VgpuGop->BackingStore +
+            (SourceY + Y) * CurrentHorizontal + SourceX,
+          SegmentSize
+          );
+      }
+    }
+    break;
+
+  default:
+    return EFI_INVALID_PARAMETER;
+  }
+
+  //
+  // For operations that wrote to the display, submit the updated area to the
+  // host -- update the host resource from guest memory.
+  //
+  ResourceOffset = sizeof (UINT32) * (DestinationY * CurrentHorizontal +
+                                      DestinationX);
+  Status = VirtioGpuTransferToHost2d (
+             VgpuGop->ParentBus,   // VgpuDev
+             (UINT32)DestinationX, // X
+             (UINT32)DestinationY, // Y
+             (UINT32)Width,        // Width
+             (UINT32)Height,       // Height
+             ResourceOffset,       // Offset
+             VgpuGop->ResourceId   // ResourceId
+             );
+  if (EFI_ERROR (Status)) {
+    return Status;
+  }
+
+  //
+  // Flush the updated resource to the display.
+  //
+  Status = VirtioGpuResourceFlush (
+             VgpuGop->ParentBus,   // VgpuDev
+             (UINT32)DestinationX, // X
+             (UINT32)DestinationY, // Y
+             (UINT32)Width,        // Width
+             (UINT32)Height,       // Height
+             VgpuGop->ResourceId   // ResourceId
+             );
+  return Status;
+}
+
+//
+// Template for initializing VGPU_GOP.Gop.
+//
+CONST EFI_GRAPHICS_OUTPUT_PROTOCOL mGopTemplate = {
+  GopQueryMode,
+  GopSetMode,
+  GopBlt,
+  NULL          // Mode, to be overwritten in the actual protocol instance
+};
