blob: 1a8c136535aa7e7db03d0324803088d78280bf71 [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 "runner.h"
#include <fbl/algorithm.h>
#include <fbl/unique_fd.h>
#include <fcntl.h>
#include <fuchsia/hardware/camera/c/fidl.h>
#include <fuchsia/hardware/display/c/fidl.h>
#include <lib/fzl/fdio.h>
#include <zircon/device/display-controller.h>
#include <cmath>
namespace display_test {
namespace internal {
constexpr const char* kDisplayController = "/dev/class/display-controller/000";
constexpr const char* kCameraDir = "/dev/class/camera";
constexpr uint32_t kDisplayRate = 60;
constexpr uint32_t kDisplayFormat = ZX_PIXEL_FORMAT_ARGB_8888;
bool is_opaque(uint32_t val) { return (val & 0xff000000) == 0xff000000; }
// Src is always premultipled
uint32_t multiply_component(uint32_t dest, uint32_t src, uint8_t offset) {
uint32_t alpha_comp = src >> 24;
uint32_t dest_val =
((((dest >> offset) & 0xff) * (255 - alpha_comp)) + 254) / 255;
uint32_t ret = dest_val + ((src >> offset) & 0xff);
if (ret > 255) {
ret = 255;
}
return ret << offset;
}
uint32_t multiply(uint32_t dest, uint32_t src) {
if (is_opaque(src)) {
return src;
}
return 0xff000000 | multiply_component(dest, src, 16) |
multiply_component(dest, src, 8) | multiply_component(dest, src, 0);
}
uint8_t clip(float in) {
return in < 0 ? 0 : (in > 255 ? 255 : static_cast<uint8_t>(in + .5));
}
// Takes 4 bytes of YUY2 and writes 8 bytes of RGBA
// TODO(stevensd): RGBA -> YUY2 might be more reliable/efficient
void yuy2_to_bgra(uint8_t* yuy2, uint32_t* argb1, uint32_t* argb2) {
uint8_t* bgra1 = reinterpret_cast<uint8_t*>(argb1);
uint8_t* bgra2 = reinterpret_cast<uint8_t*>(argb2);
int u = yuy2[1] - 128;
int y1 = yuy2[0];
int v = yuy2[3] - 128;
int y2 = yuy2[2];
bgra1[0] = clip(y1 + 1.772 * u);
bgra1[1] = clip(y1 - .344 * u - .714 * v);
bgra1[2] = clip(y1 + 1.402 * v);
bgra1[3] = 0xff;
bgra2[0] = clip(y2 + 1.772 * u);
bgra2[1] = clip(y2 - .344 * u - .714 * v);
bgra2[2] = clip(y2 + 1.402 * v);
bgra2[3] = 0xff;
}
static bool compare_component(uint32_t argb1, uint32_t argb2, uint32_t offset) {
uint8_t comp1 = (argb1 >> (offset * 8)) & 0xff;
uint8_t comp2 = (argb2 >> (offset * 8)) & 0xff;
uint8_t diff = comp1 - comp2;
// Unfortunately this is *very* permissive, since it's there are a lot of
// places where slight rounding/implementation differences can accumulate
// (i.e. the display controller blending, rgb->yuv, yuv->rgb, our blending).
return static_cast<uint8_t>(diff + 6) < 13;
}
static bool compare_colors(uint32_t argb1, uint32_t argb2) {
return compare_component(argb1, argb2, 0) &&
compare_component(argb1, argb2, 1) &&
compare_component(argb1, argb2, 2);
}
Runner::Runner(async::Loop* loop) : loop_(loop), runner_context_(this) {}
zx_pixel_format_t Runner::format() const { return kDisplayFormat; }
zx_status_t Runner::Start(const char* display_name) {
zx_status_t status;
display_name_ = display_name;
camera_watcher_ = fsl::DeviceWatcher::Create(
kCameraDir, fit::bind_member(this, &Runner::OnCameraAvailable));
camera_stream_.events().OnFrameAvailable =
fit::bind_member(this, &Runner::FrameNotifyCallback);
status = loop_->Run();
if (!display_id_ || !camera_setup_ || !display_ownership_) {
status = ZX_ERR_INTERNAL;
} else if (status == ZX_ERR_CANCELED) {
status = ZX_OK;
}
return status;
}
void Runner::OnCameraAvailable(int dir_fd, std::string filename) {
fbl::unique_fd fd{::openat(dir_fd, filename.c_str(), O_RDWR)};
if (!fd.is_valid()) {
printf("Failed to open camera %s\n", filename.c_str());
return;
}
zx::channel local, remote;
zx_status_t status = zx::channel::create(0u, &local, &remote);
FXL_CHECK(status == ZX_OK) << "Failed to create channel. status " << status;
fzl::FdioCaller dev(std::move(fd));
zx_status_t res = fuchsia_hardware_camera_DeviceGetChannel(
dev.borrow_channel(), remote.release());
if (res != ZX_OK) {
printf("Failed to obtain channel for camera %s\n", filename.c_str());
return;
}
camera_control_.Bind(std::move(local), loop_->dispatcher());
camera_control_->GetFormats(
0, fit::bind_member(this, &Runner::GetFormatCallback));
}
void Runner::GetFormatCallback(::std::vector<fuchsia::camera::VideoFormat> fmts,
uint32_t total_count, zx_status_t status) {
uint32_t idx;
for (idx = 0; idx < fmts.size(); idx++) {
auto& format = fmts[idx];
uint32_t capture_fps = round(1.0 * format.rate.frames_per_sec_numerator /
format.rate.frames_per_sec_denominator);
if (capture_fps != kDisplayRate) {
continue;
}
if (format.format.pixel_format.type ==
fuchsia::sysmem::PixelFormatType::YUY2) {
break;
}
ZX_ASSERT(format.format.width % 2 == 0);
}
if (idx >= fmts.size()) {
// Technically we should call GetFormat again, but we can handle that
// when it comes it, since this is just a test program.
printf("Failed to find matching capture format\n");
return;
}
// We found a camera, so stop watching the dir for new cameras.
camera_watcher_.reset();
auto& format = fmts[idx];
camera_stride_ = format.format.planes[0].bytes_per_row;
width_ = format.format.width;
height_ = format.format.height;
fuchsia::sysmem::BufferCollectionInfo buffer_collection;
size_t buffer_size = fbl::round_up(
format.format.height * format.format.planes[0].bytes_per_row, 4096u);
buffer_collection.buffer_count = kMaxFrames;
buffer_collection.vmo_size = buffer_size;
buffer_collection.format.set_image(std::move(format.format));
for (uint32_t i = 0; i < kMaxFrames; ++i) {
zx::vmo vmo;
status = zx::vmo::create(buffer_size, 0, &vmo);
ZX_ASSERT(status == ZX_OK);
status = zx::vmar::root_self()->map(
0, vmo, 0, buffer_size, ZX_VM_PERM_READ | ZX_VM_PERM_WRITE,
reinterpret_cast<zx_vaddr_t*>(camera_buffers_ + i));
ZX_ASSERT(status == ZX_OK);
buffer_collection.vmos[i] = std::move(vmo);
}
zx::eventpair driver_token;
status = zx::eventpair::create(0, &stream_token_, &driver_token);
ZX_ASSERT(status == ZX_OK);
camera_control_->CreateStream(std::move(buffer_collection), format.rate,
camera_stream_.NewRequest(),
std::move(driver_token));
camera_stream_->Start();
camera_setup_ = true;
InitDisplay();
}
void Runner::InitDisplay() {
zx_status_t status;
fbl::unique_fd fd(open(kDisplayController, O_RDWR));
if (!fd.is_valid()) {
ZX_ASSERT_MSG(false, "Failed to open display controller");
}
zx::channel device_server, device_client;
status = zx::channel::create(0, &device_server, &device_client);
if (status != ZX_OK) {
ZX_ASSERT_MSG(false, "Failed to create device channel %d\n", status);
}
zx::channel dc_server, dc_client;
status = zx::channel::create(0, &dc_server, &dc_client);
if (status != ZX_OK) {
ZX_ASSERT_MSG(false, "Failed to get create controller channel %d\n",
status);
}
fzl::FdioCaller dev(std::move(fd));
zx_status_t fidl_status = fuchsia_hardware_display_ProviderOpenController(
dev.borrow_channel(), device_server.release(), dc_server.release(),
&status);
if (fidl_status != ZX_OK) {
ZX_ASSERT_MSG(false, "Failed to call service handle %d\n", status);
}
if (status != ZX_OK) {
ZX_ASSERT_MSG(false, "Failed to open controller %d\n", status);
}
display_controller_conn_ = std::move(device_client);
if ((status = display_controller_.Bind(std::move(dc_client),
loop_->dispatcher())) != ZX_OK) {
ZX_ASSERT_MSG(false, "Failed to bind to display controller %d\n", status);
}
display_controller_.events().DisplaysChanged =
fit::bind_member(this, &Runner::OnDisplaysChanged);
display_controller_.events().ClientOwnershipChange =
fit::bind_member(this, &Runner::OnClientOwnershipChange);
display_controller_.events().Vsync = fit::bind_member(this, &Runner::OnVsync);
display_controller_.set_error_handler([](zx_status_t status) {
ZX_ASSERT_MSG(false, "Display controller failure");
});
calibration_image_a_ = runner_context_.CreateImage(width_, height_);
calibration_image_b_ = runner_context_.CreateImage(width_, height_);
calibration_layer_ = runner_context_.CreatePrimaryLayer(width_, height_);
runner_context_.SetLayers(std::vector<Layer*>({calibration_layer_}));
}
void Runner::OnDisplaysChanged(
::std::vector<fuchsia::hardware::display::Info> added,
::std::vector<uint64_t> removed) {
ZX_ASSERT_MSG(!display_id_, "Display change while tests are running");
for (auto& info : added) {
if (strcmp(info.monitor_name.c_str(), display_name_)) {
continue;
}
for (auto& mode : info.modes) {
if (mode.horizontal_resolution != width_ ||
mode.vertical_resolution != height_ ||
mode.refresh_rate_e2 != kDisplayRate * 100) {
continue;
}
display_id_ = info.id;
}
}
ZX_ASSERT_MSG(display_id_, "Failed to find compatible display");
display_controller_->EnableVsync(true);
OnResourceReady();
}
void Runner::OnClientOwnershipChange(bool is_owner) {
if (is_owner) {
display_ownership_ = true;
OnResourceReady();
} else {
ZX_ASSERT_MSG(false, "Lost display ownership");
}
}
void Runner::OnShutdownCallback() { ZX_ASSERT_MSG(false, "Camera shutdown"); }
void Runner::OnResourceReady() {
if (!display_id_ || !camera_setup_ || !display_ownership_) {
return;
}
if (!runner_context_.IsReady()) {
return;
}
if (!test_context_) {
loop_->Quit();
return;
} else if (!test_context_->IsReady()) {
return;
}
test_running_ = true;
// We know the first 2 calibration frames are fine, so skip them
CheckFrameConfig(2);
}
void Runner::Stop() { camera_stream_->Stop(); }
Context* Runner::StartTest() {
test_context_ = std::unique_ptr<Context>(new Context(this));
ZX_ASSERT_MSG(!test_running_, "Test starting while busy");
test_status_ = kTestStatusUnknown;
calibration_layer_->SetImage(calibration_image_a_);
runner_context_.ApplyConfig();
calibration_layer_->SetImage(calibration_image_b_);
runner_context_.ApplyConfig();
return test_context_.get();
}
void Runner::FinishTest(int32_t status) {
test_status_ = status;
test_running_ = false;
loop_->Quit();
}
int32_t Runner::CleanupTest() {
ZX_ASSERT_MSG(!test_running_, "Tried to finish running test");
test_context_.release();
for (auto id : buffer_ids_) {
camera_stream_->ReleaseFrame(id);
}
buffer_ids_.clear();
display_idx_ = 0;
capture_idx_ = 0;
for (auto frame : frames_) {
for (auto layer : frame) {
layer.first->DeleteState(layer.second);
}
}
frames_.clear();
return test_status_;
}
void Runner::ApplyConfig(std::vector<LayerImpl*> layers) {
std::vector<std::pair<LayerImpl*, const void*>> info;
for (auto l : layers) {
info.push_back(std::make_pair(l, l->ApplyState()));
}
frames_.push_back(std::move(info));
}
void Runner::SendFrameConfig(uint32_t frame_idx) {
std::vector<uint64_t> layer_ids;
auto& frame = frames_[frame_idx];
for (auto layer : frame) {
layer_ids.push_back(layer.first->id());
layer.first->SendState(layer.second);
}
display_controller_->SetDisplayLayers(
display_id_, fidl::VectorPtr<uint64_t>(std::move(layer_ids)));
}
void Runner::CheckFrameConfig(uint32_t frame_idx) {
SendFrameConfig(frame_idx);
display_controller_->CheckConfig(
frame_idx == frames_.size() - 1,
[this, frame_idx](
fuchsia::hardware::display::ConfigResult result,
::std::vector<fuchsia::hardware::display::ClientCompositionOp>) {
if (result == fuchsia::hardware::display::ConfigResult::OK) {
if (frame_idx + 1 < frames_.size()) {
CheckFrameConfig(frame_idx + 1);
} else {
ApplyFrame(0);
}
} else {
FinishTest(kTestDisplayCheckFail);
}
});
}
void Runner::ApplyFrame(uint32_t frame_idx) {
SendFrameConfig(frame_idx);
display_controller_->ApplyConfig();
}
void Runner::OnVsync(uint64_t display_id, uint64_t timestamp,
::std::vector<uint64_t> image_ids) {
if (!test_running_ || image_ids.empty()) {
return;
}
auto& frame = frames_[display_idx_];
unsigned image_idx = 0;
for (auto layer : frame) {
uint64_t expected_image = layer.first->image_id(layer.second);
if (expected_image) {
if (image_ids[image_idx++] != expected_image) {
if (display_idx_ != 0) {
FinishTest(kTestVsyncFail);
}
return;
}
}
}
if (capture_idx_ > 0) {
if (display_idx_ + 1 < frames_.size()) {
ApplyFrame(++display_idx_);
}
}
}
void Runner::FrameNotifyCallback(
const fuchsia::camera::FrameAvailableEvent& resp) {
static int64_t last_timestamp = 0;
last_timestamp = resp.metadata.timestamp;
if (test_running_) {
if (resp.frame_status != fuchsia::camera::FrameStatus::OK) {
if (capture_idx_ != 0 || ++bad_capture_count_ > 5) {
ZX_ASSERT_MSG(false, "Bad capture");
FinishTest(kTestCaptureFail);
}
} else if (capture_idx_ < 2) {
if (CheckFrame(capture_idx_, camera_buffers_[resp.buffer_id], true)) {
capture_idx_++;
}
} else {
buffer_ids_.push_back(resp.buffer_id);
capture_idx_++;
if (capture_idx_ == frames_.size()) {
for (unsigned i = 2; i < frames_.size(); i++) {
if (!CheckFrame(i, camera_buffers_[buffer_ids_[i - 2]], false)) {
FinishTest(kTestCaptureMismatch);
return;
}
}
FinishTest(kTestOk);
}
// Don't release the frame, since we need to process it later.
return;
}
}
camera_stream_->ReleaseFrame(resp.buffer_id);
}
bool Runner::CheckFrame(uint32_t frame_idx, uint8_t* capture_ptr, bool quick) {
static uint32_t quickcheck_coords[][2] = {
{0, 0},
{0, height_ - 1},
{width_ - 2, 0},
{width_ - 2, height_ - 1},
{width_ / 2, height_ / 2},
{UINT32_MAX, UINT32_MAX},
};
uint32_t quick_idx = 0;
uint32_t x = 0;
uint32_t y = 0;
while (y < height_ && x < width_) {
uint32_t expected_rgb = 0;
bool first_layer = true;
bool skip = false;
for (auto layer : frames_[frame_idx]) {
bool layer_skip;
uint32_t layer_color;
bool has_pixel =
layer.first->GetPixel(layer.second, x, y, &layer_color, &layer_skip);
if (!has_pixel) {
continue;
}
if (first_layer) {
ZX_ASSERT(is_opaque(layer_color));
first_layer = false;
}
if (layer_skip) {
skip = true;
} else if (skip && is_opaque(layer_color)) {
skip = false;
}
expected_rgb = multiply(expected_rgb, layer_color);
}
// There must be some fully opaque pixel
ZX_ASSERT(!first_layer);
if (!skip) {
uint32_t actual_rgb1, actual_rgb2;
// 2 bytes per pixel, so offset by x * 2
yuy2_to_bgra(capture_ptr + (y * camera_stride_) + (x * 2), &actual_rgb1,
&actual_rgb2);
if (!compare_colors(expected_rgb, actual_rgb1) ||
!compare_colors(expected_rgb, actual_rgb2)) {
if (!quick) {
printf("Mismatch (%d, %d) %08x=%08x,%08x\n", x, y, expected_rgb,
actual_rgb1, actual_rgb2);
}
return false;
}
}
if (quick) {
++quick_idx;
x = quickcheck_coords[quick_idx][0];
y = quickcheck_coords[quick_idx][1];
} else {
// Check a macropixel at a time, so += 2
x += 2;
if (x == width_) {
x = 0;
y++;
}
}
}
return true;
}
} // namespace internal
} // namespace display_test