| |
| /* |
| * 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 "GrTexture.h" |
| |
| #include "GrContext.h" |
| #include "GrDrawTargetCaps.h" |
| #include "GrGpu.h" |
| #include "GrRenderTarget.h" |
| #include "GrResourceCache.h" |
| |
| GrTexture::~GrTexture() { |
| if (NULL != fRenderTarget.get()) { |
| fRenderTarget.get()->owningTextureDestroyed(); |
| } |
| } |
| |
| /** |
| * This method allows us to interrupt the normal deletion process and place |
| * textures back in the texture cache when their ref count goes to zero. |
| */ |
| void GrTexture::internal_dispose() const { |
| if (this->impl()->isSetFlag((GrTextureFlags) GrTextureImpl::kReturnToCache_FlagBit) && |
| NULL != this->INHERITED::getContext()) { |
| GrTexture* nonConstThis = const_cast<GrTexture *>(this); |
| this->ref(); // restore ref count to initial setting |
| |
| nonConstThis->impl()->resetFlag((GrTextureFlags) GrTextureImpl::kReturnToCache_FlagBit); |
| nonConstThis->INHERITED::getContext()->addExistingTextureToCache(nonConstThis); |
| |
| // Note: "this" texture might be freed inside addExistingTextureToCache |
| // if it is purged. |
| return; |
| } |
| |
| this->INHERITED::internal_dispose(); |
| } |
| |
| void GrTextureImpl::dirtyMipMaps(bool mipMapsDirty) { |
| if (mipMapsDirty) { |
| if (kValid_MipMapsStatus == fMipMapsStatus) { |
| fMipMapsStatus = kAllocated_MipMapsStatus; |
| } |
| } else { |
| const bool sizeChanged = kNotAllocated_MipMapsStatus == fMipMapsStatus; |
| fMipMapsStatus = kValid_MipMapsStatus; |
| if (sizeChanged) { |
| // This must not be called until after changing fMipMapsStatus. |
| this->didChangeGpuMemorySize(); |
| } |
| } |
| } |
| |
| size_t GrTexture::gpuMemorySize() const { |
| size_t textureSize; |
| |
| if (GrPixelConfigIsCompressed(fDesc.fConfig)) { |
| textureSize = GrCompressedFormatDataSize(fDesc.fConfig, fDesc.fWidth, fDesc.fHeight); |
| } else { |
| textureSize = (size_t) fDesc.fWidth * fDesc.fHeight * GrBytesPerPixel(fDesc.fConfig); |
| } |
| |
| if (this->impl()->hasMipMaps()) { |
| // We don't have to worry about the mipmaps being a different size than |
| // we'd expect because we never change fDesc.fWidth/fHeight. |
| textureSize *= 2; |
| } |
| return textureSize; |
| } |
| |
| bool GrTexture::readPixels(int left, int top, int width, int height, |
| GrPixelConfig config, void* buffer, |
| size_t rowBytes, uint32_t pixelOpsFlags) { |
| // go through context so that all necessary flushing occurs |
| GrContext* context = this->getContext(); |
| if (NULL == context) { |
| return false; |
| } |
| return context->readTexturePixels(this, |
| left, top, width, height, |
| config, buffer, rowBytes, |
| pixelOpsFlags); |
| } |
| |
| void GrTexture::writePixels(int left, int top, int width, int height, |
| GrPixelConfig config, const void* buffer, |
| size_t rowBytes, uint32_t pixelOpsFlags) { |
| // go through context so that all necessary flushing occurs |
| GrContext* context = this->getContext(); |
| if (NULL == context) { |
| return; |
| } |
| context->writeTexturePixels(this, |
| left, top, width, height, |
| config, buffer, rowBytes, |
| pixelOpsFlags); |
| } |
| |
| void GrTexture::abandonReleaseCommon() { |
| // In debug builds the resource cache tracks removed/exclusive textures and has an unref'ed ptr. |
| // After abandon() or release() the resource cache will be unreachable (getContext() == NULL). |
| // So we readd the texture to the cache here so that it is removed from the exclusive list and |
| // there is no longer an unref'ed ptr to the texture in the cache. |
| if (this->impl()->isSetFlag((GrTextureFlags)GrTextureImpl::kReturnToCache_FlagBit)) { |
| SkASSERT(!this->wasDestroyed()); |
| this->ref(); // restores the ref the resource cache gave up when it marked this exclusive. |
| this->impl()->resetFlag((GrTextureFlags) GrTextureImpl::kReturnToCache_FlagBit); |
| this->getContext()->addExistingTextureToCache(this); |
| } |
| } |
| |
| void GrTexture::onRelease() { |
| this->abandonReleaseCommon(); |
| SkASSERT(!this->impl()->isSetFlag((GrTextureFlags) GrTextureImpl::kReturnToCache_FlagBit)); |
| INHERITED::onRelease(); |
| } |
| |
| void GrTexture::onAbandon() { |
| this->abandonReleaseCommon(); |
| if (NULL != fRenderTarget.get()) { |
| fRenderTarget->abandon(); |
| } |
| INHERITED::onAbandon(); |
| } |
| |
| void GrTexture::validateDesc() const { |
| if (NULL != this->asRenderTarget()) { |
| // This texture has a render target |
| SkASSERT(0 != (fDesc.fFlags & kRenderTarget_GrTextureFlagBit)); |
| |
| if (NULL != this->asRenderTarget()->getStencilBuffer()) { |
| SkASSERT(0 != (fDesc.fFlags & kNoStencil_GrTextureFlagBit)); |
| } else { |
| SkASSERT(0 == (fDesc.fFlags & kNoStencil_GrTextureFlagBit)); |
| } |
| |
| SkASSERT(fDesc.fSampleCnt == this->asRenderTarget()->numSamples()); |
| } else { |
| SkASSERT(0 == (fDesc.fFlags & kRenderTarget_GrTextureFlagBit)); |
| SkASSERT(0 == (fDesc.fFlags & kNoStencil_GrTextureFlagBit)); |
| SkASSERT(0 == fDesc.fSampleCnt); |
| } |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| // These flags need to fit in a GrResourceKey::ResourceFlags so they can be folded into the texture |
| // key |
| enum TextureFlags { |
| /** |
| * The kStretchToPOT bit is set when the texture is NPOT and is being repeated but the |
| * hardware doesn't support that feature. |
| */ |
| kStretchToPOT_TextureFlag = 0x1, |
| /** |
| * The kBilerp bit can only be set when the kStretchToPOT flag is set and indicates whether the |
| * stretched texture should be bilerped. |
| */ |
| kBilerp_TextureFlag = 0x2, |
| }; |
| |
| namespace { |
| GrResourceKey::ResourceFlags get_texture_flags(const GrGpu* gpu, |
| const GrTextureParams* params, |
| const GrTextureDesc& desc) { |
| GrResourceKey::ResourceFlags flags = 0; |
| bool tiled = NULL != params && params->isTiled(); |
| if (tiled && !gpu->caps()->npotTextureTileSupport()) { |
| if (!SkIsPow2(desc.fWidth) || !SkIsPow2(desc.fHeight)) { |
| flags |= kStretchToPOT_TextureFlag; |
| switch(params->filterMode()) { |
| case GrTextureParams::kNone_FilterMode: |
| break; |
| case GrTextureParams::kBilerp_FilterMode: |
| case GrTextureParams::kMipMap_FilterMode: |
| flags |= kBilerp_TextureFlag; |
| break; |
| } |
| } |
| } |
| return flags; |
| } |
| |
| GrResourceKey::ResourceType texture_resource_type() { |
| static const GrResourceKey::ResourceType gType = GrResourceKey::GenerateResourceType(); |
| return gType; |
| } |
| |
| // FIXME: This should be refactored with the code in gl/GrGpuGL.cpp. |
| GrSurfaceOrigin resolve_origin(const GrTextureDesc& desc) { |
| // By default, GrRenderTargets are GL's normal orientation so that they |
| // can be drawn to by the outside world without the client having |
| // to render upside down. |
| bool renderTarget = 0 != (desc.fFlags & kRenderTarget_GrTextureFlagBit); |
| if (kDefault_GrSurfaceOrigin == desc.fOrigin) { |
| return renderTarget ? kBottomLeft_GrSurfaceOrigin : kTopLeft_GrSurfaceOrigin; |
| } else { |
| return desc.fOrigin; |
| } |
| } |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| GrTextureImpl::GrTextureImpl(GrGpu* gpu, bool isWrapped, const GrTextureDesc& desc) |
| : INHERITED(gpu, isWrapped, desc) |
| , fMipMapsStatus(kNotAllocated_MipMapsStatus) { |
| this->setScratchKey(ComputeScratchKey(desc)); |
| } |
| |
| GrResourceKey GrTextureImpl::ComputeKey(const GrGpu* gpu, |
| const GrTextureParams* params, |
| const GrTextureDesc& desc, |
| const GrCacheID& cacheID) { |
| GrResourceKey::ResourceFlags flags = get_texture_flags(gpu, params, desc); |
| return GrResourceKey(cacheID, texture_resource_type(), flags); |
| } |
| |
| GrResourceKey GrTextureImpl::ComputeScratchKey(const GrTextureDesc& desc) { |
| GrCacheID::Key idKey; |
| // Instead of a client-provided key of the texture contents we create a key from the |
| // descriptor. |
| GR_STATIC_ASSERT(sizeof(idKey) >= 16); |
| SkASSERT(desc.fHeight < (1 << 16)); |
| SkASSERT(desc.fWidth < (1 << 16)); |
| idKey.fData32[0] = (desc.fWidth) | (desc.fHeight << 16); |
| idKey.fData32[1] = desc.fConfig | desc.fSampleCnt << 16; |
| idKey.fData32[2] = desc.fFlags; |
| idKey.fData32[3] = resolve_origin(desc); // Only needs 2 bits actually |
| static const int kPadSize = sizeof(idKey) - 16; |
| GR_STATIC_ASSERT(kPadSize >= 0); |
| memset(idKey.fData8 + 16, 0, kPadSize); |
| |
| GrCacheID cacheID(GrResourceKey::ScratchDomain(), idKey); |
| return GrResourceKey(cacheID, texture_resource_type(), 0); |
| } |
| |
| bool GrTextureImpl::NeedsResizing(const GrResourceKey& key) { |
| return SkToBool(key.getResourceFlags() & kStretchToPOT_TextureFlag); |
| } |
| |
| bool GrTextureImpl::NeedsBilerp(const GrResourceKey& key) { |
| return SkToBool(key.getResourceFlags() & kBilerp_TextureFlag); |
| } |