diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/gpu/GrTexture.cpp | 6 | ||||
-rw-r--r-- | src/gpu/GrTexturePriv.h | 8 | ||||
-rw-r--r-- | src/gpu/gl/GrGLGpu.cpp | 464 | ||||
-rw-r--r-- | src/gpu/gl/GrGLGpu.h | 20 | ||||
-rw-r--r-- | src/gpu/gl/GrGLProgram.cpp | 26 | ||||
-rw-r--r-- | src/gpu/gl/GrGLProgram.h | 9 |
6 files changed, 500 insertions, 33 deletions
diff --git a/src/gpu/GrTexture.cpp b/src/gpu/GrTexture.cpp index 033f1da891..9209dbd053 100644 --- a/src/gpu/GrTexture.cpp +++ b/src/gpu/GrTexture.cpp @@ -18,7 +18,7 @@ #include "SkMipMap.h" #include "SkTypes.h" -void GrTexture::dirtyMipMaps(bool mipMapsDirty) { +void GrTexture::dirtyMipMaps(bool mipMapsDirty, bool sRGBCorrect) { if (mipMapsDirty) { if (kValid_MipMapsStatus == fMipMapsStatus) { fMipMapsStatus = kAllocated_MipMapsStatus; @@ -26,6 +26,7 @@ void GrTexture::dirtyMipMaps(bool mipMapsDirty) { } else { const bool sizeChanged = kNotAllocated_MipMapsStatus == fMipMapsStatus; fMipMapsStatus = kValid_MipMapsStatus; + fMipMapsAreSRGBCorrect = sRGBCorrect; if (sizeChanged) { // This must not be called until after changing fMipMapsStatus. this->didChangeGpuMemorySize(); @@ -93,9 +94,12 @@ GrTexture::GrTexture(GrGpu* gpu, const GrSurfaceDesc& desc, GrSLType samplerType if (wasMipMapDataProvided) { fMipMapsStatus = kValid_MipMapsStatus; fMaxMipMapLevel = SkMipMap::ComputeLevelCount(fDesc.fWidth, fDesc.fHeight); + // At the moment, the CPU code for generating mipmaps doesn't account for sRGB: + fMipMapsAreSRGBCorrect = false; } else { fMipMapsStatus = kNotAllocated_MipMapsStatus; fMaxMipMapLevel = 0; + fMipMapsAreSRGBCorrect = false; } } diff --git a/src/gpu/GrTexturePriv.h b/src/gpu/GrTexturePriv.h index fee7ed1f96..762890fcc0 100644 --- a/src/gpu/GrTexturePriv.h +++ b/src/gpu/GrTexturePriv.h @@ -29,7 +29,9 @@ public: return 0 != (fTexture->fDesc.fFlags & flags); } - void dirtyMipMaps(bool mipMapsDirty) { fTexture->dirtyMipMaps(mipMapsDirty); } + void dirtyMipMaps(bool mipMapsDirty, bool sRGBCorrect = false) { + fTexture->dirtyMipMaps(mipMapsDirty, sRGBCorrect); + } bool mipMapsAreDirty() const { return GrTexture::kValid_MipMapsStatus != fTexture->fMipMapsStatus; @@ -47,6 +49,10 @@ public: return fTexture->fMaxMipMapLevel; } + bool mipMapsAreSRGBCorrect() const { + return fTexture->fMipMapsAreSRGBCorrect; + } + static void ComputeScratchKey(const GrSurfaceDesc&, GrScratchKey*); private: diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp index 6daf955170..1744e25036 100644 --- a/src/gpu/gl/GrGLGpu.cpp +++ b/src/gpu/gl/GrGLGpu.cpp @@ -201,6 +201,9 @@ GrGLGpu::GrGLGpu(GrGLContext* ctx, GrContext* context) for (size_t i = 0; i < SK_ARRAY_COUNT(fCopyPrograms); ++i) { fCopyPrograms[i].fProgram = 0; } + for (size_t i = 0; i < SK_ARRAY_COUNT(fMipmapPrograms); ++i) { + fMipmapPrograms[i].fProgram = 0; + } fWireRectProgram.fProgram = 0; fPLSSetupProgram.fProgram = 0; @@ -257,6 +260,7 @@ GrGLGpu::~GrGLGpu() { // to release the resources held by the objects themselves. fPathRendering.reset(); fCopyProgramArrayBuffer.reset(); + fMipmapProgramArrayBuffer.reset(); fWireRectArrayBuffer.reset(); fPLSSetupProgram.fArrayBuffer.reset(); @@ -282,6 +286,12 @@ GrGLGpu::~GrGLGpu() { } } + for (size_t i = 0; i < SK_ARRAY_COUNT(fMipmapPrograms); ++i) { + if (0 != fMipmapPrograms[i].fProgram) { + GL_CALL(DeleteProgram(fMipmapPrograms[i].fProgram)); + } + } + if (0 != fWireRectProgram.fProgram) { GL_CALL(DeleteProgram(fWireRectProgram.fProgram)); } @@ -421,6 +431,11 @@ void GrGLGpu::disconnect(DisconnectType type) { GL_CALL(DeleteProgram(fCopyPrograms[i].fProgram)); } } + for (size_t i = 0; i < SK_ARRAY_COUNT(fMipmapPrograms); ++i) { + if (fMipmapPrograms[i].fProgram) { + GL_CALL(DeleteProgram(fMipmapPrograms[i].fProgram)); + } + } if (fWireRectProgram.fProgram) { GL_CALL(DeleteProgram(fWireRectProgram.fProgram)); } @@ -444,6 +459,10 @@ void GrGLGpu::disconnect(DisconnectType type) { for (size_t i = 0; i < SK_ARRAY_COUNT(fCopyPrograms); ++i) { fCopyPrograms[i].fProgram = 0; } + fMipmapProgramArrayBuffer.reset(); + for (size_t i = 0; i < SK_ARRAY_COUNT(fMipmapPrograms); ++i) { + fMipmapPrograms[i].fProgram = 0; + } fWireRectProgram.fProgram = 0; fWireRectArrayBuffer.reset(); fPLSSetupProgram.fProgram = 0; @@ -1981,6 +2000,14 @@ void GrGLGpu::flushMinSampleShading(float minSampleShading) { } bool GrGLGpu::flushGLState(const GrPipeline& pipeline, const GrPrimitiveProcessor& primProc) { + SkAutoTUnref<GrGLProgram> program(fProgramCache->refProgram(this, pipeline, primProc)); + if (!program) { + GrCapsDebugf(this->caps(), "Failed to create program!\n"); + return false; + } + + program->generateMipmaps(primProc, pipeline); + GrXferProcessor::BlendInfo blendInfo; pipeline.getXferProcessor().getBlendInfo(&blendInfo); @@ -1988,12 +2015,6 @@ bool GrGLGpu::flushGLState(const GrPipeline& pipeline, const GrPrimitiveProcesso this->flushDrawFace(pipeline.getDrawFace()); this->flushMinSampleShading(primProc.getSampleShading()); - SkAutoTUnref<GrGLProgram> program(fProgramCache->refProgram(this, pipeline, primProc)); - if (!program) { - GrCapsDebugf(this->caps(), "Failed to create program!\n"); - return false; - } - GrGLuint programID = program->programID(); if (fHWProgramID != programID) { GL_CALL(UseProgram(programID)); @@ -2645,19 +2666,22 @@ void GrGLGpu::flushRenderTarget(GrGLRenderTarget* target, const SkIRect* bounds, } if (this->glCaps().srgbWriteControl()) { - bool enableSRGBWrite = GrPixelConfigIsSRGB(target->config()) && !disableSRGB; - if (enableSRGBWrite && kYes_TriState != fHWSRGBFramebuffer) { - GL_CALL(Enable(GR_GL_FRAMEBUFFER_SRGB)); - fHWSRGBFramebuffer = kYes_TriState; - } else if (!enableSRGBWrite && kNo_TriState != fHWSRGBFramebuffer) { - GL_CALL(Disable(GR_GL_FRAMEBUFFER_SRGB)); - fHWSRGBFramebuffer = kNo_TriState; - } + this->flushFramebufferSRGB(GrPixelConfigIsSRGB(target->config()) && !disableSRGB); } this->didWriteToSurface(target, bounds); } +void GrGLGpu::flushFramebufferSRGB(bool enable) { + if (enable && kYes_TriState != fHWSRGBFramebuffer) { + GL_CALL(Enable(GR_GL_FRAMEBUFFER_SRGB)); + fHWSRGBFramebuffer = kYes_TriState; + } else if (!enable && kNo_TriState != fHWSRGBFramebuffer) { + GL_CALL(Disable(GR_GL_FRAMEBUFFER_SRGB)); + fHWSRGBFramebuffer = kNo_TriState; + } +} + void GrGLGpu::flushViewport(const GrGLIRect& viewport) { if (fHWViewport != viewport) { viewport.pushToGLViewport(this->glInterface()); @@ -3138,17 +3162,6 @@ void GrGLGpu::bindTexture(int unitIdx, const GrTextureParams& params, bool allow bool setAll = timestamp < this->getResetTimestamp(); GrGLTexture::TexParams newTexParams; - if (this->caps()->srgbSupport()) { - // By default, the decision to allow SRGB decode is based on the destination config. - // A texture can override that by specifying a value in GrTextureParams. - newTexParams.fSRGBDecode = allowSRGBInputs ? GR_GL_DECODE_EXT : GR_GL_SKIP_DECODE_EXT; - - if (setAll || newTexParams.fSRGBDecode != oldTexParams.fSRGBDecode) { - this->setTextureUnit(unitIdx); - GL_CALL(TexParameteri(target, GR_GL_TEXTURE_SRGB_DECODE_EXT, newTexParams.fSRGBDecode)); - } - } - static GrGLenum glMinFilterModes[] = { GR_GL_NEAREST, GR_GL_LINEAR, @@ -3170,16 +3183,27 @@ void GrGLGpu::bindTexture(int unitIdx, const GrTextureParams& params, bool allow newTexParams.fMinFilter = glMinFilterModes[filterMode]; newTexParams.fMagFilter = glMagFilterModes[filterMode]; - if (GrTextureParams::kMipMap_FilterMode == filterMode) { - if (texture->texturePriv().mipMapsAreDirty()) { + bool enableSRGBDecode = false; + if (GrPixelConfigIsSRGB(texture->config())) { + enableSRGBDecode = allowSRGBInputs; + + newTexParams.fSRGBDecode = enableSRGBDecode ? GR_GL_DECODE_EXT : GR_GL_SKIP_DECODE_EXT; + if (setAll || newTexParams.fSRGBDecode != oldTexParams.fSRGBDecode) { this->setTextureUnit(unitIdx); - GL_CALL(GenerateMipmap(target)); - texture->texturePriv().dirtyMipMaps(false); - texture->texturePriv().setMaxMipMapLevel(SkMipMap::ComputeLevelCount( - texture->width(), texture->height())); + GL_CALL(TexParameteri(target, GR_GL_TEXTURE_SRGB_DECODE_EXT, newTexParams.fSRGBDecode)); } } +#ifdef SK_DEBUG + // We were supposed to ensure MipMaps were up-to-date and built correctly before getting here. + if (GrTextureParams::kMipMap_FilterMode == filterMode) { + SkASSERT(!texture->texturePriv().mipMapsAreDirty()); + if (GrPixelConfigIsSRGB(texture->config())) { + SkASSERT(texture->texturePriv().mipMapsAreSRGBCorrect() == enableSRGBDecode); + } + } +#endif + newTexParams.fMaxMipMapLevel = texture->texturePriv().maxMipMapLevel(); newTexParams.fWrapS = tile_to_gl_wrap(params.getTileModeX()); @@ -3278,6 +3302,67 @@ void GrGLGpu::bindTexelBuffer(int unitIdx, intptr_t offsetInBytes, GrPixelConfig } } +void GrGLGpu::generateMipmaps(const GrTextureParams& params, bool allowSRGBInputs, + GrGLTexture* texture) { + SkASSERT(texture); + + // First, figure out if we need mips for this texture at all: + GrTextureParams::FilterMode filterMode = params.filterMode(); + + if (GrTextureParams::kMipMap_FilterMode == filterMode) { + if (!this->caps()->mipMapSupport() || GrPixelConfigIsCompressed(texture->config())) { + filterMode = GrTextureParams::kBilerp_FilterMode; + } + } + + if (GrTextureParams::kMipMap_FilterMode != filterMode) { + return; + } + + // If this is an sRGB texture and the mips were previously built the "other" way + // (gamma-correct vs. not), then we need to rebuild them. We don't need to check for + // srgbSupport - we'll *never* get an sRGB pixel config if we don't support it. + if (GrPixelConfigIsSRGB(texture->config()) && + allowSRGBInputs != texture->texturePriv().mipMapsAreSRGBCorrect()) { + texture->texturePriv().dirtyMipMaps(true); + } + + // If the mips aren't dirty, we're done: + if (!texture->texturePriv().mipMapsAreDirty()) { + return; + } + + // If we created a rt/tex and rendered to it without using a texture and now we're texturing + // from the rt it will still be the last bound texture, but it needs resolving. + GrGLRenderTarget* texRT = static_cast<GrGLRenderTarget*>(texture->asRenderTarget()); + if (texRT) { + this->onResolveRenderTarget(texRT); + } + + GrGLenum target = texture->target(); + this->setScratchTextureUnit(); + GL_CALL(BindTexture(target, texture->textureID())); + + // Configure sRGB decode, if necessary. This state is the only thing needed for the driver + // call (glGenerateMipmap) to work correctly. Our manual method dirties other state, too. + if (GrPixelConfigIsSRGB(texture->config())) { + GL_CALL(TexParameteri(target, GR_GL_TEXTURE_SRGB_DECODE_EXT, + allowSRGBInputs ? GR_GL_DECODE_EXT : GR_GL_SKIP_DECODE_EXT)); + } + + // Either do manual mipmap generation or (if that fails), just rely on the driver: + if (!this->generateMipmap(texture, allowSRGBInputs)) { + GL_CALL(GenerateMipmap(target)); + } + + texture->texturePriv().dirtyMipMaps(false, allowSRGBInputs); + texture->texturePriv().setMaxMipMapLevel(SkMipMap::ComputeLevelCount( + texture->width(), texture->height())); + + // We have potentially set lots of state on the texture. Easiest to dirty it all: + texture->textureParamsModified(); +} + void GrGLGpu::setTextureSwizzle(int unitIdx, GrGLenum target, const GrGLenum swizzle[]) { this->setTextureUnit(unitIdx); if (this->glStandard() == kGLES_GrGLStandard) { @@ -3711,6 +3796,167 @@ bool GrGLGpu::createCopyProgram(int progIdx) { return true; } +bool GrGLGpu::createMipmapProgram(int progIdx) { + const bool oddWidth = SkToBool(progIdx & 0x2); + const bool oddHeight = SkToBool(progIdx & 0x1); + const int numTaps = (oddWidth ? 2 : 1) * (oddHeight ? 2 : 1); + + const GrGLSLCaps* glslCaps = this->glCaps().glslCaps(); + + SkASSERT(!fMipmapPrograms[progIdx].fProgram); + GL_CALL_RET(fMipmapPrograms[progIdx].fProgram, CreateProgram()); + if (!fMipmapPrograms[progIdx].fProgram) { + return false; + } + + const char* version = glslCaps->versionDeclString(); + GrGLSLShaderVar aVertex("a_vertex", kVec2f_GrSLType, GrShaderVar::kAttribute_TypeModifier); + GrGLSLShaderVar uTexCoordXform("u_texCoordXform", kVec4f_GrSLType, + GrShaderVar::kUniform_TypeModifier); + GrGLSLShaderVar uTexture("u_texture", kSampler2D_GrSLType, GrShaderVar::kUniform_TypeModifier); + // We need 1, 2, or 4 texture coordinates (depending on parity of each dimension): + GrGLSLShaderVar vTexCoords[] = { + GrGLSLShaderVar("v_texCoord0", kVec2f_GrSLType, GrShaderVar::kVaryingOut_TypeModifier), + GrGLSLShaderVar("v_texCoord1", kVec2f_GrSLType, GrShaderVar::kVaryingOut_TypeModifier), + GrGLSLShaderVar("v_texCoord2", kVec2f_GrSLType, GrShaderVar::kVaryingOut_TypeModifier), + GrGLSLShaderVar("v_texCoord3", kVec2f_GrSLType, GrShaderVar::kVaryingOut_TypeModifier), + }; + GrGLSLShaderVar oFragColor("o_FragColor", kVec4f_GrSLType, + GrShaderVar::kOut_TypeModifier); + + SkString vshaderTxt(version); + if (glslCaps->noperspectiveInterpolationSupport()) { + if (const char* extension = glslCaps->noperspectiveInterpolationExtensionString()) { + vshaderTxt.appendf("#extension %s : require\n", extension); + } + vTexCoords[0].addModifier("noperspective"); + vTexCoords[1].addModifier("noperspective"); + vTexCoords[2].addModifier("noperspective"); + vTexCoords[3].addModifier("noperspective"); + } + + aVertex.appendDecl(glslCaps, &vshaderTxt); + vshaderTxt.append(";"); + uTexCoordXform.appendDecl(glslCaps, &vshaderTxt); + vshaderTxt.append(";"); + for (int i = 0; i < numTaps; ++i) { + vTexCoords[i].appendDecl(glslCaps, &vshaderTxt); + vshaderTxt.append(";"); + } + + vshaderTxt.append( + "// Mipmap Program VS\n" + "void main() {" + " gl_Position.xy = a_vertex * vec2(2, 2) - vec2(1, 1);" + " gl_Position.zw = vec2(0, 1);" + ); + + // Insert texture coordinate computation: + if (oddWidth && oddHeight) { + vshaderTxt.append( + " v_texCoord0 = a_vertex.xy * u_texCoordXform.yw;" + " v_texCoord1 = a_vertex.xy * u_texCoordXform.yw + vec2(u_texCoordXform.x, 0);" + " v_texCoord2 = a_vertex.xy * u_texCoordXform.yw + vec2(0, u_texCoordXform.z);" + " v_texCoord3 = a_vertex.xy * u_texCoordXform.yw + u_texCoordXform.xz;" + ); + } else if (oddWidth) { + vshaderTxt.append( + " v_texCoord0 = a_vertex.xy * vec2(u_texCoordXform.y, 1);" + " v_texCoord1 = a_vertex.xy * vec2(u_texCoordXform.y, 1) + vec2(u_texCoordXform.x, 0);" + ); + } else if (oddHeight) { + vshaderTxt.append( + " v_texCoord0 = a_vertex.xy * vec2(1, u_texCoordXform.w);" + " v_texCoord1 = a_vertex.xy * vec2(1, u_texCoordXform.w) + vec2(0, u_texCoordXform.z);" + ); + } else { + vshaderTxt.append( + " v_texCoord0 = a_vertex.xy;" + ); + } + + vshaderTxt.append("}"); + + SkString fshaderTxt(version); + if (glslCaps->noperspectiveInterpolationSupport()) { + if (const char* extension = glslCaps->noperspectiveInterpolationExtensionString()) { + fshaderTxt.appendf("#extension %s : require\n", extension); + } + } + GrGLSLAppendDefaultFloatPrecisionDeclaration(kDefault_GrSLPrecision, *glslCaps, + &fshaderTxt); + for (int i = 0; i < numTaps; ++i) { + vTexCoords[i].setTypeModifier(GrShaderVar::kVaryingIn_TypeModifier); + vTexCoords[i].appendDecl(glslCaps, &fshaderTxt); + fshaderTxt.append(";"); + } + uTexture.appendDecl(glslCaps, &fshaderTxt); + fshaderTxt.append(";"); + const char* fsOutName; + if (glslCaps->mustDeclareFragmentShaderOutput()) { + oFragColor.appendDecl(glslCaps, &fshaderTxt); + fshaderTxt.append(";"); + fsOutName = oFragColor.c_str(); + } else { + fsOutName = "gl_FragColor"; + } + const char* sampleFunction = GrGLSLTexture2DFunctionName(kVec2f_GrSLType, kSampler2D_GrSLType, + this->glslGeneration()); + fshaderTxt.append( + "// Mipmap Program FS\n" + "void main() {" + ); + + if (oddWidth && oddHeight) { + fshaderTxt.appendf( + " %s = (%s(u_texture, v_texCoord0) + %s(u_texture, v_texCoord1) + " + " %s(u_texture, v_texCoord2) + %s(u_texture, v_texCoord3)) * 0.25;", + fsOutName, sampleFunction, sampleFunction, sampleFunction, sampleFunction + ); + } else if (oddWidth || oddHeight) { + fshaderTxt.appendf( + " %s = (%s(u_texture, v_texCoord0) + %s(u_texture, v_texCoord1)) * 0.5;", + fsOutName, sampleFunction, sampleFunction + ); + } else { + fshaderTxt.appendf( + " %s = %s(u_texture, v_texCoord0);", + fsOutName, sampleFunction + ); + } + + fshaderTxt.append("}"); + + const char* str; + GrGLint length; + + str = vshaderTxt.c_str(); + length = SkToInt(vshaderTxt.size()); + GrGLuint vshader = GrGLCompileAndAttachShader(*fGLContext, fMipmapPrograms[progIdx].fProgram, + GR_GL_VERTEX_SHADER, &str, &length, 1, + &fStats); + + str = fshaderTxt.c_str(); + length = SkToInt(fshaderTxt.size()); + GrGLuint fshader = GrGLCompileAndAttachShader(*fGLContext, fMipmapPrograms[progIdx].fProgram, + GR_GL_FRAGMENT_SHADER, &str, &length, 1, + &fStats); + + GL_CALL(LinkProgram(fMipmapPrograms[progIdx].fProgram)); + + GL_CALL_RET(fMipmapPrograms[progIdx].fTextureUniform, + GetUniformLocation(fMipmapPrograms[progIdx].fProgram, "u_texture")); + GL_CALL_RET(fMipmapPrograms[progIdx].fTexCoordXformUniform, + GetUniformLocation(fMipmapPrograms[progIdx].fProgram, "u_texCoordXform")); + + GL_CALL(BindAttribLocation(fMipmapPrograms[progIdx].fProgram, 0, "a_vertex")); + + GL_CALL(DeleteShader(vshader)); + GL_CALL(DeleteShader(fshader)); + + return true; +} + bool GrGLGpu::createWireRectProgram() { if (!fWireRectArrayBuffer) { static const GrGLfloat vdata[] = { @@ -4068,6 +4314,162 @@ bool GrGLGpu::copySurfaceAsBlitFramebuffer(GrSurface* dst, return true; } +bool gManualMipmaps = true; + +// Manual implementation of mipmap generation, to work around driver bugs w/sRGB. +// Uses draw calls to do a series of downsample operations to successive mips. +// If this returns false, then the calling code falls back to using glGenerateMipmap. +bool GrGLGpu::generateMipmap(GrGLTexture* texture, bool gammaCorrect) { + // Global switch for manual mipmap generation: + if (!gManualMipmaps) { + return false; + } + + // Mipmaps are only supported on 2D textures: + if (GR_GL_TEXTURE_2D != texture->target()) { + return false; + } + + // We need to be able to render to the texture for this to work: + if (!this->caps()->isConfigRenderable(texture->config(), false)) { + return false; + } + + // Our iterative downsample requires the ability to limit which level we're sampling: + if (!this->glCaps().mipMapLevelAndLodControlSupport()) { + return false; + } + + // If we're mipping an sRGB texture, we need to ensure FB sRGB is correct: + if (GrPixelConfigIsSRGB(texture->config())) { + // If we have write-control, just set the state that we want: + if (this->glCaps().srgbWriteControl()) { + this->flushFramebufferSRGB(gammaCorrect); + } else if (!gammaCorrect) { + // If we don't have write-control we can't do non-gamma-correct mipmapping: + return false; + } + } + + int width = texture->width(); + int height = texture->height(); + int levelCount = SkMipMap::ComputeLevelCount(width, height) + 1; + + // Define all mips, if we haven't previously done so: + if (0 == texture->texturePriv().maxMipMapLevel()) { + GrGLenum internalFormat; + GrGLenum externalFormat; + GrGLenum externalType; + if (!this->glCaps().getTexImageFormats(texture->config(), texture->config(), + &internalFormat, &externalFormat, &externalType)) { + return false; + } + + for (GrGLint level = 1; level < levelCount; ++level) { + // Define the next mip: + width = SkTMax(1, width / 2); + height = SkTMax(1, height / 2); + GL_ALLOC_CALL(this->glInterface(), TexImage2D(GR_GL_TEXTURE_2D, level, internalFormat, + width, height, 0, + externalFormat, externalType, nullptr)); + } + } + + // Create (if necessary), then bind temporary FBO: + if (0 == fTempDstFBOID) { + GL_CALL(GenFramebuffers(1, &fTempDstFBOID)); + } + GL_CALL(BindFramebuffer(GR_GL_FRAMEBUFFER, fTempDstFBOID)); + fHWBoundRenderTargetUniqueID = SK_InvalidUniqueID; + + // Bind the texture, to get things configured for filtering. + // We'll be changing our base level further below: + this->setTextureUnit(0); + GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kBilerp_FilterMode); + this->bindTexture(0, params, gammaCorrect, texture); + + // Vertex data: + if (!fMipmapProgramArrayBuffer) { + static const GrGLfloat vdata[] = { + 0, 0, + 0, 1, + 1, 0, + 1, 1 + }; + fMipmapProgramArrayBuffer.reset(GrGLBuffer::Create(this, sizeof(vdata), + kVertex_GrBufferType, + kStatic_GrAccessPattern, vdata)); + } + if (!fMipmapProgramArrayBuffer) { + return false; + } + + fHWVertexArrayState.setVertexArrayID(this, 0); + + GrGLAttribArrayState* attribs = fHWVertexArrayState.bindInternalVertexArray(this); + attribs->set(this, 0, fMipmapProgramArrayBuffer, kVec2f_GrVertexAttribType, + 2 * sizeof(GrGLfloat), 0); + attribs->disableUnusedArrays(this, 0x1); + + // Set "simple" state once: + GrXferProcessor::BlendInfo blendInfo; + blendInfo.reset(); + this->flushBlend(blendInfo, GrSwizzle::RGBA()); + this->flushColorWrite(true); + this->flushDrawFace(GrPipelineBuilder::kBoth_DrawFace); + this->flushHWAAState(nullptr, false, false); + this->disableScissor(); + GrStencilSettings stencil; + stencil.setDisabled(); + this->flushStencil(stencil); + + // Do all the blits: + width = texture->width(); + height = texture->height(); + GrGLIRect viewport; + viewport.fLeft = 0; + viewport.fBottom = 0; + for (GrGLint level = 1; level < levelCount; ++level) { + // Get and bind the program for this particular downsample (filter shape can vary): + int progIdx = TextureSizeToMipmapProgramIdx(width, height); + if (!fMipmapPrograms[progIdx].fProgram) { + if (!this->createMipmapProgram(progIdx)) { + SkDebugf("Failed to create mipmap program.\n"); + return false; + } + } + GL_CALL(UseProgram(fMipmapPrograms[progIdx].fProgram)); + fHWProgramID = fMipmapPrograms[progIdx].fProgram; + + // Texcoord uniform is expected to contain (1/w, (w-1)/w, 1/h, (h-1)/h) + const float invWidth = 1.0f / width; + const float invHeight = 1.0f / height; + GL_CALL(Uniform4f(fMipmapPrograms[progIdx].fTexCoordXformUniform, + invWidth, (width - 1) * invWidth, invHeight, (height - 1) * invHeight)); + GL_CALL(Uniform1i(fMipmapPrograms[progIdx].fTextureUniform, 0)); + + // Only sample from previous mip + GL_CALL(TexParameteri(GR_GL_TEXTURE_2D, GR_GL_TEXTURE_BASE_LEVEL, level - 1)); + + GL_CALL(FramebufferTexture2D(GR_GL_FRAMEBUFFER, GR_GL_COLOR_ATTACHMENT0, + GR_GL_TEXTURE_2D, texture->textureID(), level)); + + width = SkTMax(1, width / 2); + height = SkTMax(1, height / 2); + viewport.fWidth = width; + viewport.fHeight = height; + this->flushViewport(viewport); + + GL_CALL(DrawArrays(GR_GL_TRIANGLE_STRIP, 0, 4)); + } + + // Unbind: + GL_CALL(FramebufferTexture2D(GR_GL_FRAMEBUFFER, GR_GL_COLOR_ATTACHMENT0, + GR_GL_TEXTURE_2D, 0, 0)); + + return true; +} + void GrGLGpu::onGetMultisampleSpecs(GrRenderTarget* rt, const GrStencilSettings& stencil, int* effectiveSampleCnt, diff --git a/src/gpu/gl/GrGLGpu.h b/src/gpu/gl/GrGLGpu.h index 0b2198a834..06d4bd357b 100644 --- a/src/gpu/gl/GrGLGpu.h +++ b/src/gpu/gl/GrGLGpu.h @@ -62,6 +62,8 @@ public: void bindTexelBuffer(int unitIdx, intptr_t offsetInBytes, GrPixelConfig, GrGLBuffer*); + void generateMipmaps(const GrTextureParams& params, bool allowSRGBInputs, GrGLTexture* texture); + bool onGetReadPixelsInfo(GrSurface* srcSurface, int readWidth, int readHeight, size_t rowBytes, GrPixelConfig readConfig, DrawPreference*, ReadPixelTempDrawInfo*) override; @@ -237,6 +239,7 @@ private: GrSurface* src, const SkIRect& srcRect, const SkIPoint& dstPoint); + bool generateMipmap(GrGLTexture* texture, bool gammaCorrect); void stampPLSSetupRect(const SkRect& bounds); @@ -319,6 +322,8 @@ private: void flushMinSampleShading(float minSampleShading); + void flushFramebufferSRGB(bool enable); + // helper for onCreateTexture and writeTexturePixels enum UploadType { kNewTexture_UploadType, // we are creating a new texture @@ -365,6 +370,7 @@ private: SkAutoTUnref<GrGLContext> fGLContext; bool createCopyProgram(int progIdx); + bool createMipmapProgram(int progIdx); bool createWireRectProgram(); bool createPLSSetupProgram(); @@ -532,6 +538,14 @@ private: } fCopyPrograms[3]; SkAutoTUnref<GrGLBuffer> fCopyProgramArrayBuffer; + /** IDs for texture mipmap program. (4 filter configurations) */ + struct { + GrGLuint fProgram; + GrGLint fTextureUniform; + GrGLint fTexCoordXformUniform; + } fMipmapPrograms[4]; + SkAutoTUnref<GrGLBuffer> fMipmapProgramArrayBuffer; + struct { GrGLuint fProgram; GrGLint fColorUniform; @@ -553,6 +567,12 @@ private: } } + static int TextureSizeToMipmapProgramIdx(int width, int height) { + const bool wide = (width > 1) && SkToBool(width & 0x1); + const bool tall = (height > 1) && SkToBool(height & 0x1); + return (wide ? 0x2 : 0x0) | (tall ? 0x1 : 0x0); + } + struct { GrGLuint fProgram; GrGLint fPosXformUniform; diff --git a/src/gpu/gl/GrGLProgram.cpp b/src/gpu/gl/GrGLProgram.cpp index c270858ec3..040c57de3a 100644 --- a/src/gpu/gl/GrGLProgram.cpp +++ b/src/gpu/gl/GrGLProgram.cpp @@ -83,6 +83,23 @@ void GrGLProgram::setData(const GrPrimitiveProcessor& primProc, const GrPipeline } } +void GrGLProgram::generateMipmaps(const GrPrimitiveProcessor& primProc, + const GrPipeline& pipeline) { + this->generateMipmaps(primProc, pipeline.getAllowSRGBInputs()); + + int numProcessors = fFragmentProcessors.count(); + for (int i = 0; i < numProcessors; ++i) { + const GrFragmentProcessor& processor = pipeline.getFragmentProcessor(i); + this->generateMipmaps(processor, pipeline.getAllowSRGBInputs()); + } + + if (primProc.getPixelLocalStorageState() != + GrPixelLocalStorageState::kDraw_GrPixelLocalStorageState) { + const GrXferProcessor& xp = pipeline.getXferProcessor(); + this->generateMipmaps(xp, pipeline.getAllowSRGBInputs()); + } +} + void GrGLProgram::setFragmentData(const GrPrimitiveProcessor& primProc, const GrPipeline& pipeline, int* nextSamplerIdx) { @@ -146,3 +163,12 @@ void GrGLProgram::bindTextures(const GrProcessor& processor, static_cast<GrGLBuffer*>(access.buffer())); } } + +void GrGLProgram::generateMipmaps(const GrProcessor& processor, + bool allowSRGBInputs) { + for (int i = 0; i < processor.numTextures(); ++i) { + const GrTextureAccess& access = processor.textureAccess(i); + fGpu->generateMipmaps(access.getParams(), allowSRGBInputs, + static_cast<GrGLTexture*>(access.getTexture())); + } +} diff --git a/src/gpu/gl/GrGLProgram.h b/src/gpu/gl/GrGLProgram.h index 7487a1e900..9f2b2e9b74 100644 --- a/src/gpu/gl/GrGLProgram.h +++ b/src/gpu/gl/GrGLProgram.h @@ -96,6 +96,12 @@ public: */ void setData(const GrPrimitiveProcessor&, const GrPipeline&); + /** + * This function retrieves the textures that need to be used by each GrGL*Processor, and + * ensures that any textures requiring mipmaps have their mipmaps correctly built. + */ + void generateMipmaps(const GrPrimitiveProcessor&, const GrPipeline&); + protected: typedef GrGLSLProgramDataManager::UniformHandle UniformHandle; typedef GrGLProgramDataManager::UniformInfoArray UniformInfoArray; @@ -122,6 +128,9 @@ protected: // Helper for setData() that binds textures and texel buffers to the appropriate texture units void bindTextures(const GrProcessor&, bool allowSRGBInputs, int* nextSamplerIdx); + // Helper for generateMipmaps() that ensures mipmaps are up to date + void generateMipmaps(const GrProcessor&, bool allowSRGBInputs); + // these reflect the current values of uniforms (GL uniform values travel with program) RenderTargetState fRenderTargetState; BuiltinUniformHandles fBuiltinUniformHandles; |