| // Copyright 2024 The Chromium Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "gpu/command_buffer/client/test_shared_image_interface.h" | |
| #include <GLES2/gl2.h> | |
| #include <GLES2/gl2extchromium.h> | |
| #include <utility> | |
| #if BUILDFLAG(IS_FUCHSIA) | |
| #include <fuchsia/sysmem2/cpp/fidl.h> | |
| #include <lib/sys/cpp/component_context.h> | |
| #endif | |
| #include "base/check.h" | |
| #include "base/notreached.h" | |
| #include "base/numerics/checked_math.h" | |
| #include "base/numerics/safe_conversions.h" | |
| #include "base/task/single_thread_task_runner.h" | |
| #include "build/build_config.h" | |
| #include "components/viz/common/resources/shared_image_format_utils.h" | |
| #include "gpu/command_buffer/client/client_shared_image.h" | |
| #include "gpu/command_buffer/common/shared_image_capabilities.h" | |
| #include "gpu/ipc/client/gpu_channel_observer.h" | |
| #include "gpu/ipc/common/command_buffer_id.h" | |
| #include "testing/gtest/include/gtest/gtest.h" | |
| #include "ui/gfx/gpu_fence.h" | |
| #include "ui/gfx/gpu_memory_buffer_handle.h" | |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) | |
| #include <linux/memfd.h> | |
| #include <sys/mman.h> | |
| #include <unistd.h> | |
| #endif | |
| #if BUILDFLAG(IS_FUCHSIA) | |
| #include "base/fuchsia/fuchsia_logging.h" | |
| #include "base/fuchsia/koid.h" | |
| #include "base/fuchsia/process_context.h" | |
| #endif | |
| namespace gpu { | |
| namespace { | |
| gfx::GpuMemoryBufferType GetNativeBufferType() { | |
| #if BUILDFLAG(IS_APPLE) | |
| return gfx::IO_SURFACE_BUFFER; | |
| #elif BUILDFLAG(IS_ANDROID) | |
| return gfx::ANDROID_HARDWARE_BUFFER; | |
| #elif BUILDFLAG(IS_WIN) | |
| return gfx::DXGI_SHARED_HANDLE; | |
| #else | |
| // Ozone | |
| return gfx::NATIVE_PIXMAP; | |
| #endif | |
| } | |
| } // namespace | |
| #if BUILDFLAG(IS_FUCHSIA) | |
| class TestBufferCollection { | |
| public: | |
| TestBufferCollection(zx::eventpair handle, zx::channel collection_token) | |
| : handle_(std::move(handle)) { | |
| sysmem_allocator_ = base::ComponentContextForProcess() | |
| ->svc() | |
| ->Connect<fuchsia::sysmem2::Allocator>(); | |
| sysmem_allocator_.set_error_handler([](zx_status_t status) { | |
| ZX_LOG(FATAL, status) | |
| << "The fuchsia.sysmem.Allocator channel was terminated."; | |
| }); | |
| fuchsia::sysmem2::AllocatorSetDebugClientInfoRequest set_debug_request; | |
| set_debug_request.set_name("CrTestBufferCollection"); | |
| set_debug_request.set_id(base::GetCurrentProcId()); | |
| sysmem_allocator_->SetDebugClientInfo(std::move(set_debug_request)); | |
| fuchsia::sysmem2::AllocatorBindSharedCollectionRequest bind_shared_request; | |
| bind_shared_request.set_token(fidl::InterfaceHandle<fuchsia::sysmem2::BufferCollectionToken>( | |
| std::move(collection_token))); | |
| bind_shared_request.set_buffer_collection_request(buffers_collection_.NewRequest()); | |
| sysmem_allocator_->BindSharedCollection(std::move(bind_shared_request)); | |
| fuchsia::sysmem2::BufferCollectionSetConstraintsRequest set_constraints_request; | |
| auto& buffer_constraints = *set_constraints_request.mutable_constraints(); | |
| buffer_constraints.mutable_usage()->set_cpu(fuchsia::sysmem2::CPU_USAGE_READ); | |
| zx_status_t status = buffers_collection_->SetConstraints(std::move(set_constraints_request)); | |
| ZX_CHECK(status == ZX_OK, status) << "BufferCollection::SetConstraints()"; | |
| } | |
| TestBufferCollection(const TestBufferCollection&) = delete; | |
| TestBufferCollection& operator=(const TestBufferCollection&) = delete; | |
| ~TestBufferCollection() { buffers_collection_->Release(); } | |
| size_t GetNumBuffers() { | |
| if (!buffer_collection_info_) { | |
| fuchsia::sysmem2::BufferCollection_WaitForAllBuffersAllocated_Result wait_result; | |
| zx_status_t status = | |
| buffers_collection_->WaitForAllBuffersAllocated(&wait_result); | |
| if (status != ZX_OK) { | |
| ZX_LOG(FATAL, status) << | |
| "BufferCollection::WaitForAllBuffersAllocated() (status)"; | |
| } else if (wait_result.is_framework_err()) { | |
| LOG(FATAL) << | |
| "BufferCollection::WaitForAllBuffersAllocated (framework_err): " << | |
| fidl::ToUnderlying(wait_result.framework_err()); | |
| } else if (!wait_result.is_response()) { | |
| LOG(FATAL) << "BufferCollection::WaitForAllBuffersAllocated (err)" << | |
| static_cast<uint32_t>(wait_result.err()); | |
| } | |
| auto info = std::move(*wait_result.response().mutable_buffer_collection_info()); | |
| buffer_collection_info_ = std::move(info); | |
| } | |
| return buffer_collection_info_->buffers().size(); | |
| } | |
| private: | |
| zx::eventpair handle_; | |
| fuchsia::sysmem2::AllocatorPtr sysmem_allocator_; | |
| fuchsia::sysmem2::BufferCollectionSyncPtr buffers_collection_; | |
| std::optional<fuchsia::sysmem2::BufferCollectionInfo> | |
| buffer_collection_info_; | |
| }; | |
| #endif | |
| TestSharedImageInterface::TestSharedImageInterface() = default; | |
| TestSharedImageInterface::~TestSharedImageInterface() = default; | |
| // static | |
| gfx::GpuMemoryBufferHandle TestSharedImageInterface::CreateGMBHandle( | |
| const viz::SharedImageFormat& format, | |
| const gfx::Size& size) { | |
| size_t buffer_size = | |
| viz::SharedMemorySizeForSharedImageFormat(format, size).value(); | |
| CHECK(buffer_size); | |
| auto shared_memory_region = | |
| base::UnsafeSharedMemoryRegion::Create(buffer_size); | |
| CHECK(shared_memory_region.IsValid()); | |
| gfx::GpuMemoryBufferHandle handle(std::move(shared_memory_region)); | |
| handle.offset = 0; | |
| handle.stride = | |
| static_cast<uint32_t>(viz::SharedMemoryRowSizeForSharedImageFormat( | |
| format, /*plane*/ 0, size.width()) | |
| .value()); | |
| return handle; | |
| } | |
| scoped_refptr<ClientSharedImage> TestSharedImageInterface::CreateSharedImage( | |
| const SharedImageInfo& si_info, | |
| SurfaceHandle surface_handle, | |
| std::optional<SharedImagePoolId> pool_id) { | |
| SyncToken sync_token = GenUnverifiedSyncToken(); | |
| base::AutoLock locked(lock_); | |
| auto mailbox = Mailbox::Generate(); | |
| shared_images_.insert(mailbox); | |
| most_recent_size_ = si_info.size; | |
| auto gmb_handle_type = emulate_client_provided_native_buffer_ | |
| ? GetNativeBufferType() | |
| : gfx::EMPTY_BUFFER; | |
| return base::MakeRefCounted<ClientSharedImage>(mailbox, si_info, sync_token, | |
| holder_, gmb_handle_type); | |
| } | |
| scoped_refptr<ClientSharedImage> | |
| TestSharedImageInterface::CreateSharedImage( | |
| const SharedImageInfo& si_info, | |
| base::span<const uint8_t> pixel_data) { | |
| SyncToken sync_token = GenUnverifiedSyncToken(); | |
| base::AutoLock locked(lock_); | |
| auto mailbox = Mailbox::Generate(); | |
| shared_images_.insert(mailbox); | |
| return base::MakeRefCounted<ClientSharedImage>(mailbox, si_info, sync_token, | |
| holder_, gfx::EMPTY_BUFFER); | |
| } | |
| scoped_refptr<ClientSharedImage> TestSharedImageInterface::CreateSharedImage( | |
| const SharedImageInfo& si_info, | |
| SurfaceHandle surface_handle, | |
| gfx::BufferUsage buffer_usage, | |
| std::optional<SharedImagePoolId> pool_id) { | |
| DoCreateSharedImage(si_info.size, si_info.format, surface_handle, | |
| buffer_usage); | |
| if (fail_shared_image_creation_with_buffer_usage_) { | |
| return nullptr; | |
| } | |
| SyncToken sync_token = GenUnverifiedSyncToken(); | |
| // Create a ClientSharedImage with a GMB. | |
| base::AutoLock locked(lock_); | |
| auto mailbox = Mailbox::Generate(); | |
| shared_images_.insert(mailbox); | |
| most_recent_size_ = si_info.size; | |
| // Copy which can be modified. | |
| SharedImageInfo si_info_copy = si_info; | |
| // Set CPU read/write usage based on buffer usage. | |
| si_info_copy.usage |= GetCpuSIUsage(buffer_usage); | |
| // Since the GMB handle that we create here is always shared memory, clear | |
| // the external sampler prefs to avoid a CHECK within ClientSI that external | |
| // sampling is set only with a native GMB handle. | |
| if (si_info_copy.format.PrefersExternalSampler()) { | |
| si_info_copy.format.ClearPrefersExternalSampler(); | |
| if (si_info_copy.alpha_type == kPremul_SkAlphaType) { | |
| si_info_copy.alpha_type = kUnpremul_SkAlphaType; | |
| } | |
| } | |
| auto gmb_handle = CreateGMBHandle(si_info.format, si_info_copy.size); | |
| auto client_si = base::MakeRefCounted<ClientSharedImage>( | |
| mailbox, si_info_copy, sync_token, | |
| GpuMemoryBufferHandleInfo(std::move(gmb_handle), buffer_usage), holder_); | |
| most_recent_mappable_shared_image_ = client_si.get(); | |
| return client_si; | |
| } | |
| scoped_refptr<ClientSharedImage> | |
| TestSharedImageInterface::CreateSharedImage( | |
| const SharedImageInfo& si_info, | |
| SurfaceHandle surface_handle, | |
| gfx::BufferUsage buffer_usage, | |
| gfx::GpuMemoryBufferHandle buffer_handle) { | |
| SyncToken sync_token = GenUnverifiedSyncToken(); | |
| base::AutoLock locked(lock_); | |
| auto mailbox = Mailbox::Generate(); | |
| shared_images_.insert(mailbox); | |
| most_recent_size_ = si_info.size; | |
| // Copy which can be modified. | |
| SharedImageInfo si_info_copy = si_info; | |
| // Set CPU read/write usage based on buffer usage. | |
| si_info_copy.usage |= GetCpuSIUsage(buffer_usage); | |
| // If the GMB handle passed here is shared memory (e.g. because it was created | |
| // by a unittest that is simulating a production flow with native handles), | |
| // clear the external sampler prefs to avoid a CHECK within ClientSI that | |
| // external sampling is set only with a native GMB handle. | |
| if (buffer_handle.type == gfx::SHARED_MEMORY_BUFFER && | |
| si_info_copy.format.PrefersExternalSampler()) { | |
| si_info_copy.format.ClearPrefersExternalSampler(); | |
| if (si_info_copy.alpha_type == kPremul_SkAlphaType) { | |
| si_info_copy.alpha_type = kUnpremul_SkAlphaType; | |
| } | |
| } | |
| return base::MakeRefCounted<ClientSharedImage>( | |
| mailbox, si_info_copy, sync_token, | |
| GpuMemoryBufferHandleInfo(std::move(buffer_handle), buffer_usage), | |
| holder_); | |
| } | |
| scoped_refptr<ClientSharedImage> | |
| TestSharedImageInterface::CreateSharedImage( | |
| const SharedImageInfo& si_info, | |
| gfx::GpuMemoryBufferHandle buffer_handle) { | |
| SyncToken sync_token = GenUnverifiedSyncToken(); | |
| base::AutoLock locked(lock_); | |
| #if BUILDFLAG(IS_FUCHSIA) | |
| if (buffer_handle.type == gfx::GpuMemoryBufferType::NATIVE_PIXMAP) { | |
| zx_koid_t id = | |
| base::GetRelatedKoid( | |
| buffer_handle.native_pixmap_handle().buffer_collection_handle) | |
| .value(); | |
| auto collection_it = sysmem_buffer_collections_.find(id); | |
| // NOTE: Not all unittests invoke RegisterSysmemBufferCollection(), but | |
| // the below CHECK should hold for those that do. | |
| if (collection_it != sysmem_buffer_collections_.end()) { | |
| CHECK_LT(buffer_handle.native_pixmap_handle().buffer_index, | |
| collection_it->second->GetNumBuffers()); | |
| } | |
| } | |
| #endif | |
| auto mailbox = Mailbox::Generate(); | |
| shared_images_.insert(mailbox); | |
| most_recent_size_ = si_info.size; | |
| return base::MakeRefCounted<ClientSharedImage>(mailbox, si_info, sync_token, | |
| holder_, buffer_handle.type); | |
| } | |
| scoped_refptr<ClientSharedImage> | |
| TestSharedImageInterface::CreateSharedImageForSoftwareCompositor( | |
| const SharedImageInfo& si_info) { | |
| base::WritableSharedMemoryMapping mapping; | |
| gfx::GpuMemoryBufferHandle handle; | |
| CreateSharedMemoryRegionFromSIInfo(si_info, mapping, handle); | |
| auto mailbox = Mailbox::Generate(); | |
| shared_images_.insert(mailbox); | |
| most_recent_size_ = si_info.size; | |
| return base::MakeRefCounted<ClientSharedImage>( | |
| mailbox, si_info, GenUnverifiedSyncToken(), holder_, std::move(mapping)); | |
| } | |
| scoped_refptr<ClientSharedImage> | |
| TestSharedImageInterface::CreateSharedImageForMLTensor( | |
| std::string debug_label, | |
| viz::SharedImageFormat format, | |
| const gfx::Size& size, | |
| gpu::SharedImageUsageSet usage) { | |
| NOTREACHED(); | |
| } | |
| void TestSharedImageInterface::UpdateSharedImage( | |
| const SyncToken& sync_token, | |
| const Mailbox& mailbox) { | |
| base::AutoLock locked(lock_); | |
| DCHECK(shared_images_.find(mailbox) != shared_images_.end()); | |
| num_update_shared_image_no_fence_calls_++; | |
| } | |
| void TestSharedImageInterface::UpdateSharedImage( | |
| const SyncToken& sync_token, | |
| std::unique_ptr<gfx::GpuFence> acquire_fence, | |
| const Mailbox& mailbox) { | |
| base::AutoLock locked(lock_); | |
| DCHECK(shared_images_.find(mailbox) != shared_images_.end()); | |
| } | |
| scoped_refptr<ClientSharedImage> TestSharedImageInterface::ImportSharedImage( | |
| ExportedSharedImage exported_shared_image) { | |
| shared_images_.insert(exported_shared_image.mailbox_); | |
| // No pool: this interface has no readback implementation to back one, so an | |
| // imported image here serves premapped mappings only. | |
| return base::WrapRefCounted<ClientSharedImage>( | |
| new ClientSharedImage(std::move(exported_shared_image), holder_, | |
| /*shared_memory_pool=*/nullptr)); | |
| } | |
| void TestSharedImageInterface::DestroySharedImage( | |
| std::vector<SyncToken> sync_tokens, | |
| const Mailbox& mailbox) { | |
| base::AutoLock locked(lock_); | |
| if (most_recent_mappable_shared_image_ && | |
| mailbox == most_recent_mappable_shared_image_->mailbox()) { | |
| most_recent_mappable_shared_image_ = nullptr; | |
| } | |
| shared_images_.erase(mailbox); | |
| most_recent_destroy_tokens_ = std::move(sync_tokens); | |
| if (test_client_) { | |
| test_client_->DidDestroySharedImage(); | |
| } | |
| } | |
| #if BUILDFLAG(IS_FUCHSIA) | |
| void TestSharedImageInterface::RegisterSysmemBufferCollection( | |
| zx::eventpair service_handle, | |
| zx::channel sysmem_token, | |
| const viz::SharedImageFormat& format, | |
| gfx::BufferUsage usage, | |
| bool register_with_image_pipe) { | |
| EXPECT_EQ(format, viz::MultiPlaneFormat::kNV12); | |
| EXPECT_EQ(usage, gfx::BufferUsage::GPU_READ); | |
| zx_koid_t id = base::GetKoid(service_handle).value(); | |
| std::unique_ptr<TestBufferCollection>& collection = | |
| sysmem_buffer_collections_[id]; | |
| EXPECT_FALSE(collection); | |
| collection = std::make_unique<TestBufferCollection>(std::move(service_handle), | |
| std::move(sysmem_token)); | |
| } | |
| #endif // BUILDFLAG(IS_FUCHSIA) | |
| SyncToken TestSharedImageInterface::GenVerifiedSyncToken() { | |
| base::AutoLock locked(lock_); | |
| most_recent_generated_token_ = | |
| SyncToken(CommandBufferNamespace::GPU_IO, | |
| CommandBufferId::FromUnsafeValue(static_cast<uint64_t>( | |
| GpuChannelReservedRoutes::kSharedImageInterface)), | |
| ++release_id_); | |
| VerifySyncToken(most_recent_generated_token_); | |
| return most_recent_generated_token_; | |
| } | |
| SyncToken TestSharedImageInterface::GenUnverifiedSyncToken() { | |
| base::AutoLock locked(lock_); | |
| most_recent_generated_token_ = | |
| SyncToken(CommandBufferNamespace::GPU_IO, | |
| CommandBufferId::FromUnsafeValue(static_cast<uint64_t>( | |
| GpuChannelReservedRoutes::kSharedImageInterface)), | |
| ++release_id_); | |
| return most_recent_generated_token_; | |
| } | |
| void TestSharedImageInterface::VerifySyncToken(SyncToken& sync_token) { | |
| sync_token.SetVerifyFlush(); | |
| } | |
| void TestSharedImageInterface::WaitSyncToken(const SyncToken& sync_token) { | |
| NOTREACHED(); | |
| } | |
| void TestSharedImageInterface::SignalSyncToken( | |
| std::vector<SyncToken> sync_tokens, | |
| base::OnceClosure callback) { | |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( | |
| FROM_HERE, std::move(callback)); | |
| } | |
| bool TestSharedImageInterface::CanVerifySyncToken( | |
| const gpu::SyncToken& sync_token) { | |
| return true; | |
| } | |
| void TestSharedImageInterface::VerifyFlush() {} | |
| scoped_refptr<ClientSharedImage> | |
| TestSharedImageInterface::CreateSharedImageWithAsyncMapControl( | |
| const SharedImageInfo& si_info, | |
| gfx::BufferUsage buffer_usage, | |
| bool premapped, | |
| const ClientSharedImage::AsyncMapInvokedCallback& callback) { | |
| Mailbox mailbox; | |
| { | |
| base::AutoLock locked(lock_); | |
| mailbox = Mailbox::Generate(); | |
| shared_images_.insert(mailbox); | |
| } | |
| auto image = ClientSharedImage::CreateForTesting( | |
| mailbox, si_info, GenUnverifiedSyncToken(), premapped, callback, | |
| buffer_usage, holder_); | |
| return image; | |
| } | |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) | |
| scoped_refptr<ClientSharedImage> | |
| TestSharedImageInterface::CreateNativePixmapBackedSharedImage( | |
| const SharedImageInfo& si_info, | |
| SurfaceHandle surface_handle, | |
| gfx::BufferUsage buffer_usage) { | |
| const auto& format = si_info.format; | |
| const auto& size = si_info.size; | |
| gfx::NativePixmapHandle native_pixmap_handle; | |
| for (int i = 0; i < format.NumberOfPlanes(); i++) { | |
| size_t height_in_pixels = format.GetPlaneSize(i, size).height(); | |
| CHECK(height_in_pixels); | |
| size_t stride = | |
| viz::SharedMemoryRowSizeForSharedImageFormat(format, i, size.width()) | |
| .value(); | |
| // Placeholder plane fd. | |
| const uint64_t plane_size = | |
| base::CheckMul(height_in_pixels, stride).ValueOrDie<uint64_t>(); | |
| base::ScopedFD fd(memfd_create("test_shared_image", MFD_CLOEXEC)); | |
| CHECK(fd.is_valid()); | |
| CHECK_EQ(ftruncate(fd.get(), plane_size), 0); | |
| native_pixmap_handle.planes.emplace_back( | |
| base::checked_cast<uint32_t>(stride), 0, plane_size, std::move(fd)); | |
| } | |
| return CreateSharedImage( | |
| si_info, surface_handle, buffer_usage, | |
| gfx::GpuMemoryBufferHandle(std::move(native_pixmap_handle))); | |
| } | |
| #endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) | |
| bool TestSharedImageInterface::CheckSharedImageExists( | |
| const Mailbox& mailbox) const { | |
| base::AutoLock locked(lock_); | |
| return shared_images_.contains(mailbox); | |
| } | |
| const SharedImageCapabilities& | |
| TestSharedImageInterface::GetCapabilities() { | |
| return shared_image_capabilities_; | |
| } | |
| void TestSharedImageInterface::SetCapabilities( | |
| const SharedImageCapabilities& caps) { | |
| shared_image_capabilities_ = caps; | |
| } | |
| bool TestSharedImageInterface::IsLost() const { | |
| base::AutoLock locked(lock_); | |
| return is_lost_; | |
| } | |
| bool TestSharedImageInterface::AddGpuChannelLostObserver( | |
| GpuChannelLostObserver* observer) { | |
| base::AutoLock locked(lock_); | |
| if (is_lost_) { | |
| return false; | |
| } | |
| observer_list_.push_back(observer); | |
| return true; | |
| } | |
| void TestSharedImageInterface::RemoveGpuChannelLostObserver( | |
| GpuChannelLostObserver* observer) { | |
| base::AutoLock locked(lock_); | |
| std::erase(observer_list_, observer); | |
| } | |
| void TestSharedImageInterface::MarkAsLost() { | |
| base::AutoLock locked(lock_); | |
| is_lost_ = true; | |
| for (GpuChannelLostObserver* observer : observer_list_) { | |
| observer->OnGpuChannelLost(); | |
| } | |
| observer_list_.clear(); | |
| } | |
| } // namespace gpu |