blob: 34ff6280b64e45635c3f21360c66d5b9d2dfe8ac [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 <lib/driver-unit-test/utils.h>
#include <lib/fit/function.h>
#include <lib/fzl/vmo-mapper.h>
#include <lib/image-format/image_format.h>
#include <lib/sync/completion.h>
#include <memory>
#include <vector>
#include <ddk/debug.h>
#include <safemath/safe_conversions.h>
#include <zxtest/zxtest.h>
#include "src/camera/drivers/hw_accel/ge2d/ge2d.h"
#include "src/camera/drivers/hw_accel/ge2d/ge2d_regs.h"
#include "src/camera/drivers/test_utils/fake_buffer_collection.h"
namespace ge2d {
namespace {
constexpr uint32_t kImageFormatTableSize = 3;
constexpr uint32_t kWidth = 1024;
constexpr uint32_t kHeight = 1024;
constexpr uint32_t kWatermarkVerticalOffset = 10;
constexpr uint32_t kWatermarkHorizontalOffset = 10;
constexpr uint32_t kWatermarkWidth = 65;
constexpr uint32_t kWatermarkHeight = 64;
class Ge2dDeviceTest : public zxtest::Test {
public:
void SetUp() override {
zx_status_t status = ge2d::Ge2dDevice::Setup(driver_unit_test::GetParent(), &ge2d_device_);
ZX_ASSERT(status == ZX_OK);
}
void TearDown() override {
EXPECT_OK(camera::DestroyContiguousBufferCollection(input_buffer_collection_));
EXPECT_OK(camera::DestroyContiguousBufferCollection(output_buffer_collection_));
}
void SetupInput(uint32_t input_format = fuchsia_sysmem_PixelFormatType_NV12,
uint32_t output_format = fuchsia_sysmem_PixelFormatType_NV12) {
uint32_t buffer_collection_count = 2;
ASSERT_OK(
camera::GetImageFormat(output_image_format_table_[0], output_format, kWidth, kHeight));
ASSERT_OK(camera::GetImageFormat(output_image_format_table_[1], output_format, kWidth / 2,
kHeight / 2));
ASSERT_OK(camera::GetImageFormat(output_image_format_table_[2], output_format, kWidth / 4,
kHeight / 4));
// Set up fake Resize info
resize_info_.crop.x = 0;
resize_info_.crop.y = 0;
resize_info_.crop.width = kWidth;
resize_info_.crop.height = kHeight;
resize_info_.output_rotation = GE2D_ROTATION_ROTATION_0;
image_format_2_t input_image_format;
ASSERT_OK(camera::GetImageFormat(input_image_format, input_format, kWidth, kHeight));
zx_status_t status = camera::CreateContiguousBufferCollectionInfo(
input_buffer_collection_, input_image_format, ge2d_device_->bti().get(),
buffer_collection_count);
ASSERT_OK(status);
status = camera::CreateContiguousBufferCollectionInfo(
output_buffer_collection_, output_image_format_table_[0], ge2d_device_->bti().get(),
buffer_collection_count);
ASSERT_OK(status);
for (uint32_t i = 0; i < output_buffer_collection_.buffer_count; i++) {
size_t size;
ASSERT_OK(zx_vmo_get_size(output_buffer_collection_.buffers[i].vmo, &size));
ASSERT_OK(zx_vmo_op_range(output_buffer_collection_.buffers[i].vmo, ZX_VMO_OP_CACHE_CLEAN, 0,
size, nullptr, 0));
}
}
void SetupCallbacks() {
res_callback_.frame_resolution_changed = [](void* ctx, const frame_available_info* info) {
static_cast<Ge2dDeviceTest*>(ctx)->RunResolutionChangedCallback(info);
};
res_callback_.ctx = this;
remove_task_callback_.task_removed = [](void* ctx, task_remove_status_t status) {
static_cast<Ge2dDeviceTest*>(ctx)->RunTaskRemovedCallback(status);
};
remove_task_callback_.ctx = this;
frame_callback_.frame_ready = [](void* ctx, const frame_available_info* info) {
static_cast<Ge2dDeviceTest*>(ctx)->RunFrameCallback(info);
};
frame_callback_.ctx = this;
}
void SetFrameCallback(fit::function<void(const frame_available_info* info)> callback) {
client_frame_callback_ = std::move(callback);
}
void RunFrameCallback(const frame_available_info* info) { client_frame_callback_(info); }
void SetTaskRemovedCallback(fit::function<void(task_remove_status_t status)> callback) {
task_removed_callback_ = std::move(callback);
}
void RunTaskRemovedCallback(task_remove_status_t status) { task_removed_callback_(status); }
void RunResolutionChangedCallback(const frame_available_info* info) {
resolution_changed_callback_(info);
}
void SetResolutionChangedCallback(
fit::function<void(const frame_available_info* info)> callback) {
resolution_changed_callback_ = std::move(callback);
}
protected:
void CompareCroppedOutput(const frame_available_info* info, float tolerance = 0.0f);
void SetupWatermarkInfo() {
image_format_2_t watermark_image_format;
ASSERT_OK(camera::GetImageFormat(watermark_image_format,
fuchsia_sysmem_PixelFormatType_R8G8B8A8, kWatermarkWidth,
kWatermarkHeight));
watermark_info_.wm_image_format = watermark_image_format;
watermark_info_.loc_x = kWatermarkHorizontalOffset;
watermark_info_.loc_y = kWatermarkVerticalOffset;
watermark_info_.global_alpha = 1.f;
}
zx::vmo CreateWatermarkVmo() {
zx::vmo watermark_vmo;
EXPECT_OK(zx::vmo::create(watermark_info_.wm_image_format.bytes_per_row *
watermark_info_.wm_image_format.coded_height,
0, &watermark_vmo));
return watermark_vmo;
}
std::unique_ptr<Ge2dDevice> ge2d_device_;
hw_accel_frame_callback_t frame_callback_;
hw_accel_res_change_callback_t res_callback_;
hw_accel_remove_task_callback_t remove_task_callback_;
fit::function<void(const frame_available_info* info)> client_frame_callback_;
fit::function<void(task_remove_status_t status)> task_removed_callback_;
fit::function<void(const frame_available_info* info)> resolution_changed_callback_;
buffer_collection_info_2_t input_buffer_collection_;
buffer_collection_info_2_t output_buffer_collection_;
image_format_2_t output_image_format_table_[kImageFormatTableSize];
sync_completion_t completion_;
resize_info_t resize_info_;
water_mark_info_t watermark_info_;
uint32_t input_format_index_ = 0;
};
void WriteDataToVmo(zx_handle_t vmo, const image_format_2_t& format) {
uint32_t size = format.bytes_per_row * format.coded_height * 3 / 2;
std::vector<uint8_t> input_data(size);
constexpr uint32_t kRunLength = 230;
// 230 should not by a divisor of the stride or height, to ensure adjacent lines have different
// contents.
static_assert(kWidth % kRunLength != 0, "kRunLength is a bad choice");
for (uint32_t i = 0; i < size; i++) {
input_data[i] = i % kRunLength;
}
ASSERT_OK(zx_vmo_write(vmo, input_data.data(), 0, size));
ASSERT_OK(zx_vmo_op_range(vmo, ZX_VMO_OP_CACHE_CLEAN, 0, size, nullptr, 0));
}
void WriteConstantColorToVmo(zx_handle_t vmo, uint8_t y, uint8_t u, uint8_t v,
const image_format_2_t& format) {
uint32_t size = format.bytes_per_row * format.coded_height * 3 / 2;
std::vector<uint8_t> input_data(format.coded_width);
memset(input_data.data(), y, input_data.size());
uint32_t offset = 0;
for (uint32_t y = 0; y < format.coded_height; ++y) {
ASSERT_OK(zx_vmo_write(vmo, input_data.data(), offset, input_data.size()));
offset += format.bytes_per_row;
}
for (uint32_t x = 0; x < format.coded_width / 2; ++x) {
input_data[x * 2] = u;
input_data[x * 2 + 1] = v;
}
for (uint32_t y = 0; y < format.coded_height / 2; ++y) {
ASSERT_OK(zx_vmo_write(vmo, input_data.data(), offset, input_data.size()));
offset += format.bytes_per_row;
}
ASSERT_OK(zx_vmo_op_range(vmo, ZX_VMO_OP_CACHE_CLEAN, 0, size, nullptr, 0));
}
void WriteConstantRgbaToVmo(zx_handle_t vmo, uint8_t r, uint8_t g, uint8_t b, uint8_t a,
const image_format_2_t& format) {
uint32_t size = format.bytes_per_row * format.coded_height;
std::vector<uint8_t> input_data(format.coded_width * 4);
for (uint32_t x = 0; x < format.coded_width; x++) {
input_data[4 * x] = r;
input_data[4 * x + 1] = g;
input_data[4 * x + 2] = b;
input_data[4 * x + 3] = a;
}
uint32_t offset = 0;
for (uint32_t y = 0; y < format.coded_height; ++y) {
ASSERT_OK(zx_vmo_write(vmo, input_data.data(), offset, input_data.size()));
offset += format.bytes_per_row;
}
ASSERT_OK(zx_vmo_op_range(vmo, ZX_VMO_OP_CACHE_CLEAN, 0, size, nullptr, 0));
}
// Write out data without large jumps so it's easier to do a comparison using a
// tolerance.
void WriteScalingDataToVmo(zx_handle_t vmo, const image_format_2_t& format) {
std::vector<uint8_t> input_data(format.coded_width);
// Write to both Y and UV planes in this loop.
for (uint32_t y = 0; y < format.coded_height * 3 / 2; y++) {
for (uint32_t x = 0; x < format.coded_width; ++x) {
// Multiply by 2 so we can see interpolated values in the output.
uint32_t start_val = 2 * x + 4 * y;
// Ensure U and V values are very different, because we don't want to mix
// them up.
if (y >= format.coded_height && ((x & 1) == 1)) {
start_val += 63;
}
// Limit result to [0..255]
constexpr uint32_t kMaxPlus1 = 256;
// Output should go 0-255,255-0,0-255 etc. This is a smooth function so there aren't large
// jumps in output that could cause pixels near the jump to be outside the tolerance.
start_val %= kMaxPlus1 * 2;
if (start_val >= kMaxPlus1)
start_val = (kMaxPlus1 * 2 - 1) - start_val;
input_data[x] = safemath::checked_cast<uint8_t>(start_val);
}
ASSERT_OK(zx_vmo_write(vmo, input_data.data(), y * format.bytes_per_row, format.coded_width));
}
uint32_t size = format.bytes_per_row * format.coded_height * 3 / 2;
ASSERT_OK(zx_vmo_op_range(vmo, ZX_VMO_OP_CACHE_CLEAN, 0, size, nullptr, 0));
}
float lerp(float x, float y, float a) { return x + (y - x) * a; }
float BilinearInterp(fit::function<float(const uint32_t, const uint32_t)> load, float x, float y) {
auto checked_x = safemath::checked_cast<int32_t>(x);
auto checked_y = safemath::checked_cast<int32_t>(y);
auto low_x = std::max(0, checked_x);
auto low_y = std::max(0, checked_y);
// If the input is on a pixel center then read from that both times, to avoid
// reading out of bounds.
auto upper_x = low_x == checked_x ? low_x : low_x + 1;
auto upper_y = low_y == checked_y ? low_y : low_y + 1;
auto a_0_0 = load(low_x, low_y);
auto a_0_1 = load(low_x, upper_y);
auto a_1_0 = load(upper_x, low_y);
auto a_1_1 = load(upper_x, upper_y);
auto row_1 = lerp(a_0_0, a_1_0, x - static_cast<float>(low_x));
auto row_2 = lerp(a_0_1, a_1_1, x - static_cast<float>(low_x));
return lerp(row_1, row_2, y - static_cast<float>(low_y));
}
// Compare a rectangular region of |addr_a| with a rectangular region of |addr_b|.
void CheckSubPlaneEqual(void* addr_a, void* addr_b, uint32_t offset_a, uint32_t offset_b,
uint32_t stride_a, uint32_t stride_b, uint32_t width,
uint32_t bytes_per_pixel, uint32_t height, float x_scale, float y_scale,
float tolerance, const char* error_type) {
uint32_t error_count = 0;
uint8_t* region_start_a = reinterpret_cast<uint8_t*>(addr_a) + offset_a;
uint8_t* region_start_b = reinterpret_cast<uint8_t*>(addr_b) + offset_b;
for (uint32_t y = 0; y < height; y++) {
for (uint32_t x = 0; x < width / bytes_per_pixel; x++) {
uint8_t output_value[4] = {};
memcpy(&output_value, region_start_b + stride_b * y + x * bytes_per_pixel, bytes_per_pixel);
constexpr float kHalfPixel = 0.5f;
// Add and subtract half a pixel to try to account for the pixel center
// location.
float input_x = ((safemath::checked_cast<float>(x) + kHalfPixel) * x_scale - kHalfPixel);
float input_y = ((safemath::checked_cast<float>(y) + kHalfPixel) * y_scale - kHalfPixel);
for (uint32_t c = 0; c < bytes_per_pixel; c++) {
float input_value = BilinearInterp(
[&](uint32_t x, uint32_t y) {
return (region_start_a + stride_a * y + x * bytes_per_pixel)[c];
},
input_x, input_y);
EXPECT_GE(tolerance, std::abs(output_value[c] - input_value),
"%s component %d input %f vs output %d at output (%d, %d), input (%f, %f)",
error_type, c, input_value, output_value[c], x, y, input_x, input_y);
if (tolerance < std::abs(output_value[c] - input_value)) {
if (++error_count >= 10)
return;
}
}
}
}
}
void CacheInvalidateVmo(zx_handle_t vmo) {
uint64_t size;
ASSERT_OK(zx_vmo_get_size(vmo, &size));
ASSERT_OK(zx_vmo_op_range(vmo, ZX_VMO_OP_CACHE_CLEAN_INVALIDATE, 0, size, nullptr, 0));
}
void CheckEqual(zx_handle_t vmo_a, zx_handle_t vmo_b, const image_format_2_t& format) {
CacheInvalidateVmo(vmo_a);
CacheInvalidateVmo(vmo_b);
fzl::VmoMapper mapper_a;
ASSERT_OK(mapper_a.Map(*zx::unowned_vmo(vmo_a), 0, 0, ZX_VM_PERM_READ));
fzl::VmoMapper mapper_b;
ASSERT_OK(mapper_b.Map(*zx::unowned_vmo(vmo_b), 0, 0, ZX_VM_PERM_READ));
CheckSubPlaneEqual(mapper_a.start(), mapper_b.start(), 0, 0, format.bytes_per_row,
format.bytes_per_row, format.coded_width, 1, format.coded_height, 1.0f, 1.0f,
0, "Y");
uint32_t uv_offset = format.bytes_per_row * format.coded_height;
CheckSubPlaneEqual(mapper_a.start(), mapper_b.start(), uv_offset, uv_offset, format.bytes_per_row,
format.bytes_per_row, format.coded_width, 2, format.coded_height / 2, 1.0f,
1.0f, 0, "UV");
}
uint8_t* GetPointerToPixel(void* base, const image_format_2_t& format, uint32_t x, uint32_t y,
uint32_t plane = 0) {
if (format.pixel_format.type == fuchsia_sysmem_PixelFormatType_NV12) {
if (plane == 0) {
return static_cast<uint8_t*>(base) + format.bytes_per_row * y + x;
} else {
return static_cast<uint8_t*>(base) + format.bytes_per_row * (format.coded_height + y / 2) +
(x / 2) * 2;
}
} else {
return static_cast<uint8_t*>(base) + format.bytes_per_row * y + 4 * x;
}
}
// Check that a region is a YUV solid color
void CheckYUVRegion(void* data, const image_format_2_t& format, uint8_t y_component, uint8_t u,
uint8_t v, uint32_t x_start, uint32_t width, uint32_t y_start,
uint32_t height) {
uint32_t error_count = 0;
for (uint32_t y = 0; y < height; y++) {
for (uint32_t x = 0; x < width; x++) {
uint32_t value = *GetPointerToPixel(data, format, x_start + x, y_start + y, 0);
EXPECT_EQ(y_component, value, "(%d, %d)", x, y);
if (y_component != value) {
error_count++;
}
if (error_count > 10)
return;
}
}
for (uint32_t y = 0; y < height / 2; y++) {
for (uint32_t x = 0; x < width / 2; x++) {
uint32_t uv_value = *reinterpret_cast<volatile uint16_t*>(
GetPointerToPixel(data, format, x_start + x, y_start + y, 1));
EXPECT_EQ(u, uv_value & 0xff, "(%d, %d)", x, y);
EXPECT_EQ(v, uv_value >> 8, "(%d, %d)", x, y);
if (u != (uv_value & 0xff) || (v != uv_value >> 8)) {
error_count++;
}
if (error_count > 10)
return;
}
}
}
void Ge2dDeviceTest::CompareCroppedOutput(const frame_available_info* info, float tolerance) {
fprintf(stderr, "Got frame_ready, id %d\n", info->buffer_id);
zx_handle_t vmo_a = input_buffer_collection_.buffers[0].vmo;
zx_handle_t vmo_b = output_buffer_collection_.buffers[info->buffer_id].vmo;
image_format_2_t input_format = output_image_format_table_[input_format_index_];
image_format_2_t output_format = output_image_format_table_[info->metadata.image_format_index];
CacheInvalidateVmo(vmo_a);
CacheInvalidateVmo(vmo_b);
fzl::VmoMapper mapper_a;
ASSERT_OK(mapper_a.Map(*zx::unowned_vmo(vmo_a), 0, 0, ZX_VM_PERM_READ));
fzl::VmoMapper mapper_b;
ASSERT_OK(mapper_b.Map(*zx::unowned_vmo(vmo_b), 0, 0, ZX_VM_PERM_READ));
uint32_t a_start_offset = input_format.bytes_per_row * resize_info_.crop.y + resize_info_.crop.x;
float width_scale = safemath::checked_cast<float>(resize_info_.crop.width) /
safemath::checked_cast<float>(output_format.coded_width);
float height_scale = safemath::checked_cast<float>(resize_info_.crop.height) /
safemath::checked_cast<float>(output_format.coded_height);
// Account for rounding and other minor issues.
if (width_scale != 1.0f || height_scale != 1.0f)
tolerance += 0.7f;
// Pre-scaler may cause minor changes.
if (width_scale > 2.0f)
tolerance += 1;
if (height_scale > 2.0f)
tolerance += 2;
uint32_t height_to_check = output_format.coded_height;
if (height_scale < 1.0f) {
// Last row may be blended with default color, due to the fact that its pixel center
// location is greater than the largest input pixel center location.
height_to_check--;
}
uint32_t width_to_check = output_format.coded_height;
if (width_scale < 1.0f) {
// Same as height above.
width_to_check--;
}
CheckSubPlaneEqual(mapper_a.start(), mapper_b.start(), a_start_offset, 0,
input_format.bytes_per_row, output_format.bytes_per_row, width_to_check, 1,
height_to_check, width_scale, height_scale, tolerance, "Y");
// When scaling is a disabled we currently repeat the input U and V data instead of
// interpolating, so the output UV should just be a shifted version of the input.
uint32_t a_uv_offset = input_format.bytes_per_row * input_format.coded_height;
uint32_t a_uv_start_offset = a_uv_offset +
input_format.bytes_per_row * (resize_info_.crop.y / 2) +
(resize_info_.crop.x / 2) * 2;
uint32_t b_uv_offset = output_format.bytes_per_row * output_format.coded_height;
// Because subsampling reduces the precision of everything, we need to
// increase the tolerance here.
if (tolerance > 0)
tolerance = tolerance * 2 + 1;
if (width_scale < 1.0f || height_scale < 1.0f)
tolerance += 2.0f;
CheckSubPlaneEqual(mapper_a.start(), mapper_b.start(), a_uv_start_offset, b_uv_offset,
input_format.bytes_per_row, output_format.bytes_per_row, width_to_check, 2,
height_to_check / 2, width_scale, height_scale, tolerance, "UV");
}
TEST_F(Ge2dDeviceTest, SameSize) {
SetupCallbacks();
SetupInput();
WriteDataToVmo(input_buffer_collection_.buffers[0].vmo, output_image_format_table_[0]);
SetFrameCallback([this](const frame_available_info* info) {
fprintf(stderr, "Got frame_ready, id %d\n", info->buffer_id);
CheckEqual(input_buffer_collection_.buffers[0].vmo,
output_buffer_collection_.buffers[info->buffer_id].vmo,
output_image_format_table_[0]);
sync_completion_signal(&completion_);
});
uint32_t resize_task;
zx_status_t status = ge2d_device_->Ge2dInitTaskResize(
&input_buffer_collection_, &output_buffer_collection_, &resize_info_,
&output_image_format_table_[0], output_image_format_table_, kImageFormatTableSize, 0,
&frame_callback_, &res_callback_, &remove_task_callback_, &resize_task);
EXPECT_OK(status);
status = ge2d_device_->Ge2dProcessFrame(resize_task, 0, 0);
EXPECT_OK(status);
EXPECT_EQ(ZX_OK, sync_completion_wait(&completion_, ZX_TIME_INFINITE));
}
TEST_F(Ge2dDeviceTest, Crop) {
SetupCallbacks();
SetupInput();
WriteDataToVmo(input_buffer_collection_.buffers[0].vmo, output_image_format_table_[0]);
resize_info_.crop.x = kWidth / 2;
resize_info_.crop.y = kHeight / 2;
resize_info_.crop.width = kWidth / 2;
resize_info_.crop.height = kHeight / 2;
SetFrameCallback([this](const frame_available_info* info) {
CompareCroppedOutput(info);
sync_completion_signal(&completion_);
});
uint32_t resize_task;
zx_status_t status = ge2d_device_->Ge2dInitTaskResize(
&input_buffer_collection_, &output_buffer_collection_, &resize_info_,
&output_image_format_table_[0], output_image_format_table_, kImageFormatTableSize, 1,
&frame_callback_, &res_callback_, &remove_task_callback_, &resize_task);
EXPECT_OK(status);
status = ge2d_device_->Ge2dProcessFrame(resize_task, 0, 0);
EXPECT_OK(status);
EXPECT_EQ(ZX_OK, sync_completion_wait(&completion_, ZX_TIME_INFINITE));
}
TEST_F(Ge2dDeviceTest, CropOddOffset) {
SetupCallbacks();
SetupInput();
WriteDataToVmo(input_buffer_collection_.buffers[0].vmo, output_image_format_table_[0]);
resize_info_.crop.x = 1;
resize_info_.crop.y = 1;
resize_info_.crop.width = kWidth / 2;
resize_info_.crop.height = kHeight / 2;
SetFrameCallback([this](const frame_available_info* info) {
CompareCroppedOutput(info);
sync_completion_signal(&completion_);
});
uint32_t resize_task;
zx_status_t status = ge2d_device_->Ge2dInitTaskResize(
&input_buffer_collection_, &output_buffer_collection_, &resize_info_,
&output_image_format_table_[0], output_image_format_table_, kImageFormatTableSize, 1,
&frame_callback_, &res_callback_, &remove_task_callback_, &resize_task);
EXPECT_OK(status);
status = ge2d_device_->Ge2dProcessFrame(resize_task, 0, 0);
EXPECT_OK(status);
EXPECT_EQ(ZX_OK, sync_completion_wait(&completion_, ZX_TIME_INFINITE));
}
// Scale width down to 50%, but don't scale height.
TEST_F(Ge2dDeviceTest, Scale) {
SetupCallbacks();
SetupInput();
WriteScalingDataToVmo(input_buffer_collection_.buffers[0].vmo, output_image_format_table_[0]);
resize_info_.crop.x = 0;
resize_info_.crop.y = 0;
resize_info_.crop.width = kWidth;
resize_info_.crop.height = kHeight / 2;
SetFrameCallback([this](const frame_available_info* info) {
CompareCroppedOutput(info);
sync_completion_signal(&completion_);
});
uint32_t resize_task;
zx_status_t status = ge2d_device_->Ge2dInitTaskResize(
&input_buffer_collection_, &output_buffer_collection_, &resize_info_,
&output_image_format_table_[0], output_image_format_table_, kImageFormatTableSize, 1,
&frame_callback_, &res_callback_, &remove_task_callback_, &resize_task);
EXPECT_OK(status);
status = ge2d_device_->Ge2dProcessFrame(resize_task, 0, 0);
EXPECT_OK(status);
EXPECT_EQ(ZX_OK, sync_completion_wait(&completion_, ZX_TIME_INFINITE));
}
// Scale width down to 25%, but don't scale height.
TEST_F(Ge2dDeviceTest, ScaleQuarter) {
SetupCallbacks();
SetupInput();
WriteScalingDataToVmo(input_buffer_collection_.buffers[0].vmo, output_image_format_table_[0]);
resize_info_.crop.x = 0;
resize_info_.crop.y = 0;
resize_info_.crop.width = kWidth;
resize_info_.crop.height = kHeight / 4;
SetFrameCallback([this](const frame_available_info* info) {
CompareCroppedOutput(info);
sync_completion_signal(&completion_);
});
uint32_t resize_task;
zx_status_t status = ge2d_device_->Ge2dInitTaskResize(
&input_buffer_collection_, &output_buffer_collection_, &resize_info_,
&output_image_format_table_[0], output_image_format_table_, kImageFormatTableSize, 2,
&frame_callback_, &res_callback_, &remove_task_callback_, &resize_task);
EXPECT_OK(status);
status = ge2d_device_->Ge2dProcessFrame(resize_task, 0, 0);
EXPECT_OK(status);
EXPECT_EQ(ZX_OK, sync_completion_wait(&completion_, ZX_TIME_INFINITE));
}
// Scale height down to 25%, but don't scale width.
TEST_F(Ge2dDeviceTest, ScaleHeightQuarter) {
SetupCallbacks();
SetupInput();
WriteScalingDataToVmo(input_buffer_collection_.buffers[0].vmo, output_image_format_table_[0]);
resize_info_.crop.x = 0;
resize_info_.crop.y = 0;
resize_info_.crop.width = kWidth / 4;
resize_info_.crop.height = kHeight;
SetFrameCallback([this](const frame_available_info* info) {
CompareCroppedOutput(info);
sync_completion_signal(&completion_);
});
uint32_t resize_task;
zx_status_t status = ge2d_device_->Ge2dInitTaskResize(
&input_buffer_collection_, &output_buffer_collection_, &resize_info_,
&output_image_format_table_[0], output_image_format_table_, kImageFormatTableSize, 2,
&frame_callback_, &res_callback_, &remove_task_callback_, &resize_task);
EXPECT_OK(status);
status = ge2d_device_->Ge2dProcessFrame(resize_task, 0, 0);
EXPECT_OK(status);
EXPECT_EQ(ZX_OK, sync_completion_wait(&completion_, ZX_TIME_INFINITE));
}
// Scale width down to 33%, but don't scale height.
TEST_F(Ge2dDeviceTest, ScaleThird) {
SetupCallbacks();
SetupInput();
WriteScalingDataToVmo(input_buffer_collection_.buffers[0].vmo, output_image_format_table_[0]);
resize_info_.crop.x = 0;
resize_info_.crop.y = 0;
resize_info_.crop.width = kWidth / 4 * 3;
resize_info_.crop.height = kHeight / 4;
SetFrameCallback([this](const frame_available_info* info) {
CompareCroppedOutput(info);
sync_completion_signal(&completion_);
});
uint32_t resize_task;
zx_status_t status = ge2d_device_->Ge2dInitTaskResize(
&input_buffer_collection_, &output_buffer_collection_, &resize_info_,
&output_image_format_table_[0], output_image_format_table_, kImageFormatTableSize, 2,
&frame_callback_, &res_callback_, &remove_task_callback_, &resize_task);
EXPECT_OK(status);
status = ge2d_device_->Ge2dProcessFrame(resize_task, 0, 0);
EXPECT_OK(status);
EXPECT_EQ(ZX_OK, sync_completion_wait(&completion_, ZX_TIME_INFINITE));
}
// Scale width and height to 2x.
TEST_F(Ge2dDeviceTest, Scale2x) {
SetupCallbacks();
SetupInput();
WriteScalingDataToVmo(input_buffer_collection_.buffers[0].vmo, output_image_format_table_[1]);
resize_info_.crop.x = 0;
resize_info_.crop.y = 0;
resize_info_.crop.width = kWidth / 2;
resize_info_.crop.height = kHeight / 2;
SetFrameCallback([this](const frame_available_info* info) {
CompareCroppedOutput(info);
sync_completion_signal(&completion_);
});
input_format_index_ = 1;
uint32_t resize_task;
zx_status_t status = ge2d_device_->Ge2dInitTaskResize(
&input_buffer_collection_, &output_buffer_collection_, &resize_info_,
&output_image_format_table_[1], output_image_format_table_, kImageFormatTableSize, 0,
&frame_callback_, &res_callback_, &remove_task_callback_, &resize_task);
EXPECT_OK(status);
status = ge2d_device_->Ge2dProcessFrame(resize_task, 0, 0);
EXPECT_OK(status);
EXPECT_EQ(ZX_OK, sync_completion_wait(&completion_, ZX_TIME_INFINITE));
}
TEST_F(Ge2dDeviceTest, NV12ToRgba) {
SetupCallbacks();
SetupInput(fuchsia_sysmem_PixelFormatType_NV12, fuchsia_sysmem_PixelFormatType_R8G8B8A8);
image_format_2_t input_image_format;
ASSERT_OK(camera::GetImageFormat(input_image_format, fuchsia_sysmem_PixelFormatType_NV12, kWidth,
kHeight));
// Pure red in YUV.
WriteConstantColorToVmo(input_buffer_collection_.buffers[0].vmo, 82, 90, 240, input_image_format);
resize_info_.crop.x = 0;
resize_info_.crop.y = 0;
resize_info_.crop.width = kWidth;
resize_info_.crop.height = kHeight;
SetFrameCallback([this](const frame_available_info* info) {
fprintf(stderr, "Got completed conversion\n");
zx_handle_t vmo_b = output_buffer_collection_.buffers[info->buffer_id].vmo;
CacheInvalidateVmo(vmo_b);
fzl::VmoMapper mapper_b;
ASSERT_OK(mapper_b.Map(*zx::unowned_vmo(vmo_b), 0, 0, ZX_VM_PERM_READ));
uint8_t* output = static_cast<uint8_t*>(mapper_b.start());
// R
EXPECT_EQ(0xff, output[0]);
// G (minor rounding issues)
EXPECT_EQ(0x01, output[1]);
// B
EXPECT_EQ(0x00, output[2]);
// A
EXPECT_EQ(0xff, output[3]);
sync_completion_signal(&completion_);
});
uint32_t resize_task;
zx_status_t status = ge2d_device_->Ge2dInitTaskResize(
&input_buffer_collection_, &output_buffer_collection_, &resize_info_, &input_image_format,
output_image_format_table_, kImageFormatTableSize, 0, &frame_callback_, &res_callback_,
&remove_task_callback_, &resize_task);
EXPECT_OK(status);
status = ge2d_device_->Ge2dProcessFrame(resize_task, 0, 0);
EXPECT_OK(status);
EXPECT_EQ(ZX_OK, sync_completion_wait(&completion_, ZX_TIME_INFINITE));
}
// Test using SetCropRect.
TEST_F(Ge2dDeviceTest, ChangeScale) {
SetupCallbacks();
SetupInput();
WriteScalingDataToVmo(input_buffer_collection_.buffers[0].vmo, output_image_format_table_[0]);
resize_info_.crop.x = 0;
resize_info_.crop.y = kHeight / 4;
resize_info_.crop.width = kWidth / 2;
resize_info_.crop.height = kHeight / 4;
rect_t new_crop_rect = {.x = 0, .y = 0, .width = kWidth, .height = kHeight};
uint32_t frame_count = 0;
SetFrameCallback([this, new_crop_rect, &frame_count](const frame_available_info* info) {
CompareCroppedOutput(info);
resize_info_.crop = new_crop_rect;
if (++frame_count == 2)
sync_completion_signal(&completion_);
});
uint32_t resize_task;
zx_status_t status = ge2d_device_->Ge2dInitTaskResize(
&input_buffer_collection_, &output_buffer_collection_, &resize_info_,
&output_image_format_table_[0], output_image_format_table_, kImageFormatTableSize, 2,
&frame_callback_, &res_callback_, &remove_task_callback_, &resize_task);
EXPECT_OK(status);
status = ge2d_device_->Ge2dProcessFrame(resize_task, 0, 0);
EXPECT_OK(status);
ge2d_device_->Ge2dSetCropRect(resize_task, &new_crop_rect);
status = ge2d_device_->Ge2dProcessFrame(resize_task, 0, 0);
EXPECT_OK(status);
EXPECT_EQ(ZX_OK, sync_completion_wait(&completion_, ZX_TIME_INFINITE));
}
TEST_F(Ge2dDeviceTest, RemoveTask) {
SetupCallbacks();
SetupInput();
WriteScalingDataToVmo(input_buffer_collection_.buffers[0].vmo, output_image_format_table_[1]);
resize_info_.crop.x = 0;
resize_info_.crop.y = 0;
resize_info_.crop.width = kWidth / 2;
resize_info_.crop.height = kHeight / 2;
bool got_frame_callback = false;
SetFrameCallback([this, &got_frame_callback](const frame_available_info* info) {
CompareCroppedOutput(info);
got_frame_callback = true;
});
SetTaskRemovedCallback([this](const task_remove_status_t status) {
EXPECT_EQ(TASK_REMOVE_STATUS_OK, status);
sync_completion_signal(&completion_);
});
input_format_index_ = 1;
uint32_t resize_task;
zx_status_t status = ge2d_device_->Ge2dInitTaskResize(
&input_buffer_collection_, &output_buffer_collection_, &resize_info_,
&output_image_format_table_[1], output_image_format_table_, kImageFormatTableSize, 0,
&frame_callback_, &res_callback_, &remove_task_callback_, &resize_task);
EXPECT_OK(status);
status = ge2d_device_->Ge2dProcessFrame(resize_task, 0, 0);
EXPECT_OK(status);
ge2d_device_->Ge2dRemoveTask(resize_task);
EXPECT_EQ(ZX_OK, sync_completion_wait(&completion_, ZX_TIME_INFINITE));
EXPECT_TRUE(got_frame_callback);
status = ge2d_device_->Ge2dProcessFrame(resize_task, 0, 0);
EXPECT_NOT_OK(status);
}
// Test that a resize from an RGBA image to an RGBA image keeps everything
// except for setting alpha to 0xff.
TEST_F(Ge2dDeviceTest, RgbaToRgba) {
SetupCallbacks();
SetupInput(fuchsia_sysmem_PixelFormatType_R8G8B8A8, fuchsia_sysmem_PixelFormatType_R8G8B8A8);
WriteConstantRgbaToVmo(input_buffer_collection_.buffers[0].vmo, 1, 2, 3, 4,
output_image_format_table_[0]);
SetFrameCallback([this](const frame_available_info* info) {
fprintf(stderr, "Got completed conversion\n");
zx_handle_t vmo_b = output_buffer_collection_.buffers[info->buffer_id].vmo;
CacheInvalidateVmo(vmo_b);
fzl::VmoMapper mapper_b;
ASSERT_OK(mapper_b.Map(*zx::unowned_vmo(vmo_b), 0, 0, ZX_VM_PERM_READ));
uint8_t* output = static_cast<uint8_t*>(mapper_b.start());
// R
EXPECT_EQ(1, output[0]);
// G
EXPECT_EQ(2, output[1]);
// B
EXPECT_EQ(3, output[2]);
// A is forced to be 0xff.
EXPECT_EQ(0xff, output[3]);
sync_completion_signal(&completion_);
});
uint32_t resize_task;
zx_status_t status = ge2d_device_->Ge2dInitTaskResize(
&input_buffer_collection_, &output_buffer_collection_, &resize_info_,
&output_image_format_table_[0], output_image_format_table_, kImageFormatTableSize, 0,
&frame_callback_, &res_callback_, &remove_task_callback_, &resize_task);
EXPECT_OK(status);
status = ge2d_device_->Ge2dProcessFrame(resize_task, 0, 0);
EXPECT_OK(status);
EXPECT_EQ(ZX_OK, sync_completion_wait(&completion_, ZX_TIME_INFINITE));
}
static void DuplicateWatermarkInfo(const water_mark_info_t& input, const zx::vmo& vmo,
uint32_t count, std::vector<water_mark_info_t>* output) {
for (uint32_t i = 0; i < count; i++) {
output->push_back(input);
output->back().watermark_vmo = vmo.get();
}
}
// Test that a watermark with 0 alpha doesn't change the input (when copying RGBA to RGBA)
TEST_F(Ge2dDeviceTest, RGBABlankWatermark) {
SetupCallbacks();
SetupInput(fuchsia_sysmem_PixelFormatType_R8G8B8A8, fuchsia_sysmem_PixelFormatType_R8G8B8A8);
SetupWatermarkInfo();
WriteConstantRgbaToVmo(input_buffer_collection_.buffers[0].vmo, 0x00, 0xff, 0x00, 0xff,
output_image_format_table_[0]);
SetFrameCallback([this](const frame_available_info* info) {
zx_handle_t vmo_b = output_buffer_collection_.buffers[info->buffer_id].vmo;
CacheInvalidateVmo(vmo_b);
fzl::VmoMapper mapper_b;
ASSERT_OK(mapper_b.Map(*zx::unowned_vmo(vmo_b), 0, 0, ZX_VM_PERM_READ));
uint8_t* output = GetPointerToPixel(mapper_b.start(), output_image_format_table_[0],
kWatermarkHorizontalOffset, kWatermarkVerticalOffset);
// Should match the background color from above.
// R
EXPECT_EQ(0x0, output[0]);
// G
EXPECT_EQ(0xff, output[1]);
// B
EXPECT_EQ(0x00, output[2]);
// A
EXPECT_EQ(0xff, output[3]);
sync_completion_signal(&completion_);
});
// Set red to > alpha, to ensure we don't add it in.
zx::vmo watermark_vmo = CreateWatermarkVmo();
WriteConstantRgbaToVmo(watermark_vmo.get(), 0xff, 0, 0, 0x00, watermark_info_.wm_image_format);
std::vector<water_mark_info_t> duplicated;
DuplicateWatermarkInfo(watermark_info_, watermark_vmo, kImageFormatTableSize, &duplicated);
uint32_t watermark_task;
zx_status_t status = ge2d_device_->Ge2dInitTaskWaterMark(
&input_buffer_collection_, &output_buffer_collection_, duplicated.data(), duplicated.size(),
output_image_format_table_, kImageFormatTableSize, 0, &frame_callback_, &res_callback_,
&remove_task_callback_, &watermark_task);
EXPECT_OK(status);
status = ge2d_device_->Ge2dProcessFrame(watermark_task, 0, 0);
EXPECT_OK(status);
EXPECT_EQ(ZX_OK, sync_completion_wait(&completion_, ZX_TIME_INFINITE));
}
// Test that a semi-transparent changes colors in the expected way (when copying RGBA to RGBA).
TEST_F(Ge2dDeviceTest, RGBARedWatermark) {
SetupCallbacks();
SetupInput(fuchsia_sysmem_PixelFormatType_R8G8B8A8, fuchsia_sysmem_PixelFormatType_R8G8B8A8);
SetupWatermarkInfo();
WriteConstantRgbaToVmo(input_buffer_collection_.buffers[0].vmo, 0, 0xff, 0, 0,
output_image_format_table_[0]);
SetFrameCallback([this](const frame_available_info* info) {
zx_handle_t vmo_b = output_buffer_collection_.buffers[info->buffer_id].vmo;
CacheInvalidateVmo(vmo_b);
fzl::VmoMapper mapper_b;
ASSERT_OK(mapper_b.Map(*zx::unowned_vmo(vmo_b), 0, 0, ZX_VM_PERM_READ));
uint8_t* output = GetPointerToPixel(mapper_b.start(), output_image_format_table_[0],
kWatermarkHorizontalOffset, kWatermarkVerticalOffset);
// Should be an alpha-blended version of both images.
// R
EXPECT_EQ(0x7f, output[0]);
// G
EXPECT_EQ(0x80, output[1]);
// B
EXPECT_EQ(0x00, output[2]);
// A
EXPECT_EQ(0xff, output[3]);
sync_completion_signal(&completion_);
});
zx::vmo watermark_vmo = CreateWatermarkVmo();
WriteConstantRgbaToVmo(watermark_vmo.get(), 0xff, 0, 0, 0x7f, watermark_info_.wm_image_format);
std::vector<water_mark_info_t> duplicated;
DuplicateWatermarkInfo(watermark_info_, watermark_vmo, kImageFormatTableSize, &duplicated);
uint32_t watermark_task;
zx_status_t status = ge2d_device_->Ge2dInitTaskWaterMark(
&input_buffer_collection_, &output_buffer_collection_, duplicated.data(), duplicated.size(),
output_image_format_table_, kImageFormatTableSize, 0, &frame_callback_, &res_callback_,
&remove_task_callback_, &watermark_task);
EXPECT_OK(status);
status = ge2d_device_->Ge2dProcessFrame(watermark_task, 0, 0);
EXPECT_OK(status);
EXPECT_EQ(ZX_OK, sync_completion_wait(&completion_, ZX_TIME_INFINITE));
}
// Simple test that a watermark the same color as the input keeps the output color the same.
TEST_F(Ge2dDeviceTest, SameColorWatermark) {
SetupCallbacks();
SetupInput();
SetupWatermarkInfo();
// Pure red in YUV.
WriteConstantColorToVmo(input_buffer_collection_.buffers[0].vmo, 82, 90, 240,
output_image_format_table_[0]);
SetFrameCallback([this](const frame_available_info* info) {
// Red in input and output may be slightly different.
CompareCroppedOutput(info, 1.0f);
sync_completion_signal(&completion_);
});
zx::vmo watermark_vmo = CreateWatermarkVmo();
WriteConstantRgbaToVmo(watermark_vmo.get(), 0xff, 0, 0, 0xff, watermark_info_.wm_image_format);
std::vector<water_mark_info_t> duplicated;
DuplicateWatermarkInfo(watermark_info_, watermark_vmo, kImageFormatTableSize, &duplicated);
uint32_t watermark_task;
zx_status_t status = ge2d_device_->Ge2dInitTaskWaterMark(
&input_buffer_collection_, &output_buffer_collection_, duplicated.data(), duplicated.size(),
output_image_format_table_, kImageFormatTableSize, 0, &frame_callback_, &res_callback_,
&remove_task_callback_, &watermark_task);
EXPECT_OK(status);
status = ge2d_device_->Ge2dProcessFrame(watermark_task, 0, 0);
EXPECT_OK(status);
EXPECT_EQ(ZX_OK, sync_completion_wait(&completion_, ZX_TIME_INFINITE));
}
TEST_F(Ge2dDeviceTest, NewColorWatermark) {
SetupCallbacks();
SetupInput();
SetupWatermarkInfo();
// Pure red in YUV.
WriteConstantColorToVmo(input_buffer_collection_.buffers[0].vmo, 82, 90, 240,
output_image_format_table_[0]);
SetFrameCallback([this](const frame_available_info* info) {
zx_handle_t vmo_a = input_buffer_collection_.buffers[0].vmo;
zx_handle_t vmo_b = output_buffer_collection_.buffers[info->buffer_id].vmo;
image_format_2_t& format = output_image_format_table_[0];
CacheInvalidateVmo(vmo_a);
CacheInvalidateVmo(vmo_b);
fzl::VmoMapper mapper_a;
ASSERT_OK(mapper_a.Map(*zx::unowned_vmo(vmo_a), 0, 0, ZX_VM_PERM_READ));
fzl::VmoMapper mapper_b;
ASSERT_OK(mapper_b.Map(*zx::unowned_vmo(vmo_b), 0, 0, ZX_VM_PERM_READ));
// Just check the area above the watermark to make sure it's the same.
CheckSubPlaneEqual(mapper_a.start(), mapper_b.start(), 0, 0, format.bytes_per_row,
format.bytes_per_row, format.coded_width, 1, kWatermarkVerticalOffset, 1.0f,
1.0f, 0, "Y");
uint32_t uv_offset = format.bytes_per_row * format.coded_height;
CheckSubPlaneEqual(mapper_a.start(), mapper_b.start(), uv_offset, uv_offset,
format.bytes_per_row, format.bytes_per_row, format.coded_width, 2,
kWatermarkVerticalOffset / 2, 1.0f, 1.0f, 0, "UV");
CheckYUVRegion(mapper_b.start(), format, 144, 54, 34, kWatermarkHorizontalOffset,
kWatermarkWidth, kWatermarkVerticalOffset, kWatermarkHeight);
sync_completion_signal(&completion_);
});
zx::vmo watermark_vmo = CreateWatermarkVmo();
WriteConstantRgbaToVmo(watermark_vmo.get(), 0, 0xff, 0, 0xff, watermark_info_.wm_image_format);
std::vector<water_mark_info_t> duplicated;
DuplicateWatermarkInfo(watermark_info_, watermark_vmo, kImageFormatTableSize, &duplicated);
uint32_t watermark_task;
zx_status_t status = ge2d_device_->Ge2dInitTaskWaterMark(
&input_buffer_collection_, &output_buffer_collection_, duplicated.data(), duplicated.size(),
output_image_format_table_, kImageFormatTableSize, 0, &frame_callback_, &res_callback_,
&remove_task_callback_, &watermark_task);
EXPECT_OK(status);
status = ge2d_device_->Ge2dProcessFrame(watermark_task, 0, 0);
EXPECT_OK(status);
EXPECT_EQ(ZX_OK, sync_completion_wait(&completion_, ZX_TIME_INFINITE));
}
// Test that a watermark with 0 alpha doesn't change the input (when copying YUV to YUV)
TEST_F(Ge2dDeviceTest, YUVBlankWatermark) {
SetupCallbacks();
SetupInput();
SetupWatermarkInfo();
// Pure red in YUV.
WriteConstantColorToVmo(input_buffer_collection_.buffers[0].vmo, 82, 90, 240,
output_image_format_table_[0]);
SetFrameCallback([this](const frame_available_info* info) {
zx_handle_t vmo_b = output_buffer_collection_.buffers[info->buffer_id].vmo;
image_format_2_t& format = output_image_format_table_[0];
CacheInvalidateVmo(vmo_b);
fzl::VmoMapper mapper_b;
ASSERT_OK(mapper_b.Map(*zx::unowned_vmo(vmo_b), 0, 0, ZX_VM_PERM_READ));
CheckYUVRegion(mapper_b.start(), format, 82, 90, 240, kWatermarkHorizontalOffset,
kWatermarkWidth, kWatermarkVerticalOffset, kWatermarkHeight);
sync_completion_signal(&completion_);
});
// Set red to > alpha, to ensure we don't add it in.
zx::vmo watermark_vmo = CreateWatermarkVmo();
WriteConstantRgbaToVmo(watermark_vmo.get(), 0xff, 0, 0, 0x00, watermark_info_.wm_image_format);
std::vector<water_mark_info_t> duplicated;
DuplicateWatermarkInfo(watermark_info_, watermark_vmo, kImageFormatTableSize, &duplicated);
uint32_t watermark_task;
zx_status_t status = ge2d_device_->Ge2dInitTaskWaterMark(
&input_buffer_collection_, &output_buffer_collection_, duplicated.data(), duplicated.size(),
output_image_format_table_, kImageFormatTableSize, 0, &frame_callback_, &res_callback_,
&remove_task_callback_, &watermark_task);
EXPECT_OK(status);
status = ge2d_device_->Ge2dProcessFrame(watermark_task, 0, 0);
EXPECT_OK(status);
EXPECT_EQ(ZX_OK, sync_completion_wait(&completion_, ZX_TIME_INFINITE));
}
// Try switching to size 1 and ensuring the watermark is used correctly.
TEST_F(Ge2dDeviceTest, WatermarkOutputSize) {
SetupCallbacks();
SetupInput();
SetupWatermarkInfo();
uint32_t resolution_changed_count = 0;
SetResolutionChangedCallback([&resolution_changed_count](const frame_available_info* info) {
++resolution_changed_count;
});
// Pure red in YUV.
WriteConstantColorToVmo(input_buffer_collection_.buffers[0].vmo, 82, 90, 240,
output_image_format_table_[1]);
static constexpr uint32_t kSecondWatermarkOffset = 4;
SetFrameCallback([this](const frame_available_info* info) {
zx_handle_t vmo_a = input_buffer_collection_.buffers[0].vmo;
zx_handle_t vmo_b = output_buffer_collection_.buffers[info->buffer_id].vmo;
image_format_2_t& format = output_image_format_table_[1];
CacheInvalidateVmo(vmo_a);
CacheInvalidateVmo(vmo_b);
fzl::VmoMapper mapper_a;
ASSERT_OK(mapper_a.Map(*zx::unowned_vmo(vmo_a), 0, 0, ZX_VM_PERM_READ));
fzl::VmoMapper mapper_b;
ASSERT_OK(mapper_b.Map(*zx::unowned_vmo(vmo_b), 0, 0, ZX_VM_PERM_READ));
// Just check the area above the watermark to make sure it's the same.
CheckSubPlaneEqual(mapper_a.start(), mapper_b.start(), 0, 0, format.bytes_per_row,
format.bytes_per_row, format.coded_width, 1, kSecondWatermarkOffset, 1.0f,
1.0f, 0, "Y");
uint32_t uv_offset = format.bytes_per_row * format.coded_height;
CheckSubPlaneEqual(mapper_a.start(), mapper_b.start(), uv_offset, uv_offset,
format.bytes_per_row, format.bytes_per_row, format.coded_width, 2,
kSecondWatermarkOffset / 2, 1.0f, 1.0f, 0, "UV");
CheckYUVRegion(mapper_b.start(), format, 144, 54, 34, kSecondWatermarkOffset, kWatermarkWidth,
kSecondWatermarkOffset, kWatermarkHeight);
sync_completion_signal(&completion_);
});
zx::vmo watermark_vmo = CreateWatermarkVmo();
WriteConstantRgbaToVmo(watermark_vmo.get(), 0, 0xff, 0, 0xff, watermark_info_.wm_image_format);
std::vector<water_mark_info_t> duplicated;
DuplicateWatermarkInfo(watermark_info_, watermark_vmo, kImageFormatTableSize, &duplicated);
duplicated[1].loc_x = kSecondWatermarkOffset;
duplicated[1].loc_y = kSecondWatermarkOffset;
uint32_t watermark_task;
zx_status_t status = ge2d_device_->Ge2dInitTaskWaterMark(
&input_buffer_collection_, &output_buffer_collection_, duplicated.data(), duplicated.size(),
output_image_format_table_, kImageFormatTableSize, 0, &frame_callback_, &res_callback_,
&remove_task_callback_, &watermark_task);
EXPECT_OK(status);
status = ge2d_device_->Ge2dSetInputAndOutputResolution(watermark_task, 1);
EXPECT_OK(status);
status = ge2d_device_->Ge2dProcessFrame(watermark_task, 0, 0);
EXPECT_OK(status);
EXPECT_EQ(ZX_OK, sync_completion_wait(&completion_, ZX_TIME_INFINITE));
EXPECT_EQ(1u, resolution_changed_count);
}
TEST_F(Ge2dDeviceTest, GlobalAlphaWatermark) {
SetupCallbacks();
SetupInput();
SetupWatermarkInfo();
watermark_info_.global_alpha = 0.5f;
// Pure red in YUV.
WriteConstantColorToVmo(input_buffer_collection_.buffers[0].vmo, 82, 90, 240,
output_image_format_table_[0]);
SetFrameCallback([this](const frame_available_info* info) {
zx_handle_t vmo_b = output_buffer_collection_.buffers[info->buffer_id].vmo;
image_format_2_t& format = output_image_format_table_[0];
CacheInvalidateVmo(vmo_b);
fzl::VmoMapper mapper_b;
ASSERT_OK(mapper_b.Map(*zx::unowned_vmo(vmo_b), 0, 0, ZX_VM_PERM_READ));
// Blend of 50% red and 50% green.
CheckYUVRegion(mapper_b.start(), format, 113, 72, 137, kWatermarkHorizontalOffset,
kWatermarkWidth, kWatermarkVerticalOffset, kWatermarkHeight);
sync_completion_signal(&completion_);
});
zx::vmo watermark_vmo = CreateWatermarkVmo();
WriteConstantRgbaToVmo(watermark_vmo.get(), 0, 0xff, 0, 0xff, watermark_info_.wm_image_format);
std::vector<water_mark_info_t> duplicated;
DuplicateWatermarkInfo(watermark_info_, watermark_vmo, kImageFormatTableSize, &duplicated);
uint32_t watermark_task;
zx_status_t status = ge2d_device_->Ge2dInitTaskWaterMark(
&input_buffer_collection_, &output_buffer_collection_, duplicated.data(), duplicated.size(),
output_image_format_table_, kImageFormatTableSize, 0, &frame_callback_, &res_callback_,
&remove_task_callback_, &watermark_task);
EXPECT_OK(status);
status = ge2d_device_->Ge2dProcessFrame(watermark_task, 0, 0);
EXPECT_OK(status);
EXPECT_EQ(ZX_OK, sync_completion_wait(&completion_, ZX_TIME_INFINITE));
}
// Try switching the in-place watermark to size 1 and ensuring the watermark is used correctly.
TEST_F(Ge2dDeviceTest, InPlaceWatermarkOutputSize) {
SetupCallbacks();
SetupInput();
SetupWatermarkInfo();
uint32_t resolution_changed_count = 0;
SetResolutionChangedCallback([&resolution_changed_count](const frame_available_info* info) {
++resolution_changed_count;
});
// Pure red in YUV.
WriteConstantColorToVmo(input_buffer_collection_.buffers[1].vmo, 82, 90, 240,
output_image_format_table_[1]);
static constexpr uint32_t kSecondWatermarkOffset = 4;
SetFrameCallback([this](const frame_available_info* info) {
EXPECT_EQ(1u, info->buffer_id);
zx_handle_t vmo_a = input_buffer_collection_.buffers[info->buffer_id].vmo;
image_format_2_t& format = output_image_format_table_[1];
CacheInvalidateVmo(vmo_a);
fzl::VmoMapper mapper_a;
ASSERT_OK(mapper_a.Map(*zx::unowned_vmo(vmo_a), 0, 0, ZX_VM_PERM_READ));
// Check region above the watermark to ensure it hasn't changed.
CheckYUVRegion(mapper_a.start(), format, 82, 90, 240, 0, format.coded_width, 0,
kSecondWatermarkOffset);
CheckYUVRegion(mapper_a.start(), format, 144, 54, 34, kSecondWatermarkOffset, kWatermarkWidth,
kSecondWatermarkOffset, kWatermarkHeight);
sync_completion_signal(&completion_);
});
zx::vmo watermark_vmo = CreateWatermarkVmo();
WriteConstantRgbaToVmo(watermark_vmo.get(), 0, 0xff, 0, 0xff, watermark_info_.wm_image_format);
std::vector<water_mark_info_t> duplicated;
DuplicateWatermarkInfo(watermark_info_, watermark_vmo, kImageFormatTableSize, &duplicated);
duplicated[1].loc_x = kSecondWatermarkOffset;
duplicated[1].loc_y = kSecondWatermarkOffset;
uint32_t watermark_task;
zx_status_t status = ge2d_device_->Ge2dInitTaskInPlaceWaterMark(
&input_buffer_collection_, duplicated.data(), duplicated.size(), output_image_format_table_,
kImageFormatTableSize, 0, &frame_callback_, &res_callback_, &remove_task_callback_,
&watermark_task);
EXPECT_OK(status);
status = ge2d_device_->Ge2dSetInputAndOutputResolution(watermark_task, 1);
EXPECT_OK(status);
status = ge2d_device_->Ge2dProcessFrame(watermark_task, 1, 0);
EXPECT_OK(status);
EXPECT_EQ(ZX_OK, sync_completion_wait(&completion_, ZX_TIME_INFINITE));
EXPECT_EQ(1u, resolution_changed_count);
}
} // namespace
} // namespace ge2d