blob: df189224d79cbc35499ca4e466cb0445042a3d1d [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// 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.
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/time/time.h"
#include "base/trace_event/memory_dump_provider.h"
#include "build/build_config.h"
#include "gpu/command_buffer/common/cmd_buffer_common.h"
#include "gpu/command_buffer/common/command_buffer.h"
#include "gpu/gpu_export.h"
namespace gpu {
class Buffer;
#if !defined(OS_ANDROID)
const int kCommandsPerFlushCheck = 100;
const int kPeriodicFlushDelayInMicroseconds =
base::Time::kMicrosecondsPerSecond / (5 * 60);
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_EXPORT CommandBufferHelper
: public base::trace_event::MemoryDumpProvider {
explicit CommandBufferHelper(CommandBuffer* command_buffer);
~CommandBufferHelper() override;
// Initializes the CommandBufferHelper.
// Parameters:
// ring_buffer_size: The size of the ring buffer portion of the command
// buffer.
bool Initialize(int32_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();
// 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.
// Parameters:
// the value of the token to check whether it has passed
bool HasTokenPassed(int32_t token) const {
if (token > token_)
return true; // we wrapped
return last_token_read() >= token;
// 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) {
// 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.
if (flush_automatically_ &&
(commands_issued_ % kCommandsPerFlushCheck == 0)) {
// Test for immediate entries.
if (entries > immediate_entry_count_) {
if (entries > immediate_entry_count_)
return NULL;
DCHECK_LE(entries, immediate_entry_count_);
// Allocate space and advance put_.
CommandBufferEntry* space = &entries_[put_];
put_ += entries;
immediate_entry_count_ -= entries;
DCHECK_LE(put_, total_entry_count_);
return space;
template <typename T>
void ForceNullCheck(T* data) {
#if defined(COMPILER_MSVC) && defined(ARCH_CPU_64_BITS) && !defined(__clang__)
// 64-bit MSVC's alias analysis was determining that the command buffer
// entry couldn't be NULL, so it optimized out the NULL check.
// Dereferencing the same datatype through a volatile pointer seems to
// prevent that from happening.
// TODO(jbauman): Remove once we're on VC2015,
if (data)
static_cast<volatile T*>(data)->header;
// 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;
int32_t last_token_read() const { return command_buffer_->GetLastToken(); }
int32_t get_offset() const {
return command_buffer_->GetLastState().get_offset;
// Common Commands
void Noop(uint32_t skip_count) {
cmd::Noop* cmd = GetImmediateCmdSpace<cmd::Noop>(
(skip_count - 1) * sizeof(CommandBufferEntry));
if (cmd) {
void SetToken(uint32_t token) {
cmd::SetToken* cmd = GetCmdSpace<cmd::SetToken>();
if (cmd) {
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) {
void SetBucketDataImmediate(uint32_t bucket_id,
uint32_t offset,
const void* data,
uint32_t size) {
cmd::SetBucketDataImmediate* cmd =
if (cmd) {
cmd->Init(bucket_id, offset, size);
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) {
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) {
CommandBuffer* command_buffer() const {
return command_buffer_;
scoped_refptr<Buffer> get_ring_buffer() const { return ring_buffer_; }
uint32_t flush_generation() const { return flush_generation_; }
void FreeRingBuffer();
bool HaveRingBuffer() const {
return ring_buffer_id_ != -1;
bool usable () const {
return usable_;
void ClearUsable() {
usable_ = false;
context_lost_ = true;
// Overridden from base::trace_event::MemoryDumpProvider:
bool OnMemoryDump(const base::trace_event::MemoryDumpArgs& args,
base::trace_event::ProcessMemoryDump* pmd) override;
// Returns the number of available entries (they may not be contiguous).
int32_t AvailableEntries() {
return (get_offset() - put_ - 1 + total_entry_count_) % total_entry_count_;
void CalcImmediateEntries(int waiting_count);
bool AllocateRingBuffer();
void FreeResources();
// 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);
// Calls Flush if automatic flush conditions are met.
void PeriodicFlushCheck();
int32_t GetTotalFreeEntriesNoWaiting() const;
CommandBuffer* command_buffer_;
int32_t ring_buffer_id_;
int32_t ring_buffer_size_;
scoped_refptr<gpu::Buffer> ring_buffer_;
CommandBufferEntry* entries_;
int32_t total_entry_count_; // the total number of entries
int32_t immediate_entry_count_;
int32_t token_;
int32_t put_;
int32_t last_put_sent_;
int32_t last_barrier_put_sent_;
int commands_issued_;
bool usable_;
bool context_lost_;
bool flush_automatically_;
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_;
friend class CommandBufferHelperTest;
} // namespace gpu