// Copyright 2015 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/exo/buffer.h" #include #include "base/barrier_closure.h" #include "base/functional/callback_helpers.h" #include "base/run_loop.h" #include "base/test/bind.h" #include "base/test/scoped_feature_list.h" #include "cc/mojo_embedder/async_layer_tree_frame_sink.h" #include "components/exo/shell_surface.h" #include "components/exo/surface_tree_host.h" #include "components/exo/test/exo_test_base.h" #include "components/exo/test/exo_test_helper.h" #include "components/exo/test/shell_surface_builder.h" #include "components/exo/test/surface_tree_host_test_util.h" #include "components/viz/test/test_in_process_context_provider.h" #include "gpu/command_buffer/client/raster_interface.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/aura/env.h" #include "ui/compositor/compositor.h" #include "ui/gfx/color_space.h" #include "ui/gfx/gpu_fence_handle.h" #include "ui/gfx/gpu_memory_buffer.h" namespace exo { namespace { class BufferTest : public test::ExoTestBase, public testing::WithParamInterface { public: BufferTest() : test::ExoTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) { test::SetFrameSubmissionFeatureFlags(&feature_list_, GetParam()); } private: base::test::ScopedFeatureList feature_list_; }; void VerifySyncTokensInCompositorFrame(viz::CompositorFrame* frame) { std::vector sync_tokens; for (auto& resource : frame->resource_list) sync_tokens.push_back(resource.mailbox_holder.sync_token.GetData()); gpu::raster::RasterInterface* ri = aura::Env::GetInstance() ->context_factory() ->SharedMainThreadRasterContextProvider() ->RasterInterface(); ri->VerifySyncTokensCHROMIUM(sync_tokens.data(), sync_tokens.size()); } viz::CompositorFrame CreateCompositorFrame( SurfaceTreeHost* surface_tree_host, const gfx::Rect& output_rect, const gfx::Rect& damage_rect, std::vector resources) { viz::CompositorFrame frame; frame.metadata.begin_frame_ack.frame_id = viz::BeginFrameId(viz::BeginFrameArgs::kManualSourceId, viz::BeginFrameArgs::kStartingFrameNumber); frame.metadata.begin_frame_ack.has_damage = true; frame.metadata.frame_token = surface_tree_host->GenerateNextFrameToken(); frame.metadata.device_scale_factor = 1; auto pass = viz::CompositorRenderPass::Create(); pass->SetNew(viz::CompositorRenderPassId{1}, output_rect, damage_rect, gfx::Transform()); frame.render_pass_list.push_back(std::move(pass)); frame.resource_list = std::move(resources); if (!frame.resource_list.empty()) { VerifySyncTokensInCompositorFrame(&frame); } return frame; } // Instantiate the values of frame submission types in the parameterized tests. INSTANTIATE_TEST_SUITE_P( All, BufferTest, testing::Values(test::FrameSubmissionType::kNoReactive, test::FrameSubmissionType::kReactive_NoAutoNeedsBeginFrame, test::FrameSubmissionType::kReactive_AutoNeedsBeginFrame)); TEST_P(BufferTest, ReleaseCallback) { gfx::Size buffer_size(256, 256); auto buffer = std::make_unique( exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)); auto surface_tree_host = std::make_unique("BufferTest"); LayerTreeFrameSinkHolder* frame_sink_holder = surface_tree_host->layer_tree_frame_sink_holder(); // Remove wait time for efficiency. buffer->set_wait_for_release_delay_for_testing(base::TimeDelta()); // Set the release callback. int release_call_count = 0; base::RunLoop run_loop_1; buffer->set_release_callback(test::CreateReleaseBufferClosure( &release_call_count, run_loop_1.QuitClosure())); buffer->OnAttach(); viz::TransferableResource resource; // Produce a transferable resource for the contents of the buffer. int release_resource_count = 0; base::RunLoop run_loop_2; bool rv = buffer->ProduceTransferableResource( frame_sink_holder->resource_manager(), nullptr, false, &resource, gfx::ColorSpace::CreateSRGB(), nullptr, test::CreateExplicitReleaseCallback(&release_resource_count, run_loop_2.QuitClosure())); ASSERT_TRUE(rv); // Release buffer. std::vector resources; resources.emplace_back(resource.id, resource.mailbox_holder.sync_token, /*release_fence=*/gfx::GpuFenceHandle(), /*count=*/0, /*lost=*/false); frame_sink_holder->ReclaimResources(std::move(resources)); run_loop_2.Run(); ASSERT_EQ(release_call_count, 0); // The resource should have been released even if the whole buffer hasn't. ASSERT_EQ(release_resource_count, 1); buffer->OnDetach(); run_loop_1.Run(); // Release() should have been called exactly once. ASSERT_EQ(release_call_count, 1); } TEST_P(BufferTest, SolidColorReleaseCallback) { gfx::Size buffer_size(256, 256); auto buffer = std::make_unique(SkColors::kRed, buffer_size); auto surface_tree_host = std::make_unique("BufferTest"); LayerTreeFrameSinkHolder* frame_sink_holder = surface_tree_host->layer_tree_frame_sink_holder(); // Remove wait time for efficiency. buffer->set_wait_for_release_delay_for_testing(base::TimeDelta()); // Set the release callback. int release_call_count = 0; base::RunLoop run_loop; buffer->set_release_callback(test::CreateReleaseBufferClosure( &release_call_count, run_loop.QuitClosure())); buffer->OnAttach(); viz::TransferableResource resource; // Produce a transferable resource for the contents of the buffer. int release_resource_count = 0; bool rv = buffer->ProduceTransferableResource( frame_sink_holder->resource_manager(), nullptr, false, &resource, gfx::ColorSpace::CreateSRGB(), nullptr, test::CreateExplicitReleaseCallback(&release_resource_count, base::DoNothing())); // Solid color buffer is immediately released after commit. EXPECT_EQ(release_resource_count, 1); EXPECT_FALSE(rv); // Release buffer. std::vector resources; resources.emplace_back(resource.id, resource.mailbox_holder.sync_token, /*release_fence=*/gfx::GpuFenceHandle(), /*count=*/0, /*lost=*/false); frame_sink_holder->ReclaimResources(std::move(resources)); // We expect that Release() is not called, no matter whether we have a wait // here or how long the wait is. An arbitrary time period is added here so // that if the event mistakenly happens, it is more likely to find out. task_environment()->FastForwardBy(base::Seconds(1)); EXPECT_EQ(release_call_count, 0); buffer->OnDetach(); run_loop.Run(); // Release() should have been called exactly once. EXPECT_EQ(release_call_count, 1); } TEST_P(BufferTest, IsLost) { gfx::Size buffer_size(256, 256); auto buffer = std::make_unique( exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)); auto surface_tree_host = std::make_unique("BufferTest"); LayerTreeFrameSinkHolder* frame_sink_holder = surface_tree_host->layer_tree_frame_sink_holder(); buffer->OnAttach(); { // Acquire a texture transferable resource for the contents of the buffer. viz::TransferableResource resource; base::RunLoop run_loop_1; bool rv = buffer->ProduceTransferableResource( frame_sink_holder->resource_manager(), nullptr, false, &resource, gfx::ColorSpace::CreateSRGB(), nullptr, test::CreateExplicitReleaseCallback(nullptr, run_loop_1.QuitClosure())); ASSERT_TRUE(rv); scoped_refptr context_provider = aura::Env::GetInstance() ->context_factory() ->SharedMainThreadRasterContextProvider(); if (context_provider) { static_cast(context_provider.get()) ->SendOnContextLost(); } // Release buffer. std::vector resources; resources.emplace_back(resource.id, gpu::SyncToken(), /*release_fence=*/gfx::GpuFenceHandle(), /*count=*/0, /*lost=*/true); frame_sink_holder->ReclaimResources(std::move(resources)); run_loop_1.Run(); } { // Producing a new texture transferable resource for the contents of the // buffer. viz::TransferableResource new_resource; base::RunLoop run_loop_2; bool rv = buffer->ProduceTransferableResource( frame_sink_holder->resource_manager(), nullptr, false, &new_resource, gfx::ColorSpace::CreateSRGB(), nullptr, test::CreateExplicitReleaseCallback(nullptr, run_loop_2.QuitClosure())); ASSERT_TRUE(rv); buffer->OnDetach(); std::vector resources2; resources2.emplace_back(new_resource.id, gpu::SyncToken(), /*release_fence=*/gfx::GpuFenceHandle(), /*count=*/0, /*lost=*/false); frame_sink_holder->ReclaimResources(std::move(resources2)); run_loop_2.Run(); } } // Buffer::Texture::OnLostResources is called when the gpu crashes. This test // verifies that the Texture is collected properly in such event. TEST_P(BufferTest, OnLostResources) { // Create a Buffer and use it to produce a Texture. constexpr gfx::Size buffer_size(256, 256); auto buffer = std::make_unique( exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)); auto surface_tree_host = std::make_unique("BufferTest"); LayerTreeFrameSinkHolder* frame_sink_holder = surface_tree_host->layer_tree_frame_sink_holder(); buffer->OnAttach(); // Acquire a texture transferable resource for the contents of the buffer. viz::TransferableResource resource; bool rv = buffer->ProduceTransferableResource( frame_sink_holder->resource_manager(), nullptr, false, &resource, gfx::ColorSpace::CreateSRGB(), nullptr, base::DoNothing()); ASSERT_TRUE(rv); viz::RasterContextProvider* context_provider = aura::Env::GetInstance() ->context_factory() ->SharedMainThreadRasterContextProvider() .get(); static_cast(context_provider) ->SendOnContextLost(); } TEST_P(BufferTest, SurfaceTreeHostDestruction) { gfx::Size buffer_size(256, 256); // We need to setup shell surface and commit the surface, which properly // registers frame sink hierarchy and attaches begin frame source. Otherwise // OnBeginFrame requests won't be sent. auto shell_surface = test::ShellSurfaceBuilder(buffer_size).BuildShellSurface(); test::WaitForLastFrameAck(shell_surface.get()); LayerTreeFrameSinkHolder* frame_sink_holder = shell_surface->layer_tree_frame_sink_holder(); auto buffer = std::make_unique( exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)); // Remove wait time for efficiency. buffer->set_wait_for_release_delay_for_testing(base::TimeDelta()); int release_call_count = 0; base::RunLoop run_loop; auto combined_quit_closure = BarrierClosure(2, run_loop.QuitClosure()); buffer->set_release_callback(test::CreateReleaseBufferClosure( &release_call_count, combined_quit_closure)); buffer->OnAttach(); viz::TransferableResource resource; // Produce a transferable resource for the contents of the buffer. int release_resource_count = 0; bool rv = buffer->ProduceTransferableResource( frame_sink_holder->resource_manager(), nullptr, false, &resource, gfx::ColorSpace::CreateSRGB(), nullptr, test::CreateExplicitReleaseCallback(&release_resource_count, combined_quit_closure)); ASSERT_TRUE(rv); // Submit frame with resource. shell_surface->SubmitCompositorFrameForTesting( CreateCompositorFrame(shell_surface.get(), gfx::Rect(buffer_size), gfx::Rect(buffer_size), {resource})); test::WaitForLastFrameAck(shell_surface.get()); buffer->OnDetach(); // We expect that the buffer and resource should not be released yet, no // matter whether we have a wait here or how long the wait is. An arbitrary // time period is added here so that if the event mistakenly happens, it is // more likely to find out. task_environment()->FastForwardBy(base::Seconds(1)); ASSERT_EQ(release_call_count, 0); ASSERT_EQ(release_resource_count, 0); shell_surface.reset(); run_loop.Run(); ASSERT_EQ(release_call_count, 1); ASSERT_EQ(release_resource_count, 1); } TEST_P(BufferTest, SurfaceTreeHostLastFrame) { gfx::Size buffer_size(256, 256); // We need to setup shell surface and commit the surface, which properly // registers frame sink hierarchy and attaches begin frame source. Otherwise // OnBeginFrame requests won't be sent. auto shell_surface = test::ShellSurfaceBuilder(buffer_size).BuildShellSurface(); test::WaitForLastFrameAck(shell_surface.get()); LayerTreeFrameSinkHolder* frame_sink_holder = shell_surface->layer_tree_frame_sink_holder(); auto buffer = std::make_unique( exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)); // Remove wait time for efficiency. buffer->set_wait_for_release_delay_for_testing(base::TimeDelta()); int release_call_count = 0; base::RunLoop run_loop; auto combined_quit_closure = BarrierClosure(2, run_loop.QuitClosure()); buffer->set_release_callback(test::CreateReleaseBufferClosure( &release_call_count, combined_quit_closure)); buffer->OnAttach(); viz::TransferableResource resource; // Produce a transferable resource for the contents of the buffer. int release_resource_count = 0; bool rv = buffer->ProduceTransferableResource( frame_sink_holder->resource_manager(), nullptr, false, &resource, gfx::ColorSpace::CreateSRGB(), nullptr, test::CreateExplicitReleaseCallback(&release_resource_count, combined_quit_closure)); ASSERT_TRUE(rv); // Submit frame with resource. { shell_surface->SubmitCompositorFrameForTesting( CreateCompositorFrame(shell_surface.get(), gfx::Rect(buffer_size), gfx::Rect(buffer_size), {resource})); test::WaitForLastFrameAck(shell_surface.get()); // Try to release buffer in last frame. This can happen during a resize // when frame sink id changes. std::vector resources; resources.emplace_back(resource.id, resource.mailbox_holder.sync_token, /*release_fence=*/gfx::GpuFenceHandle(), /*count=*/0, /*lost=*/false); frame_sink_holder->ReclaimResources(std::move(resources)); } buffer->OnDetach(); // We expect that the buffer and resource should not be released yet, no // matter whether we have a wait here or how long the wait is. An arbitrary // time period is added here so that if the event mistakenly happens, it is // more likely to find out. task_environment()->FastForwardBy(base::Seconds(1)); // Release() should not have been called as resource is used by last frame. ASSERT_EQ(release_call_count, 0); ASSERT_EQ(release_resource_count, 0); // Submit frame without resource. This should cause buffer to be released. shell_surface->SubmitCompositorFrameForTesting(CreateCompositorFrame( shell_surface.get(), gfx::Rect(buffer_size), gfx::Rect(buffer_size), {})); run_loop.Run(); // Release() should have been called exactly once. ASSERT_EQ(release_call_count, 1); ASSERT_EQ(release_resource_count, 1); } // Tests that only apply if ExoReactiveFrameSubmission is enabled. class ReactiveFrameSubmissionBufferTest : public test::ExoTestBase, public testing::WithParamInterface { public: ReactiveFrameSubmissionBufferTest() : test::ExoTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) { test::SetFrameSubmissionFeatureFlags(&feature_list_, GetParam()); } private: base::test::ScopedFeatureList feature_list_; }; class TestLayerTreeFrameSinkHolder : public LayerTreeFrameSinkHolder { public: TestLayerTreeFrameSinkHolder( SurfaceTreeHost* surface_tree_host, std::unique_ptr frame_sink) : LayerTreeFrameSinkHolder(surface_tree_host, std::move(frame_sink)) {} ~TestLayerTreeFrameSinkHolder() override = default; using PreReclaimCallback = base::RepeatingCallback&)>; void set_pre_reclaim_callback(PreReclaimCallback callback) { pre_reclaim_callback_ = std::move(callback); } void set_post_reclaim_callback(base::RepeatingClosure callback) { post_reclaim_callback_ = std::move(callback); } void ReclaimResources(std::vector resources) override { if (pre_reclaim_callback_) { pre_reclaim_callback_.Run(resources); } LayerTreeFrameSinkHolder::ReclaimResources(std::move(resources)); if (post_reclaim_callback_) { post_reclaim_callback_.Run(); } } private: PreReclaimCallback pre_reclaim_callback_; base::RepeatingClosure post_reclaim_callback_; }; // Instantiate the values of frame submission types in the parameterized tests. INSTANTIATE_TEST_SUITE_P( All, ReactiveFrameSubmissionBufferTest, testing::Values(test::FrameSubmissionType::kReactive_NoAutoNeedsBeginFrame, test::FrameSubmissionType::kReactive_AutoNeedsBeginFrame)); TEST_P(ReactiveFrameSubmissionBufferTest, SurfaceTreeHostNotReclaimCachedFrameResources) { gfx::Size buffer_size(256, 256); auto shell_surface = test::ShellSurfaceBuilder(buffer_size).SetNoCommit().BuildShellSurface(); test::SetLayerTreeFrameSinkHolderFactory( shell_surface.get()); shell_surface->root_surface()->Commit(); test::WaitForLastFrameAck(shell_surface.get()); TestLayerTreeFrameSinkHolder* frame_sink_holder = static_cast( shell_surface->layer_tree_frame_sink_holder()); auto buffer = std::make_unique( exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)); // Remove wait time for efficiency. buffer->set_wait_for_release_delay_for_testing(base::TimeDelta()); int release_call_count = 0; base::RunLoop run_loop1; auto combined_quit_closure = BarrierClosure(2, run_loop1.QuitClosure()); buffer->set_release_callback(test::CreateReleaseBufferClosure( &release_call_count, combined_quit_closure)); buffer->OnAttach(); viz::TransferableResource resource; // Produce a transferable resource for the contents of the buffer. int release_resource_count = 0; bool rv = buffer->ProduceTransferableResource( frame_sink_holder->resource_manager(), nullptr, false, &resource, gfx::ColorSpace::CreateSRGB(), nullptr, test::CreateExplicitReleaseCallback(&release_resource_count, combined_quit_closure)); ASSERT_TRUE(rv); // Submit frame with `resource`. shell_surface->SubmitCompositorFrameForTesting( CreateCompositorFrame(shell_surface.get(), gfx::Rect(buffer_size), gfx::Rect(buffer_size), {resource})); test::WaitForLastFrameAck(shell_surface.get()); base::RunLoop run_loop2; // Set a callback that will be called when the remote side notify // ReclaimResources for `resource`. frame_sink_holder->set_pre_reclaim_callback(base::BindLambdaForTesting( [&](const std::vector& resources) { // Skip if it is not a notification for reclaiming `resource`. if (!base::Contains( resources, resource.id, [](const viz::ReturnedResource& r) { return r.id; })) { return; } run_loop2.Quit(); // Make sure that this callback is only used once. frame_sink_holder->set_pre_reclaim_callback({}); frame_sink_holder->ClearPendingBeginFramesForTesting(); // Cause a frame with `resource` is cached. This should hold off // reclaming `resource`. shell_surface->SubmitCompositorFrameForTesting( CreateCompositorFrame(shell_surface.get(), gfx::Rect(buffer_size), gfx::Rect(buffer_size), {resource})); })); // Submit a new frame without resource to cause the remote side to stop using // `resource` and notify ReclaimResources. The callback set by // set_pre_reclaim_callback() above should be run as a result. shell_surface->SubmitCompositorFrameForTesting(CreateCompositorFrame( shell_surface.get(), gfx::Rect(buffer_size), gfx::Rect(buffer_size), {})); run_loop2.Run(); // Ensure the cached frame is submitted. test::WaitForLastFrameAck(shell_surface.get()); // We expect that Release() is not called, no matter whether we have a wait // here or how long the wait is. An arbitrary time period is added here so // that if the event mistakenly happens, it is more likely to find out. task_environment()->FastForwardBy(base::Seconds(1)); // Release() should not have been called. ASSERT_EQ(release_call_count, 0); ASSERT_EQ(release_resource_count, 0); buffer->OnDetach(); // Submit a new frame without resource. This will cause buffer to be released. shell_surface->SubmitCompositorFrameForTesting(CreateCompositorFrame( shell_surface.get(), gfx::Rect(buffer_size), gfx::Rect(buffer_size), {})); run_loop1.Run(); // Release() should have been called exactly once. ASSERT_EQ(release_call_count, 1); ASSERT_EQ(release_resource_count, 1); } TEST_P(ReactiveFrameSubmissionBufferTest, SurfaceTreeHostDiscardFrameNotReclaimNewFrameResources) { gfx::Size buffer_size(256, 256); auto shell_surface = test::ShellSurfaceBuilder(buffer_size).BuildShellSurface(); test::WaitForLastFrameAck(shell_surface.get()); LayerTreeFrameSinkHolder* frame_sink_holder = shell_surface->layer_tree_frame_sink_holder(); auto buffer = std::make_unique( exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)); // Remove wait time for efficiency. buffer->set_wait_for_release_delay_for_testing(base::TimeDelta()); int release_call_count = 0; base::RunLoop run_loop; auto combined_quit_closure = BarrierClosure(2, run_loop.QuitClosure()); buffer->set_release_callback(test::CreateReleaseBufferClosure( &release_call_count, combined_quit_closure)); buffer->OnAttach(); viz::TransferableResource resource; // Produce a transferable resource for the contents of the buffer. int release_resource_count = 0; bool rv = buffer->ProduceTransferableResource( frame_sink_holder->resource_manager(), nullptr, false, &resource, gfx::ColorSpace::CreateSRGB(), nullptr, test::CreateExplicitReleaseCallback(&release_resource_count, combined_quit_closure)); ASSERT_TRUE(rv); frame_sink_holder->ClearPendingBeginFramesForTesting(); // Submit a frame with `resource`, which will be cached. shell_surface->SubmitCompositorFrameForTesting( CreateCompositorFrame(shell_surface.get(), gfx::Rect(buffer_size), gfx::Rect(buffer_size), {resource})); // Submit another frame with `resource`. It will cause the previously cached // frame to be evicted. shell_surface->SubmitCompositorFrameForTesting( CreateCompositorFrame(shell_surface.get(), gfx::Rect(buffer_size), gfx::Rect(buffer_size), {resource})); buffer->OnDetach(); // Ensure the cached frame is submitted. test::WaitForLastFrameAck(shell_surface.get()); // We expect that Release() is not called, no matter whether we have a wait // here or how long the wait is. An arbitrary time period is added here so // that if the event mistakenly happens, it is more likely to find out. task_environment()->FastForwardBy(base::Seconds(1)); // Release() should not have been called. ASSERT_EQ(release_call_count, 0); ASSERT_EQ(release_resource_count, 0); // Submit another frame without resource. shell_surface->SubmitCompositorFrameForTesting(CreateCompositorFrame( shell_surface.get(), gfx::Rect(buffer_size), gfx::Rect(buffer_size), {})); test::WaitForLastFrameAck(shell_surface.get()); run_loop.Run(); // Release() should have been called exactly once. ASSERT_EQ(release_call_count, 1); ASSERT_EQ(release_resource_count, 1); } TEST_P(ReactiveFrameSubmissionBufferTest, SurfaceTreeHostDiscardFrameNotReclaimInUseResources) { gfx::Size buffer_size(256, 256); auto shell_surface = test::ShellSurfaceBuilder(buffer_size).SetNoCommit().BuildShellSurface(); test::SetLayerTreeFrameSinkHolderFactory( shell_surface.get()); shell_surface->root_surface()->Commit(); test::WaitForLastFrameAck(shell_surface.get()); TestLayerTreeFrameSinkHolder* frame_sink_holder = static_cast( shell_surface->layer_tree_frame_sink_holder()); auto buffer = std::make_unique( exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)); // Remove wait time for efficiency. buffer->set_wait_for_release_delay_for_testing(base::TimeDelta()); int release_call_count = 0; base::RunLoop run_loop1; auto combined_quit_closure = BarrierClosure(2, run_loop1.QuitClosure()); buffer->set_release_callback(test::CreateReleaseBufferClosure( &release_call_count, combined_quit_closure)); buffer->OnAttach(); viz::TransferableResource resource; // Produce a transferable resource for the contents of the buffer. int release_resource_count = 0; bool rv = buffer->ProduceTransferableResource( frame_sink_holder->resource_manager(), nullptr, false, &resource, gfx::ColorSpace::CreateSRGB(), nullptr, test::CreateExplicitReleaseCallback(&release_resource_count, combined_quit_closure)); ASSERT_TRUE(rv); // Submit frame with `resource`. shell_surface->SubmitCompositorFrameForTesting( CreateCompositorFrame(shell_surface.get(), gfx::Rect(buffer_size), gfx::Rect(buffer_size), {resource})); test::WaitForLastFrameAck(shell_surface.get()); base::RunLoop run_loop2; // Set a callback that will be called when the remote side notify // ReclaimResources for `resource`. frame_sink_holder->set_pre_reclaim_callback(base::BindLambdaForTesting( [&](const std::vector& resources) { // Skip if it is not a notification for reclaiming `resource`. if (!base::Contains( resources, resource.id, [](const viz::ReturnedResource& r) { return r.id; })) { return; } run_loop2.Quit(); // Make sure that this callback is only used once. frame_sink_holder->set_pre_reclaim_callback({}); // The evicted cached frame with `resource` shouldn't have caused // Release() to be called. ASSERT_EQ(release_call_count, 0); ASSERT_EQ(release_resource_count, 0); })); frame_sink_holder->ClearPendingBeginFramesForTesting(); // Cause a frame with `resource` is cached. shell_surface->SubmitCompositorFrameForTesting( CreateCompositorFrame(shell_surface.get(), gfx::Rect(buffer_size), gfx::Rect(buffer_size), {resource})); buffer->OnDetach(); // Submit a new frame without resource to evict the previously cached frame. // It shouldn't cause `resource` to be reclaimed because it is still in use at // the remote side. shell_surface->SubmitCompositorFrameForTesting(CreateCompositorFrame( shell_surface.get(), gfx::Rect(buffer_size), gfx::Rect(buffer_size), {})); // Wait for the remote site to notify ReclaimResources for `resource`. run_loop2.Run(); run_loop1.Run(); // Release() should have been called exactly once. ASSERT_EQ(release_call_count, 1); ASSERT_EQ(release_resource_count, 1); } } // namespace } // namespace exo