| // Copyright 2018 The Chromium Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #ifndef GPU_COMMAND_BUFFER_CLIENT_TRANSFER_BUFFER_CMD_COPY_HELPERS_H_ | |
| #define GPU_COMMAND_BUFFER_CLIENT_TRANSFER_BUFFER_CMD_COPY_HELPERS_H_ | |
| #include <array> | |
| #include <bit> | |
| #include "base/compiler_specific.h" | |
| #include "base/numerics/safe_math.h" | |
| #include "gpu/command_buffer/client/transfer_buffer.h" | |
| namespace gpu { | |
| // Sum the sizes of the types in Ts as CheckedNumeric<T>. | |
| template <typename T, typename... Ts> | |
| constexpr base::CheckedNumeric<T> CheckedSizeOfPackedTypes() { | |
| static_assert(sizeof...(Ts) > 0, ""); | |
| base::CheckedNumeric<T> checked_elements_size = 0; | |
| for (size_t s : {sizeof(Ts)...}) { | |
| checked_elements_size += s; | |
| } | |
| return checked_elements_size; | |
| } | |
| // Compute the number of bytes required for a struct-of-arrays where each array | |
| // of type T has count items. If there is an overflow, this function returns 0. | |
| template <typename... Ts> | |
| constexpr base::CheckedNumeric<uint32_t> ComputeCheckedCombinedCopySize( | |
| uint32_t count) { | |
| static_assert(sizeof...(Ts) > 0, ""); | |
| base::CheckedNumeric<uint32_t> checked_combined_size = 0; | |
| base::CheckedNumeric<uint32_t> checked_count(count); | |
| for (auto info : {std::make_pair(sizeof(Ts), alignof(Ts))...}) { | |
| size_t alignment = info.second; | |
| DCHECK(std::has_single_bit(alignment)); | |
| checked_combined_size = | |
| (checked_combined_size + alignment - 1) & ~(alignment - 1); | |
| checked_combined_size += checked_count * info.first; | |
| } | |
| return checked_combined_size; | |
| } | |
| // Copy count items from each array in arrays starting at array[offset_count] | |
| // into the address pointed to by buffer | |
| template <typename... Ts> | |
| auto CopyArraysToBuffer(uint32_t count, | |
| uint32_t offset_count, | |
| void* buffer, | |
| Ts*... arrays) | |
| -> std::array<uint32_t, sizeof...(arrays)> { | |
| constexpr uint32_t arr_count = sizeof...(arrays); | |
| static_assert(arr_count > 0, "Requires at least one array"); | |
| DCHECK_GT(count, 0u); | |
| DCHECK(buffer); | |
| // Length of each copy | |
| std::array<size_t, arr_count> copy_lengths{{(count * sizeof(Ts))...}}; | |
| std::array<size_t, arr_count> alignments{{alignof(Ts)...}}; | |
| // Offset to the destination of each copy | |
| std::array<uint32_t, arr_count> byte_offsets{}; | |
| byte_offsets[0] = 0; | |
| base::CheckedNumeric<uint32_t> checked_byte_offset = copy_lengths[0]; | |
| for (uint32_t i = 1; i < arr_count; ++i) { | |
| DCHECK(std::has_single_bit(alignments[i])); | |
| checked_byte_offset = | |
| (checked_byte_offset + alignments[i] - 1) & ~(alignments[i] - 1); | |
| byte_offsets[i] = checked_byte_offset.ValueOrDie(); | |
| checked_byte_offset += copy_lengths[i]; | |
| } | |
| // Pointers to the copy sources | |
| std::array<const int8_t*, arr_count> byte_pointers{{( | |
| [](bool b) { DCHECK(b); }(arrays), | |
| reinterpret_cast<const int8_t*>(UNSAFE_TODO(arrays + offset_count)))...}}; | |
| for (uint32_t i = 0; i < arr_count; ++i) { | |
| UNSAFE_TODO(memcpy(static_cast<int8_t*>(buffer) + byte_offsets[i], | |
| byte_pointers[i], copy_lengths[i])); | |
| } | |
| return byte_offsets; | |
| } | |
| // Sum the sizes of the types in Ts. This will fail to compile if the result | |
| // does not fit in T. | |
| template <typename T, typename... Ts> | |
| constexpr T SizeOfPackedTypes() { | |
| constexpr base::CheckedNumeric<T> checked_elements_size = | |
| CheckedSizeOfPackedTypes<T, Ts...>(); | |
| static_assert(checked_elements_size.IsValid(), ""); | |
| return checked_elements_size.ValueOrDie(); | |
| } | |
| template <typename... Ts> | |
| constexpr uint32_t ComputeCombinedCopySize(uint32_t count) { | |
| return ComputeCheckedCombinedCopySize<Ts...>(count).ValueOrDefault( | |
| UINT32_MAX); | |
| } | |
| template <typename... Ts> | |
| constexpr uint32_t ComputeCombinedCopySize(uint32_t count, | |
| const Ts*... arrays) { | |
| return ComputeCheckedCombinedCopySize<Ts...>(count).ValueOrDefault( | |
| UINT32_MAX); | |
| } | |
| // Compute the largest array size for a struct-of-arrays that can fit inside | |
| // a buffer | |
| template <typename... Ts> | |
| constexpr uint32_t ComputeMaxCopyCount(uint32_t buffer_size) { | |
| // Start by tightly packing the elements and decrease copy_count until | |
| // the total aligned copy size fits | |
| constexpr uint32_t elements_size = SizeOfPackedTypes<uint32_t, Ts...>(); | |
| uint32_t copy_count = buffer_size / elements_size; | |
| while (copy_count > 0) { | |
| base::CheckedNumeric<uint32_t> checked_combined_size = | |
| ComputeCheckedCombinedCopySize<Ts...>(copy_count); | |
| uint32_t combined_size = 0; | |
| if (checked_combined_size.AssignIfValid(&combined_size) && | |
| combined_size <= buffer_size) { | |
| break; | |
| } | |
| copy_count--; | |
| } | |
| return copy_count; | |
| } | |
| } // namespace gpu | |
| namespace internal { | |
| // The transfer buffer may not fit all count items from each array in arrays. | |
| // This function copies in equal number of items from each array into the buffer | |
| // and calls a callback function f. It releases the buffer and repeats until | |
| // all items have been consumed. | |
| template <typename F, typename TransferBuffer, typename... Ts> | |
| bool TransferArraysAndExecute(uint32_t count, | |
| TransferBuffer* buffer, | |
| const F& f, | |
| Ts*... arrays) { | |
| static_assert(sizeof...(arrays) > 0, "Requires at least one array"); | |
| DCHECK(buffer); | |
| uint32_t offset_count = 0; | |
| while (count) { | |
| uint32_t desired_size = | |
| gpu::ComputeCheckedCombinedCopySize<Ts...>(count).ValueOrDefault( | |
| UINT32_MAX); | |
| uint32_t copy_count = gpu::ComputeMaxCopyCount<Ts...>(buffer->size()); | |
| if (!buffer->valid() || copy_count == 0) { | |
| // Reset the buffer to the desired size | |
| buffer->Reset(desired_size); | |
| if (!buffer->valid()) { | |
| return false; | |
| } | |
| // The buffer size may be less than the desired size. Recompute the number | |
| // of elements to copy. | |
| copy_count = gpu::ComputeMaxCopyCount<Ts...>(buffer->size()); | |
| if (copy_count == 0) { | |
| return false; | |
| } | |
| } | |
| std::array<uint32_t, sizeof...(arrays)> byte_offsets = | |
| gpu::CopyArraysToBuffer(copy_count, offset_count, buffer->address(), | |
| arrays...); | |
| f(byte_offsets, offset_count, copy_count); | |
| buffer->Release(); | |
| offset_count += copy_count; | |
| count -= copy_count; | |
| } | |
| return true; | |
| } | |
| } // namespace internal | |
| namespace gpu { | |
| template <typename F, typename... Ts> | |
| bool TransferArraysAndExecute(uint32_t count, | |
| ScopedTransferBufferPtr* buffer, | |
| const F& f, | |
| Ts*... arrays) { | |
| return internal::TransferArraysAndExecute<F, ScopedTransferBufferPtr, Ts...>( | |
| count, buffer, f, arrays...); | |
| } | |
| } // namespace gpu | |
| #endif // GPU_COMMAND_BUFFER_CLIENT_TRANSFER_BUFFER_CMD_COPY_HELPERS_H_ |