| /* |
| * Copyright 2026 Google LLC |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "src/gpu/GlobalResourceStats.h" |
| |
| #include "include/gpu/GpuTypes.h" |
| #include "src/core/SkTraceEvent.h" |
| |
| #if !defined(SK_DISABLE_TRACING) |
| |
| namespace skgpu { |
| |
| GlobalResourceStats& GlobalResourceStats::Singleton(Protected isProtected) { |
| static GlobalResourceStats kUnprotected{Protected::kNo}; |
| static GlobalResourceStats kProtected{Protected::kYes}; |
| return isProtected == Protected::kYes ? kProtected : kUnprotected; |
| } |
| |
| void GlobalResourceStats::recordNewResource(size_t size, Budgeted budgeted) { |
| fHasBeenUsed = true; |
| if (budgeted == Budgeted::kYes) { |
| fBudgetedBytes += size; |
| } else { |
| fUnbudgetedBytes += size; |
| } |
| } |
| |
| void GlobalResourceStats::recordPurgeResource(size_t size) { |
| fHasBeenUsed = true; |
| // This assumes all unbudgeted resources transition to budgeted resources before they are purged |
| fBudgetedBytes -= size; |
| } |
| |
| void GlobalResourceStats::recordResourceBudgetChange(size_t size, Budgeted newBudget) { |
| fHasBeenUsed = true; |
| // We're not worrying about synchronizing the update between unbudgeted and budgeted, tracing |
| // will eventually show the right state. |
| if (newBudget == Budgeted::kYes) { |
| // We're assuming that this budget change will not happen on a wrapped resource, so no need |
| // to adjust fWrappedBytes. |
| fUnbudgetedBytes -= size; |
| fBudgetedBytes += size; |
| } else { |
| fBudgetedBytes -= size; |
| fUnbudgetedBytes += size; |
| } |
| } |
| |
| void GlobalResourceStats::recordResourceUpdatedSize(size_t newSize, |
| size_t oldSize, |
| Budgeted budgeted) { |
| fHasBeenUsed = true; |
| // We could structure this as subtracting oldSize and then adding newSize, but we can keep it |
| // atomic by choosing += or -=. |
| if (budgeted == Budgeted::kYes) { |
| if (oldSize > newSize) { |
| fBudgetedBytes -= (oldSize - newSize); |
| } else { |
| fBudgetedBytes += (newSize - oldSize); |
| } |
| } else { |
| // We're assuming that this size change does not happen on a wrapped resource, so no need |
| // to adjust fWrappedBytes (this is possible because Ganesh does not query for changed |
| // resource sizes and Graphite does not cache wrapped resources so fWrappedBytes is 0). |
| if (oldSize > newSize) { |
| fUnbudgetedBytes -= (oldSize - newSize); |
| } else { |
| fUnbudgetedBytes += (newSize - oldSize); |
| } |
| } |
| } |
| |
| void GlobalResourceStats::recordResourcePurgeable(size_t size) { |
| fHasBeenUsed = true; |
| fPurgeableBytes += size; |
| } |
| |
| void GlobalResourceStats::recordResourceNonpurgeable(size_t size) { |
| fHasBeenUsed = true; |
| fPurgeableBytes -= size; |
| } |
| |
| void GlobalResourceStats::recordCreateBackendTexture(size_t size) { |
| fHasBeenUsed = true; |
| fBackendTexBytes += size; |
| // Trigger a trace for backend texture records since they happen outside the regular |
| // flush/submit flow. |
| this->traceStatsSummary(); |
| } |
| |
| void GlobalResourceStats::recordDeleteBackendTexture(size_t size) { |
| fHasBeenUsed = true; |
| fBackendTexBytes -= size; |
| this->traceStatsSummary(); |
| } |
| |
| void GlobalResourceStats::traceStatsSummary() const { |
| if (!fHasBeenUsed) { |
| return; |
| } |
| |
| // Grab a snapshot (these won't change, but they aren't loaded atomically) |
| size_t budgeted = fBudgetedBytes; |
| const size_t unbudgeted = fUnbudgetedBytes; |
| const size_t purgeable = fPurgeableBytes; |
| const size_t backendTex = fBackendTexBytes; |
| |
| // Separate budgeted and purgeable into a non-overlapping categories |
| if (budgeted > purgeable) { |
| budgeted -= purgeable; |
| } else { |
| budgeted = 0; |
| } |
| |
| // While the total could be derived within the trace data, it is helpful to trace the total |
| // as a visual anchor. |
| const size_t total = backendTex + unbudgeted + budgeted + purgeable; |
| |
| // This pushes the protected label to the front since Perfetto sorts tracks alphabetically. |
| #define TRACE_STATS(label) \ |
| TRACE_COUNTER1_ALWAYS("skia.gpu.cache", label " Total GPU Bytes", total); \ |
| TRACE_COUNTER1_ALWAYS("skia.gpu.cache", label " Purgeable GPU Bytes", purgeable); \ |
| TRACE_COUNTER1_ALWAYS("skia.gpu.cache", label " Budgeted GPU Bytes", budgeted); \ |
| TRACE_COUNTER1_ALWAYS("skia.gpu.cache", label " Unbudgeted GPU Bytes", unbudgeted); \ |
| TRACE_COUNTER1_ALWAYS("skia.gpu.cache", label " Backend Texture GPU Bytes", backendTex); |
| |
| // Issue traces with different counter names depending on protected or unprotected so they |
| // appear in separate tracks. |
| if (fProtected == Protected::kYes) { |
| TRACE_STATS("Skia Protected") |
| } else { |
| TRACE_STATS("Skia Unprotected") |
| } |
| |
| #undef TRACE_STATS |
| } |
| |
| } // namespace skgpu |
| |
| #endif // !defined(SK_DISABLE_TRACING) |