/*
* Copyright 2017 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "GrResourceAllocator.h"
#include "GrGpuResourcePriv.h"
#include "GrOpList.h"
#include "GrRenderTargetProxy.h"
#include "GrResourceCache.h"
#include "GrResourceProvider.h"
#include "GrSurfacePriv.h"
#include "GrSurfaceProxy.h"
#include "GrSurfaceProxyPriv.h"
#include "GrTextureProxy.h"
void GrResourceAllocator::Interval::assign(sk_sp<GrSurface> s) {
SkASSERT(!fAssignedSurface);
fAssignedSurface = s;
fProxy->priv().assign(std::move(s));
}
void GrResourceAllocator::markEndOfOpList(int opListIndex) {
SkASSERT(!fAssigned); // We shouldn't be adding any opLists after (or during) assignment
SkASSERT(fEndOfOpListOpIndices.count() == opListIndex);
if (!fEndOfOpListOpIndices.empty()) {
SkASSERT(fEndOfOpListOpIndices.back() < this->curOp());
}
fEndOfOpListOpIndices.push_back(this->curOp()); // This is the first op index of the next opList
}
GrResourceAllocator::~GrResourceAllocator() {
#ifndef SK_DISABLE_EXPLICIT_GPU_RESOURCE_ALLOCATION
SkASSERT(fIntvlList.empty());
SkASSERT(fActiveIntvls.empty());
SkASSERT(!fIntvlHash.count());
#endif
}
void GrResourceAllocator::addInterval(GrSurfaceProxy* proxy, unsigned int start, unsigned int end
SkDEBUGCODE(, bool isDirectDstRead)) {
SkASSERT(start <= end);
SkASSERT(!fAssigned); // We shouldn't be adding any intervals after (or during) assignment
if (Interval* intvl = fIntvlHash.find(proxy->uniqueID().asUInt())) {
// Revise the interval for an existing use
#ifdef SK_DEBUG
if (0 == start && 0 == end) {
// This interval is for the initial upload to a deferred proxy. Due to the vagaries
// of how deferred proxies are collected they can appear as uploads multiple times in a
// single opLists' list and as uploads in several opLists.
SkASSERT(0 == intvl->start());
} else if (isDirectDstRead) {
// Direct reads from the render target itself should occur w/in the existing interval
SkASSERT(intvl->start() <= start && intvl->end() >= end);
} else {
SkASSERT(intvl->end() <= start && intvl->end() <= end);
}
#endif
intvl->extendEnd(end);
return;
}
Interval* newIntvl;
if (fFreeIntervalList) {
newIntvl = fFreeIntervalList;
fFreeIntervalList = newIntvl->next();
newIntvl->resetTo(proxy, start, end);
} else {
newIntvl = fIntervalAllocator.make<Interval>(proxy, start, end);
}
fIntvlList.insertByIncreasingStart(newIntvl);
fIntvlHash.add(newIntvl);
#ifdef SK_DISABLE_EXPLICIT_GPU_RESOURCE_ALLOCATION
// FIXME: remove this once we can do the lazy instantiation from assign instead.
if (GrSurfaceProxy::LazyState::kNot != proxy->lazyInstantiationState()) {
proxy->priv().doLazyInstantiation(fResourceProvider);
}
#endif
}
GrResourceAllocator::Interval* GrResourceAllocator::IntervalList::popHead() {
Interval* temp = fHead;
if (temp) {
fHead = temp->next();
}
return temp;
}
// TODO: fuse this with insertByIncreasingEnd
void GrResourceAllocator::IntervalList::insertByIncreasingStart(Interval* intvl) {
if (!fHead) {
intvl->setNext(nullptr);
fHead = intvl;
} else if (intvl->start() <= fHead->start()) {
intvl->setNext(fHead);
fHead = intvl;
} else {
Interval* prev = fHead;
Interval* next = prev->next();
for (; next && intvl->start() > next->start(); prev = next, next = next->next()) {
}
intvl->setNext(next);
prev->setNext(intvl);
}
}
// TODO: fuse this with insertByIncreasingStart
void GrResourceAllocator::IntervalList::insertByIncreasingEnd(Interval* intvl) {
if (!fHead) {
intvl->setNext(nullptr);
fHead = intvl;
} else if (intvl->end() <= fHead->end()) {
intvl->setNext(fHead);
fHead = intvl;
} else {
Interval* prev = fHead;
Interval* next = prev->next();
for (; next && intvl->end() > next->end(); prev = next, next = next->next()) {
}
intvl->setNext(next);
prev->setNext(intvl);
}
}
// 'surface' can be reused. Add it back to the free pool.
void GrResourceAllocator::freeUpSurface(sk_sp<GrSurface> surface) {
const GrScratchKey &key = surface->resourcePriv().getScratchKey();
if (!key.isValid()) {
return; // can't do it w/o a valid scratch key
}
if (surface->getUniqueKey().isValid()) {
// If the surface has a unique key we throw it back into the resource cache.
// If things get really tight 'findSurfaceFor' may pull it back out but there is
// no need to have it in tight rotation.
return;
}
// TODO: fix this insertion so we get a more LRU-ish behavior
fFreePool.insert(key, surface.release());
}
// First try to reuse one of the recently allocated/used GrSurfaces in the free pool.
// If we can't find a useable one, create a new one.
sk_sp<GrSurface> GrResourceAllocator::findSurfaceFor(const GrSurfaceProxy* proxy,
bool needsStencil) {
// First look in the free pool
GrScratchKey key;
proxy->priv().computeScratchKey(&key);
auto filter = [&] (const GrSurface* s) {
return !proxy->priv().requiresNoPendingIO() || !s->surfacePriv().hasPendingIO();
};
sk_sp<GrSurface> surface(fFreePool.findAndRemove(key, filter));
if (surface) {
if (SkBudgeted::kYes == proxy->isBudgeted() &&
SkBudgeted::kNo == surface->resourcePriv().isBudgeted()) {
// This gets the job done but isn't quite correct. It would be better to try to
// match budgeted proxies w/ budgeted surface and unbudgeted w/ unbudgeted.
surface->resourcePriv().makeBudgeted();
}
GrSurfaceProxyPriv::AttachStencilIfNeeded(fResourceProvider, surface.get(), needsStencil);
return surface;
}
// Failing that, try to grab a new one from the resource cache
return proxy->priv().createSurface(fResourceProvider);
}
// Remove any intervals that end before the current index. Return their GrSurfaces
// to the free pool.
void GrResourceAllocator::expire(unsigned int curIndex) {
while (!fActiveIntvls.empty() && fActiveIntvls.peekHead()->end() < curIndex) {
Interval* temp = fActiveIntvls.popHead();
if (temp->wasAssignedSurface()) {
this->freeUpSurface(temp->detachSurface());
}
// Add temp to the free interval list so it can be reused
temp->setNext(fFreeIntervalList);
fFreeIntervalList = temp;
}
}
bool GrResourceAllocator::assign(int* startIndex, int* stopIndex, AssignError* outError) {
SkASSERT(outError);
*outError = AssignError::kNoError;
fIntvlHash.reset(); // we don't need the interval hash anymore
if (fIntvlList.empty()) {
return false; // nothing to render
}
*startIndex = fCurOpListIndex;
*stopIndex = fEndOfOpListOpIndices.count();
SkDEBUGCODE(fAssigned = true;)
while (Interval* cur = fIntvlList.popHead()) {
if (fEndOfOpListOpIndices[fCurOpListIndex] < cur->start()) {
fCurOpListIndex++;
}
this->expire(cur->start());
bool needsStencil = cur->proxy()->asRenderTargetProxy()
? cur->proxy()->asRenderTargetProxy()->needsStencil()
: false;
if (cur->proxy()->priv().isInstantiated()) {
GrSurfaceProxyPriv::AttachStencilIfNeeded(fResourceProvider,
cur->proxy()->priv().peekSurface(),
needsStencil);
fActiveIntvls.insertByIncreasingEnd(cur);
if (fResourceProvider->overBudget()) {
// Only force intermediate draws on opList boundaries
if (!fIntvlList.empty() &&
fEndOfOpListOpIndices[fCurOpListIndex] < fIntvlList.peekHead()->start()) {
*stopIndex = fCurOpListIndex+1;
return true;
}
}
continue;
}
if (GrSurfaceProxy::LazyState::kNot != cur->proxy()->lazyInstantiationState()) {
if (!cur->proxy()->priv().doLazyInstantiation(fResourceProvider)) {
*outError = AssignError::kFailedProxyInstantiation;
}
} else if (sk_sp<GrSurface> surface = this->findSurfaceFor(cur->proxy(), needsStencil)) {
// TODO: make getUniqueKey virtual on GrSurfaceProxy
GrTextureProxy* tex = cur->proxy()->asTextureProxy();
if (tex && tex->getUniqueKey().isValid()) {
fResourceProvider->assignUniqueKeyToResource(tex->getUniqueKey(), surface.get());
SkASSERT(surface->getUniqueKey() == tex->getUniqueKey());
}
cur->assign(std::move(surface));
} else {
SkASSERT(!cur->proxy()->priv().isInstantiated());
*outError = AssignError::kFailedProxyInstantiation;
}
fActiveIntvls.insertByIncreasingEnd(cur);
if (fResourceProvider->overBudget()) {
// Only force intermediate draws on opList boundaries
if (!fIntvlList.empty() &&
fEndOfOpListOpIndices[fCurOpListIndex] < fIntvlList.peekHead()->start()) {
*stopIndex = fCurOpListIndex+1;
return true;
}
}
}
// expire all the remaining intervals to drain the active interval list
this->expire(std::numeric_limits<unsigned int>::max());
return true;
}