blob: b43d0616aee172891867ec7226ce97bdf62008e1 [file]
/*
* Copyright 2025 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef skgpu_graphite_PipelineManager_DEFINED
#define skgpu_graphite_PipelineManager_DEFINED
#include "include/core/SkRefCnt.h"
#include "include/private/SkEnumBitMask.h"
#include "src/core/SkSpinlock.h"
#include "src/core/SkTHash.h"
#include <condition_variable>
#include <mutex>
namespace skgpu {
class UniqueKey;
}
class SkExecutor;
class SkTaskGroup;
namespace skgpu::graphite {
class GraphicsPipeline;
class GraphicsPipelineDesc;
class GraphicsPipelineHandle;
enum class PipelineCreationFlags : uint8_t;
class PipelineCreationTask;
struct RenderPassDesc;
class RuntimeEffectDictionary;
class SharedContext;
class PipelineManager {
public:
PipelineManager(SkExecutor* executor);
~PipelineManager();
// If an existing Pipeline is found, it is just wrapped in a Handle and returned.
// Otherwise, a compilation task is created and queued up for execution.
// If no Executor is provided the compilations will occur synchronously, in-line.
GraphicsPipelineHandle createHandle(
SharedContext*,
sk_sp<const RuntimeEffectDictionary> runtimeDict,
const GraphicsPipelineDesc&,
const RenderPassDesc&,
SkEnumBitMask<PipelineCreationFlags>);
sk_sp<GraphicsPipeline> resolveHandle(SharedContext*, const GraphicsPipelineHandle&);
// Wait for any in-flight tasks to complete. Additionally, disable the addition of any
// more threaded tasks.
void shutDown();
#if defined(GPU_TEST_UTILS)
void wait_TestOnly();
struct Stats {
// The number of times we find a pre-existing task for a Pipeline
int fNumPreemptivelyFoundTasks = 0;
int fNumTasksCreated = 0;
};
Stats getStats() const SK_EXCLUDES(fSpinLock);
#endif
private:
mutable SkSpinlock fSpinLock;
enum class Priority { kHigh = 0, kLow = 1 };
sk_sp<PipelineCreationTask> findOrCreateTask(
sk_sp<const RuntimeEffectDictionary> runtimeDict,
const UniqueKey& pipelineKey,
const GraphicsPipelineDesc&,
const RenderPassDesc&,
Priority) SK_EXCLUDES(fSpinLock);
void addTaskToWorkList(SharedContext*,
sk_sp<PipelineCreationTask>,
Priority);
void removeTask(PipelineCreationTask*) SK_EXCLUDES(fSpinLock);
struct Traits {
static const UniqueKey& GetKey(const sk_sp<PipelineCreationTask>&);
static uint32_t Hash(const UniqueKey& pipelineKey);
};
using TaskMap = skia_private::THashTable<sk_sp<PipelineCreationTask>, UniqueKey, Traits>;
TaskMap fActiveTasks SK_GUARDED_BY(fSpinLock);
#if defined(GPU_TEST_UTILS)
Stats fStats SK_GUARDED_BY(fSpinLock);
#endif
std::unique_ptr<SkTaskGroup> fTaskGroup SK_GUARDED_BY(fSpinLock);
void signalCompleted(PipelineCreationTask*);
void potentiallyWaitOn(SharedContext*, PipelineCreationTask*);
static void InlineCompile(SharedContext* sharedContext,
PipelineManager* pipelineManager,
PipelineCreationTask* task);
// We have the mutex and condition_variable here to limit the number of
// mutexes/semaphores we need for synchronizing access to the pipelines.
// The Context thread is the only place that resolves handles so we will only
// ever be waiting on at most one pipeline at a time and no other thread will
// need to block on waiting for a different pipeline. This means we don't need
// to add a condition_variable to every PipelineCreationTask.
std::mutex fMutex;
std::condition_variable fConditionVariable; // SK_GUARDED_BY(fMutex)
};
} // namespace skgpu::graphite
#endif // skgpu_graphite_PipelineManager_DEFINED