From 33f6b3f6ee4de24282f5e7f2dc31a5f538bcf40c Mon Sep 17 00:00:00 2001 From: brianosman Date: Thu, 2 Jun 2016 05:49:21 -0700 Subject: Manually generated sRGB mipmaps, with successively smaller draws. Dirty GL-generated mipmaps whenever an sRGB texture is used with a new value for TEXTURE_SRGB_DECODE. Add a new test rectangle to the gamma GM that tests that textures are correctly converted to linear before filtering when generating mipmaps. Added a new unit test that alternates how a texture is interpreted (sRGB or not), to verify that we rebuild mipmaps when needed, and that we get the correct results out in both modes. This test originally failed on four of our bots producing incorrect mips in three different ways. I'm not real surprised, but it looks like we can't rely on glGenerateMipmap to do the right thing, in conjunction with TEXTURE_SRGB_DECODE. Instead, actually create mip-chains using a series of draw calls. (My first attempt used glBlitFramebuffer, and that still had bugs on several bots). This approach appears to work correctly on any device that fully supports sRGB. Because the mipmap draws are fairly destructive to state, I had to hoist them out of bindTexture. That means adding a second pass over the texture accesses in the processor, at the very beginning of flush. BUG=skia: GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1840473002 Review-Url: https://codereview.chromium.org/2007973002 --- src/gpu/GrTexture.cpp | 6 +- src/gpu/GrTexturePriv.h | 8 +- src/gpu/gl/GrGLGpu.cpp | 464 ++++++++++++++++++++++++++++++++++++++++++--- src/gpu/gl/GrGLGpu.h | 20 ++ src/gpu/gl/GrGLProgram.cpp | 26 +++ src/gpu/gl/GrGLProgram.h | 9 + 6 files changed, 500 insertions(+), 33 deletions(-) (limited to 'src') 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 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 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(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 fGLContext; bool createCopyProgram(int progIdx); + bool createMipmapProgram(int progIdx); bool createWireRectProgram(); bool createPLSSetupProgram(); @@ -532,6 +538,14 @@ private: } fCopyPrograms[3]; SkAutoTUnref fCopyProgramArrayBuffer; + /** IDs for texture mipmap program. (4 filter configurations) */ + struct { + GrGLuint fProgram; + GrGLint fTextureUniform; + GrGLint fTexCoordXformUniform; + } fMipmapPrograms[4]; + SkAutoTUnref 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(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(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; -- cgit v1.2.3