/*
* Copyright 2011 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "GrGpuGL.h"
#include "GrEffect.h"
#include "GrGLEffect.h"
#include "SkRTConf.h"
#include "GrGLNameAllocator.h"
#include "SkTSearch.h"
#ifdef PROGRAM_CACHE_STATS
SK_CONF_DECLARE(bool, c_DisplayCache, "gpu.displayCache", false,
"Display program cache usage.");
#endif
typedef GrGLUniformManager::UniformHandle UniformHandle;
struct GrGpuGL::ProgramCache::Entry {
SK_DECLARE_INST_COUNT_ROOT(Entry);
Entry() : fProgram(NULL), fLRUStamp(0) {}
SkAutoTUnref<GrGLProgram> fProgram;
unsigned int fLRUStamp;
};
struct GrGpuGL::ProgramCache::ProgDescLess {
bool operator() (const GrGLProgramDesc& desc, const Entry* entry) {
SkASSERT(NULL != entry->fProgram.get());
return GrGLProgramDesc::Less(desc, entry->fProgram->getDesc());
}
bool operator() (const Entry* entry, const GrGLProgramDesc& desc) {
SkASSERT(NULL != entry->fProgram.get());
return GrGLProgramDesc::Less(entry->fProgram->getDesc(), desc);
}
};
GrGpuGL::ProgramCache::ProgramCache(GrGpuGL* gpu)
: fCount(0)
, fCurrLRUStamp(0)
, fGpu(gpu)
#ifdef PROGRAM_CACHE_STATS
, fTotalRequests(0)
, fCacheMisses(0)
, fHashMisses(0)
#endif
{
for (int i = 0; i < 1 << kHashBits; ++i) {
fHashTable[i] = NULL;
}
}
GrGpuGL::ProgramCache::~ProgramCache() {
for (int i = 0; i < fCount; ++i){
SkDELETE(fEntries[i]);
}
// dump stats
#ifdef PROGRAM_CACHE_STATS
if (c_DisplayCache) {
SkDebugf("--- Program Cache ---\n");
SkDebugf("Total requests: %d\n", fTotalRequests);
SkDebugf("Cache misses: %d\n", fCacheMisses);
SkDebugf("Cache miss %%: %f\n", (fTotalRequests > 0) ?
100.f * fCacheMisses / fTotalRequests :
0.f);
int cacheHits = fTotalRequests - fCacheMisses;
SkDebugf("Hash miss %%: %f\n", (cacheHits > 0) ? 100.f * fHashMisses / cacheHits : 0.f);
SkDebugf("---------------------\n");
}
#endif
}
void GrGpuGL::ProgramCache::abandon() {
for (int i = 0; i < fCount; ++i) {
SkASSERT(NULL != fEntries[i]->fProgram.get());
fEntries[i]->fProgram->abandon();
fEntries[i]->fProgram.reset(NULL);
}
fCount = 0;
}
int GrGpuGL::ProgramCache::search(const GrGLProgramDesc& desc) const {
ProgDescLess less;
return SkTSearch(fEntries, fCount, desc, sizeof(Entry*), less);
}
GrGLProgram* GrGpuGL::ProgramCache::getProgram(const GrGLProgramDesc& desc,
const GrEffectStage* colorStages[],
const GrEffectStage* coverageStages[]) {
#ifdef PROGRAM_CACHE_STATS
++fTotalRequests;
#endif
Entry* entry = NULL;
uint32_t hashIdx = desc.getChecksum();
hashIdx ^= hashIdx >> 16;
if (kHashBits <= 8) {
hashIdx ^= hashIdx >> 8;
}
hashIdx &=((1 << kHashBits) - 1);
Entry* hashedEntry = fHashTable[hashIdx];
if (NULL != hashedEntry && hashedEntry->fProgram->getDesc() == desc) {
SkASSERT(NULL != hashedEntry->fProgram);
entry = hashedEntry;
}
int entryIdx;
if (NULL == entry) {
entryIdx = this->search(desc);
if (entryIdx >= 0) {
entry = fEntries[entryIdx];
#ifdef PROGRAM_CACHE_STATS
++fHashMisses;
#endif
}
}
if (NULL == entry) {
// We have a cache miss
#ifdef PROGRAM_CACHE_STATS
++fCacheMisses;
#endif
GrGLProgram* program = GrGLProgram::Create(fGpu, desc, colorStages, coverageStages);
if (NULL == program) {
return NULL;
}
int purgeIdx = 0;
if (fCount < kMaxEntries) {
entry = SkNEW(Entry);
purgeIdx = fCount++;
fEntries[purgeIdx] = entry;
} else {
SkASSERT(fCount == kMaxEntries);
purgeIdx = 0;
for (int i = 1; i < kMaxEntries; ++i) {
if (fEntries[i]->fLRUStamp < fEntries[purgeIdx]->fLRUStamp) {
purgeIdx = i;
}
}
entry = fEntries[purgeIdx];
int purgedHashIdx = entry->fProgram->getDesc().getChecksum() & ((1 << kHashBits) - 1);
if (fHashTable[purgedHashIdx] == entry) {
fHashTable[purgedHashIdx] = NULL;
}
}
SkASSERT(fEntries[purgeIdx] == entry);
entry->fProgram.reset(program);
// We need to shift fEntries around so that the entry currently at purgeIdx is placed
// just before the entry at ~entryIdx (in order to keep fEntries sorted by descriptor).
entryIdx = ~entryIdx;
if (entryIdx < purgeIdx) {
// Let E and P be the entries at index entryIdx and purgeIdx, respectively.
// If the entries array looks like this:
// aaaaEbbbbbPccccc
// we rearrange it to look like this:
// aaaaPEbbbbbccccc
size_t copySize = (purgeIdx - entryIdx) * sizeof(Entry*);
memmove(fEntries + entryIdx + 1, fEntries + entryIdx, copySize);
fEntries[entryIdx] = entry;
} else if (purgeIdx < entryIdx) {
// If the entries array looks like this:
// aaaaPbbbbbEccccc
// we rearrange it to look like this:
// aaaabbbbbPEccccc
size_t copySize = (entryIdx - purgeIdx - 1) * sizeof(Entry*);
memmove(fEntries + purgeIdx, fEntries + purgeIdx + 1, copySize);
fEntries[entryIdx - 1] = entry;
}
#ifdef SK_DEBUG
SkASSERT(NULL != fEntries[0]->fProgram.get());
for (int i = 0; i < fCount - 1; ++i) {
SkASSERT(NULL != fEntries[i + 1]->fProgram.get());
const GrGLProgramDesc& a = fEntries[i]->fProgram->getDesc();
const GrGLProgramDesc& b = fEntries[i + 1]->fProgram->getDesc();
SkASSERT(GrGLProgramDesc::Less(a, b));
SkASSERT(!GrGLProgramDesc::Less(b, a));
}
#endif
}
fHashTable[hashIdx] = entry;
entry->fLRUStamp = fCurrLRUStamp;
if (SK_MaxU32 == fCurrLRUStamp) {
// wrap around! just trash our LRU, one time hit.
for (int i = 0; i < fCount; ++i) {
fEntries[i]->fLRUStamp = 0;
}
}
++fCurrLRUStamp;
return entry->fProgram;
}
////////////////////////////////////////////////////////////////////////////////
void GrGpuGL::abandonResources(){
INHERITED::abandonResources();
fProgramCache->abandon();
fHWProgramID = 0;
fPathNameAllocator.reset(NULL);
}
////////////////////////////////////////////////////////////////////////////////
#define GL_CALL(X) GR_GL_CALL(this->glInterface(), X)
bool GrGpuGL::flushGraphicsState(DrawType type, const GrDeviceCoordTexture* dstCopy) {
const GrDrawState& drawState = this->getDrawState();
// GrGpu::setupClipAndFlushState should have already checked this and bailed if not true.
SkASSERT(NULL != drawState.getRenderTarget());
if (kStencilPath_DrawType == type) {
const GrRenderTarget* rt = this->getDrawState().getRenderTarget();
SkISize size;
size.set(rt->width(), rt->height());
this->setProjectionMatrix(drawState.getViewMatrix(), size, rt->origin());
} else {
this->flushMiscFixedFunctionState();
GrBlendCoeff srcCoeff;
GrBlendCoeff dstCoeff;
GrDrawState::BlendOptFlags blendOpts = drawState.getBlendOpts(false, &srcCoeff, &dstCoeff);
if (GrDrawState::kSkipDraw_BlendOptFlag & blendOpts) {
return false;
}
SkSTArray<8, const GrEffectStage*, true> colorStages;
SkSTArray<8, const GrEffectStage*, true> coverageStages;
GrGLProgramDesc desc;
GrGLProgramDesc::Build(this->getDrawState(),
type,
blendOpts,
srcCoeff,
dstCoeff,
this,
dstCopy,
&colorStages,
&coverageStages,
&desc);
fCurrentProgram.reset(fProgramCache->getProgram(desc,
colorStages.begin(),
coverageStages.begin()));
if (NULL == fCurrentProgram.get()) {
SkDEBUGFAIL("Failed to create program!");
return false;
}
SkASSERT((kDrawPath_DrawType != type && kDrawPaths_DrawType != type)
|| !fCurrentProgram->hasVertexShader());
fCurrentProgram.get()->ref();
GrGLuint programID = fCurrentProgram->programID();
if (fHWProgramID != programID) {
GL_CALL(UseProgram(programID));
fHWProgramID = programID;
}
fCurrentProgram->overrideBlend(&srcCoeff, &dstCoeff);
this->flushBlend(kDrawLines_DrawType == type, srcCoeff, dstCoeff);
fCurrentProgram->setData(blendOpts,
colorStages.begin(),
coverageStages.begin(),
dstCopy,
&fSharedGLProgramState);
}
this->flushStencil(type);
this->flushScissor();
this->flushAAState(type);
SkIRect* devRect = NULL;
SkIRect devClipBounds;
if (drawState.isClipState()) {
this->getClip()->getConservativeBounds(drawState.getRenderTarget(), &devClipBounds);
devRect = &devClipBounds;
}
// This must come after textures are flushed because a texture may need
// to be msaa-resolved (which will modify bound FBO state).
this->flushRenderTarget(devRect);
return true;
}
void GrGpuGL::setupGeometry(const DrawInfo& info, size_t* indexOffsetInBytes) {
GrGLsizei stride = static_cast<GrGLsizei>(this->getDrawState().getVertexSize());
size_t vertexOffsetInBytes = stride * info.startVertex();
const GeometryPoolState& geoPoolState = this->getGeomPoolState();
GrGLVertexBuffer* vbuf;
switch (this->getGeomSrc().fVertexSrc) {
case kBuffer_GeometrySrcType:
vbuf = (GrGLVertexBuffer*) this->getGeomSrc().fVertexBuffer;
break;
case kArray_GeometrySrcType:
case kReserved_GeometrySrcType:
this->finalizeReservedVertices();
vertexOffsetInBytes += geoPoolState.fPoolStartVertex * this->getGeomSrc().fVertexSize;
vbuf = (GrGLVertexBuffer*) geoPoolState.fPoolVertexBuffer;
break;
default:
vbuf = NULL; // suppress warning
SkFAIL("Unknown geometry src type!");
}
SkASSERT(NULL != vbuf);
SkASSERT(!vbuf->isMapped());
vertexOffsetInBytes += vbuf->baseOffset();
GrGLIndexBuffer* ibuf = NULL;
if (info.isIndexed()) {
SkASSERT(NULL != indexOffsetInBytes);
switch (this->getGeomSrc().fIndexSrc) {
case kBuffer_GeometrySrcType:
*indexOffsetInBytes = 0;
ibuf = (GrGLIndexBuffer*)this->getGeomSrc().fIndexBuffer;
break;
case kArray_GeometrySrcType:
case kReserved_GeometrySrcType:
this->finalizeReservedIndices();
*indexOffsetInBytes = geoPoolState.fPoolStartIndex * sizeof(GrGLushort);
ibuf = (GrGLIndexBuffer*) geoPoolState.fPoolIndexBuffer;
break;
default:
ibuf = NULL; // suppress warning
SkFAIL("Unknown geometry src type!");
}
SkASSERT(NULL != ibuf);
SkASSERT(!ibuf->isMapped());
*indexOffsetInBytes += ibuf->baseOffset();
}
GrGLAttribArrayState* attribState =
fHWGeometryState.bindArrayAndBuffersToDraw(this, vbuf, ibuf);
if (fCurrentProgram->hasVertexShader()) {
int vertexAttribCount = this->getDrawState().getVertexAttribCount();
uint32_t usedAttribArraysMask = 0;
const GrVertexAttrib* vertexAttrib = this->getDrawState().getVertexAttribs();
for (int vertexAttribIndex = 0; vertexAttribIndex < vertexAttribCount;
++vertexAttribIndex, ++vertexAttrib) {
usedAttribArraysMask |= (1 << vertexAttribIndex);
GrVertexAttribType attribType = vertexAttrib->fType;
attribState->set(this,
vertexAttribIndex,
vbuf,
GrGLAttribTypeToLayout(attribType).fCount,
GrGLAttribTypeToLayout(attribType).fType,
GrGLAttribTypeToLayout(attribType).fNormalized,
stride,
reinterpret_cast<GrGLvoid*>(
vertexOffsetInBytes + vertexAttrib->fOffset));
}
attribState->disableUnusedArrays(this, usedAttribArraysMask);
}
}