| // Copyright 2014 The Chromium Authors |
| // 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/buffer_queue.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <set> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| |
| #include "base/test/bind.h" |
| #include "build/build_config.h" |
| #include "components/viz/test/fake_skia_output_surface.h" |
| #include "gpu/command_buffer/common/mailbox.h" |
| #include "gpu/command_buffer/common/shared_image_usage.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using ::testing::_; |
| using ::testing::Contains; |
| using ::testing::Expectation; |
| using ::testing::Ne; |
| using ::testing::Not; |
| using ::testing::Return; |
| |
| namespace viz { |
| |
| namespace { |
| |
| constexpr SharedImageFormat kBufferQueueFormat = SinglePlaneFormat::kRGBA_8888; |
| constexpr gfx::ColorSpace kBufferQueueColorSpace = |
| gfx::ColorSpace::CreateSRGB(); |
| |
| } // namespace |
| |
| #if BUILDFLAG(IS_WIN) |
| const gpu::SurfaceHandle kFakeSurfaceHandle = |
| reinterpret_cast<gpu::SurfaceHandle>(1); |
| #else |
| const gpu::SurfaceHandle kFakeSurfaceHandle = 1; |
| #endif |
| |
| class BufferQueueTest : public ::testing::Test { |
| public: |
| BufferQueueTest() = default; |
| |
| void SetUp() override { |
| skia_output_surface_ = FakeSkiaOutputSurface::Create3d(); |
| buffer_queue_ = std::make_unique<BufferQueue>(skia_output_surface_.get(), |
| kFakeSurfaceHandle, 3); |
| } |
| |
| void TearDown() override { buffer_queue_.reset(); } |
| |
| gpu::Mailbox current_buffer() { |
| return buffer_queue_->current_buffer_ |
| ? buffer_queue_->current_buffer_->mailbox |
| : gpu::Mailbox(); |
| } |
| const base::circular_deque<std::unique_ptr<BufferQueue::AllocatedBuffer>>& |
| available_buffers() { |
| return buffer_queue_->available_buffers_; |
| } |
| base::circular_deque<std::unique_ptr<BufferQueue::AllocatedBuffer>>& |
| in_flight_buffers() { |
| return buffer_queue_->in_flight_buffers_; |
| } |
| |
| const BufferQueue::AllocatedBuffer* displayed_frame() { |
| return buffer_queue_->displayed_buffer_.get(); |
| } |
| const BufferQueue::AllocatedBuffer* current_frame() { |
| return buffer_queue_->current_buffer_.get(); |
| } |
| const gfx::Size size() { return buffer_queue_->size_; } |
| |
| int CountBuffers() { |
| int n = available_buffers().size() + in_flight_buffers().size() + |
| (displayed_frame() ? 1 : 0); |
| if (!current_buffer().IsZero()) |
| n++; |
| return n; |
| } |
| |
| // Check that each buffer is unique if present. |
| bool CheckUnique() { |
| std::set<gpu::Mailbox> buffers; |
| if (!InsertUnique(&buffers, current_buffer())) { |
| return false; |
| } |
| if (displayed_frame() && |
| !InsertUnique(&buffers, displayed_frame()->mailbox)) { |
| return false; |
| } |
| for (auto& buffer : available_buffers()) { |
| if (!InsertUnique(&buffers, buffer->mailbox)) { |
| return false; |
| } |
| } |
| for (auto& buffer : in_flight_buffers()) { |
| if (buffer && !InsertUnique(&buffers, buffer->mailbox)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| gpu::Mailbox SendDamagedFrame(const gfx::Rect& damage) { |
| // We don't care about the GL-level implementation here, just how it uses |
| // damage rects. |
| auto mailbox = buffer_queue_->GetCurrentBuffer(); |
| buffer_queue_->SwapBuffers(damage); |
| buffer_queue_->SwapBuffersComplete(/*did_present=*/true); |
| return mailbox; |
| } |
| |
| void SendFullFrame() { SendDamagedFrame(gfx::Rect(buffer_queue_->size_)); } |
| |
| protected: |
| bool InsertUnique(std::set<gpu::Mailbox>* set, gpu::Mailbox value) { |
| if (value.IsZero()) |
| return true; |
| if (set->find(value) != set->end()) |
| return false; |
| set->insert(value); |
| return true; |
| } |
| |
| std::unique_ptr<FakeSkiaOutputSurface> skia_output_surface_; |
| std::unique_ptr<BufferQueue> buffer_queue_; |
| }; |
| |
| const gfx::Size screen_size = gfx::Size(30, 30); |
| const gfx::Rect screen_rect = gfx::Rect(screen_size); |
| const gfx::Rect small_damage = gfx::Rect(gfx::Size(10, 10)); |
| const gfx::Rect large_damage = gfx::Rect(gfx::Size(20, 20)); |
| const gfx::Rect overlapping_damage = gfx::Rect(gfx::Size(5, 20)); |
| |
| class MockedSkiaOutputSurface : public FakeSkiaOutputSurface { |
| public: |
| MockedSkiaOutputSurface() : FakeSkiaOutputSurface(nullptr) {} |
| MOCK_METHOD7(CreateSharedImage, |
| gpu::Mailbox(SharedImageFormat format, |
| const gfx::Size& size, |
| const gfx::ColorSpace& color_space, |
| RenderPassAlphaType alpha_type, |
| gpu::SharedImageUsageSet usage, |
| std::string_view debug_label, |
| gpu::SurfaceHandle surface_handle)); |
| MOCK_METHOD1(DestroySharedImage, void(const gpu::Mailbox& mailbox)); |
| }; |
| |
| TEST(BufferQueueStandaloneTest, BufferCreationAndDestruction) { |
| auto mock_skia_output_surface = std::make_unique<MockedSkiaOutputSurface>(); |
| std::unique_ptr<BufferQueue> buffer_queue = std::make_unique<BufferQueue>( |
| mock_skia_output_surface.get(), kFakeSurfaceHandle, 1); |
| |
| const gpu::Mailbox expected_mailbox = gpu::Mailbox::Generate(); |
| { |
| testing::InSequence dummy; |
| EXPECT_CALL(*mock_skia_output_surface, |
| CreateSharedImage(_, _, _, _, |
| |
| gpu::SHARED_IMAGE_USAGE_SCANOUT | |
| gpu::SHARED_IMAGE_USAGE_DISPLAY_READ | |
| gpu::SHARED_IMAGE_USAGE_DISPLAY_WRITE, |
| _, _)) |
| .WillOnce(Return(expected_mailbox)); |
| EXPECT_CALL(*mock_skia_output_surface, |
| DestroySharedImage(expected_mailbox)); |
| } |
| |
| EXPECT_TRUE(buffer_queue->Reshape(screen_size, kBufferQueueColorSpace, |
| RenderPassAlphaType::kPremul, |
| kBufferQueueFormat)); |
| EXPECT_EQ(expected_mailbox, buffer_queue->GetCurrentBuffer()); |
| } |
| |
| TEST_F(BufferQueueTest, PartialSwapReuse) { |
| EXPECT_TRUE(buffer_queue_->Reshape(screen_size, kBufferQueueColorSpace, |
| RenderPassAlphaType::kPremul, |
| kBufferQueueFormat)); |
| SendFullFrame(); |
| SendDamagedFrame(small_damage); |
| SendDamagedFrame(small_damage); |
| SendDamagedFrame(large_damage); |
| // Verify that the damage has propagated. |
| EXPECT_EQ(buffer_queue_->CurrentBufferDamage(), large_damage); |
| } |
| |
| TEST_F(BufferQueueTest, PartialSwapFullFrame) { |
| EXPECT_TRUE(buffer_queue_->Reshape(screen_size, kBufferQueueColorSpace, |
| RenderPassAlphaType::kPremul, |
| kBufferQueueFormat)); |
| SendFullFrame(); |
| SendDamagedFrame(small_damage); |
| SendFullFrame(); |
| SendFullFrame(); |
| EXPECT_EQ(buffer_queue_->CurrentBufferDamage(), screen_rect); |
| } |
| |
| // Make sure that each time we swap buffers, the damage gets propagated to the |
| // previously swapped buffers. |
| TEST_F(BufferQueueTest, PartialSwapWithTripleBuffering) { |
| EXPECT_EQ(0, CountBuffers()); |
| EXPECT_TRUE(buffer_queue_->Reshape(screen_size, kBufferQueueColorSpace, |
| RenderPassAlphaType::kPremul, |
| kBufferQueueFormat)); |
| EXPECT_EQ(3, CountBuffers()); |
| |
| SendFullFrame(); |
| SendFullFrame(); |
| // Let's triple buffer. |
| EXPECT_FALSE(buffer_queue_->GetCurrentBuffer().IsZero()); |
| buffer_queue_->SwapBuffers(small_damage); |
| EXPECT_FALSE(buffer_queue_->GetCurrentBuffer().IsZero()); |
| // The whole buffer needs to be redrawn since it's a newly allocated buffer |
| EXPECT_EQ(buffer_queue_->CurrentBufferDamage(), screen_rect); |
| |
| SendDamagedFrame(overlapping_damage); |
| // The next buffer should include damage from |overlapping_damage| and |
| // |small_damage|. |
| EXPECT_FALSE(buffer_queue_->GetCurrentBuffer().IsZero()); |
| const auto current_buffer_damage = buffer_queue_->CurrentBufferDamage(); |
| EXPECT_TRUE(current_buffer_damage.Contains(overlapping_damage)); |
| EXPECT_TRUE(current_buffer_damage.Contains(small_damage)); |
| |
| // Let's make sure the damage is not trivially the whole screen. |
| EXPECT_NE(current_buffer_damage, screen_rect); |
| } |
| |
| TEST_F(BufferQueueTest, PartialSwapOverlapping) { |
| EXPECT_TRUE(buffer_queue_->Reshape(screen_size, kBufferQueueColorSpace, |
| RenderPassAlphaType::kPremul, |
| kBufferQueueFormat)); |
| |
| SendFullFrame(); |
| SendDamagedFrame(small_damage); |
| SendDamagedFrame(overlapping_damage); |
| // Expect small_damage UNION overlapping_damage |
| EXPECT_EQ(buffer_queue_->CurrentBufferDamage(), gfx::Rect(0, 0, 10, 20)); |
| } |
| |
| TEST_F(BufferQueueTest, MultipleGetCurrentBufferCalls) { |
| // It is not valid to call GetCurrentBuffer without having set an initial |
| // size via Reshape. |
| EXPECT_TRUE(buffer_queue_->Reshape(screen_size, kBufferQueueColorSpace, |
| RenderPassAlphaType::kPremul, |
| kBufferQueueFormat)); |
| // Check that multiple bind calls do not create or change buffers. |
| EXPECT_FALSE(buffer_queue_->GetCurrentBuffer().IsZero()); |
| gpu::Mailbox fb = buffer_queue_->GetCurrentBuffer(); |
| EXPECT_FALSE(buffer_queue_->GetCurrentBuffer().IsZero()); |
| EXPECT_EQ(fb, buffer_queue_->GetCurrentBuffer()); |
| } |
| |
| TEST_F(BufferQueueTest, CheckDoubleBuffering) { |
| // Check buffer flow through double buffering path. |
| |
| // Create a BufferQueue with only 2 buffers. |
| buffer_queue_ = std::make_unique<BufferQueue>(skia_output_surface_.get(), |
| kFakeSurfaceHandle, 2); |
| |
| EXPECT_EQ(0, CountBuffers()); |
| EXPECT_TRUE(buffer_queue_->Reshape(screen_size, kBufferQueueColorSpace, |
| RenderPassAlphaType::kPremul, |
| kBufferQueueFormat)); |
| EXPECT_EQ(2, CountBuffers()); |
| |
| EXPECT_FALSE(buffer_queue_->GetCurrentBuffer().IsZero()); |
| EXPECT_FALSE(buffer_queue_->GetCurrentBuffer().IsZero()); |
| EXPECT_FALSE(displayed_frame()); |
| |
| buffer_queue_->SwapBuffers(screen_rect); |
| |
| EXPECT_EQ(1U, in_flight_buffers().size()); |
| buffer_queue_->SwapBuffersComplete(/*did_present=*/true); |
| |
| EXPECT_EQ(0U, in_flight_buffers().size()); |
| EXPECT_FALSE(displayed_frame()->mailbox.IsZero()); |
| EXPECT_FALSE(buffer_queue_->GetCurrentBuffer().IsZero()); |
| EXPECT_TRUE(CheckUnique()); |
| EXPECT_FALSE(buffer_queue_->GetCurrentBuffer().IsZero()); |
| EXPECT_EQ(0U, in_flight_buffers().size()); |
| EXPECT_FALSE(displayed_frame()->mailbox.IsZero()); |
| buffer_queue_->SwapBuffers(screen_rect); |
| EXPECT_TRUE(CheckUnique()); |
| EXPECT_EQ(1U, in_flight_buffers().size()); |
| EXPECT_FALSE(displayed_frame()->mailbox.IsZero()); |
| buffer_queue_->SwapBuffersComplete(/*did_present=*/true); |
| EXPECT_TRUE(CheckUnique()); |
| EXPECT_EQ(0U, in_flight_buffers().size()); |
| EXPECT_EQ(1U, available_buffers().size()); |
| EXPECT_FALSE(displayed_frame()->mailbox.IsZero()); |
| EXPECT_FALSE(buffer_queue_->GetCurrentBuffer().IsZero()); |
| EXPECT_TRUE(CheckUnique()); |
| EXPECT_TRUE(available_buffers().empty()); |
| } |
| |
| TEST_F(BufferQueueTest, CheckTripleBuffering) { |
| // Check buffer flow through triple buffering path. |
| EXPECT_EQ(0, CountBuffers()); |
| EXPECT_TRUE(buffer_queue_->Reshape(screen_size, kBufferQueueColorSpace, |
| RenderPassAlphaType::kPremul, |
| kBufferQueueFormat)); |
| EXPECT_EQ(3, CountBuffers()); |
| |
| // This bit is the same sequence tested in the doublebuffering case. |
| EXPECT_FALSE(buffer_queue_->GetCurrentBuffer().IsZero()); |
| EXPECT_FALSE(displayed_frame()); |
| buffer_queue_->SwapBuffers(screen_rect); |
| buffer_queue_->SwapBuffersComplete(/*did_present=*/true); |
| EXPECT_FALSE(buffer_queue_->GetCurrentBuffer().IsZero()); |
| buffer_queue_->SwapBuffers(screen_rect); |
| |
| EXPECT_TRUE(CheckUnique()); |
| EXPECT_EQ(1U, in_flight_buffers().size()); |
| EXPECT_FALSE(displayed_frame()->mailbox.IsZero()); |
| EXPECT_FALSE(buffer_queue_->GetCurrentBuffer().IsZero()); |
| EXPECT_TRUE(CheckUnique()); |
| EXPECT_FALSE(buffer_queue_->GetCurrentBuffer().IsZero()); |
| EXPECT_EQ(1U, in_flight_buffers().size()); |
| EXPECT_FALSE(displayed_frame()->mailbox.IsZero()); |
| buffer_queue_->SwapBuffersComplete(/*did_present=*/true); |
| EXPECT_TRUE(CheckUnique()); |
| EXPECT_FALSE(buffer_queue_->GetCurrentBuffer().IsZero()); |
| EXPECT_EQ(0U, in_flight_buffers().size()); |
| EXPECT_FALSE(displayed_frame()->mailbox.IsZero()); |
| EXPECT_EQ(1U, available_buffers().size()); |
| } |
| |
| TEST_F(BufferQueueTest, CheckEmptySwap) { |
| // It is not valid to call GetCurrentBuffer without having set an initial |
| // size via Reshape. |
| EXPECT_TRUE(buffer_queue_->Reshape(screen_size, kBufferQueueColorSpace, |
| RenderPassAlphaType::kPremul, |
| kBufferQueueFormat)); |
| // Check empty swap flow, in which the damage is empty. |
| gpu::Mailbox mailbox = buffer_queue_->GetCurrentBuffer(); |
| EXPECT_FALSE(mailbox.IsZero()); |
| EXPECT_FALSE(buffer_queue_->GetCurrentBuffer().IsZero()); |
| EXPECT_FALSE(displayed_frame()); |
| |
| buffer_queue_->SwapBuffers(gfx::Rect()); |
| // Make sure we won't be drawing to the buffer we just sent for scanout. |
| gpu::Mailbox new_mailbox = buffer_queue_->GetCurrentBuffer(); |
| EXPECT_FALSE(new_mailbox.IsZero()); |
| EXPECT_NE(mailbox, new_mailbox); |
| |
| EXPECT_EQ(1U, in_flight_buffers().size()); |
| buffer_queue_->SwapBuffersComplete(/*did_present=*/true); |
| |
| buffer_queue_->SwapBuffers(gfx::Rect()); |
| // Test SwapBuffers() without calling GetCurrentBuffer(). |
| buffer_queue_->SwapBuffers(gfx::Rect()); |
| EXPECT_EQ(2U, in_flight_buffers().size()); |
| |
| buffer_queue_->SwapBuffersComplete(/*did_present=*/true); |
| EXPECT_EQ(1U, in_flight_buffers().size()); |
| |
| buffer_queue_->SwapBuffersComplete(/*did_present=*/true); |
| EXPECT_EQ(0U, in_flight_buffers().size()); |
| } |
| |
| TEST_F(BufferQueueTest, CheckCorrectBufferOrdering) { |
| EXPECT_TRUE(buffer_queue_->Reshape(screen_size, kBufferQueueColorSpace, |
| RenderPassAlphaType::kPremul, |
| kBufferQueueFormat)); |
| const size_t kSwapCount = 3; |
| for (size_t i = 0; i < kSwapCount; ++i) { |
| EXPECT_FALSE(buffer_queue_->GetCurrentBuffer().IsZero()); |
| buffer_queue_->SwapBuffers(screen_rect); |
| } |
| |
| EXPECT_EQ(kSwapCount, in_flight_buffers().size()); |
| for (size_t i = 0; i < kSwapCount; ++i) { |
| gpu::Mailbox next_mailbox = in_flight_buffers().front()->mailbox; |
| buffer_queue_->SwapBuffersComplete(/*did_present=*/true); |
| EXPECT_EQ(displayed_frame()->mailbox, next_mailbox); |
| } |
| } |
| |
| TEST_F(BufferQueueTest, ReshapeWithInFlightBuffers) { |
| EXPECT_TRUE(buffer_queue_->Reshape(screen_size, kBufferQueueColorSpace, |
| RenderPassAlphaType::kPremul, |
| kBufferQueueFormat)); |
| const size_t kSwapCount = 3; |
| for (size_t i = 0; i < kSwapCount; ++i) { |
| EXPECT_FALSE(buffer_queue_->GetCurrentBuffer().IsZero()); |
| buffer_queue_->SwapBuffers(screen_rect); |
| } |
| |
| EXPECT_TRUE(buffer_queue_->Reshape(gfx::Size(10, 20), kBufferQueueColorSpace, |
| RenderPassAlphaType::kPremul, |
| kBufferQueueFormat)); |
| EXPECT_EQ(3u, in_flight_buffers().size()); |
| EXPECT_EQ(3u, available_buffers().size()); |
| // The inflight images are destroyed, but the buffers are still around for |
| // now, in addition to the newly created buffers. |
| EXPECT_EQ(6, CountBuffers()); |
| |
| for (size_t i = 0; i < kSwapCount; ++i) { |
| buffer_queue_->SwapBuffersComplete(/*did_present=*/true); |
| EXPECT_FALSE(displayed_frame()); |
| } |
| |
| // The dummy buffers left should be discarded. |
| EXPECT_EQ(3u, available_buffers().size()); |
| } |
| |
| TEST_F(BufferQueueTest, SwapAfterReshape) { |
| EXPECT_TRUE(buffer_queue_->Reshape(screen_size, kBufferQueueColorSpace, |
| RenderPassAlphaType::kPremul, |
| kBufferQueueFormat)); |
| const size_t kSwapCount = 3; |
| for (size_t i = 0; i < kSwapCount; ++i) { |
| EXPECT_FALSE(buffer_queue_->GetCurrentBuffer().IsZero()); |
| buffer_queue_->SwapBuffers(screen_rect); |
| } |
| |
| EXPECT_TRUE(buffer_queue_->Reshape(gfx::Size(10, 20), kBufferQueueColorSpace, |
| RenderPassAlphaType::kPremul, |
| kBufferQueueFormat)); |
| |
| for (size_t i = 0; i < kSwapCount; ++i) { |
| EXPECT_FALSE(buffer_queue_->GetCurrentBuffer().IsZero()); |
| buffer_queue_->SwapBuffers(screen_rect); |
| } |
| EXPECT_EQ(2 * kSwapCount, in_flight_buffers().size()); |
| |
| for (size_t i = 0; i < kSwapCount; ++i) { |
| buffer_queue_->SwapBuffersComplete(/*did_present=*/true); |
| EXPECT_FALSE(displayed_frame()); |
| } |
| |
| EXPECT_TRUE(CheckUnique()); |
| |
| for (size_t i = 0; i < kSwapCount; ++i) { |
| gpu::Mailbox next_mailbox = in_flight_buffers().front()->mailbox; |
| buffer_queue_->SwapBuffersComplete(/*did_present=*/true); |
| EXPECT_EQ(displayed_frame()->mailbox, next_mailbox); |
| EXPECT_TRUE(displayed_frame()); |
| } |
| |
| for (size_t i = 0; i < kSwapCount; ++i) { |
| EXPECT_FALSE(buffer_queue_->GetCurrentBuffer().IsZero()); |
| buffer_queue_->SwapBuffers(screen_rect); |
| buffer_queue_->SwapBuffersComplete(/*did_present=*/true); |
| } |
| } |
| |
| TEST_F(BufferQueueTest, SwapBuffersSkippedByDisplay) { |
| EXPECT_TRUE(buffer_queue_->Reshape(screen_size, kBufferQueueColorSpace, |
| RenderPassAlphaType::kPremul, |
| kBufferQueueFormat)); |
| SendDamagedFrame(small_damage); |
| SendDamagedFrame(small_damage); |
| SendDamagedFrame(small_damage); |
| |
| EXPECT_EQ(buffer_queue_->CurrentBufferDamage(), small_damage); |
| |
| auto mailbox1 = buffer_queue_->GetCurrentBuffer(); |
| buffer_queue_->SwapBuffersSkipped(large_damage); |
| auto mailbox2 = buffer_queue_->GetCurrentBuffer(); |
| |
| // SwapBuffersSkipped() didn't advance the current buffer. |
| EXPECT_EQ(mailbox1, mailbox2); |
| EXPECT_EQ(buffer_queue_->CurrentBufferDamage(), small_damage); |
| |
| // Swap on the next frame with no additional damage. |
| buffer_queue_->SwapBuffers(gfx::Rect()); |
| buffer_queue_->SwapBuffersComplete(/*did_present=*/true); |
| |
| // The next frame has the damage from the last SwapBuffersSkipped(). |
| EXPECT_EQ(buffer_queue_->CurrentBufferDamage(), large_damage); |
| } |
| |
| TEST_F(BufferQueueTest, SwapBuffersSkippedByGpuThread) { |
| EXPECT_TRUE(buffer_queue_->Reshape(screen_size, kBufferQueueColorSpace, |
| RenderPassAlphaType::kPremul, |
| kBufferQueueFormat)); |
| |
| // Start with a partially damaged frame. |
| SendDamagedFrame(small_damage); |
| SendDamagedFrame(small_damage); |
| SendDamagedFrame(small_damage); |
| EXPECT_EQ(buffer_queue_->CurrentBufferDamage(), small_damage); |
| |
| // Start two swaps to use up the remaining available buffers. |
| auto mailbox1 = buffer_queue_->GetCurrentBuffer(); |
| buffer_queue_->SwapBuffers(small_damage); |
| buffer_queue_->GetCurrentBuffer(); |
| buffer_queue_->SwapBuffers(small_damage); |
| |
| // There are no more available buffers at this point. Have the fist swap |
| // be skipped by the GPU thread. After that verify that mailbox1 has been |
| // recycled and is available for the next swap |
| buffer_queue_->SwapBuffersComplete(/*did_present=*/false); |
| EXPECT_EQ(mailbox1, buffer_queue_->GetCurrentBuffer()); |
| |
| // The second pending swap also fails since it didn't have full damage. |
| buffer_queue_->SwapBuffersComplete(/*did_present=*/false); |
| |
| // Have a swap complete successfully since it now has full damage. |
| buffer_queue_->SwapBuffers(gfx::Rect(screen_size)); |
| buffer_queue_->SwapBuffersComplete(/*did_present=*/true); |
| } |
| |
| TEST_F(BufferQueueTest, EnsureMinNumberOfBuffers) { |
| EXPECT_EQ(CountBuffers(), 0); |
| |
| buffer_queue_->EnsureMinNumberOfBuffers(4); |
| |
| // EnsureMinNumberOfBuffers does nothing before Reshape() is called. |
| EXPECT_EQ(CountBuffers(), 0); |
| |
| EXPECT_TRUE(buffer_queue_->Reshape(screen_size, kBufferQueueColorSpace, |
| RenderPassAlphaType::kPremul, |
| kBufferQueueFormat)); |
| |
| EXPECT_EQ(CountBuffers(), 4); |
| |
| buffer_queue_->EnsureMinNumberOfBuffers(2); |
| |
| // EnsureMinNumberOfBuffers will never decrease the number of buffers. |
| EXPECT_EQ(CountBuffers(), 4); |
| |
| SendDamagedFrame(small_damage); |
| SendDamagedFrame(small_damage); |
| SendDamagedFrame(small_damage); |
| SendDamagedFrame(small_damage); |
| |
| EXPECT_EQ(buffer_queue_->CurrentBufferDamage(), small_damage); |
| |
| buffer_queue_->EnsureMinNumberOfBuffers(5); |
| |
| EXPECT_EQ(CountBuffers(), 5); |
| |
| // 3 of the existing buffers will be in available_buffers_ already, with |
| // existing small_damage. |
| EXPECT_EQ(buffer_queue_->CurrentBufferDamage(), small_damage); |
| SendDamagedFrame(small_damage); |
| EXPECT_EQ(buffer_queue_->CurrentBufferDamage(), small_damage); |
| SendDamagedFrame(small_damage); |
| EXPECT_EQ(buffer_queue_->CurrentBufferDamage(), small_damage); |
| SendDamagedFrame(small_damage); |
| |
| // The new buffer will come next with full damage. |
| EXPECT_EQ(buffer_queue_->CurrentBufferDamage(), screen_rect); |
| SendDamagedFrame(small_damage); |
| |
| // Finally the buffer that was being displayed while we added the 5th buffer |
| // will also have small damage. |
| EXPECT_EQ(buffer_queue_->CurrentBufferDamage(), small_damage); |
| SendDamagedFrame(small_damage); |
| } |
| |
| TEST_F(BufferQueueTest, GetLastSwappedBuffer) { |
| // No images allocated, so zero-mailbox is returned. |
| EXPECT_TRUE(buffer_queue_->GetLastSwappedBuffer().IsZero()); |
| |
| // After reshape we'll get the last buffer in the queue. |
| EXPECT_TRUE(buffer_queue_->Reshape(screen_size, kBufferQueueColorSpace, |
| RenderPassAlphaType::kPremul, |
| kBufferQueueFormat)); |
| gpu::Mailbox last_swapped1 = buffer_queue_->GetLastSwappedBuffer(); |
| EXPECT_FALSE(last_swapped1.IsZero()); |
| |
| // The last swapped buffer won't change until calling SwapBuffersComplete. |
| gpu::Mailbox mailbox1 = buffer_queue_->GetCurrentBuffer(); |
| EXPECT_NE(last_swapped1, mailbox1); |
| EXPECT_EQ(last_swapped1, buffer_queue_->GetLastSwappedBuffer()); |
| buffer_queue_->SwapBuffers(screen_rect); |
| EXPECT_EQ(last_swapped1, buffer_queue_->GetLastSwappedBuffer()); |
| buffer_queue_->SwapBuffersComplete(/*did_present=*/true); |
| EXPECT_EQ(buffer_queue_->GetLastSwappedBuffer(), mailbox1); |
| |
| // Swap another frame. Last swapped only updates after SwapBuffersComplete(). |
| gpu::Mailbox mailbox2 = buffer_queue_->GetCurrentBuffer(); |
| buffer_queue_->SwapBuffers(screen_rect); |
| EXPECT_EQ(buffer_queue_->GetLastSwappedBuffer(), mailbox1); |
| buffer_queue_->SwapBuffersComplete(/*did_present=*/true); |
| EXPECT_EQ(buffer_queue_->GetLastSwappedBuffer(), mailbox2); |
| |
| // Swap a third frame. Last swapped only updates after SwapBuffersComplete(). |
| gpu::Mailbox mailbox3 = buffer_queue_->GetCurrentBuffer(); |
| // The third mailbox is the first one we got from GetLastSwappedBuffer(). |
| EXPECT_EQ(mailbox3, last_swapped1); |
| buffer_queue_->SwapBuffers(screen_rect); |
| EXPECT_EQ(buffer_queue_->GetLastSwappedBuffer(), mailbox2); |
| buffer_queue_->SwapBuffersComplete(/*did_present=*/true); |
| EXPECT_EQ(buffer_queue_->GetLastSwappedBuffer(), mailbox3); |
| |
| // Empty swap, Last swapped stays the same. |
| buffer_queue_->SwapBuffers(gfx::Rect()); |
| EXPECT_EQ(buffer_queue_->GetLastSwappedBuffer(), mailbox3); |
| buffer_queue_->SwapBuffersComplete(/*did_present=*/true); |
| EXPECT_EQ(buffer_queue_->GetLastSwappedBuffer(), mailbox3); |
| |
| // Swap a fourth frame. Last swapped only updates after SwapBuffersComplete(). |
| EXPECT_EQ(buffer_queue_->GetCurrentBuffer(), mailbox1); |
| buffer_queue_->SwapBuffers(screen_rect); |
| EXPECT_EQ(buffer_queue_->GetLastSwappedBuffer(), mailbox3); |
| buffer_queue_->SwapBuffersComplete(/*did_present=*/true); |
| EXPECT_EQ(buffer_queue_->GetLastSwappedBuffer(), mailbox1); |
| } |
| |
| TEST_F(BufferQueueTest, RecreateBuffers) { |
| EXPECT_TRUE(buffer_queue_->Reshape(screen_size, kBufferQueueColorSpace, |
| RenderPassAlphaType::kPremul, |
| kBufferQueueFormat)); |
| auto mb1 = SendDamagedFrame(small_damage); |
| auto mb2 = SendDamagedFrame(small_damage); |
| auto mb3 = SendDamagedFrame(small_damage); |
| std::vector<gpu::Mailbox> original_buffers = {mb1, mb2, mb3}; |
| |
| EXPECT_EQ(buffer_queue_->GetCurrentBuffer(), mb1); |
| buffer_queue_->SwapBuffers(small_damage); |
| EXPECT_EQ(buffer_queue_->GetCurrentBuffer(), mb2); |
| buffer_queue_->SwapBuffers(small_damage); |
| |
| buffer_queue_->RecreateBuffers(); |
| buffer_queue_->SwapBuffersComplete(/*did_present=*/true); // mb1 |
| |
| auto mb4 = buffer_queue_->GetCurrentBuffer(); |
| EXPECT_THAT(original_buffers, Not(Contains(mb4))); |
| buffer_queue_->SwapBuffers(small_damage); |
| |
| buffer_queue_->SwapBuffersComplete(/*did_present=*/true); // mb2 |
| |
| auto mb5 = buffer_queue_->GetCurrentBuffer(); |
| EXPECT_THAT(original_buffers, Not(Contains(mb5))); |
| buffer_queue_->SwapBuffers(small_damage); |
| buffer_queue_->SwapBuffersComplete(/*did_present=*/true); // mb4 |
| buffer_queue_->SwapBuffersComplete(/*did_present=*/true); // mb5 |
| |
| auto mb6 = SendDamagedFrame(small_damage); |
| EXPECT_THAT(original_buffers, Not(Contains(mb6))); |
| |
| // New queue of buffers loops. |
| EXPECT_EQ(buffer_queue_->GetCurrentBuffer(), mb4); |
| } |
| |
| TEST_F(BufferQueueTest, DestroyBuffers) { |
| EXPECT_TRUE(buffer_queue_->Reshape(screen_size, kBufferQueueColorSpace, |
| RenderPassAlphaType::kPremul, |
| kBufferQueueFormat)); |
| auto mb1 = SendDamagedFrame(small_damage); |
| auto mb2 = SendDamagedFrame(small_damage); |
| auto mb3 = SendDamagedFrame(small_damage); |
| std::vector<gpu::Mailbox> original_buffers = {mb1, mb2, mb3}; |
| |
| EXPECT_EQ(buffer_queue_->GetCurrentBuffer(), mb1); |
| buffer_queue_->SwapBuffers(small_damage); |
| EXPECT_EQ(buffer_queue_->GetCurrentBuffer(), mb2); |
| buffer_queue_->SwapBuffers(small_damage); |
| |
| buffer_queue_->DestroyBuffers(); |
| |
| // All buffers are destroyed, and GetLastSwappedBuffer should not recreate |
| // them. |
| EXPECT_TRUE(buffer_queue_->GetLastSwappedBuffer().IsZero()); |
| buffer_queue_->SwapBuffersComplete(/*did_present=*/true); // mb1 |
| EXPECT_TRUE(buffer_queue_->GetLastSwappedBuffer().IsZero()); |
| // Reshape should not reallocate buffers. |
| EXPECT_TRUE(buffer_queue_->Reshape(gfx::Size(20, 20), kBufferQueueColorSpace, |
| RenderPassAlphaType::kPremul, |
| kBufferQueueFormat)); |
| EXPECT_TRUE(buffer_queue_->GetLastSwappedBuffer().IsZero()); |
| |
| // GetCurrentBuffer should create the new buffers. |
| auto mb4 = buffer_queue_->GetCurrentBuffer(); |
| EXPECT_FALSE(mb4.IsZero()); |
| EXPECT_THAT(original_buffers, Not(Contains(mb4))); |
| EXPECT_FALSE(buffer_queue_->GetLastSwappedBuffer().IsZero()); |
| } |
| |
| TEST_F(BufferQueueTest, SetPurgeable) { |
| testing::MockFunction<void(const gpu::Mailbox&, bool)> mock; |
| skia_output_surface_->SetSharedImagePurgeableCallback( |
| base::BindLambdaForTesting(mock.AsStdFunction())); |
| |
| EXPECT_TRUE(buffer_queue_->Reshape(screen_size, kBufferQueueColorSpace, |
| RenderPassAlphaType::kPremul, |
| kBufferQueueFormat)); |
| auto mb1 = SendDamagedFrame(small_damage); |
| auto mb2 = SendDamagedFrame(small_damage); |
| auto mb3 = SendDamagedFrame(small_damage); |
| EXPECT_EQ(buffer_queue_->GetLastSwappedBuffer(), mb3); |
| |
| // Queue up `mb1` and `mb2` so they are in flight. `mb3` is still the last |
| // swapped buffer. |
| EXPECT_EQ(buffer_queue_->GetCurrentBuffer(), mb1); |
| buffer_queue_->SwapBuffers(small_damage); |
| EXPECT_EQ(buffer_queue_->GetCurrentBuffer(), mb2); |
| buffer_queue_->SwapBuffers(small_damage); |
| EXPECT_EQ(buffer_queue_->GetLastSwappedBuffer(), mb3); |
| |
| // Set buffers as purgeable. |
| buffer_queue_->SetBuffersPurgeable(); |
| |
| // When the next swap finishes `mb3` is available and gets marked purgeable. |
| EXPECT_CALL(mock, Call(mb3, true)); |
| buffer_queue_->SwapBuffersComplete(/*did_present=*/true); |
| EXPECT_EQ(buffer_queue_->GetLastSwappedBuffer(), mb1); |
| |
| // When the next swap finishes `mb1` is available and gets marked purgeable. |
| EXPECT_CALL(mock, Call(mb1, true)); |
| buffer_queue_->SwapBuffersComplete(/*did_present=*/true); |
| EXPECT_EQ(buffer_queue_->GetLastSwappedBuffer(), mb2); |
| |
| // `mb2` is last swapped buffer now and there are no pending swaps. Push an |
| // empty swap and complete that so `mb2` is available. |
| EXPECT_CALL(mock, Call(mb2, true)); |
| buffer_queue_->SwapBuffers(small_damage); |
| buffer_queue_->SwapBuffersComplete(/*did_present=*/true); |
| |
| // The next non-delegated draw will get a primary plane buffer. This will |
| // cause all three buffers to be marked as not purgeable anymore. |
| EXPECT_CALL(mock, Call(mb3, false)); |
| EXPECT_CALL(mock, Call(mb1, false)); |
| EXPECT_CALL(mock, Call(mb2, false)); |
| EXPECT_EQ(buffer_queue_->GetCurrentBuffer(), mb3); |
| buffer_queue_->SwapBuffers(small_damage); |
| buffer_queue_->SwapBuffersComplete(/*did_present=*/true); |
| |
| // Start another swap here. There should be no change in buffer purgeable |
| // state. |
| EXPECT_EQ(buffer_queue_->GetCurrentBuffer(), mb1); |
| buffer_queue_->SwapBuffers(small_damage); |
| |
| // Start purging buffers again. When the swap completes one of the buffers |
| // will be marked purgeable. |
| buffer_queue_->SetBuffersPurgeable(); |
| EXPECT_CALL(mock, Call(_, true)); |
| buffer_queue_->SwapBuffersComplete(/*did_present=*/true); |
| |
| // Reset callback since it points to stack allocated mock. |
| skia_output_surface_->SetSharedImagePurgeableCallback({}); |
| } |
| |
| TEST_F(BufferQueueTest, SetPurgeableThenReshape) { |
| testing::MockFunction<void(const gpu::Mailbox&, bool)> mock; |
| skia_output_surface_->SetSharedImagePurgeableCallback( |
| base::BindLambdaForTesting(mock.AsStdFunction())); |
| |
| // This test will reshape before any buffers can be marked as purgeable. |
| EXPECT_CALL(mock, Call(testing::_, testing::_)).Times(0); |
| |
| EXPECT_TRUE(buffer_queue_->Reshape(screen_size, kBufferQueueColorSpace, |
| RenderPassAlphaType::kPremul, |
| kBufferQueueFormat)); |
| |
| // Swap three buffers. First buffer swap completes so there is one displayed |
| // buffer and two in flight buffers. |
| buffer_queue_->GetCurrentBuffer(); |
| buffer_queue_->SwapBuffers(small_damage); |
| buffer_queue_->GetCurrentBuffer(); |
| buffer_queue_->SwapBuffers(small_damage); |
| buffer_queue_->SwapBuffersComplete(/*did_present=*/true); |
| buffer_queue_->GetCurrentBuffer(); |
| buffer_queue_->SwapBuffers(small_damage); |
| EXPECT_FALSE(buffer_queue_->GetLastSwappedBuffer().IsZero()); |
| |
| // Set the buffers as purgeable before the next swap buffers complete and then |
| // immediately reshape. The reshape will cause buffers to be deleted but not |
| // recreated at the new size until they will be used. |
| buffer_queue_->SetBuffersPurgeable(); |
| EXPECT_TRUE(buffer_queue_->Reshape(gfx::Size(1, 1), kBufferQueueColorSpace, |
| RenderPassAlphaType::kPremul, |
| kBufferQueueFormat)); |
| |
| // Complete the last two swaps. Since the reshape deleted all the buffers |
| // they will not be marked as purgeable. |
| buffer_queue_->SwapBuffersComplete(/*did_present=*/true); |
| buffer_queue_->SwapBuffersComplete(/*did_present=*/true); |
| |
| // Reset callback since it points to stack allocated mock. |
| skia_output_surface_->SetSharedImagePurgeableCallback({}); |
| } |
| |
| } // namespace viz |