blob: 52ebeea515cb95025451877699afd708e6b81901 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fuchsia/element/cpp/fidl.h>
#include <fuchsia/logger/cpp/fidl.h>
#include <fuchsia/sysmem/cpp/fidl.h>
#include <fuchsia/sysmem2/cpp/fidl.h>
#include <fuchsia/tracing/provider/cpp/fidl.h>
#include <fuchsia/ui/composition/cpp/fidl.h>
#include <fuchsia/virtualization/hardware/cpp/fidl.h>
#include <lib/sys/component/cpp/testing/realm_builder.h>
#include <lib/sys/component/cpp/testing/realm_builder_types.h>
#include <lib/zx/result.h>
#include <cmath>
#include <numeric>
#include <virtio/gpu.h>
#include "src/ui/scenic/lib/utils/pixel.h"
#include "src/ui/testing/ui_test_manager/ui_test_manager.h"
#include "src/ui/testing/ui_test_realm/ui_test_realm.h"
#include "src/virtualization/bin/vmm/device/tests/test_with_device.h"
#include "src/virtualization/bin/vmm/device/tests/virtio_queue_fake.h"
namespace {
enum Virtqueues : uint16_t {
kControlQ,
kCursorQ,
kNumQueues,
};
constexpr uint16_t kQueueSize = 16;
const uint32_t kPageSize = zx_system_get_page_size();
constexpr uint32_t kPixelFormat = VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM;
constexpr size_t kPixelSizeInBytes = 4;
constexpr uint32_t kCursorWidth = 64;
constexpr uint32_t kCursorHeight = 64;
const std::array<uint32_t, kNumQueues> kQueueDataSizes = {
// A single full framebuffer for a 1280x800 scanout requires almost 4MiB. A cursor resource
// (64x64) requires 16KiB. Ensure the controlq can safely accommodate these amounts as well as
// any miscellaneous descriptors
8 * 1024 * 1024,
kPageSize,
};
// Resource IDs are client allocated, so any value here is fine except for 0. Some GPU commands (ex
// SET_SCANOUT) use resource_id == 0 to mean no resource so some implementations may fail to create
// a resource with resource_id == 0.
//
// Section 5.7.6.8: controlq: ...The driver can use resource_id = 0 to disable a scanout.
constexpr uint32_t kResourceId = 1;
constexpr uint32_t kScanoutId = 0;
static constexpr uint32_t kGpuStartupWidth = 1280;
static constexpr uint32_t kGpuStartupHeight = 720;
constexpr auto kComponentUrl = "#meta/virtio_gpu.cm";
constexpr auto kGraphicalPresenterUrl = "#meta/test_graphical_presenter.cm";
struct VirtioGpuTestParam {
std::string test_name;
bool configure_cursor_queue;
};
class VirtioGpuTest : public TestWithDevice,
public ::testing::WithParamInterface<VirtioGpuTestParam> {
protected:
VirtioGpuTest()
: // Place both queues after the miscellaneous data segment
control_queue_(phys_mem_,
std::accumulate(kQueueDataSizes.begin(), kQueueDataSizes.end(), 0),
kQueueSize),
cursor_queue_(phys_mem_, control_queue_.end(), kQueueSize) {}
void SetUp() override {
using component_testing::ChildRef;
using component_testing::ParentRef;
using component_testing::Protocol;
using component_testing::RealmBuilder;
using component_testing::RealmRoot;
using component_testing::Route;
ui_testing::UITestRealm::Config ui_config;
ui_config.use_scene_owner = true;
ui_config.accessibility_owner = ui_testing::UITestRealm::AccessibilityOwnerType::FAKE;
ui_config.ui_to_client_services = {fuchsia::ui::composition::Flatland::Name_,
fuchsia::ui::composition::Allocator::Name_};
ui_config.exposed_client_services = {fuchsia::virtualization::hardware::VirtioGpu::Name_};
ui_test_manager_.emplace(std::move(ui_config));
constexpr auto kComponentName = "virtio_gpu";
constexpr auto kGraphicalPresenterComponentName = "graphical_presenter";
auto realm_builder = ui_test_manager_->AddSubrealm();
realm_builder.AddChild(kComponentName, kComponentUrl);
realm_builder.AddChild(kGraphicalPresenterComponentName, kGraphicalPresenterUrl);
realm_builder
.AddRoute(Route{
.capabilities =
{
Protocol{fuchsia::logger::LogSink::Name_},
Protocol{fuchsia::ui::composition::Flatland::Name_},
},
.source = ParentRef(),
.targets = {ChildRef{kComponentName}, ChildRef{kGraphicalPresenterComponentName}}})
.AddRoute(Route{.capabilities =
{
Protocol{fuchsia::sysmem::Allocator::Name_},
Protocol{fuchsia::sysmem2::Allocator::Name_},
Protocol{fuchsia::tracing::provider::Registry::Name_},
Protocol{fuchsia::ui::composition::Allocator::Name_},
},
.source = ParentRef(),
.targets = {ChildRef{kComponentName}}})
.AddRoute(Route{.capabilities =
{
Protocol{fuchsia::virtualization::hardware::VirtioGpu::Name_},
},
.source = ChildRef{kComponentName},
.targets = {ParentRef()}})
.AddRoute(Route{.capabilities =
{
Protocol{fuchsia::ui::app::ViewProvider::Name_},
},
.source = ChildRef{kGraphicalPresenterComponentName},
.targets = {ParentRef()}})
.AddRoute(Route{.capabilities =
{
Protocol{fuchsia::element::GraphicalPresenter::Name_},
},
.source = ChildRef{kGraphicalPresenterComponentName},
.targets = {ChildRef{kComponentName}}});
ui_test_manager_->BuildRealm();
exposed_client_services_ = ui_test_manager_->CloneExposedServicesDirectory();
ui_test_manager_->InitializeScene();
fuchsia::virtualization::hardware::StartInfo start_info;
// The cursor queue is placed at the very end of the guest memory range.
zx_status_t status = MakeStartInfo(cursor_queue_.end(), &start_info);
ASSERT_EQ(ZX_OK, status);
status = exposed_client_services_->Connect(gpu_.NewRequest());
ASSERT_EQ(ZX_OK, status);
status = gpu_->Start(std::move(start_info), nullptr, nullptr);
ASSERT_EQ(ZX_OK, status);
// Configure device queues.
VirtioQueueFake* queues[kNumQueues] = {&control_queue_, &cursor_queue_};
for (uint16_t i = 0; i < kNumQueues; i++) {
auto q = queues[i];
q->Configure(std::accumulate(kQueueDataSizes.begin(), kQueueDataSizes.begin() + i, 0),
kQueueDataSizes[i]);
status = gpu_->ConfigureQueue(i, q->size(), q->desc(), q->avail(), q->used());
ASSERT_EQ(ZX_OK, status);
if (!GetParam().configure_cursor_queue) {
break;
}
}
// Finish negotiating features.
status = gpu_->Ready(0);
ASSERT_EQ(ZX_OK, status);
}
void TearDown() override {
bool complete = false;
ui_test_manager_->TeardownRealm(
[&](fit::result<fuchsia::component::Error> result) { complete = true; });
RunLoopUntil([&]() { return complete; });
}
std::optional<fuchsia::ui::observation::geometry::ViewDescriptor> FindGpuView() {
auto presenter_koid = ui_test_manager_->ClientViewRefKoid();
if (!presenter_koid) {
return {};
}
auto presenter = ui_test_manager_->FindViewFromSnapshotByKoid(*presenter_koid);
if (!presenter || !presenter->has_children() || presenter->children().empty()) {
return {};
}
return ui_test_manager_->FindViewFromSnapshotByKoid(presenter->children()[0]);
}
zx::result<std::pair<uint32_t, uint32_t>> WaitForScanout() {
bool view_created =
RunLoopWithTimeoutOrUntil([this] { return FindGpuView().has_value(); }, zx::sec(20));
if (!view_created) {
return zx::error(ZX_ERR_TIMED_OUT);
}
auto gpu_view = *FindGpuView();
const auto& extent = gpu_view.layout().extent;
return zx::ok(std::make_pair(std::round(extent.max.x - extent.min.x),
std::round(extent.max.y - extent.min.y)));
}
template <typename T>
zx_status_t SendRequest(uint16_t queue_id, VirtioQueueFake& queue, const T& request,
virtio_gpu_ctrl_hdr_t** response) {
zx_status_t status = DescriptorChainBuilder(queue)
.AppendReadableDescriptor(&request, sizeof(request))
.AppendWritableDescriptor(response, sizeof(virtio_gpu_ctrl_hdr_t))
.Build();
if (status != ZX_OK) {
return status;
}
status = gpu_->NotifyQueue(queue_id);
if (status != ZX_OK) {
return status;
}
return WaitOnInterrupt();
}
template <typename T>
zx_status_t SendControlRequest(const T& request, virtio_gpu_ctrl_hdr_t** response) {
return SendRequest(kControlQ, control_queue_, request, response);
}
template <typename T>
zx_status_t SendCursorRequest(const T& request, virtio_gpu_ctrl_hdr_t** response) {
return SendRequest(kCursorQ, cursor_queue_, request, response);
}
void ResourceCreate2d() {
virtio_gpu_ctrl_hdr_t* response;
ASSERT_EQ(SendControlRequest(
virtio_gpu_resource_create_2d_t{
.hdr = {.type = VIRTIO_GPU_CMD_RESOURCE_CREATE_2D},
.resource_id = kResourceId,
.format = kPixelFormat,
.width = kGpuStartupWidth,
.height = kGpuStartupHeight,
},
&response),
ZX_OK);
ASSERT_EQ(VIRTIO_GPU_RESP_OK_NODATA, response->type);
}
void ResourceAttachBacking() {
virtio_gpu_ctrl_hdr_t* response;
ASSERT_EQ(SendControlRequest(
virtio_gpu_resource_attach_backing_t{
.hdr = {.type = VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING},
.resource_id = kResourceId,
.nr_entries = 0,
},
&response),
ZX_OK);
ASSERT_EQ(VIRTIO_GPU_RESP_OK_NODATA, response->type);
}
void SetScanout(uint32_t resource_id, uint32_t response_type) {
virtio_gpu_ctrl_hdr_t* response;
ASSERT_EQ(SendControlRequest(
virtio_gpu_set_scanout_t{
.hdr = {.type = VIRTIO_GPU_CMD_SET_SCANOUT},
.r = {.x = 0, .y = 0, .width = kGpuStartupWidth, .height = kGpuStartupHeight},
.scanout_id = kScanoutId,
.resource_id = resource_id,
},
&response),
ZX_OK);
EXPECT_EQ(response_type, response->type);
}
std::optional<ui_testing::UITestManager> ui_test_manager_;
// Note: use of sync can be problematic here if the test environment needs to handle
// some incoming FIDL requests.
fuchsia::virtualization::hardware::VirtioGpuSyncPtr gpu_;
VirtioQueueFake control_queue_;
VirtioQueueFake cursor_queue_;
std::unique_ptr<sys::ServiceDirectory> exposed_client_services_;
};
TEST_P(VirtioGpuTest, GetDisplayInfo) {
auto geometry = WaitForScanout();
ASSERT_TRUE(geometry.is_ok());
auto [gpu_width, gpu_height] = *geometry;
virtio_gpu_ctrl_hdr_t request = {
.type = VIRTIO_GPU_CMD_GET_DISPLAY_INFO,
};
virtio_gpu_resp_display_info_t* response;
zx_status_t status = DescriptorChainBuilder(control_queue_)
.AppendReadableDescriptor(&request, sizeof(request))
.AppendWritableDescriptor(&response, sizeof(*response))
.Build();
ASSERT_EQ(ZX_OK, status);
status = gpu_->NotifyQueue(0);
ASSERT_EQ(ZX_OK, status);
status = WaitOnInterrupt();
ASSERT_EQ(ZX_OK, status);
EXPECT_EQ(response->hdr.type, VIRTIO_GPU_RESP_OK_DISPLAY_INFO);
EXPECT_EQ(response->pmodes[0].r.x, 0u);
EXPECT_EQ(response->pmodes[0].r.y, 0u);
EXPECT_EQ(response->pmodes[0].r.width, gpu_width);
EXPECT_EQ(response->pmodes[0].r.height, gpu_height);
}
TEST_P(VirtioGpuTest, SetScanout) {
ASSERT_TRUE(WaitForScanout().is_ok());
ResourceCreate2d();
ResourceAttachBacking();
SetScanout(kResourceId, VIRTIO_GPU_RESP_OK_NODATA);
}
TEST_P(VirtioGpuTest, SetScanoutWithInvalidResourceId) {
ASSERT_TRUE(WaitForScanout().is_ok());
ResourceCreate2d();
ResourceAttachBacking();
SetScanout(UINT32_MAX, VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID);
}
TEST_P(VirtioGpuTest, CreateLargeResource) {
virtio_gpu_ctrl_hdr_t* response;
ASSERT_EQ(SendControlRequest(
virtio_gpu_resource_create_2d_t{
.hdr = {.type = VIRTIO_GPU_CMD_RESOURCE_CREATE_2D},
.resource_id = kResourceId,
.width = UINT32_MAX,
.height = UINT32_MAX,
},
&response),
ZX_OK);
EXPECT_EQ(response->type, VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY);
}
TEST_P(VirtioGpuTest, InvalidTransferToHostParams) {
ResourceCreate2d();
ResourceAttachBacking();
// Select a x/y/width/height values that overflow in a way that (x+width)
// and (y+height) are within the buffer, but other calculations will not be.
constexpr virtio_gpu_rect_t kBadRectangle = {
.x = 0x0004'c000,
.y = 0x0000'0008,
.width = 0xfffb'4500,
.height = 0x0000'02c8,
};
static_assert(kBadRectangle.width + kBadRectangle.x <= kGpuStartupWidth);
static_assert(kBadRectangle.height + kBadRectangle.y <= kGpuStartupHeight);
virtio_gpu_ctrl_hdr_t* response;
ASSERT_EQ(
SendControlRequest(
virtio_gpu_transfer_to_host_2d_t{
.hdr = {.type = VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D},
.r = kBadRectangle,
.offset = (kBadRectangle.y * kGpuStartupWidth + kBadRectangle.x) * kPixelSizeInBytes,
.resource_id = kResourceId,
},
&response),
ZX_OK);
EXPECT_EQ(response->type, VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER);
}
TEST_P(VirtioGpuTest, UpdateCursor) {
if (!GetParam().configure_cursor_queue)
return;
auto geometry = WaitForScanout();
ASSERT_TRUE(geometry.is_ok());
auto [display_width, display_height] = *geometry;
constexpr uint32_t kBackgroundResourceId = 10;
constexpr uint32_t kCursorResourceId = 20;
// Cursor hotspot position, relative to the cursor resource's top-left corner.
constexpr uint32_t kCursorHotX = 63;
constexpr uint32_t kCursorHotY = 63;
// Position at which to draw the cursor resource.
constexpr uint32_t kCursorX = 100;
constexpr uint32_t kCursorY = 100;
const auto& kWhite = utils::Pixel(0xff, 0xff, 0xff, 0xff);
const auto& kBlack = utils::kBlack;
const auto& kMagenta = utils::kMagenta;
// Create background resource
{
virtio_gpu_ctrl_hdr_t* response;
ASSERT_EQ(SendControlRequest(
virtio_gpu_resource_create_2d_t{
.hdr = {.type = VIRTIO_GPU_CMD_RESOURCE_CREATE_2D},
.resource_id = kBackgroundResourceId,
.format = kPixelFormat,
.width = display_width,
.height = display_height,
},
&response),
ZX_OK);
ASSERT_EQ(VIRTIO_GPU_RESP_OK_NODATA, response->type);
}
// Initialize backing memory for background resource
uint32_t background_data_size = display_width * display_height * kPixelSizeInBytes;
auto background_allocation = control_queue_.AllocData(background_data_size);
std::memset(background_allocation.device_mem, 0xff, background_data_size);
// Attach backing memory to background resource
{
struct {
virtio_gpu_resource_attach_backing_t attach_backing;
virtio_gpu_mem_entry_t entry;
} __PACKED request = {
.attach_backing =
{
.hdr = {.type = VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING},
.resource_id = kBackgroundResourceId,
.nr_entries = 1,
},
.entry =
{
.addr = background_allocation.driver_mem,
.length = background_data_size,
},
};
virtio_gpu_ctrl_hdr_t* response;
ASSERT_EQ(SendControlRequest(request, &response), ZX_OK);
ASSERT_EQ(VIRTIO_GPU_RESP_OK_NODATA, response->type);
}
const virtio_gpu_rect_t full_screen_rect = {
.x = 0,
.y = 0,
.width = display_width,
.height = display_height,
};
ASSERT_LE(full_screen_rect.width + full_screen_rect.x, display_width);
ASSERT_LE(full_screen_rect.height + full_screen_rect.y, display_height);
// Set scanout to resource
{
virtio_gpu_ctrl_hdr_t* response;
ASSERT_EQ(SendControlRequest(
virtio_gpu_set_scanout_t{
.hdr = {.type = VIRTIO_GPU_CMD_SET_SCANOUT},
.r = full_screen_rect,
.scanout_id = kScanoutId,
.resource_id = kBackgroundResourceId,
},
&response),
ZX_OK);
ASSERT_EQ(VIRTIO_GPU_RESP_OK_NODATA, response->type);
}
// Transfer background resource to host
{
virtio_gpu_ctrl_hdr_t* response;
ASSERT_EQ(SendControlRequest(
virtio_gpu_transfer_to_host_2d_t{
.hdr = {.type = VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D},
.r = full_screen_rect,
.offset = 0,
.resource_id = kBackgroundResourceId,
},
&response),
ZX_OK);
ASSERT_EQ(VIRTIO_GPU_RESP_OK_NODATA, response->type);
}
// Flush resource to display
{
virtio_gpu_ctrl_hdr_t* response;
ASSERT_EQ(SendControlRequest(
virtio_gpu_resource_flush_t{
.hdr = {.type = VIRTIO_GPU_CMD_RESOURCE_FLUSH},
.r = full_screen_rect,
.resource_id = kBackgroundResourceId,
},
&response),
ZX_OK);
ASSERT_EQ(VIRTIO_GPU_RESP_OK_NODATA, response->type);
}
// We should have an opaque white background at this point.
{
std::map<utils::Pixel, uint32_t> expected_histogram{
std::tuple{kWhite, display_width * display_height}};
RunLoopUntil(
[&]() { return ui_test_manager_->TakeScreenshot().Histogram() == expected_histogram; });
auto screenshot = ui_test_manager_->TakeScreenshot();
EXPECT_EQ(screenshot.width(), display_width);
EXPECT_EQ(screenshot.height(), display_height);
ASSERT_EQ(screenshot.Histogram(), expected_histogram);
}
// Create cursor resource
{
virtio_gpu_ctrl_hdr_t* response;
ASSERT_EQ(SendControlRequest(
virtio_gpu_resource_create_2d_t{
.hdr = {.type = VIRTIO_GPU_CMD_RESOURCE_CREATE_2D},
.resource_id = kCursorResourceId,
.format = kPixelFormat,
.width = kCursorWidth,
.height = kCursorHeight,
},
&response),
ZX_OK);
ASSERT_EQ(VIRTIO_GPU_RESP_OK_NODATA, response->type);
}
// Initialize backing memory for cursor resource. It will be all black except for the
// cursor hotspot pixel which will be magenta.
uint32_t cursor_data_size = kCursorWidth * kCursorHeight * kPixelSizeInBytes;
auto cursor_allocation = control_queue_.AllocData(cursor_data_size);
uint8_t* cursor_data = static_cast<uint8_t*>(cursor_allocation.device_mem);
// Pixel format is BGRA
for (size_t i = 0; i < cursor_data_size; i += kPixelSizeInBytes) {
cursor_data[i + 0] = kBlack.blue;
cursor_data[i + 1] = kBlack.green;
cursor_data[i + 2] = kBlack.red;
cursor_data[i + 3] = kBlack.alpha;
}
size_t hotspot_idx =
kCursorWidth * kPixelSizeInBytes * kCursorHotX + kPixelSizeInBytes * kCursorHotY;
cursor_data[hotspot_idx + 0] = kMagenta.blue;
cursor_data[hotspot_idx + 1] = kMagenta.green;
cursor_data[hotspot_idx + 2] = kMagenta.red;
cursor_data[hotspot_idx + 3] = kMagenta.alpha;
// Attach backing memory to cursor resource
{
struct {
virtio_gpu_resource_attach_backing_t attach_backing;
virtio_gpu_mem_entry_t entry;
} __PACKED request = {
.attach_backing =
{
.hdr = {.type = VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING},
.resource_id = kCursorResourceId,
.nr_entries = 1,
},
.entry =
{
.addr = cursor_allocation.driver_mem,
.length = cursor_data_size,
},
};
virtio_gpu_ctrl_hdr_t* response;
ASSERT_EQ(SendControlRequest(request, &response), ZX_OK);
ASSERT_EQ(VIRTIO_GPU_RESP_OK_NODATA, response->type);
}
// Transfer cursor resource to host
{
virtio_gpu_ctrl_hdr_t* response;
ASSERT_EQ(SendControlRequest(
virtio_gpu_transfer_to_host_2d_t{
.hdr = {.type = VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D},
.r =
{
.x = 0,
.y = 0,
.width = kCursorWidth,
.height = kCursorHeight,
},
.offset = 0,
.resource_id = kCursorResourceId,
},
&response),
ZX_OK);
ASSERT_EQ(VIRTIO_GPU_RESP_OK_NODATA, response->type);
}
// Try to update the cursor. (hot_x, hot_y) is the active pixel's coordinate within the cursor
// image and should be the pixel eventually found at the (x, y) coordinate.
{
virtio_gpu_ctrl_hdr_t* response;
ASSERT_EQ(SendCursorRequest(
virtio_gpu_update_cursor_t{
.hdr = {.type = VIRTIO_GPU_CMD_UPDATE_CURSOR},
.pos =
{
.scanout_id = kScanoutId,
.x = kCursorX,
.y = kCursorY,
},
.resource_id = kCursorResourceId,
.hot_x = kCursorHotX,
.hot_y = kCursorHotY,
},
&response),
ZX_OK);
ASSERT_EQ(VIRTIO_GPU_RESP_OK_NODATA, response->type);
}
// Verify the screenshot is as expected.
{
std::map<utils::Pixel, uint32_t> expected_histogram{
std::tuple{kWhite, display_width * display_height - kCursorWidth * kCursorHeight},
std::tuple{kBlack, kCursorWidth * kCursorHeight - 1}, std::tuple{kMagenta, 1}};
RunLoopUntil(
[&]() { return ui_test_manager_->TakeScreenshot().Histogram() == expected_histogram; });
auto screen = ui_test_manager_->TakeScreenshot();
EXPECT_EQ(screen.Histogram(), expected_histogram);
auto screenshot = screen.screenshot();
ASSERT_EQ(screenshot.size(), display_height);
ASSERT_EQ(screenshot[0].size(), display_width);
size_t below_cursor = kCursorY + kCursorHeight;
size_t right_of_cursor = kCursorX + kCursorWidth;
std::vector<utils::Pixel> scanline{display_width, kWhite};
for (size_t row_idx = 0; row_idx < kCursorY; row_idx++) {
EXPECT_EQ(screenshot[row_idx], scanline);
}
for (size_t row_idx = below_cursor; row_idx < display_height; row_idx++) {
EXPECT_EQ(screenshot[row_idx], scanline);
}
for (size_t col_idx = kCursorX; col_idx < right_of_cursor; col_idx++) {
scanline[col_idx] = kBlack;
}
for (size_t row_idx = kCursorY; row_idx < below_cursor; row_idx++) {
if (row_idx != kCursorY + kCursorHotY) {
EXPECT_EQ(screenshot[row_idx], scanline);
}
}
scanline[kCursorX + kCursorHotX] = kMagenta;
EXPECT_EQ(screenshot[kCursorY + kCursorHotY], scanline);
}
// Clear the cursor with resource_id 0.
{
virtio_gpu_ctrl_hdr_t* response;
ASSERT_EQ(SendCursorRequest(
virtio_gpu_update_cursor_t{
.hdr = {.type = VIRTIO_GPU_CMD_UPDATE_CURSOR},
.pos =
{
.scanout_id = kScanoutId,
.x = kCursorX,
.y = kCursorY,
},
.resource_id = 0,
.hot_x = kCursorHotX,
.hot_y = kCursorHotY,
},
&response),
ZX_OK);
ASSERT_EQ(VIRTIO_GPU_RESP_OK_NODATA, response->type);
}
// Once more we should have just a white background.
{
std::map<utils::Pixel, uint32_t> expected_histogram{
std::tuple{kWhite, display_width * display_height}};
RunLoopUntil(
[&]() { return ui_test_manager_->TakeScreenshot().Histogram() == expected_histogram; });
auto screenshot = ui_test_manager_->TakeScreenshot();
EXPECT_EQ(screenshot.width(), display_width);
EXPECT_EQ(screenshot.height(), display_height);
ASSERT_EQ(screenshot.Histogram(), expected_histogram);
}
}
INSTANTIATE_TEST_SUITE_P(VirtioGpuComponentsTest, VirtioGpuTest,
testing::Values(VirtioGpuTestParam{"cursorq", true},
VirtioGpuTestParam{"nocursorq", false}),
[](const testing::TestParamInfo<VirtioGpuTestParam>& info) {
return info.param.test_name;
});
} // namespace