michael@0: michael@0: /* michael@0: * Copyright 2010 Google Inc. michael@0: * michael@0: * Use of this source code is governed by a BSD-style license that can be michael@0: * found in the LICENSE file. michael@0: */ michael@0: michael@0: michael@0: michael@0: #include "GrDrawTarget.h" michael@0: #include "GrContext.h" michael@0: #include "GrDrawTargetCaps.h" michael@0: #include "GrPath.h" michael@0: #include "GrRenderTarget.h" michael@0: #include "GrTexture.h" michael@0: #include "GrVertexBuffer.h" michael@0: michael@0: #include "SkStrokeRec.h" michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: GrDrawTarget::DrawInfo& GrDrawTarget::DrawInfo::operator =(const DrawInfo& di) { michael@0: fPrimitiveType = di.fPrimitiveType; michael@0: fStartVertex = di.fStartVertex; michael@0: fStartIndex = di.fStartIndex; michael@0: fVertexCount = di.fVertexCount; michael@0: fIndexCount = di.fIndexCount; michael@0: michael@0: fInstanceCount = di.fInstanceCount; michael@0: fVerticesPerInstance = di.fVerticesPerInstance; michael@0: fIndicesPerInstance = di.fIndicesPerInstance; michael@0: michael@0: if (NULL != di.fDevBounds) { michael@0: SkASSERT(di.fDevBounds == &di.fDevBoundsStorage); michael@0: fDevBoundsStorage = di.fDevBoundsStorage; michael@0: fDevBounds = &fDevBoundsStorage; michael@0: } else { michael@0: fDevBounds = NULL; michael@0: } michael@0: michael@0: fDstCopy = di.fDstCopy; michael@0: michael@0: return *this; michael@0: } michael@0: michael@0: #ifdef SK_DEBUG michael@0: bool GrDrawTarget::DrawInfo::isInstanced() const { michael@0: if (fInstanceCount > 0) { michael@0: SkASSERT(0 == fIndexCount % fIndicesPerInstance); michael@0: SkASSERT(0 == fVertexCount % fVerticesPerInstance); michael@0: SkASSERT(fIndexCount / fIndicesPerInstance == fInstanceCount); michael@0: SkASSERT(fVertexCount / fVerticesPerInstance == fInstanceCount); michael@0: // there is no way to specify a non-zero start index to drawIndexedInstances(). michael@0: SkASSERT(0 == fStartIndex); michael@0: return true; michael@0: } else { michael@0: SkASSERT(!fVerticesPerInstance); michael@0: SkASSERT(!fIndicesPerInstance); michael@0: return false; michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: void GrDrawTarget::DrawInfo::adjustInstanceCount(int instanceOffset) { michael@0: SkASSERT(this->isInstanced()); michael@0: SkASSERT(instanceOffset + fInstanceCount >= 0); michael@0: fInstanceCount += instanceOffset; michael@0: fVertexCount = fVerticesPerInstance * fInstanceCount; michael@0: fIndexCount = fIndicesPerInstance * fInstanceCount; michael@0: } michael@0: michael@0: void GrDrawTarget::DrawInfo::adjustStartVertex(int vertexOffset) { michael@0: fStartVertex += vertexOffset; michael@0: SkASSERT(fStartVertex >= 0); michael@0: } michael@0: michael@0: void GrDrawTarget::DrawInfo::adjustStartIndex(int indexOffset) { michael@0: SkASSERT(this->isIndexed()); michael@0: fStartIndex += indexOffset; michael@0: SkASSERT(fStartIndex >= 0); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: #define DEBUG_INVAL_BUFFER 0xdeadcafe michael@0: #define DEBUG_INVAL_START_IDX -1 michael@0: michael@0: GrDrawTarget::GrDrawTarget(GrContext* context) michael@0: : fClip(NULL) michael@0: , fContext(context) michael@0: , fPushGpuTraceCount(0) { michael@0: SkASSERT(NULL != context); michael@0: michael@0: fDrawState = &fDefaultDrawState; michael@0: // We assume that fDrawState always owns a ref to the object it points at. michael@0: fDefaultDrawState.ref(); michael@0: GeometrySrcState& geoSrc = fGeoSrcStateStack.push_back(); michael@0: #ifdef SK_DEBUG michael@0: geoSrc.fVertexCount = DEBUG_INVAL_START_IDX; michael@0: geoSrc.fVertexBuffer = (GrVertexBuffer*)DEBUG_INVAL_BUFFER; michael@0: geoSrc.fIndexCount = DEBUG_INVAL_START_IDX; michael@0: geoSrc.fIndexBuffer = (GrIndexBuffer*)DEBUG_INVAL_BUFFER; michael@0: #endif michael@0: geoSrc.fVertexSrc = kNone_GeometrySrcType; michael@0: geoSrc.fIndexSrc = kNone_GeometrySrcType; michael@0: } michael@0: michael@0: GrDrawTarget::~GrDrawTarget() { michael@0: SkASSERT(1 == fGeoSrcStateStack.count()); michael@0: SkDEBUGCODE(GeometrySrcState& geoSrc = fGeoSrcStateStack.back()); michael@0: SkASSERT(kNone_GeometrySrcType == geoSrc.fIndexSrc); michael@0: SkASSERT(kNone_GeometrySrcType == geoSrc.fVertexSrc); michael@0: fDrawState->unref(); michael@0: } michael@0: michael@0: void GrDrawTarget::releaseGeometry() { michael@0: int popCnt = fGeoSrcStateStack.count() - 1; michael@0: while (popCnt) { michael@0: this->popGeometrySource(); michael@0: --popCnt; michael@0: } michael@0: this->resetVertexSource(); michael@0: this->resetIndexSource(); michael@0: } michael@0: michael@0: void GrDrawTarget::setClip(const GrClipData* clip) { michael@0: clipWillBeSet(clip); michael@0: fClip = clip; michael@0: } michael@0: michael@0: const GrClipData* GrDrawTarget::getClip() const { michael@0: return fClip; michael@0: } michael@0: michael@0: void GrDrawTarget::setDrawState(GrDrawState* drawState) { michael@0: SkASSERT(NULL != fDrawState); michael@0: if (NULL == drawState) { michael@0: drawState = &fDefaultDrawState; michael@0: } michael@0: if (fDrawState != drawState) { michael@0: fDrawState->unref(); michael@0: drawState->ref(); michael@0: fDrawState = drawState; michael@0: } michael@0: } michael@0: michael@0: bool GrDrawTarget::reserveVertexSpace(size_t vertexSize, michael@0: int vertexCount, michael@0: void** vertices) { michael@0: GeometrySrcState& geoSrc = fGeoSrcStateStack.back(); michael@0: bool acquired = false; michael@0: if (vertexCount > 0) { michael@0: SkASSERT(NULL != vertices); michael@0: this->releasePreviousVertexSource(); michael@0: geoSrc.fVertexSrc = kNone_GeometrySrcType; michael@0: michael@0: acquired = this->onReserveVertexSpace(vertexSize, michael@0: vertexCount, michael@0: vertices); michael@0: } michael@0: if (acquired) { michael@0: geoSrc.fVertexSrc = kReserved_GeometrySrcType; michael@0: geoSrc.fVertexCount = vertexCount; michael@0: geoSrc.fVertexSize = vertexSize; michael@0: } else if (NULL != vertices) { michael@0: *vertices = NULL; michael@0: } michael@0: return acquired; michael@0: } michael@0: michael@0: bool GrDrawTarget::reserveIndexSpace(int indexCount, michael@0: void** indices) { michael@0: GeometrySrcState& geoSrc = fGeoSrcStateStack.back(); michael@0: bool acquired = false; michael@0: if (indexCount > 0) { michael@0: SkASSERT(NULL != indices); michael@0: this->releasePreviousIndexSource(); michael@0: geoSrc.fIndexSrc = kNone_GeometrySrcType; michael@0: michael@0: acquired = this->onReserveIndexSpace(indexCount, indices); michael@0: } michael@0: if (acquired) { michael@0: geoSrc.fIndexSrc = kReserved_GeometrySrcType; michael@0: geoSrc.fIndexCount = indexCount; michael@0: } else if (NULL != indices) { michael@0: *indices = NULL; michael@0: } michael@0: return acquired; michael@0: michael@0: } michael@0: michael@0: bool GrDrawTarget::reserveVertexAndIndexSpace(int vertexCount, michael@0: int indexCount, michael@0: void** vertices, michael@0: void** indices) { michael@0: size_t vertexSize = this->drawState()->getVertexSize(); michael@0: this->willReserveVertexAndIndexSpace(vertexCount, indexCount); michael@0: if (vertexCount) { michael@0: if (!this->reserveVertexSpace(vertexSize, vertexCount, vertices)) { michael@0: if (indexCount) { michael@0: this->resetIndexSource(); michael@0: } michael@0: return false; michael@0: } michael@0: } michael@0: if (indexCount) { michael@0: if (!this->reserveIndexSpace(indexCount, indices)) { michael@0: if (vertexCount) { michael@0: this->resetVertexSource(); michael@0: } michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool GrDrawTarget::geometryHints(int32_t* vertexCount, michael@0: int32_t* indexCount) const { michael@0: if (NULL != vertexCount) { michael@0: *vertexCount = -1; michael@0: } michael@0: if (NULL != indexCount) { michael@0: *indexCount = -1; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: void GrDrawTarget::releasePreviousVertexSource() { michael@0: GeometrySrcState& geoSrc = fGeoSrcStateStack.back(); michael@0: switch (geoSrc.fVertexSrc) { michael@0: case kNone_GeometrySrcType: michael@0: break; michael@0: case kArray_GeometrySrcType: michael@0: this->releaseVertexArray(); michael@0: break; michael@0: case kReserved_GeometrySrcType: michael@0: this->releaseReservedVertexSpace(); michael@0: break; michael@0: case kBuffer_GeometrySrcType: michael@0: geoSrc.fVertexBuffer->unref(); michael@0: #ifdef SK_DEBUG michael@0: geoSrc.fVertexBuffer = (GrVertexBuffer*)DEBUG_INVAL_BUFFER; michael@0: #endif michael@0: break; michael@0: default: michael@0: GrCrash("Unknown Vertex Source Type."); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: void GrDrawTarget::releasePreviousIndexSource() { michael@0: GeometrySrcState& geoSrc = fGeoSrcStateStack.back(); michael@0: switch (geoSrc.fIndexSrc) { michael@0: case kNone_GeometrySrcType: // these two don't require michael@0: break; michael@0: case kArray_GeometrySrcType: michael@0: this->releaseIndexArray(); michael@0: break; michael@0: case kReserved_GeometrySrcType: michael@0: this->releaseReservedIndexSpace(); michael@0: break; michael@0: case kBuffer_GeometrySrcType: michael@0: geoSrc.fIndexBuffer->unref(); michael@0: #ifdef SK_DEBUG michael@0: geoSrc.fIndexBuffer = (GrIndexBuffer*)DEBUG_INVAL_BUFFER; michael@0: #endif michael@0: break; michael@0: default: michael@0: GrCrash("Unknown Index Source Type."); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: void GrDrawTarget::setVertexSourceToArray(const void* vertexArray, michael@0: int vertexCount) { michael@0: this->releasePreviousVertexSource(); michael@0: GeometrySrcState& geoSrc = fGeoSrcStateStack.back(); michael@0: geoSrc.fVertexSrc = kArray_GeometrySrcType; michael@0: geoSrc.fVertexSize = this->drawState()->getVertexSize(); michael@0: geoSrc.fVertexCount = vertexCount; michael@0: this->onSetVertexSourceToArray(vertexArray, vertexCount); michael@0: } michael@0: michael@0: void GrDrawTarget::setIndexSourceToArray(const void* indexArray, michael@0: int indexCount) { michael@0: this->releasePreviousIndexSource(); michael@0: GeometrySrcState& geoSrc = fGeoSrcStateStack.back(); michael@0: geoSrc.fIndexSrc = kArray_GeometrySrcType; michael@0: geoSrc.fIndexCount = indexCount; michael@0: this->onSetIndexSourceToArray(indexArray, indexCount); michael@0: } michael@0: michael@0: void GrDrawTarget::setVertexSourceToBuffer(const GrVertexBuffer* buffer) { michael@0: this->releasePreviousVertexSource(); michael@0: GeometrySrcState& geoSrc = fGeoSrcStateStack.back(); michael@0: geoSrc.fVertexSrc = kBuffer_GeometrySrcType; michael@0: geoSrc.fVertexBuffer = buffer; michael@0: buffer->ref(); michael@0: geoSrc.fVertexSize = this->drawState()->getVertexSize(); michael@0: } michael@0: michael@0: void GrDrawTarget::setIndexSourceToBuffer(const GrIndexBuffer* buffer) { michael@0: this->releasePreviousIndexSource(); michael@0: GeometrySrcState& geoSrc = fGeoSrcStateStack.back(); michael@0: geoSrc.fIndexSrc = kBuffer_GeometrySrcType; michael@0: geoSrc.fIndexBuffer = buffer; michael@0: buffer->ref(); michael@0: } michael@0: michael@0: void GrDrawTarget::resetVertexSource() { michael@0: this->releasePreviousVertexSource(); michael@0: GeometrySrcState& geoSrc = fGeoSrcStateStack.back(); michael@0: geoSrc.fVertexSrc = kNone_GeometrySrcType; michael@0: } michael@0: michael@0: void GrDrawTarget::resetIndexSource() { michael@0: this->releasePreviousIndexSource(); michael@0: GeometrySrcState& geoSrc = fGeoSrcStateStack.back(); michael@0: geoSrc.fIndexSrc = kNone_GeometrySrcType; michael@0: } michael@0: michael@0: void GrDrawTarget::pushGeometrySource() { michael@0: this->geometrySourceWillPush(); michael@0: GeometrySrcState& newState = fGeoSrcStateStack.push_back(); michael@0: newState.fIndexSrc = kNone_GeometrySrcType; michael@0: newState.fVertexSrc = kNone_GeometrySrcType; michael@0: #ifdef SK_DEBUG michael@0: newState.fVertexCount = ~0; michael@0: newState.fVertexBuffer = (GrVertexBuffer*)~0; michael@0: newState.fIndexCount = ~0; michael@0: newState.fIndexBuffer = (GrIndexBuffer*)~0; michael@0: #endif michael@0: } michael@0: michael@0: void GrDrawTarget::popGeometrySource() { michael@0: // if popping last element then pops are unbalanced with pushes michael@0: SkASSERT(fGeoSrcStateStack.count() > 1); michael@0: michael@0: this->geometrySourceWillPop(fGeoSrcStateStack.fromBack(1)); michael@0: this->releasePreviousVertexSource(); michael@0: this->releasePreviousIndexSource(); michael@0: fGeoSrcStateStack.pop_back(); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: bool GrDrawTarget::checkDraw(GrPrimitiveType type, int startVertex, michael@0: int startIndex, int vertexCount, michael@0: int indexCount) const { michael@0: const GrDrawState& drawState = this->getDrawState(); michael@0: #ifdef SK_DEBUG michael@0: const GeometrySrcState& geoSrc = fGeoSrcStateStack.back(); michael@0: int maxVertex = startVertex + vertexCount; michael@0: int maxValidVertex; michael@0: switch (geoSrc.fVertexSrc) { michael@0: case kNone_GeometrySrcType: michael@0: GrCrash("Attempting to draw without vertex src."); michael@0: case kReserved_GeometrySrcType: // fallthrough michael@0: case kArray_GeometrySrcType: michael@0: maxValidVertex = geoSrc.fVertexCount; michael@0: break; michael@0: case kBuffer_GeometrySrcType: michael@0: maxValidVertex = static_cast(geoSrc.fVertexBuffer->sizeInBytes() / geoSrc.fVertexSize); michael@0: break; michael@0: } michael@0: if (maxVertex > maxValidVertex) { michael@0: GrCrash("Drawing outside valid vertex range."); michael@0: } michael@0: if (indexCount > 0) { michael@0: int maxIndex = startIndex + indexCount; michael@0: int maxValidIndex; michael@0: switch (geoSrc.fIndexSrc) { michael@0: case kNone_GeometrySrcType: michael@0: GrCrash("Attempting to draw indexed geom without index src."); michael@0: case kReserved_GeometrySrcType: // fallthrough michael@0: case kArray_GeometrySrcType: michael@0: maxValidIndex = geoSrc.fIndexCount; michael@0: break; michael@0: case kBuffer_GeometrySrcType: michael@0: maxValidIndex = static_cast(geoSrc.fIndexBuffer->sizeInBytes() / sizeof(uint16_t)); michael@0: break; michael@0: } michael@0: if (maxIndex > maxValidIndex) { michael@0: GrCrash("Index reads outside valid index range."); michael@0: } michael@0: } michael@0: michael@0: SkASSERT(NULL != drawState.getRenderTarget()); michael@0: michael@0: for (int s = 0; s < drawState.numColorStages(); ++s) { michael@0: const GrEffectRef& effect = *drawState.getColorStage(s).getEffect(); michael@0: int numTextures = effect->numTextures(); michael@0: for (int t = 0; t < numTextures; ++t) { michael@0: GrTexture* texture = effect->texture(t); michael@0: SkASSERT(texture->asRenderTarget() != drawState.getRenderTarget()); michael@0: } michael@0: } michael@0: for (int s = 0; s < drawState.numCoverageStages(); ++s) { michael@0: const GrEffectRef& effect = *drawState.getCoverageStage(s).getEffect(); michael@0: int numTextures = effect->numTextures(); michael@0: for (int t = 0; t < numTextures; ++t) { michael@0: GrTexture* texture = effect->texture(t); michael@0: SkASSERT(texture->asRenderTarget() != drawState.getRenderTarget()); michael@0: } michael@0: } michael@0: michael@0: SkASSERT(drawState.validateVertexAttribs()); michael@0: #endif michael@0: if (NULL == drawState.getRenderTarget()) { michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool GrDrawTarget::setupDstReadIfNecessary(GrDeviceCoordTexture* dstCopy, const SkRect* drawBounds) { michael@0: if (this->caps()->dstReadInShaderSupport() || !this->getDrawState().willEffectReadDstColor()) { michael@0: return true; michael@0: } michael@0: GrRenderTarget* rt = this->drawState()->getRenderTarget(); michael@0: SkIRect copyRect; michael@0: const GrClipData* clip = this->getClip(); michael@0: clip->getConservativeBounds(rt, ©Rect); michael@0: michael@0: if (NULL != drawBounds) { michael@0: SkIRect drawIBounds; michael@0: drawBounds->roundOut(&drawIBounds); michael@0: if (!copyRect.intersect(drawIBounds)) { michael@0: #ifdef SK_DEBUG michael@0: GrPrintf("Missed an early reject. Bailing on draw from setupDstReadIfNecessary.\n"); michael@0: #endif michael@0: return false; michael@0: } michael@0: } else { michael@0: #ifdef SK_DEBUG michael@0: //GrPrintf("No dev bounds when dst copy is made.\n"); michael@0: #endif michael@0: } michael@0: michael@0: // MSAA consideration: When there is support for reading MSAA samples in the shader we could michael@0: // have per-sample dst values by making the copy multisampled. michael@0: GrTextureDesc desc; michael@0: this->initCopySurfaceDstDesc(rt, &desc); michael@0: desc.fWidth = copyRect.width(); michael@0: desc.fHeight = copyRect.height(); michael@0: michael@0: GrAutoScratchTexture ast(fContext, desc, GrContext::kApprox_ScratchTexMatch); michael@0: michael@0: if (NULL == ast.texture()) { michael@0: GrPrintf("Failed to create temporary copy of destination texture.\n"); michael@0: return false; michael@0: } michael@0: SkIPoint dstPoint = {0, 0}; michael@0: if (this->copySurface(ast.texture(), rt, copyRect, dstPoint)) { michael@0: dstCopy->setTexture(ast.texture()); michael@0: dstCopy->setOffset(copyRect.fLeft, copyRect.fTop); michael@0: return true; michael@0: } else { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: void GrDrawTarget::drawIndexed(GrPrimitiveType type, michael@0: int startVertex, michael@0: int startIndex, michael@0: int vertexCount, michael@0: int indexCount, michael@0: const SkRect* devBounds) { michael@0: if (indexCount > 0 && this->checkDraw(type, startVertex, startIndex, vertexCount, indexCount)) { michael@0: DrawInfo info; michael@0: info.fPrimitiveType = type; michael@0: info.fStartVertex = startVertex; michael@0: info.fStartIndex = startIndex; michael@0: info.fVertexCount = vertexCount; michael@0: info.fIndexCount = indexCount; michael@0: michael@0: info.fInstanceCount = 0; michael@0: info.fVerticesPerInstance = 0; michael@0: info.fIndicesPerInstance = 0; michael@0: michael@0: if (NULL != devBounds) { michael@0: info.setDevBounds(*devBounds); michael@0: } michael@0: // TODO: We should continue with incorrect blending. michael@0: if (!this->setupDstReadIfNecessary(&info)) { michael@0: return; michael@0: } michael@0: this->onDraw(info); michael@0: } michael@0: } michael@0: michael@0: void GrDrawTarget::drawNonIndexed(GrPrimitiveType type, michael@0: int startVertex, michael@0: int vertexCount, michael@0: const SkRect* devBounds) { michael@0: if (vertexCount > 0 && this->checkDraw(type, startVertex, -1, vertexCount, -1)) { michael@0: DrawInfo info; michael@0: info.fPrimitiveType = type; michael@0: info.fStartVertex = startVertex; michael@0: info.fStartIndex = 0; michael@0: info.fVertexCount = vertexCount; michael@0: info.fIndexCount = 0; michael@0: michael@0: info.fInstanceCount = 0; michael@0: info.fVerticesPerInstance = 0; michael@0: info.fIndicesPerInstance = 0; michael@0: michael@0: if (NULL != devBounds) { michael@0: info.setDevBounds(*devBounds); michael@0: } michael@0: // TODO: We should continue with incorrect blending. michael@0: if (!this->setupDstReadIfNecessary(&info)) { michael@0: return; michael@0: } michael@0: this->onDraw(info); michael@0: } michael@0: } michael@0: michael@0: void GrDrawTarget::stencilPath(const GrPath* path, SkPath::FillType fill) { michael@0: // TODO: extract portions of checkDraw that are relevant to path stenciling. michael@0: SkASSERT(NULL != path); michael@0: SkASSERT(this->caps()->pathRenderingSupport()); michael@0: SkASSERT(!SkPath::IsInverseFillType(fill)); michael@0: this->onStencilPath(path, fill); michael@0: } michael@0: michael@0: void GrDrawTarget::drawPath(const GrPath* path, SkPath::FillType fill) { michael@0: // TODO: extract portions of checkDraw that are relevant to path rendering. michael@0: SkASSERT(NULL != path); michael@0: SkASSERT(this->caps()->pathRenderingSupport()); michael@0: const GrDrawState* drawState = &getDrawState(); michael@0: michael@0: SkRect devBounds; michael@0: if (SkPath::IsInverseFillType(fill)) { michael@0: devBounds = SkRect::MakeWH(SkIntToScalar(drawState->getRenderTarget()->width()), michael@0: SkIntToScalar(drawState->getRenderTarget()->height())); michael@0: } else { michael@0: devBounds = path->getBounds(); michael@0: } michael@0: SkMatrix viewM = drawState->getViewMatrix(); michael@0: viewM.mapRect(&devBounds); michael@0: michael@0: GrDeviceCoordTexture dstCopy; michael@0: if (!this->setupDstReadIfNecessary(&dstCopy, &devBounds)) { michael@0: return; michael@0: } michael@0: michael@0: this->onDrawPath(path, fill, dstCopy.texture() ? &dstCopy : NULL); michael@0: } michael@0: michael@0: void GrDrawTarget::instantGpuTraceEvent(const char* marker) { michael@0: if (this->caps()->gpuTracingSupport()) { michael@0: this->onInstantGpuTraceEvent(marker); michael@0: } michael@0: } michael@0: michael@0: void GrDrawTarget::pushGpuTraceEvent(const char* marker) { michael@0: SkASSERT(fPushGpuTraceCount >= 0); michael@0: if (this->caps()->gpuTracingSupport()) { michael@0: this->onPushGpuTraceEvent(marker); michael@0: ++fPushGpuTraceCount; michael@0: } michael@0: } michael@0: michael@0: void GrDrawTarget::popGpuTraceEvent() { michael@0: SkASSERT(fPushGpuTraceCount >= 1); michael@0: if (this->caps()->gpuTracingSupport()) { michael@0: this->onPopGpuTraceEvent(); michael@0: --fPushGpuTraceCount; michael@0: } michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: bool GrDrawTarget::willUseHWAALines() const { michael@0: // There is a conflict between using smooth lines and our use of premultiplied alpha. Smooth michael@0: // lines tweak the incoming alpha value but not in a premul-alpha way. So we only use them when michael@0: // our alpha is 0xff and tweaking the color for partial coverage is OK michael@0: if (!this->caps()->hwAALineSupport() || michael@0: !this->getDrawState().isHWAntialiasState()) { michael@0: return false; michael@0: } michael@0: GrDrawState::BlendOptFlags opts = this->getDrawState().getBlendOpts(); michael@0: return (GrDrawState::kDisableBlend_BlendOptFlag & opts) && michael@0: (GrDrawState::kCoverageAsAlpha_BlendOptFlag & opts); michael@0: } michael@0: michael@0: bool GrDrawTarget::canApplyCoverage() const { michael@0: // we can correctly apply coverage if a) we have dual source blending michael@0: // or b) one of our blend optimizations applies. michael@0: return this->caps()->dualSourceBlendingSupport() || michael@0: GrDrawState::kNone_BlendOpt != this->getDrawState().getBlendOpts(true); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: void GrDrawTarget::drawIndexedInstances(GrPrimitiveType type, michael@0: int instanceCount, michael@0: int verticesPerInstance, michael@0: int indicesPerInstance, michael@0: const SkRect* devBounds) { michael@0: if (!verticesPerInstance || !indicesPerInstance) { michael@0: return; michael@0: } michael@0: michael@0: int maxInstancesPerDraw = this->indexCountInCurrentSource() / indicesPerInstance; michael@0: if (!maxInstancesPerDraw) { michael@0: return; michael@0: } michael@0: michael@0: DrawInfo info; michael@0: info.fPrimitiveType = type; michael@0: info.fStartIndex = 0; michael@0: info.fStartVertex = 0; michael@0: info.fIndicesPerInstance = indicesPerInstance; michael@0: info.fVerticesPerInstance = verticesPerInstance; michael@0: michael@0: // Set the same bounds for all the draws. michael@0: if (NULL != devBounds) { michael@0: info.setDevBounds(*devBounds); michael@0: } michael@0: // TODO: We should continue with incorrect blending. michael@0: if (!this->setupDstReadIfNecessary(&info)) { michael@0: return; michael@0: } michael@0: michael@0: while (instanceCount) { michael@0: info.fInstanceCount = GrMin(instanceCount, maxInstancesPerDraw); michael@0: info.fVertexCount = info.fInstanceCount * verticesPerInstance; michael@0: info.fIndexCount = info.fInstanceCount * indicesPerInstance; michael@0: michael@0: if (this->checkDraw(type, michael@0: info.fStartVertex, michael@0: info.fStartIndex, michael@0: info.fVertexCount, michael@0: info.fIndexCount)) { michael@0: this->onDraw(info); michael@0: } michael@0: info.fStartVertex += info.fVertexCount; michael@0: instanceCount -= info.fInstanceCount; michael@0: } michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: namespace { michael@0: michael@0: // position + (optional) texture coord michael@0: extern const GrVertexAttrib gBWRectPosUVAttribs[] = { michael@0: {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding}, michael@0: {kVec2f_GrVertexAttribType, sizeof(GrPoint), kLocalCoord_GrVertexAttribBinding} michael@0: }; michael@0: michael@0: void set_vertex_attributes(GrDrawState* drawState, bool hasUVs) { michael@0: if (hasUVs) { michael@0: drawState->setVertexAttribs(2); michael@0: } else { michael@0: drawState->setVertexAttribs(1); michael@0: } michael@0: } michael@0: michael@0: }; michael@0: michael@0: void GrDrawTarget::onDrawRect(const SkRect& rect, michael@0: const SkMatrix* matrix, michael@0: const SkRect* localRect, michael@0: const SkMatrix* localMatrix) { michael@0: michael@0: GrDrawState::AutoViewMatrixRestore avmr; michael@0: if (NULL != matrix) { michael@0: avmr.set(this->drawState(), *matrix); michael@0: } michael@0: michael@0: set_vertex_attributes(this->drawState(), NULL != localRect); michael@0: michael@0: AutoReleaseGeometry geo(this, 4, 0); michael@0: if (!geo.succeeded()) { michael@0: GrPrintf("Failed to get space for vertices!\n"); michael@0: return; michael@0: } michael@0: michael@0: size_t vsize = this->drawState()->getVertexSize(); michael@0: geo.positions()->setRectFan(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, vsize); michael@0: if (NULL != localRect) { michael@0: GrPoint* coords = GrTCast(GrTCast(geo.vertices()) + michael@0: sizeof(GrPoint)); michael@0: coords->setRectFan(localRect->fLeft, localRect->fTop, michael@0: localRect->fRight, localRect->fBottom, michael@0: vsize); michael@0: if (NULL != localMatrix) { michael@0: localMatrix->mapPointsWithStride(coords, vsize, 4); michael@0: } michael@0: } michael@0: SkRect bounds; michael@0: this->getDrawState().getViewMatrix().mapRect(&bounds, rect); michael@0: michael@0: this->drawNonIndexed(kTriangleFan_GrPrimitiveType, 0, 4, &bounds); michael@0: } michael@0: michael@0: void GrDrawTarget::clipWillBeSet(const GrClipData* clipData) { michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: GrDrawTarget::AutoStateRestore::AutoStateRestore() { michael@0: fDrawTarget = NULL; michael@0: } michael@0: michael@0: GrDrawTarget::AutoStateRestore::AutoStateRestore(GrDrawTarget* target, michael@0: ASRInit init, michael@0: const SkMatrix* vm) { michael@0: fDrawTarget = NULL; michael@0: this->set(target, init, vm); michael@0: } michael@0: michael@0: GrDrawTarget::AutoStateRestore::~AutoStateRestore() { michael@0: if (NULL != fDrawTarget) { michael@0: fDrawTarget->setDrawState(fSavedState); michael@0: fSavedState->unref(); michael@0: } michael@0: } michael@0: michael@0: void GrDrawTarget::AutoStateRestore::set(GrDrawTarget* target, ASRInit init, const SkMatrix* vm) { michael@0: SkASSERT(NULL == fDrawTarget); michael@0: fDrawTarget = target; michael@0: fSavedState = target->drawState(); michael@0: SkASSERT(fSavedState); michael@0: fSavedState->ref(); michael@0: if (kReset_ASRInit == init) { michael@0: if (NULL == vm) { michael@0: // calls the default cons michael@0: fTempState.init(); michael@0: } else { michael@0: SkNEW_IN_TLAZY(&fTempState, GrDrawState, (*vm)); michael@0: } michael@0: } else { michael@0: SkASSERT(kPreserve_ASRInit == init); michael@0: if (NULL == vm) { michael@0: fTempState.set(*fSavedState); michael@0: } else { michael@0: SkNEW_IN_TLAZY(&fTempState, GrDrawState, (*fSavedState, *vm)); michael@0: } michael@0: } michael@0: target->setDrawState(fTempState.get()); michael@0: } michael@0: michael@0: bool GrDrawTarget::AutoStateRestore::setIdentity(GrDrawTarget* target, ASRInit init) { michael@0: SkASSERT(NULL == fDrawTarget); michael@0: fDrawTarget = target; michael@0: fSavedState = target->drawState(); michael@0: SkASSERT(fSavedState); michael@0: fSavedState->ref(); michael@0: if (kReset_ASRInit == init) { michael@0: // calls the default cons michael@0: fTempState.init(); michael@0: } else { michael@0: SkASSERT(kPreserve_ASRInit == init); michael@0: // calls the copy cons michael@0: fTempState.set(*fSavedState); michael@0: if (!fTempState.get()->setIdentityViewMatrix()) { michael@0: // let go of any resources held by the temp michael@0: fTempState.get()->reset(); michael@0: fDrawTarget = NULL; michael@0: fSavedState->unref(); michael@0: fSavedState = NULL; michael@0: return false; michael@0: } michael@0: } michael@0: target->setDrawState(fTempState.get()); michael@0: return true; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: GrDrawTarget::AutoReleaseGeometry::AutoReleaseGeometry( michael@0: GrDrawTarget* target, michael@0: int vertexCount, michael@0: int indexCount) { michael@0: fTarget = NULL; michael@0: this->set(target, vertexCount, indexCount); michael@0: } michael@0: michael@0: GrDrawTarget::AutoReleaseGeometry::AutoReleaseGeometry() { michael@0: fTarget = NULL; michael@0: } michael@0: michael@0: GrDrawTarget::AutoReleaseGeometry::~AutoReleaseGeometry() { michael@0: this->reset(); michael@0: } michael@0: michael@0: bool GrDrawTarget::AutoReleaseGeometry::set(GrDrawTarget* target, michael@0: int vertexCount, michael@0: int indexCount) { michael@0: this->reset(); michael@0: fTarget = target; michael@0: bool success = true; michael@0: if (NULL != fTarget) { michael@0: fTarget = target; michael@0: success = target->reserveVertexAndIndexSpace(vertexCount, michael@0: indexCount, michael@0: &fVertices, michael@0: &fIndices); michael@0: if (!success) { michael@0: fTarget = NULL; michael@0: this->reset(); michael@0: } michael@0: } michael@0: SkASSERT(success == (NULL != fTarget)); michael@0: return success; michael@0: } michael@0: michael@0: void GrDrawTarget::AutoReleaseGeometry::reset() { michael@0: if (NULL != fTarget) { michael@0: if (NULL != fVertices) { michael@0: fTarget->resetVertexSource(); michael@0: } michael@0: if (NULL != fIndices) { michael@0: fTarget->resetIndexSource(); michael@0: } michael@0: fTarget = NULL; michael@0: } michael@0: fVertices = NULL; michael@0: fIndices = NULL; michael@0: } michael@0: michael@0: GrDrawTarget::AutoClipRestore::AutoClipRestore(GrDrawTarget* target, const SkIRect& newClip) { michael@0: fTarget = target; michael@0: fClip = fTarget->getClip(); michael@0: fStack.init(); michael@0: fStack.get()->clipDevRect(newClip, SkRegion::kReplace_Op); michael@0: fReplacementClip.fClipStack = fStack.get(); michael@0: target->setClip(&fReplacementClip); michael@0: } michael@0: michael@0: namespace { michael@0: // returns true if the read/written rect intersects the src/dst and false if not. michael@0: bool clip_srcrect_and_dstpoint(const GrSurface* dst, michael@0: const GrSurface* src, michael@0: const SkIRect& srcRect, michael@0: const SkIPoint& dstPoint, michael@0: SkIRect* clippedSrcRect, michael@0: SkIPoint* clippedDstPoint) { michael@0: *clippedSrcRect = srcRect; michael@0: *clippedDstPoint = dstPoint; michael@0: michael@0: // clip the left edge to src and dst bounds, adjusting dstPoint if necessary michael@0: if (clippedSrcRect->fLeft < 0) { michael@0: clippedDstPoint->fX -= clippedSrcRect->fLeft; michael@0: clippedSrcRect->fLeft = 0; michael@0: } michael@0: if (clippedDstPoint->fX < 0) { michael@0: clippedSrcRect->fLeft -= clippedDstPoint->fX; michael@0: clippedDstPoint->fX = 0; michael@0: } michael@0: michael@0: // clip the top edge to src and dst bounds, adjusting dstPoint if necessary michael@0: if (clippedSrcRect->fTop < 0) { michael@0: clippedDstPoint->fY -= clippedSrcRect->fTop; michael@0: clippedSrcRect->fTop = 0; michael@0: } michael@0: if (clippedDstPoint->fY < 0) { michael@0: clippedSrcRect->fTop -= clippedDstPoint->fY; michael@0: clippedDstPoint->fY = 0; michael@0: } michael@0: michael@0: // clip the right edge to the src and dst bounds. michael@0: if (clippedSrcRect->fRight > src->width()) { michael@0: clippedSrcRect->fRight = src->width(); michael@0: } michael@0: if (clippedDstPoint->fX + clippedSrcRect->width() > dst->width()) { michael@0: clippedSrcRect->fRight = clippedSrcRect->fLeft + dst->width() - clippedDstPoint->fX; michael@0: } michael@0: michael@0: // clip the bottom edge to the src and dst bounds. michael@0: if (clippedSrcRect->fBottom > src->height()) { michael@0: clippedSrcRect->fBottom = src->height(); michael@0: } michael@0: if (clippedDstPoint->fY + clippedSrcRect->height() > dst->height()) { michael@0: clippedSrcRect->fBottom = clippedSrcRect->fTop + dst->height() - clippedDstPoint->fY; michael@0: } michael@0: michael@0: // The above clipping steps may have inverted the rect if it didn't intersect either the src or michael@0: // dst bounds. michael@0: return !clippedSrcRect->isEmpty(); michael@0: } michael@0: } michael@0: michael@0: bool GrDrawTarget::copySurface(GrSurface* dst, michael@0: GrSurface* src, michael@0: const SkIRect& srcRect, michael@0: const SkIPoint& dstPoint) { michael@0: SkASSERT(NULL != dst); michael@0: SkASSERT(NULL != src); michael@0: michael@0: SkIRect clippedSrcRect; michael@0: SkIPoint clippedDstPoint; michael@0: // If the rect is outside the src or dst then we've already succeeded. michael@0: if (!clip_srcrect_and_dstpoint(dst, michael@0: src, michael@0: srcRect, michael@0: dstPoint, michael@0: &clippedSrcRect, michael@0: &clippedDstPoint)) { michael@0: SkASSERT(this->canCopySurface(dst, src, srcRect, dstPoint)); michael@0: return true; michael@0: } michael@0: michael@0: bool result = this->onCopySurface(dst, src, clippedSrcRect, clippedDstPoint); michael@0: SkASSERT(result == this->canCopySurface(dst, src, clippedSrcRect, clippedDstPoint)); michael@0: return result; michael@0: } michael@0: michael@0: bool GrDrawTarget::canCopySurface(GrSurface* dst, michael@0: GrSurface* src, michael@0: const SkIRect& srcRect, michael@0: const SkIPoint& dstPoint) { michael@0: SkASSERT(NULL != dst); michael@0: SkASSERT(NULL != src); michael@0: michael@0: SkIRect clippedSrcRect; michael@0: SkIPoint clippedDstPoint; michael@0: // If the rect is outside the src or dst then we're guaranteed success michael@0: if (!clip_srcrect_and_dstpoint(dst, michael@0: src, michael@0: srcRect, michael@0: dstPoint, michael@0: &clippedSrcRect, michael@0: &clippedDstPoint)) { michael@0: return true; michael@0: } michael@0: return this->onCanCopySurface(dst, src, clippedSrcRect, clippedDstPoint); michael@0: } michael@0: michael@0: bool GrDrawTarget::onCanCopySurface(GrSurface* dst, michael@0: GrSurface* src, michael@0: const SkIRect& srcRect, michael@0: const SkIPoint& dstPoint) { michael@0: // Check that the read/write rects are contained within the src/dst bounds. michael@0: SkASSERT(!srcRect.isEmpty()); michael@0: SkASSERT(SkIRect::MakeWH(src->width(), src->height()).contains(srcRect)); michael@0: SkASSERT(dstPoint.fX >= 0 && dstPoint.fY >= 0); michael@0: SkASSERT(dstPoint.fX + srcRect.width() <= dst->width() && michael@0: dstPoint.fY + srcRect.height() <= dst->height()); michael@0: michael@0: return !dst->isSameAs(src) && NULL != dst->asRenderTarget() && NULL != src->asTexture(); michael@0: } michael@0: michael@0: bool GrDrawTarget::onCopySurface(GrSurface* dst, michael@0: GrSurface* src, michael@0: const SkIRect& srcRect, michael@0: const SkIPoint& dstPoint) { michael@0: if (!GrDrawTarget::onCanCopySurface(dst, src, srcRect, dstPoint)) { michael@0: return false; michael@0: } michael@0: michael@0: GrRenderTarget* rt = dst->asRenderTarget(); michael@0: GrTexture* tex = src->asTexture(); michael@0: michael@0: GrDrawTarget::AutoStateRestore asr(this, kReset_ASRInit); michael@0: this->drawState()->setRenderTarget(rt); michael@0: SkMatrix matrix; michael@0: matrix.setTranslate(SkIntToScalar(srcRect.fLeft - dstPoint.fX), michael@0: SkIntToScalar(srcRect.fTop - dstPoint.fY)); michael@0: matrix.postIDiv(tex->width(), tex->height()); michael@0: this->drawState()->addColorTextureEffect(tex, matrix); michael@0: SkIRect dstRect = SkIRect::MakeXYWH(dstPoint.fX, michael@0: dstPoint.fY, michael@0: srcRect.width(), michael@0: srcRect.height()); michael@0: this->drawSimpleRect(dstRect); michael@0: return true; michael@0: } michael@0: michael@0: void GrDrawTarget::initCopySurfaceDstDesc(const GrSurface* src, GrTextureDesc* desc) { michael@0: // Make the dst of the copy be a render target because the default copySurface draws to the dst. michael@0: desc->fOrigin = kDefault_GrSurfaceOrigin; michael@0: desc->fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit; michael@0: desc->fConfig = src->config(); michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: void GrDrawTargetCaps::reset() { michael@0: f8BitPaletteSupport = false; michael@0: fMipMapSupport = false; michael@0: fNPOTTextureTileSupport = false; michael@0: fTwoSidedStencilSupport = false; michael@0: fStencilWrapOpsSupport = false; michael@0: fHWAALineSupport = false; michael@0: fShaderDerivativeSupport = false; michael@0: fGeometryShaderSupport = false; michael@0: fDualSourceBlendingSupport = false; michael@0: fBufferLockSupport = false; michael@0: fPathRenderingSupport = false; michael@0: fDstReadInShaderSupport = false; michael@0: fReuseScratchTextures = true; michael@0: fGpuTracingSupport = false; michael@0: michael@0: fMaxRenderTargetSize = 0; michael@0: fMaxTextureSize = 0; michael@0: fMaxSampleCount = 0; michael@0: michael@0: memset(fConfigRenderSupport, 0, sizeof(fConfigRenderSupport)); michael@0: } michael@0: michael@0: GrDrawTargetCaps& GrDrawTargetCaps::operator=(const GrDrawTargetCaps& other) { michael@0: f8BitPaletteSupport = other.f8BitPaletteSupport; michael@0: fMipMapSupport = other.fMipMapSupport; michael@0: fNPOTTextureTileSupport = other.fNPOTTextureTileSupport; michael@0: fTwoSidedStencilSupport = other.fTwoSidedStencilSupport; michael@0: fStencilWrapOpsSupport = other.fStencilWrapOpsSupport; michael@0: fHWAALineSupport = other.fHWAALineSupport; michael@0: fShaderDerivativeSupport = other.fShaderDerivativeSupport; michael@0: fGeometryShaderSupport = other.fGeometryShaderSupport; michael@0: fDualSourceBlendingSupport = other.fDualSourceBlendingSupport; michael@0: fBufferLockSupport = other.fBufferLockSupport; michael@0: fPathRenderingSupport = other.fPathRenderingSupport; michael@0: fDstReadInShaderSupport = other.fDstReadInShaderSupport; michael@0: fReuseScratchTextures = other.fReuseScratchTextures; michael@0: fGpuTracingSupport = other.fGpuTracingSupport; michael@0: michael@0: fMaxRenderTargetSize = other.fMaxRenderTargetSize; michael@0: fMaxTextureSize = other.fMaxTextureSize; michael@0: fMaxSampleCount = other.fMaxSampleCount; michael@0: michael@0: memcpy(fConfigRenderSupport, other.fConfigRenderSupport, sizeof(fConfigRenderSupport)); michael@0: michael@0: return *this; michael@0: } michael@0: michael@0: SkString GrDrawTargetCaps::dump() const { michael@0: SkString r; michael@0: static const char* gNY[] = {"NO", "YES"}; michael@0: r.appendf("8 Bit Palette Support : %s\n", gNY[f8BitPaletteSupport]); michael@0: r.appendf("MIP Map Support : %s\n", gNY[fMipMapSupport]); michael@0: r.appendf("NPOT Texture Tile Support : %s\n", gNY[fNPOTTextureTileSupport]); michael@0: r.appendf("Two Sided Stencil Support : %s\n", gNY[fTwoSidedStencilSupport]); michael@0: r.appendf("Stencil Wrap Ops Support : %s\n", gNY[fStencilWrapOpsSupport]); michael@0: r.appendf("HW AA Lines Support : %s\n", gNY[fHWAALineSupport]); michael@0: r.appendf("Shader Derivative Support : %s\n", gNY[fShaderDerivativeSupport]); michael@0: r.appendf("Geometry Shader Support : %s\n", gNY[fGeometryShaderSupport]); michael@0: r.appendf("Dual Source Blending Support: %s\n", gNY[fDualSourceBlendingSupport]); michael@0: r.appendf("Buffer Lock Support : %s\n", gNY[fBufferLockSupport]); michael@0: r.appendf("Path Rendering Support : %s\n", gNY[fPathRenderingSupport]); michael@0: r.appendf("Dst Read In Shader Support : %s\n", gNY[fDstReadInShaderSupport]); michael@0: r.appendf("Reuse Scratch Textures : %s\n", gNY[fReuseScratchTextures]); michael@0: r.appendf("Gpu Tracing Support : %s\n", gNY[fGpuTracingSupport]); michael@0: r.appendf("Max Texture Size : %d\n", fMaxTextureSize); michael@0: r.appendf("Max Render Target Size : %d\n", fMaxRenderTargetSize); michael@0: r.appendf("Max Sample Count : %d\n", fMaxSampleCount); michael@0: michael@0: static const char* kConfigNames[] = { michael@0: "Unknown", // kUnknown_GrPixelConfig michael@0: "Alpha8", // kAlpha_8_GrPixelConfig, michael@0: "Index8", // kIndex_8_GrPixelConfig, michael@0: "RGB565", // kRGB_565_GrPixelConfig, michael@0: "RGBA444", // kRGBA_4444_GrPixelConfig, michael@0: "RGBA8888", // kRGBA_8888_GrPixelConfig, michael@0: "BGRA8888", // kBGRA_8888_GrPixelConfig, michael@0: }; michael@0: GR_STATIC_ASSERT(0 == kUnknown_GrPixelConfig); michael@0: GR_STATIC_ASSERT(1 == kAlpha_8_GrPixelConfig); michael@0: GR_STATIC_ASSERT(2 == kIndex_8_GrPixelConfig); michael@0: GR_STATIC_ASSERT(3 == kRGB_565_GrPixelConfig); michael@0: GR_STATIC_ASSERT(4 == kRGBA_4444_GrPixelConfig); michael@0: GR_STATIC_ASSERT(5 == kRGBA_8888_GrPixelConfig); michael@0: GR_STATIC_ASSERT(6 == kBGRA_8888_GrPixelConfig); michael@0: GR_STATIC_ASSERT(SK_ARRAY_COUNT(kConfigNames) == kGrPixelConfigCnt); michael@0: michael@0: SkASSERT(!fConfigRenderSupport[kUnknown_GrPixelConfig][0]); michael@0: SkASSERT(!fConfigRenderSupport[kUnknown_GrPixelConfig][1]); michael@0: for (size_t i = 0; i < SK_ARRAY_COUNT(kConfigNames); ++i) { michael@0: if (i != kUnknown_GrPixelConfig) { michael@0: r.appendf("%s is renderable: %s, with MSAA: %s\n", michael@0: kConfigNames[i], michael@0: gNY[fConfigRenderSupport[i][0]], michael@0: gNY[fConfigRenderSupport[i][1]]); michael@0: } michael@0: } michael@0: return r; michael@0: }