| // Copyright 2020 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/service/shared_memory_region_wrapper.h" | |
| #include "base/compiler_specific.h" | |
| #include "base/logging.h" | |
| #include "base/numerics/checked_math.h" | |
| #include "base/system/sys_info.h" | |
| #include "components/viz/common/resources/shared_image_format_utils.h" | |
| #include "ui/gfx/gpu_memory_buffer_handle.h" | |
| namespace gpu { | |
| namespace { | |
| size_t RowByteAlignmentForSharedImageFormatFirstPlane( | |
| viz::SharedImageFormat format) { | |
| if (format == viz::SinglePlaneFormat::kRGBA_F16) { | |
| return 8; | |
| } | |
| if (format.is_single_plane()) { | |
| return 4; | |
| } | |
| return format.MultiplanarStorageBytesPerChannel(); | |
| } | |
| // Validate that |stride| will work for pixels with |size| and |format|. | |
| bool ValidateStride(const gfx::Size size, | |
| viz::SharedImageFormat format, | |
| uint32_t stride) { | |
| if (!base::IsValueInRangeForNumericType<size_t>(stride)) | |
| return false; | |
| // Check for first plane index since we can't handle different plane strides | |
| // anyway. | |
| size_t alignment = RowByteAlignmentForSharedImageFormatFirstPlane(format); | |
| if (stride % alignment != 0) | |
| return false; | |
| std::optional<size_t> min_width_in_bytes = | |
| viz::SharedMemoryRowSizeForSharedImageFormat(format, /*plane=*/0, | |
| size.width()); | |
| if (!min_width_in_bytes) { | |
| return false; | |
| } | |
| if (stride < min_width_in_bytes.value()) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| } // namespace | |
| SharedMemoryRegionWrapper::SharedMemoryRegionWrapper() = default; | |
| SharedMemoryRegionWrapper::SharedMemoryRegionWrapper( | |
| SharedMemoryRegionWrapper&& other) = default; | |
| SharedMemoryRegionWrapper& SharedMemoryRegionWrapper::operator=( | |
| SharedMemoryRegionWrapper&& other) = default; | |
| SharedMemoryRegionWrapper::~SharedMemoryRegionWrapper() = default; | |
| bool SharedMemoryRegionWrapper::Initialize( | |
| const gfx::GpuMemoryBufferHandle& handle, | |
| const gfx::Size& size, | |
| viz::SharedImageFormat format) { | |
| DCHECK(!mapping_.IsValid()); | |
| if (!handle.region().IsValid()) { | |
| DLOG(ERROR) << "Invalid GMB shared memory region."; | |
| return false; | |
| } | |
| if (!ValidateStride(size, format, handle.stride)) { | |
| DLOG(ERROR) << "Invalid GMB stride."; | |
| return false; | |
| } | |
| std::optional<size_t> buffer_size = | |
| viz::SharedMemorySizeForSharedImageFormat(format, size); | |
| if (!buffer_size) { | |
| DLOG(ERROR) << "Invalid GMB size."; | |
| return false; | |
| } | |
| // Minimize the amount of address space we use but make sure offset is a | |
| // multiple of page size as required by MapAt(). | |
| // TODO(sunnyps): This doesn't seem to be a requirement of MapAt() anymore. | |
| const size_t allocation_granularity = | |
| base::SysInfo::VMAllocationGranularity(); | |
| const size_t memory_offset = handle.offset % allocation_granularity; | |
| const size_t map_offset = | |
| allocation_granularity * (handle.offset / allocation_granularity); | |
| base::CheckedNumeric<size_t> checked_size = buffer_size.value(); | |
| checked_size += memory_offset; | |
| if (!checked_size.IsValid()) { | |
| DLOG(ERROR) << "Invalid shared memory region map size."; | |
| return false; | |
| } | |
| const size_t map_size = checked_size.ValueOrDie(); | |
| mapping_ = handle.region().MapAt(static_cast<off_t>(map_offset), map_size); | |
| if (!mapping_.IsValid()) { | |
| DLOG(ERROR) << "Failed to map shared memory."; | |
| return false; | |
| } | |
| int num_planes = format.NumberOfPlanes(); | |
| planes_.resize(num_planes); | |
| // The offset/stride only make sense when GpuMemoryBufferHandle is for a | |
| // single plane. Stride should be set as the expected stride for first plane | |
| // and offset should always be zero. | |
| DCHECK_EQ(static_cast<size_t>(handle.stride), | |
| viz::SharedMemoryRowSizeForSharedImageFormat(format, /*plane=*/0, | |
| size.width()) | |
| .value()); | |
| DCHECK_EQ(handle.offset, 0u); | |
| for (int plane_index = 0; plane_index < num_planes; ++plane_index) { | |
| const size_t plane_offset = | |
| viz::SharedMemoryOffsetForSharedImageFormat(format, plane_index, size); | |
| planes_[plane_index].offset = memory_offset + plane_offset; | |
| planes_[plane_index].stride = viz::SharedMemoryRowSizeForSharedImageFormat( | |
| format, plane_index, size.width()) | |
| .value(); | |
| } | |
| return true; | |
| } | |
| bool SharedMemoryRegionWrapper::IsValid() const { | |
| return mapping_.IsValid(); | |
| } | |
| const uint8_t* SharedMemoryRegionWrapper::GetMemory(int plane_index) const { | |
| DCHECK(IsValid()); | |
| return UNSAFE_TODO(mapping_.GetMemoryAs<const uint8_t>() + | |
| planes_[plane_index].offset); | |
| } | |
| size_t SharedMemoryRegionWrapper::GetStride(int plane_index) const { | |
| DCHECK(IsValid()); | |
| return planes_[plane_index].stride; | |
| } | |
| base::span<const uint8_t> SharedMemoryRegionWrapper::GetMemoryPlanes() const { | |
| DCHECK(IsValid()); | |
| auto full_mapped_span = UNSAFE_TODO(base::span( | |
| mapping_.GetMemoryAs<const uint8_t>(), mapping_.mapped_size())); | |
| // It is possible that the first plane starts at a non-zero offset. So we | |
| // subspan at this offset. | |
| return full_mapped_span.subspan(planes_[0].offset); | |
| } | |
| SkPixmap SharedMemoryRegionWrapper::MakePixmapForPlane(const SkImageInfo& info, | |
| int plane_index) const { | |
| DCHECK(IsValid()); | |
| SkPixmap pixmap(info, GetMemory(plane_index), GetStride(plane_index)); | |
| DCHECK_LE(planes_[plane_index].offset + pixmap.computeByteSize(), | |
| mapping_.mapped_size()); | |
| return pixmap; | |
| } | |
| const base::UnguessableToken& SharedMemoryRegionWrapper::GetMappingGuid() | |
| const { | |
| return mapping_.guid(); | |
| } | |
| } // namespace gpu |