// Copyright 2015 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/exo/buffer.h" #include #include "ash/shell.h" #include "base/bind.h" #include "base/run_loop.h" #include "components/exo/frame_sink_resource_manager.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/viz/common/gpu/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/compositor/test/in_process_context_provider.h" #include "ui/gfx/gpu_fence_handle.h" #include "ui/gfx/gpu_memory_buffer.h" namespace exo { namespace { using BufferTest = test::ExoTestBase; void Release(int* release_call_count) { (*release_call_count)++; } void ExplicitRelease(int* release_call_count, gfx::GpuFenceHandle release_fence) { (*release_call_count)++; } 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()); } TEST_F(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(); // This is needed to ensure that base::RunLoop().RunUntilIdle() call below // is always sufficient for buffer to be released. buffer->set_wait_for_release_delay_for_testing(base::TimeDelta()); // Set the release callback. int release_call_count = 0; buffer->set_release_callback( base::BindRepeating(&Release, base::Unretained(&release_call_count))); 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, nullptr, base::BindOnce(&ExplicitRelease, base::Unretained(&release_resource_count))); 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)); base::RunLoop().RunUntilIdle(); 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(); // Release() should have been called exactly once. ASSERT_EQ(release_call_count, 1); } TEST_F(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(); // This is needed to ensure that base::RunLoop().RunUntilIdle() call below // is always sufficient for buffer to be released. buffer->set_wait_for_release_delay_for_testing(base::TimeDelta()); // Set the release callback. int release_call_count = 0; buffer->set_release_callback( base::BindRepeating(&Release, base::Unretained(&release_call_count))); 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, nullptr, base::BindOnce(&ExplicitRelease, base::Unretained(&release_resource_count))); // 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)); base::RunLoop().RunUntilIdle(); EXPECT_EQ(release_call_count, 0); buffer->OnDetach(); // Release() should have been called exactly once. EXPECT_EQ(release_call_count, 1); } TEST_F(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; bool rv = buffer->ProduceTransferableResource( frame_sink_holder->resource_manager(), nullptr, false, &resource, nullptr, base::DoNothing()); ASSERT_TRUE(rv); scoped_refptr context_provider = aura::Env::GetInstance() ->context_factory() ->SharedMainThreadRasterContextProvider(); if (context_provider) { gpu::raster::RasterInterface* ri = context_provider->RasterInterface(); ri->LoseContextCHROMIUM(GL_GUILTY_CONTEXT_RESET_ARB, GL_INNOCENT_CONTEXT_RESET_ARB); } // 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)); base::RunLoop().RunUntilIdle(); // Producing a new texture transferable resource for the contents of the // buffer. viz::TransferableResource new_resource; rv = buffer->ProduceTransferableResource( frame_sink_holder->resource_manager(), nullptr, false, &new_resource, nullptr, base::DoNothing()); 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)); base::RunLoop().RunUntilIdle(); } // Buffer::Texture::OnLostResources is called when the gpu crashes. This test // verifies that the Texture is collected properly in such event. TEST_F(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, nullptr, base::DoNothing()); ASSERT_TRUE(rv); viz::RasterContextProvider* context_provider = aura::Env::GetInstance() ->context_factory() ->SharedMainThreadRasterContextProvider() .get(); static_cast(context_provider) ->SendOnContextLost(); } TEST_F(BufferTest, SurfaceTreeHostDestruction) { 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(); // This is needed to ensure that base::RunLoop().RunUntilIdle() call below // is always sufficient for buffer to be released. buffer->set_wait_for_release_delay_for_testing(base::TimeDelta()); // Set the release callback. int release_call_count = 0; buffer->set_release_callback( base::BindRepeating(&Release, base::Unretained(&release_call_count))); 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, nullptr, base::BindOnce(&ExplicitRelease, base::Unretained(&release_resource_count))); ASSERT_TRUE(rv); // Submit frame with resource. { viz::CompositorFrame frame; frame.metadata.begin_frame_ack.frame_id.source_id = viz::BeginFrameArgs::kManualSourceId; frame.metadata.begin_frame_ack.frame_id.sequence_number = viz::BeginFrameArgs::kStartingFrameNumber; frame.metadata.begin_frame_ack.has_damage = true; frame.metadata.frame_token = 1; frame.metadata.device_scale_factor = 1; auto pass = viz::CompositorRenderPass::Create(); pass->SetNew(viz::CompositorRenderPassId{1}, gfx::Rect(buffer_size), gfx::Rect(buffer_size), gfx::Transform()); frame.render_pass_list.push_back(std::move(pass)); frame.resource_list.push_back(resource); VerifySyncTokensInCompositorFrame(&frame); frame_sink_holder->SubmitCompositorFrame(std::move(frame)); } buffer->OnDetach(); base::RunLoop().RunUntilIdle(); ASSERT_EQ(release_call_count, 0); ASSERT_EQ(release_resource_count, 0); surface_tree_host.reset(); base::RunLoop().RunUntilIdle(); ASSERT_EQ(release_call_count, 1); ASSERT_EQ(release_resource_count, 1); } TEST_F(BufferTest, SurfaceTreeHostLastFrame) { 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(); // This is needed to ensure that base::RunLoop().RunUntilIdle() call below // is always sufficient for buffer to be released. buffer->set_wait_for_release_delay_for_testing(base::TimeDelta()); // Set the release callback. int release_call_count = 0; buffer->set_release_callback( base::BindRepeating(&Release, base::Unretained(&release_call_count))); 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, nullptr, base::BindOnce(&ExplicitRelease, base::Unretained(&release_resource_count))); ASSERT_TRUE(rv); // Submit frame with resource. { 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 = 1; frame.metadata.device_scale_factor = 1; auto pass = viz::CompositorRenderPass::Create(); pass->SetNew(viz::CompositorRenderPassId{1}, gfx::Rect(buffer_size), gfx::Rect(buffer_size), gfx::Transform()); frame.render_pass_list.push_back(std::move(pass)); frame.resource_list.push_back(resource); VerifySyncTokensInCompositorFrame(&frame); frame_sink_holder->SubmitCompositorFrame(std::move(frame)); // 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)); } base::RunLoop().RunUntilIdle(); buffer->OnDetach(); // 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. { 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 = 1; frame.metadata.device_scale_factor = 1; auto pass = viz::CompositorRenderPass::Create(); pass->SetNew(viz::CompositorRenderPassId{1}, gfx::Rect(buffer_size), gfx::Rect(buffer_size), gfx::Transform()); frame.render_pass_list.push_back(std::move(pass)); frame_sink_holder->SubmitCompositorFrame(std::move(frame)); } base::RunLoop().RunUntilIdle(); // Release() should have been called exactly once. ASSERT_EQ(release_call_count, 1); ASSERT_EQ(release_resource_count, 1); } } // namespace } // namespace exo