| // 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. | |
| // This file contains the command buffer helper class. | |
| #ifndef GPU_COMMAND_BUFFER_CLIENT_CMD_BUFFER_HELPER_H_ | |
| #define GPU_COMMAND_BUFFER_CLIENT_CMD_BUFFER_HELPER_H_ | |
| #include <stddef.h> | |
| #include <stdint.h> | |
| #include <string.h> | |
| #include "base/check_op.h" | |
| #include "base/compiler_specific.h" | |
| #include "base/functional/function_ref.h" | |
| #include "base/memory/raw_ptr.h" | |
| #include "base/memory/scoped_refptr.h" | |
| #include "base/time/time.h" | |
| #include "base/trace_event/memory_dump_provider.h" | |
| #include "build/build_config.h" | |
| #include "gpu/command_buffer/client/gpu_command_buffer_client_export.h" | |
| #include "gpu/command_buffer/common/cmd_buffer_common.h" | |
| #include "gpu/command_buffer/common/command_buffer.h" | |
| #include "gpu/command_buffer/common/context_result.h" | |
| namespace gpu { | |
| class Buffer; | |
| #if !BUILDFLAG(IS_ANDROID) | |
| #define CMD_HELPER_PERIODIC_FLUSH_CHECK | |
| const int kCommandsPerFlushCheck = 100; | |
| const int kPeriodicFlushDelayInMicroseconds = 500; | |
| #endif | |
| const int kAutoFlushSmall = 16; // 1/16 of the buffer | |
| const int kAutoFlushBig = 2; // 1/2 of the buffer | |
| // Command buffer helper class. This class simplifies ring buffer management: | |
| // it will allocate the buffer, give it to the buffer interface, and let the | |
| // user add commands to it, while taking care of the synchronization (put and | |
| // get). It also provides a way to ensure commands have been executed, through | |
| // the token mechanism: | |
| // | |
| // helper.AddCommand(...); | |
| // helper.AddCommand(...); | |
| // int32_t token = helper.InsertToken(); | |
| // helper.AddCommand(...); | |
| // helper.AddCommand(...); | |
| // [...] | |
| // | |
| // helper.WaitForToken(token); // this doesn't return until the first two | |
| // // commands have been executed. | |
| class GPU_COMMAND_BUFFER_CLIENT_EXPORT CommandBufferHelper { | |
| public: | |
| explicit CommandBufferHelper(CommandBuffer* command_buffer); | |
| CommandBufferHelper(const CommandBufferHelper&) = delete; | |
| CommandBufferHelper& operator=(const CommandBufferHelper&) = delete; | |
| virtual ~CommandBufferHelper(); | |
| // Initializes the CommandBufferHelper. | |
| // Parameters: | |
| // ring_buffer_size: The size of the ring buffer portion of the command | |
| // buffer. | |
| gpu::ContextResult Initialize(uint32_t ring_buffer_size); | |
| // Sets whether the command buffer should automatically flush periodically | |
| // to try to increase performance. Defaults to true. | |
| void SetAutomaticFlushes(bool enabled); | |
| // True if the context is lost. | |
| bool IsContextLost(); | |
| // Asynchronously flushes the commands, setting the put pointer to let the | |
| // buffer interface know that new commands have been added. After a flush | |
| // returns, the command buffer service is aware of all pending commands. | |
| void Flush(); | |
| // Flushes if the put pointer has changed since the last flush. | |
| void FlushLazy(); | |
| // Ensures that commands up to the put pointer will be processed in the | |
| // command buffer service before any future commands on other command buffers | |
| // sharing a channel. | |
| void OrderingBarrier(); | |
| // Waits until all the commands have been executed. Returns whether it | |
| // was successful. The function will fail if the command buffer service has | |
| // disconnected. | |
| bool Finish(); | |
| // Waits until a given number of available entries are available. | |
| // Parameters: | |
| // count: number of entries needed. This value must be at most | |
| // the size of the buffer minus one. | |
| void WaitForAvailableEntries(int32_t count); | |
| // Inserts a new token into the command buffer. This token either has a value | |
| // different from previously inserted tokens, or ensures that previously | |
| // inserted tokens with that value have already passed through the command | |
| // stream. | |
| // Returns: | |
| // the value of the new token or -1 if the command buffer reader has | |
| // shutdown. | |
| int32_t InsertToken(); | |
| // Returns true if the token has passed. This combines RefreshCachedToken | |
| // and HasCachedTokenPassed. Don't call this function if you have to call | |
| // it repeatedly, and instead use those alternative functions. | |
| // Parameters: | |
| // the value of the token to check whether it has passed | |
| bool HasTokenPassed(int32_t token); | |
| // Returns true if the token has passed, but doesn't take a lock and check | |
| // for what the latest token state is. | |
| bool HasCachedTokenPassed(int32_t token); | |
| // Update the state of the latest passed token. | |
| void RefreshCachedToken(); | |
| // Waits until the token of a particular value has passed through the command | |
| // stream (i.e. commands inserted before that token have been executed). | |
| // NOTE: This will call Flush if it needs to block. | |
| // Parameters: | |
| // the value of the token to wait for. | |
| void WaitForToken(int32_t token); | |
| // Called prior to each command being issued. Waits for a certain amount of | |
| // space to be available. Returns address of space. | |
| void* GetSpace(int32_t entries) { | |
| #if defined(CMD_HELPER_PERIODIC_FLUSH_CHECK) | |
| // Allow this command buffer to be pre-empted by another if a "reasonable" | |
| // amount of work has been done. On highend machines, this reduces the | |
| // latency of GPU commands. However, on Android, this can cause the | |
| // kernel to thrash between generating GPU commands and executing them. | |
| ++commands_issued_; | |
| if (flush_automatically_ && | |
| (commands_issued_ % kCommandsPerFlushCheck == 0)) { | |
| PeriodicFlushCheck(); | |
| } | |
| #endif | |
| // Test for immediate entries. | |
| if (entries > immediate_entry_count_) { | |
| WaitForAvailableEntries(entries); | |
| if (entries > immediate_entry_count_) | |
| return nullptr; | |
| } | |
| DCHECK_LE(entries, immediate_entry_count_); | |
| // Allocate space and advance put_. | |
| CommandBufferEntry* space = UNSAFE_TODO(&entries_[put_]); | |
| put_ += entries; | |
| immediate_entry_count_ -= entries; | |
| DCHECK_LE(put_, total_entry_count_); | |
| return space; | |
| } | |
| // Typed version of GetSpace. Gets enough room for the given type and returns | |
| // a reference to it. | |
| template <typename T> | |
| T* GetCmdSpace() { | |
| static_assert(T::kArgFlags == cmd::kFixed, | |
| "T::kArgFlags should equal cmd::kFixed"); | |
| int32_t space_needed = ComputeNumEntries(sizeof(T)); | |
| T* data = static_cast<T*>(GetSpace(space_needed)); | |
| return data; | |
| } | |
| // Typed version of GetSpace for immediate commands. | |
| template <typename T> | |
| T* GetImmediateCmdSpace(size_t data_space) { | |
| static_assert(T::kArgFlags == cmd::kAtLeastN, | |
| "T::kArgFlags should equal cmd::kAtLeastN"); | |
| int32_t space_needed = ComputeNumEntries(sizeof(T) + data_space); | |
| T* data = static_cast<T*>(GetSpace(space_needed)); | |
| return data; | |
| } | |
| // Typed version of GetSpace for immediate commands. | |
| template <typename T> | |
| T* GetImmediateCmdSpaceTotalSize(size_t total_space) { | |
| static_assert(T::kArgFlags == cmd::kAtLeastN, | |
| "T::kArgFlags should equal cmd::kAtLeastN"); | |
| int32_t space_needed = ComputeNumEntries(total_space); | |
| T* data = static_cast<T*>(GetSpace(space_needed)); | |
| return data; | |
| } | |
| // Common Commands | |
| void Noop(uint32_t skip_count) { | |
| cmd::Noop* cmd = GetImmediateCmdSpace<cmd::Noop>( | |
| (skip_count - 1) * sizeof(CommandBufferEntry)); | |
| if (cmd) { | |
| cmd->Init(skip_count); | |
| } | |
| } | |
| void SetToken(uint32_t token) { | |
| cmd::SetToken* cmd = GetCmdSpace<cmd::SetToken>(); | |
| if (cmd) { | |
| cmd->Init(token); | |
| } | |
| } | |
| void SetBucketSize(uint32_t bucket_id, uint32_t size) { | |
| cmd::SetBucketSize* cmd = GetCmdSpace<cmd::SetBucketSize>(); | |
| if (cmd) { | |
| cmd->Init(bucket_id, size); | |
| } | |
| } | |
| void SetBucketData(uint32_t bucket_id, | |
| uint32_t offset, | |
| uint32_t size, | |
| uint32_t shared_memory_id, | |
| uint32_t shared_memory_offset) { | |
| cmd::SetBucketData* cmd = GetCmdSpace<cmd::SetBucketData>(); | |
| if (cmd) { | |
| cmd->Init(bucket_id, offset, size, shared_memory_id, | |
| shared_memory_offset); | |
| } | |
| } | |
| void SetBucketDataImmediate(uint32_t bucket_id, | |
| uint32_t offset, | |
| const void* data, | |
| uint32_t size) { | |
| cmd::SetBucketDataImmediate* cmd = | |
| GetImmediateCmdSpace<cmd::SetBucketDataImmediate>(size); | |
| if (cmd) { | |
| cmd->Init(bucket_id, offset, size); | |
| UNSAFE_TODO(memcpy(ImmediateDataAddress(cmd), data, size)); | |
| } | |
| } | |
| void GetBucketStart(uint32_t bucket_id, | |
| uint32_t result_memory_id, | |
| uint32_t result_memory_offset, | |
| uint32_t data_memory_size, | |
| uint32_t data_memory_id, | |
| uint32_t data_memory_offset) { | |
| cmd::GetBucketStart* cmd = GetCmdSpace<cmd::GetBucketStart>(); | |
| if (cmd) { | |
| cmd->Init(bucket_id, result_memory_id, result_memory_offset, | |
| data_memory_size, data_memory_id, data_memory_offset); | |
| } | |
| } | |
| void GetBucketData(uint32_t bucket_id, | |
| uint32_t offset, | |
| uint32_t size, | |
| uint32_t shared_memory_id, | |
| uint32_t shared_memory_offset) { | |
| cmd::GetBucketData* cmd = GetCmdSpace<cmd::GetBucketData>(); | |
| if (cmd) { | |
| cmd->Init(bucket_id, offset, size, shared_memory_id, | |
| shared_memory_offset); | |
| } | |
| } | |
| uint64_t InsertFenceSync(base::FunctionRef<uint64_t()> sync_token_generator) { | |
| cmd::InsertFenceSync* cmd = GetCmdSpace<cmd::InsertFenceSync>(); | |
| // Please note that it is important to generate the sync token after | |
| // GetCmdSpace(). | |
| // 1) If InsertFenceSync command `cmd` is not successfully allocated, a sync | |
| // token shouldn't be created either. Otherwise, it results in waiting | |
| // for a fence sync that is never released. | |
| // 2) Even if `cmd` is successfully allocated, we still need to generate the | |
| // sync token afterwards: The GetCmdSpace() call may result in a flush of | |
| // the command buffer. On the other hand, command buffer implementations | |
| // (such as CommandBufferProxyImpl) may assume that when a flush happens, | |
| // the commands releasing the previously-generated sync tokens are | |
| // already in the buffer and thus all flushed. | |
| if (cmd) { | |
| uint64_t release_count = sync_token_generator(); | |
| cmd->Init(release_count); | |
| return release_count; | |
| } | |
| return 0; | |
| } | |
| CommandBuffer* command_buffer() const { return command_buffer_; } | |
| scoped_refptr<Buffer> get_ring_buffer() const { return ring_buffer_; } | |
| int32_t get_ring_buffer_id() const { return ring_buffer_id_; } | |
| uint32_t flush_generation() const { return flush_generation_; } | |
| void FreeRingBuffer(); | |
| bool HaveRingBuffer() const { | |
| bool have_ring_buffer = !!ring_buffer_; | |
| DCHECK(usable() || !have_ring_buffer); | |
| return have_ring_buffer; | |
| } | |
| bool usable() const { return usable_; } | |
| bool OnMemoryDump(const base::trace_event::MemoryDumpArgs& args, | |
| base::trace_event::ProcessMemoryDump* pmd); | |
| int32_t GetPutOffsetForTest() const { return put_; } | |
| private: | |
| void CalcImmediateEntries(int waiting_count); | |
| bool AllocateRingBuffer(); | |
| void SetGetBuffer(int32_t id, scoped_refptr<Buffer> buffer); | |
| // Waits for the get offset to be in a specific range, inclusive. Returns | |
| // false if there was an error. | |
| bool WaitForGetOffsetInRange(int32_t start, int32_t end); | |
| #if defined(CMD_HELPER_PERIODIC_FLUSH_CHECK) | |
| // Calls Flush if automatic flush conditions are met. | |
| void PeriodicFlushCheck(); | |
| #endif | |
| int32_t GetTotalFreeEntriesNoWaiting() const; | |
| // Updates |cached_get_offset_|, |cached_last_token_read_| and |context_lost_| | |
| // from given command buffer state. | |
| void UpdateCachedState(const CommandBuffer::State& state); | |
| const raw_ptr<CommandBuffer> command_buffer_; | |
| int32_t ring_buffer_id_ = -1; | |
| uint32_t ring_buffer_size_ = 0; | |
| scoped_refptr<gpu::Buffer> ring_buffer_; | |
| raw_ptr<CommandBufferEntry, AllowPtrArithmetic> entries_ = nullptr; | |
| int32_t total_entry_count_ = 0; // the total number of entries | |
| int32_t immediate_entry_count_ = 0; | |
| int32_t token_ = 0; | |
| int32_t put_ = 0; | |
| int32_t cached_last_token_read_ = 0; | |
| int32_t cached_get_offset_ = 0; | |
| uint32_t set_get_buffer_count_ = 0; | |
| bool service_on_old_buffer_ = false; | |
| #if defined(CMD_HELPER_PERIODIC_FLUSH_CHECK) | |
| int commands_issued_ = 0; | |
| #endif | |
| bool usable_ = true; | |
| bool context_lost_ = false; | |
| bool flush_automatically_ = true; | |
| // We track last put offset to avoid redundant automatic flushes. We track | |
| // both flush and ordering barrier put offsets so that an automatic flush | |
| // after an ordering barrier forces a flush. Automatic flushes are enabled on | |
| // desktop, and are also used to flush before waiting for free space in the | |
| // command buffer. If the auto flush logic is wrong, we might call | |
| // WaitForGetOffsetInRange without flushing, causing the service to go idle, | |
| // and the client to hang. See https://crbug.com/798400 for details. | |
| int32_t last_flush_put_ = 0; | |
| int32_t last_ordering_barrier_put_ = 0; | |
| base::TimeTicks last_flush_time_; | |
| // Incremented every time the helper flushes the command buffer. | |
| // Can be used to track when prior commands have been flushed. | |
| uint32_t flush_generation_ = 0; | |
| friend class CommandBufferHelperTest; | |
| }; | |
| } // namespace gpu | |
| #endif // GPU_COMMAND_BUFFER_CLIENT_CMD_BUFFER_HELPER_H_ |