blob: d75f465e88eb9fed11286872ff345608ff6d2c30 [file] [log] [blame]
// Copyright 2019 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 "vc-display.h"
#include <fcntl.h>
#include <fuchsia/hardware/display/controller/c/banjo.h>
#include <fuchsia/hardware/display/llcpp/fidl.h>
#include <fuchsia/sysmem/llcpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/io.h>
#include <lib/fidl/coding.h>
#include <lib/image-format-llcpp/image-format-llcpp.h>
#include <lib/stdcompat/optional.h>
#include <lib/zx/channel.h>
#include <lib/zx/vmo.h>
#include <string.h>
#include <zircon/assert.h>
#include <zircon/process.h>
#include <zircon/processargs.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <list>
#include <unordered_map>
#include <fbl/unique_fd.h>
#include <zxtest/zxtest.h>
#include "vc.h"
namespace fhd = ::llcpp::fuchsia::hardware::display;
namespace sysmem = ::llcpp::fuchsia::sysmem;
namespace {
// Arbitrary
constexpr uint32_t kSingleBufferStride = 4;
class StubDisplayController : public fhd::Controller::RawChannelInterface {
public:
StubDisplayController() {}
virtual ~StubDisplayController() {}
void ImportVmoImage(fhd::ImageConfig image_config, ::zx::vmo vmo, int32_t offset,
ImportVmoImageCompleter::Sync& _completer) override {
EXPECT_TRUE(false);
}
void ImportImage(fhd::ImageConfig image_config, uint64_t collection_id, uint32_t index,
ImportImageCompleter::Sync& _completer) override {
EXPECT_TRUE(false);
}
void ReleaseImage(uint64_t image_id, ReleaseImageCompleter::Sync& _completer) override {
images_.remove_if([image_id](uint64_t image) { return (image == image_id); });
}
void ImportEvent(::zx::event event, uint64_t id,
ImportEventCompleter::Sync& _completer) override {
EXPECT_TRUE(false);
}
void ReleaseEvent(uint64_t id, ReleaseEventCompleter::Sync& _completer) override {
EXPECT_TRUE(false);
}
void CreateLayer(CreateLayerCompleter::Sync& _completer) override {
layers_.push_back(next_layer_);
_completer.Reply(ZX_OK, next_layer_++);
}
void DestroyLayer(uint64_t layer_id, DestroyLayerCompleter::Sync& _completer) override {
layers_.remove_if([layer_id](uint64_t layer) { return (layer == layer_id); });
}
void ImportGammaTable(uint64_t gamma_table_id, ::fidl::Array<float, 256> r,
::fidl::Array<float, 256> g, ::fidl::Array<float, 256> b,
ImportGammaTableCompleter::Sync& _completer) override {
EXPECT_TRUE(false);
}
void ReleaseGammaTable(uint64_t gamma_table_id,
ReleaseGammaTableCompleter::Sync& _completer) override {
EXPECT_TRUE(false);
}
void SetDisplayMode(uint64_t display_id, fhd::Mode mode,
SetDisplayModeCompleter::Sync& _completer) override {
EXPECT_TRUE(false);
}
void SetDisplayColorConversion(uint64_t display_id, ::fidl::Array<float, 3> preoffsets,
::fidl::Array<float, 9> coefficients,
::fidl::Array<float, 3> postoffsets,
SetDisplayColorConversionCompleter::Sync& _completer) override {
EXPECT_TRUE(false);
}
void SetDisplayGammaTable(uint64_t display_id, uint64_t gamma_table_id,
SetDisplayGammaTableCompleter::Sync& _completer) override {
EXPECT_TRUE(false);
}
void SetDisplayLayers(uint64_t display_id, ::fidl::VectorView<uint64_t> layer_ids,
SetDisplayLayersCompleter::Sync& _completer) override {
// Ignore
}
void SetLayerPrimaryConfig(uint64_t layer_id, fhd::ImageConfig image_config,
SetLayerPrimaryConfigCompleter::Sync& _completer) override {
// Ignore
}
void SetLayerPrimaryPosition(uint64_t layer_id, fhd::Transform transform, fhd::Frame src_frame,
fhd::Frame dest_frame,
SetLayerPrimaryPositionCompleter::Sync& _completer) override {
// Ignore
}
void SetLayerPrimaryAlpha(uint64_t layer_id, fhd::AlphaMode mode, float val,
SetLayerPrimaryAlphaCompleter::Sync& _completer) override {
EXPECT_TRUE(false);
}
void SetLayerCursorConfig(uint64_t layer_id, fhd::ImageConfig image_config,
SetLayerCursorConfigCompleter::Sync& _completer) override {
EXPECT_TRUE(false);
}
void SetLayerCursorPosition(uint64_t layer_id, int32_t x, int32_t y,
SetLayerCursorPositionCompleter::Sync& _completer) override {
EXPECT_TRUE(false);
}
void SetLayerColorConfig(uint64_t layer_id, uint32_t pixel_format,
::fidl::VectorView<uint8_t> color_bytes,
SetLayerColorConfigCompleter::Sync& _completer) override {
EXPECT_TRUE(false);
}
void SetLayerImage(uint64_t layer_id, uint64_t image_id, uint64_t wait_event_id,
uint64_t signal_event_id, SetLayerImageCompleter::Sync& _completer) override {
// Ignore
}
void CheckConfig(bool discard, CheckConfigCompleter::Sync& _completer) override {
_completer.Reply(fhd::ConfigResult::OK, fidl::VectorView<fhd::ClientCompositionOp>());
}
void ApplyConfig(ApplyConfigCompleter::Sync& _completer) override {
// Ignore
}
void EnableVsync(bool enable, EnableVsyncCompleter::Sync& _completer) override {
EXPECT_TRUE(false);
}
void SetVirtconMode(uint8_t mode, SetVirtconModeCompleter::Sync& _completer) override {
EXPECT_TRUE(false);
}
void ImportBufferCollection(uint64_t collection_id, ::zx::channel collection_token,
ImportBufferCollectionCompleter::Sync& _completer) override {
EXPECT_TRUE(false);
}
void ReleaseBufferCollection(uint64_t collection_id,
ReleaseBufferCollectionCompleter::Sync& _completer) override {}
void SetBufferCollectionConstraints(
uint64_t collection_id, fhd::ImageConfig config,
SetBufferCollectionConstraintsCompleter::Sync& _completer) override {
EXPECT_TRUE(false);
}
void GetSingleBufferFramebuffer(GetSingleBufferFramebufferCompleter::Sync& _completer) override {
EXPECT_TRUE(false);
}
void IsCaptureSupported(IsCaptureSupportedCompleter::Sync& _completer) override {
EXPECT_TRUE(false);
}
void ImportImageForCapture(fhd::ImageConfig image_config, uint64_t collection_id, uint32_t index,
ImportImageForCaptureCompleter::Sync& _completer) override {
EXPECT_TRUE(false);
}
void StartCapture(uint64_t signal_event_id, uint64_t image_id,
StartCaptureCompleter::Sync& _completer) override {
EXPECT_TRUE(false);
}
void ReleaseCapture(uint64_t image_id, ReleaseCaptureCompleter::Sync& _completer) override {
EXPECT_TRUE(false);
}
void SetMinimumRgb(uint8_t minimum_rgb, SetMinimumRgbCompleter::Sync& _completer) override {
EXPECT_TRUE(false);
}
void AcknowledgeVsync(uint64_t cookie, AcknowledgeVsyncCompleter::Sync& _completer) override {
EXPECT_TRUE(false);
};
const std::list<uint64_t>& images() const { return images_; }
const std::list<uint64_t>& layers() const { return layers_; }
const std::unordered_map<uint64_t, std::unique_ptr<sysmem::BufferCollection::SyncClient>>&
buffer_collections() const {
return buffer_collections_;
}
protected:
std::list<uint64_t> images_;
uint64_t next_image_ = 1;
// We want to make sure that we destroy every layer we create.
std::list<uint64_t> layers_;
uint64_t next_layer_ = 1;
std::unordered_map<uint64_t, std::unique_ptr<sysmem::BufferCollection::SyncClient>>
buffer_collections_;
};
class StubSingleBufferDisplayController : public StubDisplayController {
public:
void ImportVmoImage(fhd::ImageConfig image_config, ::zx::vmo vmo, int32_t offset,
ImportVmoImageCompleter::Sync& _completer) override {
EXPECT_NE(ZX_HANDLE_INVALID, vmo.get());
images_.push_back(next_image_);
_completer.Reply(ZX_OK, next_image_++);
}
void GetSingleBufferFramebuffer(GetSingleBufferFramebufferCompleter::Sync& _completer) override {
zx::vmo vmo;
zx::vmo::create(4096, 0, &vmo);
_completer.Reply(ZX_OK, std::move(vmo), kSingleBufferStride);
}
};
class StubMultiBufferDisplayController : public StubDisplayController {
public:
void GetSingleBufferFramebuffer(GetSingleBufferFramebufferCompleter::Sync& _completer) override {
_completer.Reply(ZX_ERR_NOT_SUPPORTED, zx::vmo(), 0);
}
void ImportImage(fhd::ImageConfig image_config, uint64_t collection_id, uint32_t index,
ImportImageCompleter::Sync& _completer) override {
images_.push_back(next_image_);
_completer.Reply(ZX_OK, next_image_++);
}
void ImportBufferCollection(uint64_t collection_id, ::zx::channel collection_token,
ImportBufferCollectionCompleter::Sync& _completer) override {
zx::channel server, client;
ASSERT_OK(zx::channel::create(0, &server, &client));
ASSERT_OK(get_sysmem_allocator()
->BindSharedCollection(std::move(collection_token), std::move(server))
.status());
buffer_collections_[collection_id] =
std::make_unique<sysmem::BufferCollection::SyncClient>(std::move(client));
_completer.Reply(ZX_OK);
}
void ReleaseBufferCollection(uint64_t collection_id,
ReleaseBufferCollectionCompleter::Sync& _completer) override {
buffer_collections_.erase(collection_id);
}
void SetBufferCollectionConstraints(
uint64_t collection_id, fhd::ImageConfig config,
SetBufferCollectionConstraintsCompleter::Sync& _completer) override {
sysmem::BufferCollectionConstraints constraints;
constraints.usage.cpu = sysmem::cpuUsageWriteOften | sysmem::cpuUsageRead;
constraints.min_buffer_count = 1;
constraints.image_format_constraints_count = 2;
for (uint32_t i = 0; i < 2; i++) {
auto& image_constraints = constraints.image_format_constraints[i];
image_constraints = image_format::GetDefaultImageFormatConstraints();
if (i == 0) {
image_constraints.pixel_format.type = sysmem::PixelFormatType::BGRA32;
} else {
image_constraints.pixel_format.type = sysmem::PixelFormatType::RGB565;
}
image_constraints.pixel_format.has_format_modifier = true;
image_constraints.pixel_format.format_modifier.value = sysmem::FORMAT_MODIFIER_LINEAR;
image_constraints.color_spaces_count = 1;
image_constraints.color_space[0].type = sysmem::ColorSpaceType::SRGB;
image_constraints.max_coded_width = 0xffffffff;
image_constraints.max_coded_height = 0xffffffff;
image_constraints.max_bytes_per_row = 0xffffffff;
image_constraints.bytes_per_row_divisor = 4;
}
buffer_collections_[collection_id]->SetConstraints(true, constraints);
_completer.Reply(ZX_OK);
}
};
} // namespace
zx_status_t log_create_vc(vc_gfx_t* graphics, vc_t** vc_out) { return ZX_OK; }
void log_delete_vc(vc_t* vc) {}
void set_log_listener_active(bool active) {}
void vc_attach_gfx(vc_t* vc) {}
zx_status_t vc_init_gfx(vc_gfx_t* gfx, zx_handle_t fb_vmo, int32_t width, int32_t height,
zx_pixel_format_t format, int32_t stride) {
return ZX_OK;
}
void vc_change_graphics(vc_gfx_t* graphics) {}
class VcDisplayTest : public zxtest::Test {
void SetUp() override { ASSERT_TRUE(vc_sysmem_connect()); }
void TearDown() override {
if (loop_) {
// Ensure the loop processes all queued FIDL messages.
loop_->Quit();
loop_->JoinThreads();
loop_->ResetQuit();
loop_->RunUntilIdle();
}
loop_.reset();
if (controller_) {
ASSERT_EQ(controller_->layers().size(), 0);
ASSERT_EQ(controller_->images().size(), 0);
ASSERT_EQ(controller_->buffer_collections().size(), 0u);
}
controller_.reset();
}
protected:
void InitializeServer() {
zx::channel client_end, server_end;
zx::channel::create(0u, &server_end, &client_end);
initialize_display_channel(std::move(client_end));
loop_ = std::make_unique<async::Loop>(&kAsyncLoopConfigNoAttachToCurrentThread);
loop_->StartThread();
auto result = fidl::BindServer(loop_->dispatcher(), std::move(server_end), controller_.get());
ASSERT_TRUE(result.is_ok());
server_binding_ = result.take_value();
}
void SendAddDisplay(fhd::Info* display) {
server_binding_.value()->OnDisplaysChanged(fidl::VectorView(fidl::unowned_ptr(display), 1),
fidl::VectorView<uint64_t>());
}
void SendRemoveDisplay(uint64_t id) {
server_binding_.value()->OnDisplaysChanged(
fidl::VectorView<fhd::Info>(), fidl::VectorView<uint64_t>(fidl::unowned_ptr(&id), 1));
}
void ProcessEvent() { ASSERT_OK(dc_callback_handler(ZX_CHANNEL_READABLE)); }
std::unique_ptr<StubDisplayController> controller_;
// Loop needs to be torn down before controller, because that causes the
// binding to close.
std::unique_ptr<async::Loop> loop_;
// Server binding reference used to send events.
cpp17::optional<fidl::ServerBindingRef<fhd::Controller>> server_binding_;
};
TEST_F(VcDisplayTest, EmptyRebind) { ASSERT_EQ(rebind_display(true), ZX_ERR_NO_RESOURCES); }
TEST_F(VcDisplayTest, OneDisplay) {
controller_ = std::make_unique<StubSingleBufferDisplayController>();
InitializeServer();
fhd::Info info;
info.id = 1;
uint32_t format = 0x0;
fhd::Mode mode;
info.modes = fidl::VectorView(fidl::unowned_ptr(&mode), 1);
info.pixel_format = fidl::VectorView(fidl::unowned_ptr(&format), 1);
SendAddDisplay(&info);
ProcessEvent();
ASSERT_TRUE(is_primary_bound());
display_info_t* primary = list_peek_head_type(get_display_list(), display_info_t, node);
ASSERT_TRUE(primary->bound);
handle_display_removed(1);
ASSERT_FALSE(is_primary_bound());
}
TEST_F(VcDisplayTest, TwoDisplays) {
controller_ = std::make_unique<StubSingleBufferDisplayController>();
InitializeServer();
fhd::Info hardware_display;
hardware_display.id = 1;
uint32_t format = 0x0;
fhd::Mode mode;
hardware_display.modes = fidl::VectorView(fidl::unowned_ptr(&mode), 1);
hardware_display.pixel_format = fidl::VectorView(fidl::unowned_ptr(&format), 1);
// Add the first display.
SendAddDisplay(&hardware_display);
ProcessEvent();
ASSERT_TRUE(is_primary_bound());
display_info_t* primary = list_peek_head_type(get_display_list(), display_info_t, node);
ASSERT_TRUE(primary->bound);
// Add the second display.
hardware_display.id = 2;
SendAddDisplay(&hardware_display);
ProcessEvent();
ASSERT_TRUE(is_primary_bound());
// Check that all of the displays were bound.
display_info_t* info;
int num_displays = 0;
list_for_every_entry (get_display_list(), info, display_info_t, node) {
ASSERT_TRUE(info->bound);
num_displays++;
}
ASSERT_EQ(num_displays, 2);
// Remove the second display.
SendRemoveDisplay(2u);
ProcessEvent();
// handle_display_removed(2);
ASSERT_TRUE(is_primary_bound());
// Remove the first display.
SendRemoveDisplay(1u);
ProcessEvent();
ASSERT_FALSE(is_primary_bound());
}
// This test checks that the primary display switches over correctly.
// It allocates display 1 and then display 2, then removes display 1.
// Display 2 should switch over to the primary display.
TEST_F(VcDisplayTest, ChangePrimaryDisplay) {
controller_ = std::make_unique<StubSingleBufferDisplayController>();
InitializeServer();
fhd::Info hardware_display;
hardware_display.id = 1;
uint32_t format = 0x0;
fhd::Mode mode;
hardware_display.modes = fidl::VectorView(fidl::unowned_ptr(&mode), 1);
hardware_display.pixel_format = fidl::VectorView(fidl::unowned_ptr(&format), 1);
// Add the first display.
SendAddDisplay(&hardware_display);
ProcessEvent();
ASSERT_TRUE(is_primary_bound());
display_info_t* primary = list_peek_head_type(get_display_list(), display_info_t, node);
ASSERT_TRUE(primary->bound);
// Add the second display.
hardware_display.id = 2;
SendAddDisplay(&hardware_display);
ProcessEvent();
ASSERT_TRUE(is_primary_bound());
// Check that all of the displays were bound.
display_info_t* info;
int num_displays = 0;
list_for_every_entry (get_display_list(), info, display_info_t, node) {
ASSERT_TRUE(info->bound);
num_displays++;
}
ASSERT_EQ(num_displays, 2);
// Remove the first display.
SendRemoveDisplay(1);
ProcessEvent();
ASSERT_TRUE(is_primary_bound());
// Remove the second display.
SendRemoveDisplay(2);
ProcessEvent();
ASSERT_FALSE(is_primary_bound());
}
TEST_F(VcDisplayTest, SingleBufferVmo) {
controller_ = std::make_unique<StubSingleBufferDisplayController>();
InitializeServer();
fhd::Info hardware_display;
hardware_display.id = 1;
uint32_t format = 0x0;
fhd::Mode mode;
hardware_display.modes = fidl::VectorView(fidl::unowned_ptr(&mode), 1);
hardware_display.pixel_format = fidl::VectorView(fidl::unowned_ptr(&format), 1);
// Add the first display.
SendAddDisplay(&hardware_display);
ProcessEvent();
ASSERT_TRUE(is_primary_bound());
display_info_t* primary = list_peek_head_type(get_display_list(), display_info_t, node);
ASSERT_TRUE(primary->bound);
EXPECT_EQ(1u, controller_->images().size());
EXPECT_NE(ZX_HANDLE_INVALID, primary->image_vmo);
EXPECT_EQ(kSingleBufferStride, primary->stride);
SendRemoveDisplay(1);
ProcessEvent();
}
class VcDisplayMultibufferTest : public VcDisplayTest {
public:
void SetupMode(zx_pixel_format_t format) {
controller_ = std::make_unique<StubMultiBufferDisplayController>();
InitializeServer();
fhd::Info hardware_display;
hardware_display.id = 1;
fhd::Mode mode;
mode.horizontal_resolution = 641;
mode.vertical_resolution = 480;
hardware_display.modes = fidl::VectorView(fidl::unowned_ptr(&mode), 1);
hardware_display.pixel_format = fidl::VectorView(fidl::unowned_ptr(&format), 1);
// Add the first display.
SendAddDisplay(&hardware_display);
ProcessEvent();
ASSERT_TRUE(is_primary_bound());
}
void TeardownDisplay() {
SendRemoveDisplay(1);
ProcessEvent();
}
};
TEST_F(VcDisplayMultibufferTest, RGBA32) {
SetupMode(ZX_PIXEL_FORMAT_ARGB_8888);
display_info_t* primary = list_peek_head_type(get_display_list(), display_info_t, node);
ASSERT_TRUE(primary->bound);
EXPECT_EQ(primary->format, ZX_PIXEL_FORMAT_ARGB_8888);
EXPECT_NE(ZX_HANDLE_INVALID, primary->image_vmo);
EXPECT_EQ(641, primary->stride);
TeardownDisplay();
}
TEST_F(VcDisplayMultibufferTest, RGB565) {
SetupMode(ZX_PIXEL_FORMAT_RGB_565);
display_info_t* primary = list_peek_head_type(get_display_list(), display_info_t, node);
ASSERT_TRUE(primary->bound);
EXPECT_EQ(primary->format, ZX_PIXEL_FORMAT_RGB_565);
EXPECT_NE(ZX_HANDLE_INVALID, primary->image_vmo);
// Stride should be rounded up to be a multiple of 4 bytes.
EXPECT_EQ(642, primary->stride);
TeardownDisplay();
}