blob: b6581c04e34ef95b0e3c26d236f33d52ab2bf510 [file] [log] [blame]
/*
* 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 "GrInOrderDrawBuffer.h"
#include "GrRenderTarget.h"
#include "GrTexture.h"
#include "GrBufferAllocPool.h"
#include "GrIndexBuffer.h"
#include "GrVertexBuffer.h"
#include "GrGpu.h"
GrInOrderDrawBuffer::GrInOrderDrawBuffer(const GrGpu* gpu,
GrVertexBufferAllocPool* vertexPool,
GrIndexBufferAllocPool* indexPool)
: fAutoFlushTarget(NULL)
, fClipSet(true)
, fLastRectVertexLayout(0)
, fQuadIndexBuffer(NULL)
, fMaxQuads(0)
, fCurrQuad(0)
, fVertexPool(*vertexPool)
, fIndexPool(*indexPool) {
fCaps = gpu->getCaps();
GrAssert(NULL != vertexPool);
GrAssert(NULL != indexPool);
GeometryPoolState& poolState = fGeoPoolStateStack.push_back();
poolState.fUsedPoolVertexBytes = 0;
poolState.fUsedPoolIndexBytes = 0;
#if GR_DEBUG
poolState.fPoolVertexBuffer = (GrVertexBuffer*)~0;
poolState.fPoolStartVertex = ~0;
poolState.fPoolIndexBuffer = (GrIndexBuffer*)~0;
poolState.fPoolStartIndex = ~0;
#endif
}
GrInOrderDrawBuffer::~GrInOrderDrawBuffer() {
this->reset();
// This must be called by before the GrDrawTarget destructor
this->releaseGeometry();
GrSafeUnref(fQuadIndexBuffer);
GrSafeUnref(fAutoFlushTarget);
}
void GrInOrderDrawBuffer::initializeDrawStateAndClip(const GrDrawTarget& target) {
this->copyDrawState(target);
this->setClip(target.getClip());
}
void GrInOrderDrawBuffer::setQuadIndexBuffer(const GrIndexBuffer* indexBuffer) {
bool newIdxBuffer = fQuadIndexBuffer != indexBuffer;
if (newIdxBuffer) {
GrSafeUnref(fQuadIndexBuffer);
fQuadIndexBuffer = indexBuffer;
GrSafeRef(fQuadIndexBuffer);
fCurrQuad = 0;
fMaxQuads = (NULL == indexBuffer) ? 0 : indexBuffer->maxQuads();
} else {
GrAssert((NULL == indexBuffer && 0 == fMaxQuads) ||
(indexBuffer->maxQuads() == fMaxQuads));
}
}
void GrInOrderDrawBuffer::drawRect(const GrRect& rect,
const GrMatrix* matrix,
StageMask stageMask,
const GrRect* srcRects[],
const GrMatrix* srcMatrices[]) {
GrAssert(!(NULL == fQuadIndexBuffer && fCurrQuad));
GrAssert(!(fDraws.empty() && fCurrQuad));
GrAssert(!(0 != fMaxQuads && NULL == fQuadIndexBuffer));
GrDrawState* drawState = this->drawState();
// if we have a quad IB then either append to the previous run of
// rects or start a new run
if (fMaxQuads) {
bool appendToPreviousDraw = false;
GrVertexLayout layout = GetRectVertexLayout(stageMask, srcRects);
AutoReleaseGeometry geo(this, layout, 4, 0);
if (!geo.succeeded()) {
GrPrintf("Failed to get space for vertices!\n");
return;
}
GrMatrix combinedMatrix = drawState->getViewMatrix();
// We go to device space so that matrix changes allow us to concat
// rect draws. When the caller has provided explicit source rects
// then we don't want to modify the sampler matrices. Otherwise we do
// we have to account for the view matrix change in the sampler
// matrices.
StageMask devCoordMask = (NULL == srcRects) ? stageMask : 0;
GrDrawTarget::AutoDeviceCoordDraw adcd(this, devCoordMask);
if (NULL != matrix) {
combinedMatrix.preConcat(*matrix);
}
SetRectVertices(rect, &combinedMatrix, srcRects, srcMatrices, layout, geo.vertices());
// we don't want to miss an opportunity to batch rects together
// simply because the clip has changed if the clip doesn't affect
// the rect.
bool disabledClip = false;
if (drawState->isClipState() && fClip.isRect()) {
GrRect clipRect = fClip.getRect(0);
// If the clip rect touches the edge of the viewport, extended it
// out (close) to infinity to avoid bogus intersections.
// We might consider a more exact clip to viewport if this
// conservative test fails.
const GrRenderTarget* target = drawState->getRenderTarget();
if (0 >= clipRect.fLeft) {
clipRect.fLeft = GR_ScalarMin;
}
if (target->width() <= clipRect.fRight) {
clipRect.fRight = GR_ScalarMax;
}
if (0 >= clipRect.top()) {
clipRect.fTop = GR_ScalarMin;
}
if (target->height() <= clipRect.fBottom) {
clipRect.fBottom = GR_ScalarMax;
}
int stride = VertexSize(layout);
bool insideClip = true;
for (int v = 0; v < 4; ++v) {
const GrPoint& p = *GetVertexPoint(geo.vertices(), v, stride);
if (!clipRect.contains(p)) {
insideClip = false;
break;
}
}
if (insideClip) {
drawState->disableState(GrDrawState::kClip_StateBit);
disabledClip = true;
}
}
if (!needsNewClip() && !needsNewState() && fCurrQuad > 0 &&
fCurrQuad < fMaxQuads && layout == fLastRectVertexLayout) {
int vsize = VertexSize(layout);
Draw& lastDraw = fDraws.back();
GrAssert(lastDraw.fIndexBuffer == fQuadIndexBuffer);
GrAssert(kTriangles_PrimitiveType == lastDraw.fPrimitiveType);
GrAssert(0 == lastDraw.fVertexCount % 4);
GrAssert(0 == lastDraw.fIndexCount % 6);
GrAssert(0 == lastDraw.fStartIndex);
GeometryPoolState& poolState = fGeoPoolStateStack.back();
bool clearSinceLastDraw =
fClears.count() &&
fClears.back().fBeforeDrawIdx == fDraws.count();
appendToPreviousDraw =
!clearSinceLastDraw &&
lastDraw.fVertexBuffer == poolState.fPoolVertexBuffer &&
(fCurrQuad * 4 + lastDraw.fStartVertex) == poolState.fPoolStartVertex;
if (appendToPreviousDraw) {
lastDraw.fVertexCount += 4;
lastDraw.fIndexCount += 6;
fCurrQuad += 1;
// we reserved above, so we should be the first
// use of this vertex reserveation.
GrAssert(0 == poolState.fUsedPoolVertexBytes);
poolState.fUsedPoolVertexBytes = 4 * vsize;
}
}
if (!appendToPreviousDraw) {
this->setIndexSourceToBuffer(fQuadIndexBuffer);
this->drawIndexed(kTriangles_PrimitiveType, 0, 0, 4, 6);
fCurrQuad = 1;
fLastRectVertexLayout = layout;
}
if (disabledClip) {
drawState->enableState(GrDrawState::kClip_StateBit);
}
} else {
INHERITED::drawRect(rect, matrix, stageMask, srcRects, srcMatrices);
}
}
void GrInOrderDrawBuffer::onDrawIndexed(GrPrimitiveType primitiveType,
int startVertex,
int startIndex,
int vertexCount,
int indexCount) {
if (!vertexCount || !indexCount) {
return;
}
fCurrQuad = 0;
GeometryPoolState& poolState = fGeoPoolStateStack.back();
Draw& draw = fDraws.push_back();
draw.fPrimitiveType = primitiveType;
draw.fStartVertex = startVertex;
draw.fStartIndex = startIndex;
draw.fVertexCount = vertexCount;
draw.fIndexCount = indexCount;
draw.fClipChanged = this->needsNewClip();
if (draw.fClipChanged) {
this->pushClip();
}
draw.fStateChanged = this->needsNewState();
if (draw.fStateChanged) {
this->pushState();
}
draw.fVertexLayout = this->getGeomSrc().fVertexLayout;
switch (this->getGeomSrc().fVertexSrc) {
case kBuffer_GeometrySrcType:
draw.fVertexBuffer = this->getGeomSrc().fVertexBuffer;
break;
case kReserved_GeometrySrcType: // fallthrough
case kArray_GeometrySrcType: {
size_t vertexBytes = (vertexCount + startVertex) *
VertexSize(this->getGeomSrc().fVertexLayout);
poolState.fUsedPoolVertexBytes =
GrMax(poolState.fUsedPoolVertexBytes, vertexBytes);
draw.fVertexBuffer = poolState.fPoolVertexBuffer;
draw.fStartVertex += poolState.fPoolStartVertex;
break;
}
default:
GrCrash("unknown geom src type");
}
draw.fVertexBuffer->ref();
switch (this->getGeomSrc().fIndexSrc) {
case kBuffer_GeometrySrcType:
draw.fIndexBuffer = this->getGeomSrc().fIndexBuffer;
break;
case kReserved_GeometrySrcType: // fallthrough
case kArray_GeometrySrcType: {
size_t indexBytes = (indexCount + startIndex) * sizeof(uint16_t);
poolState.fUsedPoolIndexBytes =
GrMax(poolState.fUsedPoolIndexBytes, indexBytes);
draw.fIndexBuffer = poolState.fPoolIndexBuffer;
draw.fStartIndex += poolState.fPoolStartIndex;
break;
}
default:
GrCrash("unknown geom src type");
}
draw.fIndexBuffer->ref();
}
void GrInOrderDrawBuffer::onDrawNonIndexed(GrPrimitiveType primitiveType,
int startVertex,
int vertexCount) {
if (!vertexCount) {
return;
}
fCurrQuad = 0;
GeometryPoolState& poolState = fGeoPoolStateStack.back();
Draw& draw = fDraws.push_back();
draw.fPrimitiveType = primitiveType;
draw.fStartVertex = startVertex;
draw.fStartIndex = 0;
draw.fVertexCount = vertexCount;
draw.fIndexCount = 0;
draw.fClipChanged = this->needsNewClip();
if (draw.fClipChanged) {
this->pushClip();
}
draw.fStateChanged = this->needsNewState();
if (draw.fStateChanged) {
this->pushState();
}
draw.fVertexLayout = this->getGeomSrc().fVertexLayout;
switch (this->getGeomSrc().fVertexSrc) {
case kBuffer_GeometrySrcType:
draw.fVertexBuffer = this->getGeomSrc().fVertexBuffer;
break;
case kReserved_GeometrySrcType: // fallthrough
case kArray_GeometrySrcType: {
size_t vertexBytes = (vertexCount + startVertex) *
VertexSize(this->getGeomSrc().fVertexLayout);
poolState.fUsedPoolVertexBytes =
GrMax(poolState.fUsedPoolVertexBytes, vertexBytes);
draw.fVertexBuffer = poolState.fPoolVertexBuffer;
draw.fStartVertex += poolState.fPoolStartVertex;
break;
}
default:
GrCrash("unknown geom src type");
}
draw.fVertexBuffer->ref();
draw.fIndexBuffer = NULL;
}
void GrInOrderDrawBuffer::clear(const GrIRect* rect, GrColor color) {
GrIRect r;
if (NULL == rect) {
// We could do something smart and remove previous draws and clears to
// the current render target. If we get that smart we have to make sure
// those draws aren't read before this clear (render-to-texture).
r.setLTRB(0, 0,
this->getDrawState().getRenderTarget()->width(),
this->getDrawState().getRenderTarget()->height());
rect = &r;
}
Clear& clr = fClears.push_back();
clr.fColor = color;
clr.fBeforeDrawIdx = fDraws.count();
clr.fRect = *rect;
}
void GrInOrderDrawBuffer::reset() {
GrAssert(1 == fGeoPoolStateStack.count());
this->resetVertexSource();
this->resetIndexSource();
uint32_t numStates = fStates.count();
for (uint32_t i = 0; i < numStates; ++i) {
const GrDrawState& dstate = this->accessSavedDrawState(fStates[i]);
for (int s = 0; s < GrDrawState::kNumStages; ++s) {
GrSafeUnref(dstate.getTexture(s));
}
GrSafeUnref(dstate.getRenderTarget());
}
int numDraws = fDraws.count();
for (int d = 0; d < numDraws; ++d) {
// we always have a VB, but not always an IB
GrAssert(NULL != fDraws[d].fVertexBuffer);
fDraws[d].fVertexBuffer->unref();
GrSafeUnref(fDraws[d].fIndexBuffer);
}
fDraws.reset();
fStates.reset();
fClears.reset();
fVertexPool.reset();
fIndexPool.reset();
fClips.reset();
fCurrQuad = 0;
}
void GrInOrderDrawBuffer::playback(GrDrawTarget* target) {
GrAssert(kReserved_GeometrySrcType != this->getGeomSrc().fVertexSrc);
GrAssert(kReserved_GeometrySrcType != this->getGeomSrc().fIndexSrc);
GrAssert(NULL != target);
GrAssert(target != this); // not considered and why?
int numDraws = fDraws.count();
if (!numDraws) {
return;
}
fVertexPool.unlock();
fIndexPool.unlock();
GrDrawTarget::AutoStateRestore asr(target);
GrDrawTarget::AutoClipRestore acr(target);
AutoGeometryPush agp(target);
int currState = ~0;
int currClip = ~0;
int currClear = 0;
for (int i = 0; i < numDraws; ++i) {
while (currClear < fClears.count() &&
i == fClears[currClear].fBeforeDrawIdx) {
target->clear(&fClears[currClear].fRect, fClears[currClear].fColor);
++currClear;
}
const Draw& draw = fDraws[i];
if (draw.fStateChanged) {
++currState;
target->restoreDrawState(fStates[currState]);
}
if (draw.fClipChanged) {
++currClip;
target->setClip(fClips[currClip]);
}
target->setVertexSourceToBuffer(draw.fVertexLayout, draw.fVertexBuffer);
if (draw.fIndexCount) {
target->setIndexSourceToBuffer(draw.fIndexBuffer);
}
if (draw.fIndexCount) {
target->drawIndexed(draw.fPrimitiveType,
draw.fStartVertex,
draw.fStartIndex,
draw.fVertexCount,
draw.fIndexCount);
} else {
target->drawNonIndexed(draw.fPrimitiveType,
draw.fStartVertex,
draw.fVertexCount);
}
}
while (currClear < fClears.count()) {
GrAssert(fDraws.count() == fClears[currClear].fBeforeDrawIdx);
target->clear(&fClears[currClear].fRect, fClears[currClear].fColor);
++currClear;
}
}
void GrInOrderDrawBuffer::setAutoFlushTarget(GrDrawTarget* target) {
GrSafeAssign(fAutoFlushTarget, target);
}
void GrInOrderDrawBuffer::willReserveVertexAndIndexSpace(
GrVertexLayout vertexLayout,
int vertexCount,
int indexCount) {
if (NULL != fAutoFlushTarget) {
// We use geometryHints() to know whether to flush the draw buffer. We
// can't flush if we are inside an unbalanced pushGeometrySource.
// Moreover, flushing blows away vertex and index data that was
// previously reserved. So if the vertex or index data is pulled from
// reserved space and won't be released by this request then we can't
// flush.
bool insideGeoPush = fGeoPoolStateStack.count() > 1;
bool unreleasedVertexSpace =
!vertexCount &&
kReserved_GeometrySrcType == this->getGeomSrc().fVertexSrc;
bool unreleasedIndexSpace =
!indexCount &&
kReserved_GeometrySrcType == this->getGeomSrc().fIndexSrc;
// we don't want to finalize any reserved geom on the target since
// we don't know that the client has finished writing to it.
bool targetHasReservedGeom =
fAutoFlushTarget->hasReservedVerticesOrIndices();
int vcount = vertexCount;
int icount = indexCount;
if (!insideGeoPush &&
!unreleasedVertexSpace &&
!unreleasedIndexSpace &&
!targetHasReservedGeom &&
this->geometryHints(vertexLayout, &vcount, &icount)) {
this->flushTo(fAutoFlushTarget);
}
}
}
bool GrInOrderDrawBuffer::geometryHints(GrVertexLayout vertexLayout,
int* vertexCount,
int* indexCount) const {
// we will recommend a flush if the data could fit in a single
// preallocated buffer but none are left and it can't fit
// in the current buffer (which may not be prealloced).
bool flush = false;
if (NULL != indexCount) {
int32_t currIndices = fIndexPool.currentBufferIndices();
if (*indexCount > currIndices &&
(!fIndexPool.preallocatedBuffersRemaining() &&
*indexCount <= fIndexPool.preallocatedBufferIndices())) {
flush = true;
}
*indexCount = currIndices;
}
if (NULL != vertexCount) {
int32_t currVertices = fVertexPool.currentBufferVertices(vertexLayout);
if (*vertexCount > currVertices &&
(!fVertexPool.preallocatedBuffersRemaining() &&
*vertexCount <= fVertexPool.preallocatedBufferVertices(vertexLayout))) {
flush = true;
}
*vertexCount = currVertices;
}
return flush;
}
bool GrInOrderDrawBuffer::onReserveVertexSpace(GrVertexLayout vertexLayout,
int vertexCount,
void** vertices) {
GeometryPoolState& poolState = fGeoPoolStateStack.back();
GrAssert(vertexCount > 0);
GrAssert(NULL != vertices);
GrAssert(0 == poolState.fUsedPoolVertexBytes);
*vertices = fVertexPool.makeSpace(vertexLayout,
vertexCount,
&poolState.fPoolVertexBuffer,
&poolState.fPoolStartVertex);
return NULL != *vertices;
}
bool GrInOrderDrawBuffer::onReserveIndexSpace(int indexCount, void** indices) {
GeometryPoolState& poolState = fGeoPoolStateStack.back();
GrAssert(indexCount > 0);
GrAssert(NULL != indices);
GrAssert(0 == poolState.fUsedPoolIndexBytes);
*indices = fIndexPool.makeSpace(indexCount,
&poolState.fPoolIndexBuffer,
&poolState.fPoolStartIndex);
return NULL != *indices;
}
void GrInOrderDrawBuffer::releaseReservedVertexSpace() {
GeometryPoolState& poolState = fGeoPoolStateStack.back();
const GeometrySrcState& geoSrc = this->getGeomSrc();
GrAssert(kReserved_GeometrySrcType == geoSrc.fVertexSrc);
// When the caller reserved vertex buffer space we gave it back a pointer
// provided by the vertex buffer pool. At each draw we tracked the largest
// offset into the pool's pointer that was referenced. Now we return to the
// pool any portion at the tail of the allocation that no draw referenced.
size_t reservedVertexBytes = VertexSize(geoSrc.fVertexLayout) *
geoSrc.fVertexCount;
fVertexPool.putBack(reservedVertexBytes -
poolState.fUsedPoolVertexBytes);
poolState.fUsedPoolVertexBytes = 0;
poolState.fPoolVertexBuffer = NULL;
poolState.fPoolStartVertex = 0;
}
void GrInOrderDrawBuffer::releaseReservedIndexSpace() {
GeometryPoolState& poolState = fGeoPoolStateStack.back();
const GeometrySrcState& geoSrc = this->getGeomSrc();
GrAssert(kReserved_GeometrySrcType == geoSrc.fIndexSrc);
// Similar to releaseReservedVertexSpace we return any unused portion at
// the tail
size_t reservedIndexBytes = sizeof(uint16_t) * geoSrc.fIndexCount;
fIndexPool.putBack(reservedIndexBytes - poolState.fUsedPoolIndexBytes);
poolState.fUsedPoolIndexBytes = 0;
poolState.fPoolIndexBuffer = NULL;
poolState.fPoolStartIndex = 0;
}
void GrInOrderDrawBuffer::onSetVertexSourceToArray(const void* vertexArray,
int vertexCount) {
GeometryPoolState& poolState = fGeoPoolStateStack.back();
GrAssert(0 == poolState.fUsedPoolVertexBytes);
#if GR_DEBUG
bool success =
#endif
fVertexPool.appendVertices(this->getGeomSrc().fVertexLayout,
vertexCount,
vertexArray,
&poolState.fPoolVertexBuffer,
&poolState.fPoolStartVertex);
GR_DEBUGASSERT(success);
}
void GrInOrderDrawBuffer::onSetIndexSourceToArray(const void* indexArray,
int indexCount) {
GeometryPoolState& poolState = fGeoPoolStateStack.back();
GrAssert(0 == poolState.fUsedPoolIndexBytes);
#if GR_DEBUG
bool success =
#endif
fIndexPool.appendIndices(indexCount,
indexArray,
&poolState.fPoolIndexBuffer,
&poolState.fPoolStartIndex);
GR_DEBUGASSERT(success);
}
void GrInOrderDrawBuffer::releaseVertexArray() {
// When the client provides an array as the vertex source we handled it
// by copying their array into reserved space.
this->GrInOrderDrawBuffer::releaseReservedVertexSpace();
}
void GrInOrderDrawBuffer::releaseIndexArray() {
// When the client provides an array as the index source we handled it
// by copying their array into reserved space.
this->GrInOrderDrawBuffer::releaseReservedIndexSpace();
}
void GrInOrderDrawBuffer::geometrySourceWillPush() {
GeometryPoolState& poolState = fGeoPoolStateStack.push_back();
poolState.fUsedPoolVertexBytes = 0;
poolState.fUsedPoolIndexBytes = 0;
#if GR_DEBUG
poolState.fPoolVertexBuffer = (GrVertexBuffer*)~0;
poolState.fPoolStartVertex = ~0;
poolState.fPoolIndexBuffer = (GrIndexBuffer*)~0;
poolState.fPoolStartIndex = ~0;
#endif
}
void GrInOrderDrawBuffer::geometrySourceWillPop(
const GeometrySrcState& restoredState) {
GrAssert(fGeoPoolStateStack.count() > 1);
fGeoPoolStateStack.pop_back();
GeometryPoolState& poolState = fGeoPoolStateStack.back();
// we have to assume that any slack we had in our vertex/index data
// is now unreleasable because data may have been appended later in the
// pool.
if (kReserved_GeometrySrcType == restoredState.fVertexSrc ||
kArray_GeometrySrcType == restoredState.fVertexSrc) {
poolState.fUsedPoolVertexBytes =
VertexSize(restoredState.fVertexLayout) *
restoredState.fVertexCount;
}
if (kReserved_GeometrySrcType == restoredState.fIndexSrc ||
kArray_GeometrySrcType == restoredState.fIndexSrc) {
poolState.fUsedPoolIndexBytes = sizeof(uint16_t) *
restoredState.fIndexCount;
}
}
bool GrInOrderDrawBuffer::needsNewState() const {
if (fStates.empty()) {
return true;
} else {
const GrDrawState& old = this->accessSavedDrawState(fStates.back());
return old != fCurrDrawState;
}
}
void GrInOrderDrawBuffer::pushState() {
const GrDrawState& drawState = this->getDrawState();
for (int s = 0; s < GrDrawState::kNumStages; ++s) {
GrSafeRef(drawState.getTexture(s));
}
GrSafeRef(drawState.getRenderTarget());
this->saveCurrentDrawState(&fStates.push_back());
}
bool GrInOrderDrawBuffer::needsNewClip() const {
if (this->getDrawState().isClipState()) {
if (fClips.empty() || (fClipSet && fClips.back() != fClip)) {
return true;
}
}
return false;
}
void GrInOrderDrawBuffer::pushClip() {
fClips.push_back() = fClip;
fClipSet = false;
}
void GrInOrderDrawBuffer::clipWillBeSet(const GrClip& newClip) {
INHERITED::clipWillBeSet(newClip);
fClipSet = true;
}