| // Copyright 2012 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_H_ | |
| #define GPU_COMMAND_BUFFER_CLIENT_TRANSFER_BUFFER_H_ | |
| #include <stddef.h> | |
| #include <stdint.h> | |
| #include <memory> | |
| #include "base/compiler_specific.h" | |
| #include "base/containers/circular_deque.h" | |
| #include "base/containers/span.h" | |
| #include "base/memory/raw_ptr.h" | |
| #include "base/unguessable_token.h" | |
| #include "gpu/command_buffer/client/gpu_command_buffer_client_export.h" | |
| #include "gpu/command_buffer/client/ring_buffer.h" | |
| #include "gpu/command_buffer/common/buffer.h" | |
| namespace gpu { | |
| class CommandBufferHelper; | |
| template <typename> | |
| class ScopedResultPtr; | |
| // Interface for managing the transfer buffer. | |
| class GPU_COMMAND_BUFFER_CLIENT_EXPORT TransferBufferInterface { | |
| public: | |
| TransferBufferInterface() = default; | |
| virtual ~TransferBufferInterface() = default; | |
| // Returns 128-bit GUID of the shared memory's region when the back end is | |
| // base::UnsafeSharedMemoryRegion. Otherwise, this returns an empty GUID. | |
| virtual base::UnguessableToken shared_memory_guid() const = 0; | |
| virtual bool Initialize(unsigned int buffer_size, | |
| unsigned int result_size, | |
| unsigned int min_buffer_size, | |
| unsigned int max_buffer_size, | |
| unsigned int alignment) = 0; | |
| virtual int GetShmId() = 0; | |
| virtual void Free() = 0; | |
| virtual bool HaveBuffer() const = 0; | |
| // Allocates up to size bytes. The actual allocated size will be the span's | |
| // size(). | |
| virtual base::span<uint8_t> AllocUpTo(unsigned int size) = 0; | |
| // Allocates size bytes. | |
| // Note: Alloc will fail if it can not return size bytes. | |
| virtual base::span<uint8_t> Alloc(unsigned int size) = 0; | |
| virtual RingBuffer::Offset GetOffset(void* pointer) const = 0; | |
| virtual void DiscardBlock(void* p) = 0; | |
| virtual void FreePendingToken(void* p, unsigned int token) = 0; | |
| virtual unsigned int GetSize() const = 0; | |
| virtual unsigned int GetFreeSize() const = 0; | |
| virtual unsigned int GetFragmentedFreeSize() const = 0; | |
| virtual void ShrinkLastBlock(unsigned int new_size) = 0; | |
| virtual unsigned int GetMaxSize() const = 0; | |
| protected: | |
| template <typename> | |
| friend class ScopedResultPtr; | |
| // Use ScopedResultPtr instead of calling these directly. The acquire/release | |
| // semantics allow TransferBuffer to detect if there is an outstanding result | |
| // pointer when the buffer is resized, which would otherwise cause a | |
| // use-after-free bug. | |
| virtual void* AcquireResultBuffer() = 0; | |
| virtual void ReleaseResultBuffer() = 0; | |
| virtual int GetResultOffset() = 0; | |
| }; | |
| // Class that manages the transfer buffer. | |
| class GPU_COMMAND_BUFFER_CLIENT_EXPORT TransferBuffer | |
| : public TransferBufferInterface { | |
| public: | |
| TransferBuffer(CommandBufferHelper* helper); | |
| ~TransferBuffer() override; | |
| // Overridden from TransferBufferInterface. | |
| base::UnguessableToken shared_memory_guid() const override; | |
| bool Initialize(unsigned int default_buffer_size, | |
| unsigned int result_size, | |
| unsigned int min_buffer_size, | |
| unsigned int max_buffer_size, | |
| unsigned int alignment) override; | |
| int GetShmId() override; | |
| void* AcquireResultBuffer() override; | |
| void ReleaseResultBuffer() override; | |
| int GetResultOffset() override; | |
| void Free() override; | |
| bool HaveBuffer() const override; | |
| base::span<uint8_t> AllocUpTo(unsigned int size) override; | |
| base::span<uint8_t> Alloc(unsigned int size) override; | |
| RingBuffer::Offset GetOffset(void* pointer) const override; | |
| void DiscardBlock(void* p) override; | |
| void FreePendingToken(void* p, unsigned int token) override; | |
| unsigned int GetSize() const override; | |
| unsigned int GetFreeSize() const override; | |
| unsigned int GetFragmentedFreeSize() const override; | |
| void ShrinkLastBlock(unsigned int new_size) override; | |
| unsigned int GetMaxSize() const override; | |
| // These are for testing. | |
| unsigned int GetCurrentMaxAllocationWithoutRealloc() const; | |
| // We will attempt to shrink the ring buffer once the number of bytes | |
| // allocated reaches this threshold times the high water mark. | |
| static const int kShrinkThreshold = 120; | |
| private: | |
| // Tries to reallocate the ring buffer if it's not large enough for size. | |
| void ReallocateRingBuffer(unsigned int size, bool shrink = false); | |
| void AllocateRingBuffer(unsigned int size); | |
| void ShrinkOrExpandRingBufferIfNecessary(unsigned int size); | |
| // Returns the number of bytes that are still in use in ring buffers that we | |
| // previously freed. | |
| unsigned int GetPreviousRingBufferUsedBytes(); | |
| raw_ptr<CommandBufferHelper> helper_; | |
| std::unique_ptr<RingBuffer> ring_buffer_; | |
| base::circular_deque<std::unique_ptr<RingBuffer>> previous_ring_buffers_; | |
| // size reserved for results | |
| unsigned int result_size_; | |
| // default size. Size we want when starting or re-allocating | |
| unsigned int default_buffer_size_; | |
| // min size we'll consider successful | |
| unsigned int min_buffer_size_; | |
| // max size we'll let the buffer grow | |
| unsigned int max_buffer_size_; | |
| // Size of the currently allocated ring buffer. | |
| unsigned int last_allocated_size_ = 0; | |
| // The size to shrink the ring buffer to next time shrinking happens. | |
| unsigned int high_water_mark_ = 0; | |
| // alignment for allocations | |
| unsigned int alignment_; | |
| // Number of bytes since we last attempted to shrink the ring buffer. | |
| unsigned int bytes_since_last_shrink_ = 0; | |
| // the current buffer. | |
| scoped_refptr<gpu::Buffer> buffer_; | |
| // id of buffer. -1 = no buffer | |
| int32_t buffer_id_; | |
| // address of result area | |
| raw_ptr<void> result_buffer_; | |
| // offset to result area | |
| uint32_t result_shm_offset_; | |
| // false if we failed to allocate min_buffer_size | |
| bool usable_; | |
| // While a ScopedResultPtr exists, we can't resize the transfer buffer. Only | |
| // one ScopedResultPtr should exist at a time. This tracks whether one exists. | |
| bool outstanding_result_pointer_ = false; | |
| }; | |
| // A class that will manage the lifetime of a transferbuffer allocation. | |
| class GPU_COMMAND_BUFFER_CLIENT_EXPORT ScopedTransferBufferPtr { | |
| public: | |
| ScopedTransferBufferPtr(unsigned int size, | |
| CommandBufferHelper* helper, | |
| TransferBufferInterface* transfer_buffer) | |
| : helper_(helper), transfer_buffer_(transfer_buffer) { | |
| Reset(size); | |
| } | |
| // Constructs an empty and invalid allocation that should be Reset() later. | |
| ScopedTransferBufferPtr(CommandBufferHelper* helper, | |
| TransferBufferInterface* transfer_buffer) | |
| : helper_(helper), transfer_buffer_(transfer_buffer) {} | |
| ScopedTransferBufferPtr(const ScopedTransferBufferPtr&) = delete; | |
| ScopedTransferBufferPtr& operator=(const ScopedTransferBufferPtr&) = delete; | |
| ~ScopedTransferBufferPtr() { | |
| Release(); | |
| } | |
| ScopedTransferBufferPtr(ScopedTransferBufferPtr&& other); | |
| bool valid() const { return buffer_.data() != nullptr; } | |
| unsigned int size() const { return buffer_.size(); } | |
| int shm_id() const { | |
| return transfer_buffer_->GetShmId(); | |
| } | |
| RingBuffer::Offset offset() const { | |
| return transfer_buffer_->GetOffset(buffer_.data()); | |
| } | |
| void* address() const { return buffer_.data(); } | |
| base::span<uint8_t> as_byte_span() { return buffer_; } | |
| base::span<const uint8_t> as_byte_span() const { return buffer_; } | |
| // Returns true if |memory| lies inside this buffer. | |
| bool BelongsToBuffer(uint8_t* memory) const; | |
| void Release(); | |
| void Discard(); | |
| void Reset(unsigned int new_size); | |
| // Shrinks this transfer buffer to a given size. | |
| void Shrink(unsigned int new_size); | |
| private: | |
| base::raw_span<uint8_t> buffer_; | |
| // Found dangling on `linux-rel` in | |
| // `gpu_tests.trace_integration_test.TraceIntegrationTest. | |
| // WebGPUCachingTraceTest_ComputePipelineMainThread`. | |
| raw_ptr<CommandBufferHelper, DanglingUntriaged> helper_; | |
| raw_ptr<TransferBufferInterface, DanglingUntriaged> transfer_buffer_; | |
| }; | |
| template <typename T> | |
| class ScopedTransferBufferArray : public ScopedTransferBufferPtr { | |
| public: | |
| ScopedTransferBufferArray( | |
| unsigned int num_elements, | |
| CommandBufferHelper* helper, TransferBufferInterface* transfer_buffer) | |
| : ScopedTransferBufferPtr( | |
| num_elements * sizeof(T), helper, transfer_buffer) { | |
| } | |
| T* elements() { | |
| return static_cast<T*>(address()); | |
| } | |
| unsigned int num_elements() const { | |
| return size() / sizeof(T); | |
| } | |
| }; | |
| // ScopedResultPtr is a move-only smart pointer that calls AcquireResultBuffer | |
| // and ReleaseResultBuffer for you. | |
| template <typename T> | |
| class ScopedResultPtr { | |
| public: | |
| explicit ScopedResultPtr(TransferBufferInterface* tb) | |
| : result_(static_cast<T*>(tb->AcquireResultBuffer())), | |
| transfer_buffer_(tb) {} | |
| ScopedResultPtr(const ScopedResultPtr&) = delete; | |
| ScopedResultPtr& operator=(const ScopedResultPtr&) = delete; | |
| ~ScopedResultPtr() { | |
| if (transfer_buffer_) | |
| transfer_buffer_->ReleaseResultBuffer(); | |
| } | |
| int offset() const { return transfer_buffer_->GetResultOffset(); } | |
| // Make this a move-only class like unique_ptr. | |
| ScopedResultPtr(ScopedResultPtr<T>&& other) { *this = std::move(other); } | |
| ScopedResultPtr& operator=(ScopedResultPtr<T>&& other) { | |
| this->result_ = other.result_; | |
| this->transfer_buffer_ = other.transfer_buffer_; | |
| other.result_ = nullptr; | |
| other.transfer_buffer_ = nullptr; | |
| return *this; | |
| } | |
| // Dereferencing behaviors | |
| T& operator*() const { return *result_; } | |
| T* operator->() const { return result_; } | |
| explicit operator bool() { return result_; } | |
| private: | |
| raw_ptr<T> result_; | |
| raw_ptr<TransferBufferInterface> transfer_buffer_; | |
| }; | |
| } // namespace gpu | |
| #endif // GPU_COMMAND_BUFFER_CLIENT_TRANSFER_BUFFER_H_ |