1 /* 2 * Copyright 2016 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8 #include "Test.h" 9 #if SK_SUPPORT_GPU 10 #include "GrCaps.h" 11 #include "GrClip.h" 12 #include "GrContext.h" 13 #include "GrContextPriv.h" 14 #include "GrProxyProvider.h" 15 #include "GrRenderTargetContext.h" 16 #include "SkCanvas.h" 17 #include "SkGr.h" 18 #include "SkSurface.h" 19 #include "gl/GrGLGpu.h" 20 21 // using anonymous namespace because these functions are used as template params. 22 namespace { 23 /** convert 0..1 srgb value to 0..1 linear */ 24 float srgb_to_linear(float srgb) { 25 if (srgb <= 0.04045f) { 26 return srgb / 12.92f; 27 } else { 28 return powf((srgb + 0.055f) / 1.055f, 2.4f); 29 } 30 } 31 32 /** convert 0..1 linear value to 0..1 srgb */ 33 float linear_to_srgb(float linear) { 34 if (linear <= 0.0031308) { 35 return linear * 12.92f; 36 } else { 37 return 1.055f * powf(linear, 1.f / 2.4f) - 0.055f; 38 } 39 } 40 } 41 42 static bool check_value(U8CPU value, U8CPU expected, U8CPU error) { 43 if (value >= expected) { 44 return (value - expected) <= error; 45 } else { 46 return (expected - value) <= error; 47 } 48 } 49 50 void read_and_check_pixels(skiatest::Reporter* reporter, GrSurfaceContext* context, 51 U8CPU expected, const SkImageInfo& dstInfo, 52 U8CPU error, const char* subtestName) { 53 int w = dstInfo.width(); 54 int h = dstInfo.height(); 55 SkAutoTMalloc<uint32_t> readData(w * h); 56 memset(readData.get(), 0, sizeof(uint32_t) * w * h); 57 58 if (!context->readPixels(dstInfo, readData.get(), 0, 0, 0)) { 59 ERRORF(reporter, "Could not read pixels for %s.", subtestName); 60 return; 61 } 62 63 for (int j = 0; j < h; ++j) { 64 for (int i = 0; i < w; ++i) { 65 uint32_t read = readData[j * w + i]; 66 67 bool success = 68 check_value(read & 0xff, expected, error) && 69 check_value((read >> 8) & 0xff, expected, error) && 70 check_value((read >> 16) & 0xff, expected, error); 71 72 if (!success) { 73 ERRORF(reporter, "Expected 0xff%02x%02x%02x, read back as 0x%08x in %s at %d, %d.", 74 expected, expected, expected, read, subtestName, i, j); 75 return; 76 } 77 } 78 } 79 } 80 81 DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(SRGBMipMaps, reporter, ctxInfo) { 82 GrContext* context = ctxInfo.grContext(); 83 if (!context->caps()->srgbSupport()) { 84 return; 85 } 86 87 const int rtS = 16; 88 const int texS = rtS * 2; 89 90 // Fill texture with a dither of black and 60% sRGB (~ 32.5% linear) gray. Although there is 91 // only one likely failure mode (doing a direct downsample of the sRGB values), this pattern 92 // maximizes the minimum error across all three conceivable failure modes: 93 // 1) Likely incorrect: 94 // (A + B) / 2 95 // 2) No input decode, decode output: 96 // linear_to_srgb((A + B) / 2) 97 // 3) Decode input, no output encode: 98 // (srgb_to_linear(A) + srgb_to_linear(B)) / 2 99 100 const U8CPU srgb60 = sk_float_round2int(0.6f * 255.0f); 101 static const SkPMColor colors[2] = { 102 SkPackARGB32(0xFF, srgb60, srgb60, srgb60), 103 SkPackARGB32(0xFF, 0x00, 0x00, 0x00) 104 }; 105 uint32_t texData[texS * texS]; 106 for (int y = 0; y < texS; ++y) { 107 for (int x = 0; x < texS; ++x) { 108 texData[y * texS + x] = colors[(x + y) % 2]; 109 } 110 } 111 112 // We can be pretty generous with the error detection, thanks to the choice of input. 113 // The closest likely failure mode is off by > 0.1, so anything that encodes within 114 // 10/255 of optimal is more than good enough for this test. 115 const U8CPU expectedSRGB = sk_float_round2int( 116 linear_to_srgb(srgb_to_linear(srgb60 / 255.0f) / 2.0f) * 255.0f); 117 const U8CPU expectedLinear = srgb60 / 2; 118 const U8CPU error = 10; 119 120 const SkImageInfo iiSRGBA = SkImageInfo::Make(rtS, rtS, kRGBA_8888_SkColorType, 121 kPremul_SkAlphaType, 122 SkColorSpace::MakeSRGB()); 123 const SkImageInfo iiRGBA = SkImageInfo::Make(rtS, rtS, kRGBA_8888_SkColorType, 124 kPremul_SkAlphaType); 125 126 // Create our test texture 127 GrSurfaceDesc desc; 128 desc.fFlags = kNone_GrSurfaceFlags; 129 desc.fOrigin = kTopLeft_GrSurfaceOrigin; 130 desc.fWidth = texS; 131 desc.fHeight = texS; 132 desc.fConfig = kSRGBA_8888_GrPixelConfig; 133 134 GrProxyProvider* proxyProvider = context->contextPriv().proxyProvider(); 135 sk_sp<GrTextureProxy> proxy = proxyProvider->createTextureProxy( 136 desc, SkBudgeted::kNo, texData, 0); 137 138 // Create two render target contexts (L32 and S32) 139 sk_sp<SkColorSpace> srgbColorSpace = SkColorSpace::MakeSRGB(); 140 sk_sp<GrRenderTargetContext> l32RenderTargetContext = context->makeDeferredRenderTargetContext( 141 SkBackingFit::kExact, rtS, rtS, kRGBA_8888_GrPixelConfig, nullptr); 142 sk_sp<GrRenderTargetContext> s32RenderTargetContext = context->makeDeferredRenderTargetContext( 143 SkBackingFit::kExact, rtS, rtS, kSRGBA_8888_GrPixelConfig, std::move(srgbColorSpace)); 144 145 SkRect rect = SkRect::MakeWH(SkIntToScalar(rtS), SkIntToScalar(rtS)); 146 GrNoClip noClip; 147 GrPaint paint; 148 paint.setPorterDuffXPFactory(SkBlendMode::kSrc); 149 GrSamplerState mipMapSamplerState(GrSamplerState::WrapMode::kRepeat, 150 GrSamplerState::Filter::kMipMap); 151 paint.addColorTextureProcessor(std::move(proxy), SkMatrix::MakeScale(rtS), mipMapSamplerState); 152 153 // 1) Draw texture to S32 surface (should generate/use sRGB mips) 154 paint.setGammaCorrect(true); 155 s32RenderTargetContext->drawRect(noClip, GrPaint::Clone(paint), GrAA::kNo, SkMatrix::I(), rect); 156 read_and_check_pixels(reporter, s32RenderTargetContext.get(), expectedSRGB, iiSRGBA, error, 157 "first render of sRGB"); 158 159 // 2) Draw texture to L32 surface (should generate/use linear mips) 160 paint.setGammaCorrect(false); 161 l32RenderTargetContext->drawRect(noClip, GrPaint::Clone(paint), GrAA::kNo, SkMatrix::I(), rect); 162 163 // Right now, this test only runs on GL (because Vulkan doesn't support legacy mip-mapping 164 // skbug.com/5048). On GL, we may not have sRGB decode support. In that case, rendering sRGB 165 // textures to a legacy surface produces nonsense, so this part of the test is meaningless. 166 // 167 // We also skip this part of the test on command buffer (via srgbDecodeDisableAffectsMipmaps), 168 // because that implementation of the extension doesn't ensure that mips respect the setting. 169 // 170 // TODO: Once Vulkan supports legacy mip-mapping, we can promote this to GrCaps. Right now, 171 // Vulkan has most of the functionality, but not the mip-mapping part that's being tested here. 172 GrGLGpu* glGpu = static_cast<GrGLGpu*>(context->contextPriv().getGpu()); 173 if (glGpu->glCaps().srgbDecodeDisableSupport() && 174 glGpu->glCaps().srgbDecodeDisableAffectsMipmaps()) { 175 read_and_check_pixels(reporter, l32RenderTargetContext.get(), expectedLinear, iiRGBA, 176 error, "re-render as linear"); 177 } 178 179 // 3) Go back to sRGB 180 paint.setGammaCorrect(true); 181 s32RenderTargetContext->drawRect(noClip, std::move(paint), GrAA::kNo, SkMatrix::I(), rect); 182 read_and_check_pixels(reporter, s32RenderTargetContext.get(), expectedSRGB, iiSRGBA, error, 183 "re-render as sRGB"); 184 } 185 #endif 186