blob: 3ee73ec562f16de7af18948fffdc42765e34f734 [file] [log] [blame]
// Copyright 2019 The Chromium 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 "components/viz/service/display_embedder/skia_output_device_buffer_queue.h"
#include <stddef.h>
#include <stdint.h>
#include <set>
#include <utility>
#include "base/callback_helpers.h"
#include "base/memory/ptr_util.h"
#include "base/test/bind.h"
#include "base/test/mock_callback.h"
#include "build/build_config.h"
#include "components/viz/common/resources/resource_sizes.h"
#include "components/viz/service/display_embedder/output_presenter_gl.h"
#include "components/viz/service/display_embedder/skia_output_device.h"
#include "components/viz/service/display_embedder/skia_output_surface_dependency_impl.h"
#include "components/viz/service/gl/gpu_service_impl.h"
#include "components/viz/test/test_gpu_service_holder.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "gpu/command_buffer/service/shared_image_backing_factory.h"
#include "gpu/command_buffer/service/test_shared_image_backing.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/display/types/display_snapshot.h"
#include "ui/gl/gl_surface_stub.h"
using ::testing::_;
using ::testing::Expectation;
using ::testing::Ne;
using ::testing::Return;
namespace viz {
namespace {
// These MACRO and TestOnGpu class make it easier to write tests that runs on
// GPU Thread
// Use TEST_F_GPU instead of TEST_F in the same manner and in your subclass
// of TestOnGpu implement SetUpOnMain/SetUpOnGpu and
// TearDownOnMain/TearDownOnGpu instead of SetUp and TearDown respectively.
//
// NOTE: Most likely you need to implement TearDownOnGpu instead of relying on
// destructor to ensure that necessary cleanup happens on GPU Thread.
// TODO(vasilyt): Extract this for others to use?
#define GTEST_TEST_GPU_(test_suite_name, test_name, parent_class, parent_id) \
class GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \
: public parent_class { \
public: \
GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() {} \
\
private: \
virtual void TestBodyOnGpu(); \
static ::testing::TestInfo* const test_info_ GTEST_ATTRIBUTE_UNUSED_; \
GTEST_DISALLOW_COPY_AND_ASSIGN_(GTEST_TEST_CLASS_NAME_(test_suite_name, \
test_name)); \
}; \
\
::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_suite_name, \
test_name)::test_info_ = \
::testing::internal::MakeAndRegisterTestInfo( \
#test_suite_name, #test_name, nullptr, nullptr, \
::testing::internal::CodeLocation(__FILE__, __LINE__), (parent_id), \
::testing::internal::SuiteApiResolver< \
parent_class>::GetSetUpCaseOrSuite(__FILE__, __LINE__), \
::testing::internal::SuiteApiResolver< \
parent_class>::GetTearDownCaseOrSuite(__FILE__, __LINE__), \
new ::testing::internal::TestFactoryImpl<GTEST_TEST_CLASS_NAME_( \
test_suite_name, test_name)>); \
void GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)::TestBodyOnGpu()
#define TEST_F_GPU(test_fixture, test_name) \
GTEST_TEST_GPU_(test_fixture, test_name, test_fixture, \
::testing::internal::GetTypeId<test_fixture>())
class TestOnGpu : public ::testing::Test {
protected:
TestOnGpu()
: wait_(base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED) {}
void TestBody() override {
auto callback =
base::BindLambdaForTesting([&]() { this->TestBodyOnGpu(); });
ScheduleGpuTask(std::move(callback));
}
void SetUp() override {
gpu_service_holder_ = TestGpuServiceHolder::GetInstance();
SetUpOnMain();
auto setup = base::BindLambdaForTesting([&]() { this->SetUpOnGpu(); });
ScheduleGpuTask(setup);
}
void TearDown() override {
auto teardown =
base::BindLambdaForTesting([&]() { this->TearDownOnGpu(); });
ScheduleGpuTask(teardown);
TearDownOnMain();
}
void CallOnGpuAndUnblockMain(base::OnceClosure callback) {
DCHECK(!wait_.IsSignaled());
std::move(callback).Run();
wait_.Signal();
}
void ScheduleGpuTask(base::OnceClosure callback) {
auto wrap = base::BindOnce(&TestOnGpu::CallOnGpuAndUnblockMain,
base::Unretained(this), std::move(callback));
gpu_service_holder_->ScheduleGpuTask(std::move(wrap));
wait_.Wait();
}
virtual void SetUpOnMain() {}
virtual void SetUpOnGpu() {}
virtual void TearDownOnMain() {}
virtual void TearDownOnGpu() {}
virtual void TestBodyOnGpu() {}
TestGpuServiceHolder* gpu_service_holder_;
base::WaitableEvent wait_;
};
// Here starts SkiaOutputDeviceBufferQueue test related code
class TestSharedImageBackingFactory : public gpu::SharedImageBackingFactory {
public:
TestSharedImageBackingFactory() = default;
~TestSharedImageBackingFactory() override = default;
// gpu::SharedImageBackingFactory implementation.
std::unique_ptr<gpu::SharedImageBacking> CreateSharedImage(
const gpu::Mailbox& mailbox,
ResourceFormat format,
gpu::SurfaceHandle surface_handle,
const gfx::Size& size,
const gfx::ColorSpace& color_space,
GrSurfaceOrigin surface_origin,
SkAlphaType alpha_type,
uint32_t usage,
bool is_thread_safe) override {
size_t estimated_size =
ResourceSizes::CheckedSizeInBytes<size_t>(size, format);
return std::make_unique<gpu::TestSharedImageBacking>(
mailbox, format, size, color_space, surface_origin, alpha_type, usage,
estimated_size);
}
std::unique_ptr<gpu::SharedImageBacking> CreateSharedImage(
const gpu::Mailbox& mailbox,
ResourceFormat format,
const gfx::Size& size,
const gfx::ColorSpace& color_space,
GrSurfaceOrigin surface_origin,
SkAlphaType alpha_type,
uint32_t usage,
base::span<const uint8_t> pixel_data) override {
return std::make_unique<gpu::TestSharedImageBacking>(
mailbox, format, size, color_space, surface_origin, alpha_type, usage,
pixel_data.size());
}
std::unique_ptr<gpu::SharedImageBacking> CreateSharedImage(
const gpu::Mailbox& mailbox,
int client_id,
gfx::GpuMemoryBufferHandle handle,
gfx::BufferFormat format,
gfx::BufferPlane plane,
gpu::SurfaceHandle surface_handle,
const gfx::Size& size,
const gfx::ColorSpace& color_space,
GrSurfaceOrigin surface_origin,
SkAlphaType alpha_type,
uint32_t usage) override {
NOTREACHED();
return nullptr;
}
bool IsSupported(uint32_t usage,
ResourceFormat format,
bool thread_safe,
gfx::GpuMemoryBufferType gmb_type,
gpu::GrContextType gr_context_type_,
bool* allow_legacy_mailbox) override {
return true;
}
};
class MockGLSurfaceAsync : public gl::GLSurfaceStub {
public:
bool SupportsAsyncSwap() override { return true; }
void SwapBuffersAsync(SwapCompletionCallback completion_callback,
PresentationCallback presentation_callback) override {
swap_completion_callbacks_.push_back(std::move(completion_callback));
presentation_callbacks_.push_back(std::move(presentation_callback));
}
void CommitOverlayPlanesAsync(
SwapCompletionCallback completion_callback,
PresentationCallback presentation_callback) override {
swap_completion_callbacks_.push_back(std::move(completion_callback));
presentation_callbacks_.push_back(std::move(presentation_callback));
}
bool ScheduleOverlayPlane(int z_order,
gfx::OverlayTransform transform,
gl::GLImage* image,
const gfx::Rect& bounds_rect,
const gfx::RectF& crop_rect,
bool enable_blend,
std::unique_ptr<gfx::GpuFence> gpu_fence) override {
return true;
}
gfx::SurfaceOrigin GetOrigin() const override {
return gfx::SurfaceOrigin::kTopLeft;
}
void SwapComplete() {
DCHECK(!swap_completion_callbacks_.empty());
std::move(swap_completion_callbacks_.front())
.Run(gfx::SwapCompletionResult(gfx::SwapResult::SWAP_ACK));
swap_completion_callbacks_.pop_front();
DCHECK(!presentation_callbacks_.empty());
std::move(presentation_callbacks_.front()).Run({});
presentation_callbacks_.pop_front();
}
protected:
~MockGLSurfaceAsync() override = default;
base::circular_deque<SwapCompletionCallback> swap_completion_callbacks_;
base::circular_deque<PresentationCallback> presentation_callbacks_;
};
class MemoryTrackerStub : public gpu::MemoryTracker {
public:
MemoryTrackerStub() = default;
MemoryTrackerStub(const MemoryTrackerStub&) = delete;
MemoryTrackerStub& operator=(const MemoryTrackerStub&) = delete;
~MemoryTrackerStub() override { DCHECK(!size_); }
// MemoryTracker implementation:
void TrackMemoryAllocatedChange(int64_t delta) override {
DCHECK(delta >= 0 || size_ >= static_cast<uint64_t>(-delta));
size_ += delta;
}
uint64_t GetSize() const override { return size_; }
uint64_t ClientTracingId() const override { return client_tracing_id_; }
int ClientId() const override {
return gpu::ChannelIdFromCommandBufferId(command_buffer_id_);
}
uint64_t ContextGroupTracingId() const override {
return command_buffer_id_.GetUnsafeValue();
}
private:
gpu::CommandBufferId command_buffer_id_;
const uint64_t client_tracing_id_ = 0;
uint64_t size_ = 0;
};
} // namespace
using DidSwapBufferCompleteCallback =
base::RepeatingCallback<void(gpu::SwapBuffersCompleteParams,
const gfx::Size& pixel_size,
gfx::GpuFenceHandle release_fence)>;
using BufferPresentedCallback =
base::OnceCallback<void(const gfx::PresentationFeedback& feedback)>;
class SkiaOutputDeviceBufferQueueTest : public TestOnGpu {
public:
SkiaOutputDeviceBufferQueueTest() = default;
void SetUpOnMain() override {
gpu::SurfaceHandle surface_handle_ = gpu::kNullSurfaceHandle;
dependency_ = std::make_unique<SkiaOutputSurfaceDependencyImpl>(
gpu_service_holder_->gpu_service(), surface_handle_);
}
virtual DidSwapBufferCompleteCallback GetDidSwapBuffersCompleteCallback() {
return base::DoNothing::Repeatedly<gpu::SwapBuffersCompleteParams,
const gfx::Size&, gfx::GpuFenceHandle>();
}
void SetUpOnGpu() override {
gl_surface_ = base::MakeRefCounted<MockGLSurfaceAsync>();
memory_tracker_ = std::make_unique<MemoryTrackerStub>();
shared_image_factory_ = std::make_unique<gpu::SharedImageFactory>(
dependency_->GetGpuPreferences(),
dependency_->GetGpuDriverBugWorkarounds(),
dependency_->GetGpuFeatureInfo(),
dependency_->GetSharedContextState().get(),
dependency_->GetMailboxManager(), dependency_->GetSharedImageManager(),
dependency_->GetGpuImageFactory(), memory_tracker_.get(), true),
shared_image_factory_->RegisterSharedImageBackingFactoryForTesting(
&test_backing_factory_);
shared_image_representation_factory_ =
std::make_unique<gpu::SharedImageRepresentationFactory>(
dependency_->GetSharedImageManager(), memory_tracker_.get());
auto present_callback = GetDidSwapBuffersCompleteCallback();
output_device_ = std::make_unique<SkiaOutputDeviceBufferQueue>(
std::make_unique<OutputPresenterGL>(
gl_surface_, dependency_.get(), shared_image_factory_.get(),
shared_image_representation_factory_.get()),
dependency_.get(), shared_image_representation_factory_.get(),
memory_tracker_.get(), present_callback, false);
}
void TearDownOnGpu() override {
output_device_.reset();
shared_image_representation_factory_.reset();
shared_image_factory_.reset();
memory_tracker_.reset();
gl_surface_.reset();
}
using Image = OutputPresenter::Image;
const std::vector<std::unique_ptr<Image>>& images() {
return output_device_->images_;
}
Image* current_image() { return output_device_->current_image_; }
const base::circular_deque<Image*>& available_images() {
return output_device_->available_images_;
}
Image* submitted_image() { return output_device_->submitted_image_; }
Image* displayed_image() { return output_device_->displayed_image_; }
base::circular_deque<std::unique_ptr<
SkiaOutputDeviceBufferQueue::CancelableSwapCompletionCallback>>&
swap_completion_callbacks() {
return output_device_->swap_completion_callbacks_;
}
const gpu::MemoryTracker& memory_tracker() { return *memory_tracker_; }
int CountBuffers() {
int n = available_images().size() + swap_completion_callbacks().size();
if (displayed_image())
n++;
if (current_image())
n++;
return n;
}
void CheckUnique() {
std::set<Image*> images;
for (auto* image : available_images())
images.insert(image);
if (displayed_image())
images.insert(displayed_image());
if (current_image())
images.insert(current_image());
EXPECT_EQ(images.size() + swap_completion_callbacks().size(),
(size_t)CountBuffers());
}
Image* PaintPrimaryPlane() {
std::vector<GrBackendSemaphore> end_semaphores;
output_device_->BeginPaint(/*allocate_frame_buffer=*/false,
&end_semaphores);
output_device_->EndPaint();
return current_image();
}
Image* PaintAndSchedulePrimaryPlane() {
PaintPrimaryPlane();
SchedulePrimaryPlane();
return current_image();
}
void SchedulePrimaryPlane() {
output_device_->SchedulePrimaryPlane(
OverlayProcessorInterface::OutputSurfaceOverlayPlane());
}
void ScheduleNoPrimaryPlane() {
absl::optional<OverlayProcessorInterface::OutputSurfaceOverlayPlane>
no_plane;
output_device_->SchedulePrimaryPlane(no_plane);
}
virtual void SwapBuffers() {
auto present_callback =
base::DoNothing::Once<const gfx::PresentationFeedback&>();
output_device_->SwapBuffers(std::move(present_callback),
OutputSurfaceFrame());
}
void CommitOverlayPlanes() {
auto present_callback =
base::DoNothing::Once<const gfx::PresentationFeedback&>();
output_device_->CommitOverlayPlanes(std::move(present_callback),
OutputSurfaceFrame());
}
void PageFlipComplete() { gl_surface_->SwapComplete(); }
protected:
std::unique_ptr<SkiaOutputSurfaceDependency> dependency_;
scoped_refptr<MockGLSurfaceAsync> gl_surface_;
std::unique_ptr<MemoryTrackerStub> memory_tracker_;
TestSharedImageBackingFactory test_backing_factory_;
std::unique_ptr<gpu::SharedImageFactory> shared_image_factory_;
std::unique_ptr<gpu::SharedImageRepresentationFactory>
shared_image_representation_factory_;
std::unique_ptr<SkiaOutputDeviceBufferQueue> output_device_;
};
namespace {
const gfx::Size screen_size = gfx::Size(30, 30);
const gfx::BufferFormat kDefaultFormat = gfx::BufferFormat::RGBA_8888;
TEST_F_GPU(SkiaOutputDeviceBufferQueueTest, MultipleGetCurrentBufferCalls) {
// Check that multiple bind calls do not create or change surfaces.
output_device_->Reshape(screen_size, 1.0f, gfx::ColorSpace(), kDefaultFormat,
gfx::OVERLAY_TRANSFORM_NONE);
EXPECT_NE(0U, memory_tracker().GetSize());
EXPECT_NE(PaintPrimaryPlane(), nullptr);
EXPECT_NE(0U, memory_tracker().GetSize());
EXPECT_EQ(3, CountBuffers());
auto* fb = current_image();
EXPECT_NE(PaintAndSchedulePrimaryPlane(), nullptr);
EXPECT_NE(0U, memory_tracker().GetSize());
EXPECT_EQ(3, CountBuffers());
EXPECT_EQ(fb, current_image());
}
TEST_F_GPU(SkiaOutputDeviceBufferQueueTest, CheckDoubleBuffering) {
// Check buffer flow through double buffering path.
output_device_->Reshape(screen_size, 1.0f, gfx::ColorSpace(), kDefaultFormat,
gfx::OVERLAY_TRANSFORM_NONE);
EXPECT_NE(0U, memory_tracker().GetSize());
EXPECT_EQ(3, CountBuffers());
EXPECT_NE(PaintAndSchedulePrimaryPlane(), nullptr);
EXPECT_NE(0U, memory_tracker().GetSize());
EXPECT_EQ(3, CountBuffers());
EXPECT_NE(current_image(), nullptr);
EXPECT_FALSE(displayed_image());
SwapBuffers();
EXPECT_EQ(1U, swap_completion_callbacks().size());
PageFlipComplete();
EXPECT_EQ(0U, swap_completion_callbacks().size());
EXPECT_TRUE(displayed_image());
EXPECT_NE(PaintAndSchedulePrimaryPlane(), nullptr);
EXPECT_NE(0U, memory_tracker().GetSize());
EXPECT_EQ(3, CountBuffers());
CheckUnique();
EXPECT_NE(current_image(), nullptr);
EXPECT_EQ(0U, swap_completion_callbacks().size());
EXPECT_TRUE(displayed_image());
SwapBuffers();
CheckUnique();
EXPECT_EQ(1U, swap_completion_callbacks().size());
EXPECT_TRUE(displayed_image());
PageFlipComplete();
CheckUnique();
EXPECT_EQ(0U, swap_completion_callbacks().size());
EXPECT_EQ(2U, available_images().size());
EXPECT_TRUE(displayed_image());
EXPECT_NE(PaintAndSchedulePrimaryPlane(), nullptr);
EXPECT_NE(0U, memory_tracker().GetSize());
EXPECT_EQ(3, CountBuffers());
CheckUnique();
EXPECT_EQ(1u, available_images().size());
}
TEST_F_GPU(SkiaOutputDeviceBufferQueueTest, CheckTripleBuffering) {
// Check buffer flow through triple buffering path.
output_device_->Reshape(screen_size, 1.0f, gfx::ColorSpace(), kDefaultFormat,
gfx::OVERLAY_TRANSFORM_NONE);
EXPECT_NE(0U, memory_tracker().GetSize());
// This bit is the same sequence tested in the doublebuffering case.
EXPECT_NE(PaintAndSchedulePrimaryPlane(), nullptr);
EXPECT_FALSE(displayed_image());
SwapBuffers();
PageFlipComplete();
EXPECT_NE(PaintAndSchedulePrimaryPlane(), nullptr);
SwapBuffers();
EXPECT_NE(0U, memory_tracker().GetSize());
EXPECT_EQ(3, CountBuffers());
CheckUnique();
EXPECT_EQ(1U, swap_completion_callbacks().size());
EXPECT_TRUE(displayed_image());
EXPECT_NE(PaintAndSchedulePrimaryPlane(), nullptr);
EXPECT_NE(0U, memory_tracker().GetSize());
EXPECT_EQ(3, CountBuffers());
CheckUnique();
EXPECT_NE(current_image(), nullptr);
EXPECT_EQ(1U, swap_completion_callbacks().size());
EXPECT_TRUE(displayed_image());
PageFlipComplete();
EXPECT_EQ(3, CountBuffers());
CheckUnique();
EXPECT_NE(current_image(), nullptr);
EXPECT_EQ(0U, swap_completion_callbacks().size());
EXPECT_TRUE(displayed_image());
EXPECT_EQ(1U, available_images().size());
}
TEST_F_GPU(SkiaOutputDeviceBufferQueueTest, CheckEmptySwap) {
// Check empty swap flow, in which the damage is empty and BindFramebuffer
// might not be called.
output_device_->Reshape(screen_size, 1.0f, gfx::ColorSpace(), kDefaultFormat,
gfx::OVERLAY_TRANSFORM_NONE);
EXPECT_EQ(3, CountBuffers());
EXPECT_NE(0U, memory_tracker().GetSize());
auto* image = PaintAndSchedulePrimaryPlane();
EXPECT_NE(image, nullptr);
EXPECT_NE(0U, memory_tracker().GetSize());
EXPECT_EQ(3, CountBuffers());
EXPECT_NE(current_image(), nullptr);
EXPECT_FALSE(displayed_image());
SwapBuffers();
// Make sure we won't be drawing to the texture we just sent for scanout.
auto* new_image = PaintAndSchedulePrimaryPlane();
EXPECT_NE(new_image, nullptr);
EXPECT_NE(image, new_image);
EXPECT_EQ(1U, swap_completion_callbacks().size());
PageFlipComplete();
// Test CommitOverlayPlanes without calling BeginPaint/EndPaint (i.e without
// PaintAndSchedulePrimaryPlane)
SwapBuffers();
EXPECT_EQ(1U, swap_completion_callbacks().size());
// Schedule the primary plane without drawing.
SchedulePrimaryPlane();
PageFlipComplete();
EXPECT_EQ(0U, swap_completion_callbacks().size());
EXPECT_EQ(current_image(), nullptr);
CommitOverlayPlanes();
EXPECT_EQ(1U, swap_completion_callbacks().size());
PageFlipComplete();
EXPECT_EQ(0U, swap_completion_callbacks().size());
}
TEST_F_GPU(SkiaOutputDeviceBufferQueueTest, NoPrimaryPlane) {
// Check empty swap flow, in which the damage is empty and BindFramebuffer
// might not be called.
output_device_->Reshape(screen_size, 1.0f, gfx::ColorSpace(), kDefaultFormat,
gfx::OVERLAY_TRANSFORM_NONE);
// Do a swap and commit overlay planes with no primary plane.
for (size_t i = 0; i < 2; ++i) {
ScheduleNoPrimaryPlane();
EXPECT_EQ(current_image(), nullptr);
EXPECT_FALSE(displayed_image());
if (i == 0)
SwapBuffers();
else if (i == 1)
CommitOverlayPlanes();
EXPECT_FALSE(displayed_image());
PageFlipComplete();
}
// Do it again with a paint in between.
for (size_t i = 0; i < 2; ++i) {
PaintAndSchedulePrimaryPlane();
EXPECT_NE(current_image(), nullptr);
EXPECT_FALSE(displayed_image());
SwapBuffers();
PageFlipComplete();
EXPECT_TRUE(displayed_image());
ScheduleNoPrimaryPlane();
EXPECT_EQ(current_image(), nullptr);
if (i == 0)
SwapBuffers();
else if (i == 1)
CommitOverlayPlanes();
EXPECT_TRUE(displayed_image());
PageFlipComplete();
EXPECT_FALSE(displayed_image());
}
// Do a final commit with no primary.
{
ScheduleNoPrimaryPlane();
EXPECT_EQ(current_image(), nullptr);
CommitOverlayPlanes();
PageFlipComplete();
EXPECT_FALSE(displayed_image());
}
}
TEST_F_GPU(SkiaOutputDeviceBufferQueueTest, CheckCorrectBufferOrdering) {
output_device_->Reshape(screen_size, 1.0f, gfx::ColorSpace(), kDefaultFormat,
gfx::OVERLAY_TRANSFORM_NONE);
const size_t kSwapCount = 5;
EXPECT_NE(PaintAndSchedulePrimaryPlane(), nullptr);
for (size_t i = 0; i < kSwapCount; ++i) {
SwapBuffers();
EXPECT_NE(PaintAndSchedulePrimaryPlane(), nullptr);
PageFlipComplete();
}
// Note: this must be three, not kSwapCount
EXPECT_EQ(3, CountBuffers());
for (size_t i = 0; i < kSwapCount; ++i) {
auto* next_image = current_image();
SwapBuffers();
EXPECT_EQ(current_image(), nullptr);
EXPECT_EQ(1U, swap_completion_callbacks().size());
PageFlipComplete();
EXPECT_EQ(displayed_image(), next_image);
EXPECT_NE(PaintAndSchedulePrimaryPlane(), nullptr);
}
}
TEST_F_GPU(SkiaOutputDeviceBufferQueueTest, ReshapeWithInFlightSurfaces) {
output_device_->Reshape(screen_size, 1.0f, gfx::ColorSpace(), kDefaultFormat,
gfx::OVERLAY_TRANSFORM_NONE);
const size_t kSwapCount = 5;
EXPECT_NE(PaintAndSchedulePrimaryPlane(), nullptr);
for (size_t i = 0; i < kSwapCount; ++i) {
SwapBuffers();
EXPECT_NE(PaintAndSchedulePrimaryPlane(), nullptr);
PageFlipComplete();
}
SwapBuffers();
output_device_->Reshape(
gfx::Size(screen_size.width() - 1, screen_size.height() - 1), 1.0f,
gfx::ColorSpace(), kDefaultFormat, gfx::OVERLAY_TRANSFORM_NONE);
// swap completion callbacks should not be cleared.
EXPECT_EQ(1u, swap_completion_callbacks().size());
PageFlipComplete();
EXPECT_FALSE(displayed_image());
// The dummy surfacess left should be discarded.
EXPECT_EQ(3u, available_images().size());
// Test swap after reshape
EXPECT_NE(PaintAndSchedulePrimaryPlane(), nullptr);
SwapBuffers();
PageFlipComplete();
EXPECT_NE(displayed_image(), nullptr);
}
TEST_F_GPU(SkiaOutputDeviceBufferQueueTest, BufferIsInOrder) {
output_device_->Reshape(screen_size, 1.0f, gfx::ColorSpace(), kDefaultFormat,
gfx::OVERLAY_TRANSFORM_NONE);
EXPECT_EQ(3u, available_images().size());
int current_index = -1;
int submitted_index = -1;
int displayed_index = -1;
EXPECT_NE(PaintAndSchedulePrimaryPlane(), nullptr);
++current_index;
EXPECT_EQ(current_image(), images()[current_index % 3].get());
EXPECT_EQ(submitted_image(), submitted_index < 0
? nullptr
: images()[submitted_index % 3].get());
EXPECT_EQ(displayed_image(), displayed_index < 0
? nullptr
: images()[displayed_index % 3].get());
SwapBuffers();
++submitted_index;
EXPECT_EQ(current_image(), nullptr);
EXPECT_EQ(submitted_image(), submitted_index < 0
? nullptr
: images()[submitted_index % 3].get());
EXPECT_EQ(displayed_image(), displayed_index < 0
? nullptr
: images()[displayed_index % 3].get());
const size_t kSwapCount = 10;
for (size_t i = 0; i < kSwapCount; ++i) {
EXPECT_NE(PaintAndSchedulePrimaryPlane(), nullptr);
++current_index;
EXPECT_EQ(current_image(), images()[current_index % 3].get());
EXPECT_EQ(submitted_image(), submitted_index < 0
? nullptr
: images()[submitted_index % 3].get());
EXPECT_EQ(displayed_image(), displayed_index < 0
? nullptr
: images()[displayed_index % 3].get());
SwapBuffers();
++submitted_index;
EXPECT_EQ(current_image(), nullptr);
EXPECT_EQ(submitted_image(), submitted_index < 0
? nullptr
: images()[submitted_index % 3].get());
EXPECT_EQ(displayed_image(), displayed_index < 0
? nullptr
: images()[displayed_index % 3].get());
PageFlipComplete();
++displayed_index;
EXPECT_EQ(current_image(), nullptr);
EXPECT_EQ(submitted_image(), submitted_index < 0
? nullptr
: images()[submitted_index % 3].get());
EXPECT_EQ(displayed_image(), displayed_index < 0
? nullptr
: images()[displayed_index % 3].get());
}
PageFlipComplete();
++displayed_index;
EXPECT_EQ(current_image(), nullptr);
EXPECT_EQ(submitted_image(), submitted_index < 0
? nullptr
: images()[submitted_index % 3].get());
EXPECT_EQ(displayed_image(), displayed_index < 0
? nullptr
: images()[displayed_index % 3].get());
}
} // namespace
class SkiaOutputDeviceSwapSkippedTest : public SkiaOutputDeviceBufferQueueTest {
public:
SkiaOutputDeviceSwapSkippedTest() = default;
DidSwapBufferCompleteCallback GetDidSwapBuffersCompleteCallback() override {
return swap_buffers_complete_cb.Get();
}
void SwapBuffers() override {
output_device_->SwapBuffers(buffer_presented_cb.Get(),
OutputSurfaceFrame());
}
void SwapBuffersSkipped() {
output_device_->SwapBuffersSkipped(buffer_presented_cb.Get(),
OutputSurfaceFrame());
}
base::MockCallback<DidSwapBufferCompleteCallback> swap_buffers_complete_cb;
base::MockCallback<BufferPresentedCallback> buffer_presented_cb;
};
namespace {
MATCHER_P2(CheckSwapResponse, expected_swap_id, expected_result, "") {
return arg.swap_response.swap_id == expected_swap_id &&
arg.swap_response.result == expected_result;
}
MATCHER_P(CheckPresentationFeedback, expected_fail, "") {
return expected_fail == arg.failed();
}
TEST_F_GPU(SkiaOutputDeviceSwapSkippedTest, SkipWithoutPending) {
// Check that skipping a SwapBuffers without any pending swaps immediately
// invokes the complete/presented callbacks.
output_device_->Reshape(screen_size, 1.0f, gfx::ColorSpace(), kDefaultFormat,
gfx::OVERLAY_TRANSFORM_NONE);
EXPECT_CALL(swap_buffers_complete_cb,
Run(CheckSwapResponse(1U, gfx::SwapResult::SWAP_SKIPPED), _, _));
EXPECT_CALL(buffer_presented_cb,
Run(CheckPresentationFeedback(true /* failed */)));
SwapBuffersSkipped();
}
TEST_F_GPU(SkiaOutputDeviceSwapSkippedTest, SkipWithPending) {
// Check that skipping a SwapBuffers with existing pending swaps waits for
// the pending swaps to complete before invoking the complete/presented
// callbacks.
output_device_->Reshape(screen_size, 1.0f, gfx::ColorSpace(), kDefaultFormat,
gfx::OVERLAY_TRANSFORM_NONE);
EXPECT_NE(PaintAndSchedulePrimaryPlane(), nullptr);
EXPECT_NE(current_image(), nullptr);
SwapBuffers();
SwapBuffersSkipped();
EXPECT_CALL(swap_buffers_complete_cb,
Run(CheckSwapResponse(1U, gfx::SwapResult::SWAP_ACK), _, _));
EXPECT_CALL(buffer_presented_cb,
Run(CheckPresentationFeedback(false /* failed */)));
EXPECT_CALL(swap_buffers_complete_cb,
Run(CheckSwapResponse(2U, gfx::SwapResult::SWAP_SKIPPED), _, _));
EXPECT_CALL(buffer_presented_cb,
Run(CheckPresentationFeedback(true /* failed */)));
PageFlipComplete();
}
} // namespace
} // namespace viz