blob: 543910de289c7d4a378a03c0b2f669a558e96ea4 [file] [log] [blame]
/*
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include <memory>
#include "testing/gmock/include/gmock/gmock.h"
#include "webrtc/base/constructormagic.h"
#include "webrtc/modules/desktop_capture/differ.h"
#include "webrtc/modules/desktop_capture/differ_block.h"
namespace webrtc {
// 96x96 screen gives a 4x4 grid of blocks.
const int kScreenWidth= 96;
const int kScreenHeight = 96;
// To test partial blocks, we need a width and height that are not multiples
// of 16 (or 32, depending on current block size).
const int kPartialScreenWidth = 70;
const int kPartialScreenHeight = 70;
class DifferTest : public testing::Test {
public:
DifferTest() {
}
protected:
void InitDiffer(int width, int height) {
width_ = width;
height_ = height;
bytes_per_pixel_ = kBytesPerPixel;
stride_ = (kBytesPerPixel * width);
buffer_size_ = width_ * height_ * bytes_per_pixel_;
differ_.reset(new Differ(width_, height_, bytes_per_pixel_, stride_));
prev_.reset(new uint8_t[buffer_size_]);
memset(prev_.get(), 0, buffer_size_);
curr_.reset(new uint8_t[buffer_size_]);
memset(curr_.get(), 0, buffer_size_);
}
void ClearBuffer(uint8_t* buffer) {
memset(buffer, 0, buffer_size_);
}
// Here in DifferTest so that tests can access private methods of Differ.
void MarkDirtyBlocks(const uint8_t* prev_buffer, const uint8_t* curr_buffer) {
differ_->MarkDirtyBlocks(prev_buffer, curr_buffer);
}
void MergeBlocks(DesktopRegion* dirty) {
differ_->MergeBlocks(dirty);
}
// Convenience method to count rectangles in a region.
int RegionRectCount(const DesktopRegion& region) {
int count = 0;
for (DesktopRegion::Iterator iter(region);
!iter.IsAtEnd(); iter.Advance()) {
++count;
}
return count;
}
// Convenience wrapper for Differ's DiffBlock that calculates the appropriate
// offset to the start of the desired block.
bool DiffBlock(int block_x, int block_y) {
// Offset from upper-left of buffer to upper-left of requested block.
int block_offset = ((block_y * stride_) + (block_x * bytes_per_pixel_))
* kBlockSize;
return BlockDifference(prev_.get() + block_offset,
curr_.get() + block_offset,
stride_);
}
// Write the pixel |value| into the specified block in the |buffer|.
// This is a convenience wrapper around WritePixel().
void WriteBlockPixel(uint8_t* buffer, int block_x, int block_y,
int pixel_x, int pixel_y, uint32_t value) {
WritePixel(buffer, (block_x * kBlockSize) + pixel_x,
(block_y * kBlockSize) + pixel_y, value);
}
// Write the test pixel |value| into the |buffer| at the specified |x|,|y|
// location.
// Only the low-order bytes from |value| are written (assuming little-endian).
// So, for |value| = 0xaabbccdd:
// If bytes_per_pixel = 4, then ddccbbaa will be written as the pixel value.
// If = 3, ddccbb
// If = 2, ddcc
// If = 1, dd
void WritePixel(uint8_t* buffer, int x, int y, uint32_t value) {
uint8_t* pixel = reinterpret_cast<uint8_t*>(&value);
buffer += (y * stride_) + (x * bytes_per_pixel_);
for (int b = bytes_per_pixel_ - 1; b >= 0; b--) {
*buffer++ = pixel[b];
}
}
// DiffInfo utility routines.
// These are here so that we don't have to make each DifferText_Xxx_Test
// class a friend class to Differ.
// Clear out the entire |diff_info_| buffer.
void ClearDiffInfo() {
memset(differ_->diff_info_.get(), 0, differ_->diff_info_size_);
}
// Get the value in the |diff_info_| array at (x,y).
bool GetDiffInfo(int x, int y) {
bool* diff_info = differ_->diff_info_.get();
return diff_info[(y * GetDiffInfoWidth()) + x];
}
// Width of |diff_info_| array.
int GetDiffInfoWidth() {
return differ_->diff_info_width_;
}
// Height of |diff_info_| array.
int GetDiffInfoHeight() {
return differ_->diff_info_height_;
}
// Size of |diff_info_| array.
int GetDiffInfoSize() {
return differ_->diff_info_size_;
}
void SetDiffInfo(int x, int y, bool value) {
bool* diff_info = differ_->diff_info_.get();
diff_info[(y * GetDiffInfoWidth()) + x] = value;
}
// Mark the range of blocks specified.
void MarkBlocks(int x_origin, int y_origin, int width, int height) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
SetDiffInfo(x_origin + x, y_origin + y, true);
}
}
}
// Verify that |region| contains a rectangle defined by |x|, |y|, |width| and
// |height|.
// |x|, |y|, |width| and |height| are specified in block (not pixel) units.
bool CheckDirtyRegionContainsRect(const DesktopRegion& region,
int x, int y,
int width, int height) {
DesktopRect r =
DesktopRect::MakeXYWH(x * kBlockSize, y * kBlockSize,
width * kBlockSize, height * kBlockSize);
for (DesktopRegion::Iterator i(region); !i.IsAtEnd(); i.Advance()) {
if (i.rect().equals(r))
return true;
}
return false;
}
// Mark the range of blocks specified and then verify that they are
// merged correctly.
// Only one rectangular region of blocks can be checked with this routine.
bool MarkBlocksAndCheckMerge(int x_origin, int y_origin,
int width, int height) {
ClearDiffInfo();
MarkBlocks(x_origin, y_origin, width, height);
DesktopRegion dirty;
MergeBlocks(&dirty);
DesktopRect expected_rect = DesktopRect::MakeXYWH(
x_origin * kBlockSize, y_origin * kBlockSize,
width * kBlockSize, height * kBlockSize);
// Verify that the region contains expected_rect and it's the only
// rectangle.
DesktopRegion::Iterator it(dirty);
return !it.IsAtEnd() && expected_rect.equals(it.rect()) &&
(it.Advance(), it.IsAtEnd());
}
// The differ class we're testing.
std::unique_ptr<Differ> differ_;
// Screen/buffer info.
int width_;
int height_;
int bytes_per_pixel_;
int stride_;
// Size of each screen buffer.
int buffer_size_;
// Previous and current screen buffers.
std::unique_ptr<uint8_t[]> prev_;
std::unique_ptr<uint8_t[]> curr_;
private:
RTC_DISALLOW_COPY_AND_ASSIGN(DifferTest);
};
TEST_F(DifferTest, Setup) {
InitDiffer(kScreenWidth, kScreenHeight);
// 96x96 pixels results in 3x3 array. Add 1 to each dimension as boundary.
// +---+---+---+---+
// | o | o | o | _ |
// +---+---+---+---+ o = blocks mapped to screen pixels
// | o | o | o | _ |
// +---+---+---+---+ _ = boundary blocks
// | o | o | o | _ |
// +---+---+---+---+
// | _ | _ | _ | _ |
// +---+---+---+---+
EXPECT_EQ(4, GetDiffInfoWidth());
EXPECT_EQ(4, GetDiffInfoHeight());
EXPECT_EQ(16, GetDiffInfoSize());
}
TEST_F(DifferTest, MarkDirtyBlocks_All) {
InitDiffer(kScreenWidth, kScreenHeight);
ClearDiffInfo();
// Update a pixel in each block.
for (int y = 0; y < GetDiffInfoHeight() - 1; y++) {
for (int x = 0; x < GetDiffInfoWidth() - 1; x++) {
WriteBlockPixel(curr_.get(), x, y, 10, 10, 0xff00ff);
}
}
MarkDirtyBlocks(prev_.get(), curr_.get());
// Make sure each block is marked as dirty.
for (int y = 0; y < GetDiffInfoHeight() - 1; y++) {
for (int x = 0; x < GetDiffInfoWidth() - 1; x++) {
EXPECT_TRUE(GetDiffInfo(x, y))
<< "when x = " << x << ", and y = " << y;
}
}
}
TEST_F(DifferTest, MarkDirtyBlocks_Sampling) {
InitDiffer(kScreenWidth, kScreenHeight);
ClearDiffInfo();
// Update some pixels in image.
WriteBlockPixel(curr_.get(), 1, 0, 10, 10, 0xff00ff);
WriteBlockPixel(curr_.get(), 2, 1, 10, 10, 0xff00ff);
WriteBlockPixel(curr_.get(), 0, 2, 10, 10, 0xff00ff);
MarkDirtyBlocks(prev_.get(), curr_.get());
// Make sure corresponding blocks are updated.
EXPECT_FALSE(GetDiffInfo(0, 0));
EXPECT_FALSE(GetDiffInfo(0, 1));
EXPECT_TRUE(GetDiffInfo(0, 2));
EXPECT_TRUE(GetDiffInfo(1, 0));
EXPECT_FALSE(GetDiffInfo(1, 1));
EXPECT_FALSE(GetDiffInfo(1, 2));
EXPECT_FALSE(GetDiffInfo(2, 0));
EXPECT_TRUE(GetDiffInfo(2, 1));
EXPECT_FALSE(GetDiffInfo(2, 2));
}
TEST_F(DifferTest, DiffBlock) {
InitDiffer(kScreenWidth, kScreenHeight);
// Verify no differences at start.
EXPECT_FALSE(DiffBlock(0, 0));
EXPECT_FALSE(DiffBlock(1, 1));
// Write new data into the 4 corners of the middle block and verify that
// neighboring blocks are not affected.
int max = kBlockSize - 1;
WriteBlockPixel(curr_.get(), 1, 1, 0, 0, 0xffffff);
WriteBlockPixel(curr_.get(), 1, 1, 0, max, 0xffffff);
WriteBlockPixel(curr_.get(), 1, 1, max, 0, 0xffffff);
WriteBlockPixel(curr_.get(), 1, 1, max, max, 0xffffff);
EXPECT_FALSE(DiffBlock(0, 0));
EXPECT_FALSE(DiffBlock(0, 1));
EXPECT_FALSE(DiffBlock(0, 2));
EXPECT_FALSE(DiffBlock(1, 0));
EXPECT_TRUE(DiffBlock(1, 1)); // Only this block should change.
EXPECT_FALSE(DiffBlock(1, 2));
EXPECT_FALSE(DiffBlock(2, 0));
EXPECT_FALSE(DiffBlock(2, 1));
EXPECT_FALSE(DiffBlock(2, 2));
}
TEST_F(DifferTest, Partial_Setup) {
InitDiffer(kPartialScreenWidth, kPartialScreenHeight);
// 70x70 pixels results in 3x3 array: 2x2 full blocks + partials around
// the edge. One more is added to each dimension as a boundary.
// +---+---+---+---+
// | o | o | + | _ |
// +---+---+---+---+ o = blocks mapped to screen pixels
// | o | o | + | _ |
// +---+---+---+---+ + = partial blocks (top/left mapped to screen pixels)
// | + | + | + | _ |
// +---+---+---+---+ _ = boundary blocks
// | _ | _ | _ | _ |
// +---+---+---+---+
EXPECT_EQ(4, GetDiffInfoWidth());
EXPECT_EQ(4, GetDiffInfoHeight());
EXPECT_EQ(16, GetDiffInfoSize());
}
TEST_F(DifferTest, Partial_FirstPixel) {
InitDiffer(kPartialScreenWidth, kPartialScreenHeight);
ClearDiffInfo();
// Update the first pixel in each block.
for (int y = 0; y < GetDiffInfoHeight() - 1; y++) {
for (int x = 0; x < GetDiffInfoWidth() - 1; x++) {
WriteBlockPixel(curr_.get(), x, y, 0, 0, 0xff00ff);
}
}
MarkDirtyBlocks(prev_.get(), curr_.get());
// Make sure each block is marked as dirty.
for (int y = 0; y < GetDiffInfoHeight() - 1; y++) {
for (int x = 0; x < GetDiffInfoWidth() - 1; x++) {
EXPECT_TRUE(GetDiffInfo(x, y))
<< "when x = " << x << ", and y = " << y;
}
}
}
TEST_F(DifferTest, Partial_BorderPixel) {
InitDiffer(kPartialScreenWidth, kPartialScreenHeight);
ClearDiffInfo();
// Update the right/bottom border pixels.
for (int y = 0; y < height_; y++) {
WritePixel(curr_.get(), width_ - 1, y, 0xff00ff);
}
for (int x = 0; x < width_; x++) {
WritePixel(curr_.get(), x, height_ - 1, 0xff00ff);
}
MarkDirtyBlocks(prev_.get(), curr_.get());
// Make sure last (partial) block in each row/column is marked as dirty.
int x_last = GetDiffInfoWidth() - 2;
for (int y = 0; y < GetDiffInfoHeight() - 1; y++) {
EXPECT_TRUE(GetDiffInfo(x_last, y))
<< "when x = " << x_last << ", and y = " << y;
}
int y_last = GetDiffInfoHeight() - 2;
for (int x = 0; x < GetDiffInfoWidth() - 1; x++) {
EXPECT_TRUE(GetDiffInfo(x, y_last))
<< "when x = " << x << ", and y = " << y_last;
}
// All other blocks are clean.
for (int y = 0; y < GetDiffInfoHeight() - 2; y++) {
for (int x = 0; x < GetDiffInfoWidth() - 2; x++) {
EXPECT_FALSE(GetDiffInfo(x, y)) << "when x = " << x << ", and y = " << y;
}
}
}
TEST_F(DifferTest, MergeBlocks_Empty) {
InitDiffer(kScreenWidth, kScreenHeight);
// No blocks marked:
// +---+---+---+---+
// | | | | _ |
// +---+---+---+---+
// | | | | _ |
// +---+---+---+---+
// | | | | _ |
// +---+---+---+---+
// | _ | _ | _ | _ |
// +---+---+---+---+
ClearDiffInfo();
DesktopRegion dirty;
MergeBlocks(&dirty);
EXPECT_TRUE(dirty.is_empty());
}
TEST_F(DifferTest, MergeBlocks_SingleBlock) {
InitDiffer(kScreenWidth, kScreenHeight);
// Mark a single block and make sure that there is a single merged
// rect with the correct bounds.
for (int y = 0; y < GetDiffInfoHeight() - 1; y++) {
for (int x = 0; x < GetDiffInfoWidth() - 1; x++) {
ASSERT_TRUE(MarkBlocksAndCheckMerge(x, y, 1, 1)) << "x: " << x
<< "y: " << y;
}
}
}
TEST_F(DifferTest, MergeBlocks_BlockRow) {
InitDiffer(kScreenWidth, kScreenHeight);
// +---+---+---+---+
// | X | X | | _ |
// +---+---+---+---+
// | | | | _ |
// +---+---+---+---+
// | | | | _ |
// +---+---+---+---+
// | _ | _ | _ | _ |
// +---+---+---+---+
ASSERT_TRUE(MarkBlocksAndCheckMerge(0, 0, 2, 1));
// +---+---+---+---+
// | | | | _ |
// +---+---+---+---+
// | X | X | X | _ |
// +---+---+---+---+
// | | | | _ |
// +---+---+---+---+
// | _ | _ | _ | _ |
// +---+---+---+---+
ASSERT_TRUE(MarkBlocksAndCheckMerge(0, 1, 3, 1));
// +---+---+---+---+
// | | | | _ |
// +---+---+---+---+
// | | | | _ |
// +---+---+---+---+
// | | X | X | _ |
// +---+---+---+---+
// | _ | _ | _ | _ |
// +---+---+---+---+
ASSERT_TRUE(MarkBlocksAndCheckMerge(1, 2, 2, 1));
}
TEST_F(DifferTest, MergeBlocks_BlockColumn) {
InitDiffer(kScreenWidth, kScreenHeight);
// +---+---+---+---+
// | X | | | _ |
// +---+---+---+---+
// | X | | | _ |
// +---+---+---+---+
// | | | | _ |
// +---+---+---+---+
// | _ | _ | _ | _ |
// +---+---+---+---+
ASSERT_TRUE(MarkBlocksAndCheckMerge(0, 0, 1, 2));
// +---+---+---+---+
// | | | | _ |
// +---+---+---+---+
// | | X | | _ |
// +---+---+---+---+
// | | X | | _ |
// +---+---+---+---+
// | _ | _ | _ | _ |
// +---+---+---+---+
ASSERT_TRUE(MarkBlocksAndCheckMerge(1, 1, 1, 2));
// +---+---+---+---+
// | | | X | _ |
// +---+---+---+---+
// | | | X | _ |
// +---+---+---+---+
// | | | X | _ |
// +---+---+---+---+
// | _ | _ | _ | _ |
// +---+---+---+---+
ASSERT_TRUE(MarkBlocksAndCheckMerge(2, 0, 1, 3));
}
TEST_F(DifferTest, MergeBlocks_BlockRect) {
InitDiffer(kScreenWidth, kScreenHeight);
// +---+---+---+---+
// | X | X | | _ |
// +---+---+---+---+
// | X | X | | _ |
// +---+---+---+---+
// | | | | _ |
// +---+---+---+---+
// | _ | _ | _ | _ |
// +---+---+---+---+
ASSERT_TRUE(MarkBlocksAndCheckMerge(0, 0, 2, 2));
// +---+---+---+---+
// | | | | _ |
// +---+---+---+---+
// | | X | X | _ |
// +---+---+---+---+
// | | X | X | _ |
// +---+---+---+---+
// | _ | _ | _ | _ |
// +---+---+---+---+
ASSERT_TRUE(MarkBlocksAndCheckMerge(1, 1, 2, 2));
// +---+---+---+---+
// | | X | X | _ |
// +---+---+---+---+
// | | X | X | _ |
// +---+---+---+---+
// | | X | X | _ |
// +---+---+---+---+
// | _ | _ | _ | _ |
// +---+---+---+---+
ASSERT_TRUE(MarkBlocksAndCheckMerge(1, 0, 2, 3));
// +---+---+---+---+
// | | | | _ |
// +---+---+---+---+
// | X | X | X | _ |
// +---+---+---+---+
// | X | X | X | _ |
// +---+---+---+---+
// | _ | _ | _ | _ |
// +---+---+---+---+
ASSERT_TRUE(MarkBlocksAndCheckMerge(0, 1, 3, 2));
// +---+---+---+---+
// | X | X | X | _ |
// +---+---+---+---+
// | X | X | X | _ |
// +---+---+---+---+
// | X | X | X | _ |
// +---+---+---+---+
// | _ | _ | _ | _ |
// +---+---+---+---+
ASSERT_TRUE(MarkBlocksAndCheckMerge(0, 0, 3, 3));
}
// This tests marked regions that require more than 1 single dirty rect.
// The exact rects returned depend on the current implementation, so these
// may need to be updated if we modify how we merge blocks.
TEST_F(DifferTest, MergeBlocks_MultiRect) {
InitDiffer(kScreenWidth, kScreenHeight);
DesktopRegion dirty;
// +---+---+---+---+ +---+---+---+
// | | X | | _ | | | 0 | |
// +---+---+---+---+ +---+---+---+
// | X | | | _ | | 1 | | |
// +---+---+---+---+ => +---+---+---+
// | | | X | _ | | | | 2 |
// +---+---+---+---+ +---+---+---+
// | _ | _ | _ | _ |
// +---+---+---+---+
ClearDiffInfo();
MarkBlocks(1, 0, 1, 1);
MarkBlocks(0, 1, 1, 1);
MarkBlocks(2, 2, 1, 1);
dirty.Clear();
MergeBlocks(&dirty);
ASSERT_EQ(3, RegionRectCount(dirty));
ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 1, 0, 1, 1));
ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 1, 1, 1));
ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 2, 2, 1, 1));
// +---+---+---+---+ +---+---+---+
// | | | X | _ | | | | 0 |
// +---+---+---+---+ +---+---+---+
// | X | X | X | _ | | 1 1 1 |
// +---+---+---+---+ => + +
// | X | X | X | _ | | 1 1 1 |
// +---+---+---+---+ +---+---+---+
// | _ | _ | _ | _ |
// +---+---+---+---+
ClearDiffInfo();
MarkBlocks(2, 0, 1, 1);
MarkBlocks(0, 1, 3, 2);
dirty.Clear();
MergeBlocks(&dirty);
ASSERT_EQ(2, RegionRectCount(dirty));
ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 2, 0, 1, 1));
ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 1, 3, 2));
// +---+---+---+---+ +---+---+---+
// | | | | _ | | | | |
// +---+---+---+---+ +---+---+---+
// | X | | X | _ | | 0 | | 1 |
// +---+---+---+---+ => +---+---+---+
// | X | X | X | _ | | 2 2 2 |
// +---+---+---+---+ +---+---+---+
// | _ | _ | _ | _ |
// +---+---+---+---+
ClearDiffInfo();
MarkBlocks(0, 1, 1, 1);
MarkBlocks(2, 1, 1, 1);
MarkBlocks(0, 2, 3, 1);
dirty.Clear();
MergeBlocks(&dirty);
ASSERT_EQ(3, RegionRectCount(dirty));
ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 1, 1, 1));
ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 2, 1, 1, 1));
ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 2, 3, 1));
// +---+---+---+---+ +---+---+---+
// | X | X | X | _ | | 0 0 0 |
// +---+---+---+---+ +---+---+---+
// | X | | X | _ | | 1 | | 2 |
// +---+---+---+---+ => +---+---+---+
// | X | X | X | _ | | 3 3 3 |
// +---+---+---+---+ +---+---+---+
// | _ | _ | _ | _ |
// +---+---+---+---+
ClearDiffInfo();
MarkBlocks(0, 0, 3, 1);
MarkBlocks(0, 1, 1, 1);
MarkBlocks(2, 1, 1, 1);
MarkBlocks(0, 2, 3, 1);
dirty.Clear();
MergeBlocks(&dirty);
ASSERT_EQ(4, RegionRectCount(dirty));
ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 0, 3, 1));
ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 1, 1, 1));
ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 2, 1, 1, 1));
ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 2, 3, 1));
// +---+---+---+---+ +---+---+---+
// | X | X | | _ | | 0 0 | |
// +---+---+---+---+ + +---+
// | X | X | | _ | | 0 0 | |
// +---+---+---+---+ => +---+---+---+
// | | X | | _ | | | 1 | |
// +---+---+---+---+ +---+---+---+
// | _ | _ | _ | _ |
// +---+---+---+---+
ClearDiffInfo();
MarkBlocks(0, 0, 2, 2);
MarkBlocks(1, 2, 1, 1);
dirty.Clear();
MergeBlocks(&dirty);
ASSERT_EQ(2, RegionRectCount(dirty));
ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 0, 2, 2));
ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 1, 2, 1, 1));
}
} // namespace webrtc