blob: e30c0896fbe2adf5ba5715ba53d1996e207234e0 [file] [log] [blame] [edit]
// 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 CC_RESOURCES_RESOURCE_POOL_H_
#define CC_RESOURCES_RESOURCE_POOL_H_
#include <stddef.h>
#include <stdint.h>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include "base/containers/circular_deque.h"
#include "base/gtest_prod_util.h"
#include "base/memory/memory_pressure_listener.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/time/tick_clock.h"
#include "base/time/time.h"
#include "base/trace_event/memory_allocator_dump_guid.h"
#include "base/trace_event/memory_dump_provider.h"
#include "cc/cc_export.h"
#include "components/viz/common/resources/resource_id.h"
#include "components/viz/common/resources/resource_sizes.h"
#include "components/viz/common/resources/shared_image_format.h"
#include "components/viz/common/resources/transferable_resource.h"
#include "gpu/command_buffer/client/client_shared_image.h"
#include "gpu/command_buffer/client/shared_image_interface.h"
#include "gpu/command_buffer/common/sync_token.h"
#include "ui/gfx/color_space.h"
#include "ui/gfx/geometry/rect.h"
namespace base {
class SingleThreadTaskRunner;
}
namespace viz {
class ClientResourceProvider;
class RasterContextProvider;
}
namespace cc {
class CC_EXPORT ResourcePool : public base::trace_event::MemoryDumpProvider {
class PoolResource;
public:
// Delay before a resource is considered expired.
static constexpr base::TimeDelta kDefaultExpirationDelay = base::Seconds(5);
// Max delay before an evicted resource is flushed.
static constexpr base::TimeDelta kDefaultMaxFlushDelay = base::Seconds(1);
// A class to hold ownership of PoolResources.
class CC_EXPORT Backing {
public:
Backing(const gfx::Size& size,
viz::SharedImageFormat format,
const gfx::ColorSpace& color_space);
virtual ~Backing();
// Creates a SharedImage with the given `usage`. After invocation,
// `shared_image()` is guaranteed to be non-null.
void CreateSharedImage(gpu::SharedImageInterface* sii,
const gpu::SharedImageUsageSet& usage,
std::string_view debug_label);
// Creates a software SharedImage with the given `usage`. After invocation,
// `shared_image()` is guaranteed to be non-null.
void CreateSharedImageForSoftwareCompositor(gpu::SharedImageInterface* sii,
std::string_view debug_label);
// Creates a mappable SharedImage with the given `usage` and `buffer_usage`.
// Returns whether creation succeeded. After invocation, `shared_image()`
// will be non-null if creation succeeded.
// NOTE: This can be called on worker threads but the client must ensure
// that `can_access_shared_image_on_compositor_thread` is false at any time
// this this method would be invoked on a worker thread. The standard usage
// model for doing so is to check on the compositor thread whether the
// backing's SharedImage exists *before* the initiation of the flow that
// would cause it to be created on a worker thread, set
// `can_access_shared_image_on_compositor_thread` to false if the
// SharedImage does not exist at that point, and set
// `can_access_shared_image_on_compositor_thread` back to true *after* the
// worker thread flow is known to have finished.
bool CreateSharedImage(gpu::SharedImageInterface* sii,
const gpu::SharedImageUsageSet& usage,
std::string_view debug_label,
gfx::BufferUsage buffer_usage);
void CreateSharedImageForTesting();
void CreateSharedImageForTesting(uint32_t texture_target);
// NOTE: This can be called on worker threads but the client must ensure
// that `can_access_shared_image_on_compositor_thread` is true at any time
// this method would be invoked on a worker thread. As the client can not in
// general a prior know when `clear_shared_image()` might need to be called
// as part of the worker thread flow (since it generally depends on some
// operation on the SharedImage failing), the standard usage model is *not*
// to call this on a worker thread but rather to record a boolean on the
// worker thread indicating whether the SharedImage needs to be cleared and
// to read that boolean on the compositor thread after the worker thread
// flow is known to have finished in order to actually clear the
// SharedImage.
void clear_shared_image() { shared_image_.reset(); }
scoped_refptr<gpu::ClientSharedImage> shared_image() {
return shared_image_;
}
const gfx::Size& size() const { return size_; }
const viz::SharedImageFormat& format() const { return format_; }
const gfx::ColorSpace& color_space() const { return color_space_; }
// If this field is set to false, the backing's SharedImage is in the
// process of being created on a worker thread and should not be accessed on
// the compositor thread.
bool can_access_shared_image_on_compositor_thread = true;
gpu::SyncToken mailbox_sync_token;
// For resources that are modified directly on the gpu, outside the command
// stream, a fence must be used to know when the backing is not in use and
// may be returned to and reused by the pool.
bool wait_on_fence_required = false;
// Set by the ResourcePool when a resource is returned from the display
// compositor, or when the resource texture and mailbox are created for the
// first time, if the resource is shared with another context. The client of
// ResourcePool needs to wait on this token if it exists, before using a
// resource handed out by the ResourcePool.
gpu::SyncToken returned_sync_token;
// True if the backing is using raw draw.
bool is_using_raw_draw = false;
private:
scoped_refptr<gpu::ClientSharedImage> shared_image_;
const gfx::Size size_;
const viz::SharedImageFormat format_;
const gfx::ColorSpace color_space_;
};
// Scoped move-only object returned when getting a resource from the pool.
// Ownership must be given back to the pool to release the resource.
class CC_EXPORT InUsePoolResource {
public:
InUsePoolResource() = default;
~InUsePoolResource() {
DCHECK(!resource_) << "Must be returned to ResourcePool to be freed.";
}
InUsePoolResource(InUsePoolResource&& other) {
resource_ = other.resource_;
other.resource_ = nullptr;
}
InUsePoolResource& operator=(InUsePoolResource&& other) {
resource_ = other.resource_;
other.resource_ = nullptr;
return *this;
}
InUsePoolResource(const InUsePoolResource&) = delete;
InUsePoolResource& operator=(const InUsePoolResource&) = delete;
explicit operator bool() const { return !!resource_; }
const gfx::Size& size() const { return resource_->size(); }
const viz::SharedImageFormat& format() const { return resource_->format(); }
const gfx::ColorSpace& color_space() const {
return resource_->color_space();
}
// The ResourceId when the backing is given to the ResourceProvider for
// export to the display compositor.
const viz::ResourceId& resource_id_for_export() const {
// The ResourceId should not be accessed before it is created!
DCHECK(resource_->resource_id());
return resource_->resource_id();
}
Backing* backing() const { return resource_->backing(); }
void set_backing(std::unique_ptr<Backing> backing) const {
return resource_->set_backing(std::move(backing));
}
// Creates a SharedImage based on the configuration of this resource and
// installs a backing for this resource that is itself backed by that SI.
void InstallGpuBacking(gpu::SharedImageInterface* sii,
bool is_overlay_candidate,
bool use_gpu_rasterization,
std::string_view debug_label) const;
// Creates a software SharedImage based on the configuration of this
// resource and installs a backing for this resource that is itself backed
// by that SI.
void InstallSoftwareBacking(scoped_refptr<gpu::SharedImageInterface> sii,
std::string_view debug_label) const;
size_t memory_usage() const {
DCHECK(resource_);
return resource_->memory_usage();
}
// Production code should not be built around these ids, but tests use them
// to check for identity.
size_t unique_id_for_testing() const { return resource_->unique_id(); }
private:
friend ResourcePool;
explicit InUsePoolResource(PoolResource* resource) : resource_(resource) {
DCHECK_EQ(resource->state(), PoolResource::kInUse);
}
void SetWasFreedByResourcePool() { resource_ = nullptr; }
// `resource_` is not a raw_ptr<...> for performance reasons (based on
// analysis of sampling profiler data and tab_search:top100:2020).
RAW_PTR_EXCLUSION PoolResource* resource_ = nullptr;
};
// When holding gpu resources, the |context_provider| should be non-null,
// and when holding software resources, it should be null. It is used for
// consistency checking as well as for correctness.
ResourcePool(viz::ClientResourceProvider* resource_provider,
viz::RasterContextProvider* context_provider,
scoped_refptr<base::SingleThreadTaskRunner> task_runner,
const base::TimeDelta& expiration_delay,
bool disallow_non_exact_reuse);
ResourcePool(const ResourcePool&) = delete;
~ResourcePool() override;
ResourcePool& operator=(const ResourcePool&) = delete;
// Tries to reuse a resource. If none are available, makes a new one.
InUsePoolResource AcquireResource(
const gfx::Size& size,
viz::SharedImageFormat format,
const gfx::ColorSpace& color_space,
const std::string& debug_name = std::string());
// Tries to acquire the resource with |previous_content_id| for us in partial
// raster. If successful, this function will return the invalidated rect which
// must be re-rastered in |total_invalidated_rect|.
InUsePoolResource TryAcquireResourceForPartialRaster(
uint64_t new_content_id,
const gfx::Rect& new_invalidated_rect,
uint64_t previous_content_id,
gfx::Rect* total_invalidated_rect,
const gfx::ColorSpace& raster_color_space,
const std::string& debug_name = std::string());
// Gives the InUsePoolResource a |resource_id_for_export()| in order to allow
// exporting of the resource to the display compositor. This must be called
// with a resource only after it has a backing allocated for it. Initially an
// acquired InUsePoolResource will be only metadata, and the backing is given
// to it by code which is aware of the expected backing type - currently by
// RasterBufferProvider::AcquireBufferForRaster().
// Returns false if the backing does not contain valid data, in particular
// a zero mailbox for Backing, in which case the resource is not exported,
// and true otherwise.
bool PrepareForExport(
const InUsePoolResource& resource,
viz::TransferableResource::ResourceSource resource_source);
// Marks any resources in the pool as invalid, preventing their reuse. Call if
// previous resources were allocated in one way, but future resources should
// be allocated in a different way.
void InvalidateResources();
// Called when a resource's content has been fully replaced (and is completely
// valid). Updates the resource's content ID to its new value.
void OnContentReplaced(const InUsePoolResource& in_use_resource,
uint64_t content_id);
void ReleaseResource(InUsePoolResource resource);
void SetResourceUsageLimits(size_t max_memory_usage_bytes,
size_t max_resource_count);
void ReduceResourceUsage();
bool ResourceUsageTooHigh();
size_t memory_usage_bytes() const {
return total_memory_usage_bytes_ - unused_memory_usage_bytes_;
}
size_t resource_count() const { return in_use_resources_.size(); }
// Overridden from base::trace_event::MemoryDumpProvider:
bool OnMemoryDump(const base::trace_event::MemoryDumpArgs& args,
base::trace_event::ProcessMemoryDump* pmd) override;
void OnMemoryPressure(
base::MemoryPressureListener::MemoryPressureLevel level);
size_t GetTotalMemoryUsageForTesting() const {
return total_memory_usage_bytes_;
}
size_t GetTotalResourceCountForTesting() const {
return total_resource_count_;
}
size_t GetBusyResourceCountForTesting() const {
return busy_resources_.size();
}
bool AllowsNonExactReUseForTesting() const {
return !disallow_non_exact_reuse_;
}
// Overrides internal clock for testing purposes.
void SetClockForTesting(const base::TickClock* clock) { clock_ = clock; }
int tracing_id() const { return tracing_id_; }
private:
FRIEND_TEST_ALL_PREFIXES(ResourcePoolTest, ReuseResource);
FRIEND_TEST_ALL_PREFIXES(ResourcePoolTest, ExactRequestsRespected);
class PoolResource {
public:
PoolResource(ResourcePool* resource_pool,
size_t unique_id,
const gfx::Size& size,
viz::SharedImageFormat format,
const gfx::ColorSpace& color_space);
~PoolResource();
size_t unique_id() const { return unique_id_; }
const gfx::Size& size() const { return size_; }
const viz::SharedImageFormat& format() const { return format_; }
const gfx::ColorSpace& color_space() const { return color_space_; }
const viz::ResourceId& resource_id() const { return resource_id_; }
void set_resource_id(viz::ResourceId id) { resource_id_ = id; }
Backing* backing() const { return backing_.get(); }
void set_backing(std::unique_ptr<Backing> backing) {
DCHECK(backing);
DCHECK(!backing_);
backing_ = std::move(backing);
resource_pool_->OnBackingAllocated(this);
}
uint64_t content_id() const { return content_id_; }
void set_content_id(uint64_t content_id) { content_id_ = content_id; }
base::TimeTicks last_usage() const { return last_usage_; }
void set_last_usage(base::TimeTicks time) { last_usage_ = time; }
const gfx::Rect& invalidated_rect() const { return invalidated_rect_; }
void set_invalidated_rect(const gfx::Rect& invalidated_rect) {
invalidated_rect_ = invalidated_rect;
}
bool avoid_reuse() const { return avoid_reuse_; }
void mark_avoid_reuse() { avoid_reuse_ = true; }
void OnMemoryDump(base::trace_event::ProcessMemoryDump* pmd,
int tracing_id,
const viz::ClientResourceProvider* resource_provider,
bool is_free,
bool is_busy) const;
void set_debug_name(const std::string& name) { debug_name_ = name; }
const std::string& debug_name() const { return debug_name_; }
ResourcePool* resource_pool() const { return resource_pool_; }
enum State {
// kUnused means the resource is free for reusing or releasing.
// A new created resource is in kUnused as well.
kUnused,
// kInUse means the resource is being used viz InUsePoolResource.
// The InUsePoolResource can be released by calling
// ResourcePool::ReleaseResource(), after that, the state will be changed
// to kBusy or kUnused depends on if the resource is exported.
kInUse,
// The resource has been exported (sent) to viz process for compositing.
// When the resource is returned from the viz, the state will be changed
// to kUnused.
kBusy,
};
State state() const { return state_; }
void set_state(State state) { state_ = state; }
size_t memory_usage() const {
if (!backing_) {
return 0;
}
size_t memory_usage = format().EstimatedSizeInBytes(size());
// Early research found with raw draw, GPU memory usage is reduced to
// 50%, so we consider a raw draw backing uses 50% of a normal backing
// in average.
// TODO(crbug.com/40214331): use accurate size for raw draw backings.
if (backing_->is_using_raw_draw) {
memory_usage = memory_usage / 2;
}
return memory_usage;
}
private:
const raw_ptr<ResourcePool> resource_pool_;
const size_t unique_id_;
const gfx::Size size_;
const viz::SharedImageFormat format_;
const gfx::ColorSpace color_space_;
uint64_t content_id_ = 0;
base::TimeTicks last_usage_;
gfx::Rect invalidated_rect_;
// Set to true for resources that should be destroyed instead of returned to
// the pool for reuse.
bool avoid_reuse_ = false;
// An id used to name the backing for transfer to the display compositor.
viz::ResourceId resource_id_ = viz::kInvalidResourceId;
// The backing for this resource. Initially null for resources given
// out by ResourcePool, to be filled in by the client. Is destroyed on the
// compositor thread.
std::unique_ptr<Backing> backing_;
// Used for debugging and tracing.
std::string debug_name_;
// The current resource state. See enum State for detail.
State state_ = kUnused;
};
// Called when backing is set for the PoolResource.
void OnBackingAllocated(PoolResource* resource);
// Callback from the ResourceProvider to notify when an exported PoolResource
// is not busy and may be reused.
void OnResourceReleased(size_t unique_id,
const gpu::SyncToken& sync_token,
bool lost);
// Tries to reuse a resource. Returns |nullptr| if none are available.
PoolResource* ReuseResource(const gfx::Size& size,
viz::SharedImageFormat format,
const gfx::ColorSpace& color_space);
// Creates a new resource without trying to reuse an old one.
PoolResource* CreateResource(const gfx::Size& size,
viz::SharedImageFormat format,
const gfx::ColorSpace& color_space);
void DidFinishUsingResource(std::unique_ptr<PoolResource> resource);
void DeleteResource(std::unique_ptr<PoolResource> resource);
static void UpdateResourceContentIdAndInvalidation(
PoolResource* resource,
uint64_t new_content_id,
const gfx::Rect& new_invalidated_rect);
// Functions which manage periodic eviction of expired resources.
void ScheduleEvictExpiredResourcesIn(base::TimeDelta time_from_now);
void EvictExpiredResources();
void EvictResourcesNotUsedSince(base::TimeTicks time_limit);
bool HasEvictableResources() const;
base::TimeTicks GetUsageTimeForLRUResource() const;
void FlushEvictedResources();
const raw_ptr<viz::ClientResourceProvider> resource_provider_;
const raw_ptr<viz::RasterContextProvider> context_provider_;
const scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
const base::TimeDelta resource_expiration_delay_;
const bool disallow_non_exact_reuse_ = false;
const int tracing_id_;
size_t next_resource_unique_id_ = 1;
size_t max_memory_usage_bytes_ = 0;
size_t max_resource_count_ = 0;
size_t unused_memory_usage_bytes_ = 0;
size_t total_memory_usage_bytes_ = 0;
size_t total_resource_count_ = 0;
bool evict_expired_resources_pending_ = false;
bool evict_busy_resources_when_unused_ = false;
// Holds most recently used resources at the front of the queue.
base::circular_deque<std::unique_ptr<PoolResource>> unused_resources_;
base::circular_deque<std::unique_ptr<PoolResource>> busy_resources_;
// Map from the PoolResource |unique_id| to the PoolResource.
std::map<size_t, std::unique_ptr<PoolResource>> in_use_resources_;
std::unique_ptr<base::MemoryPressureListener> memory_pressure_listener_;
base::TimeTicks flush_evicted_resources_deadline_;
raw_ptr<const base::TickClock> clock_;
base::WeakPtrFactory<ResourcePool> weak_ptr_factory_{this};
};
} // namespace cc
#endif // CC_RESOURCES_RESOURCE_POOL_H_