aboutsummaryrefslogtreecommitdiffhomepage
path: root/tests/SRGBMipMapTest.cpp
blob: e01d676554fda9ad5b16cdac488bc06e9f038379 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/*
 * Copyright 2016 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "Test.h"
#if SK_SUPPORT_GPU
#include "GrCaps.h"
#include "GrContext.h"
#include "GrRenderTargetContext.h"
#include "GrResourceProvider.h"
#include "gl/GrGLGpu.h"
#include "SkCanvas.h"
#include "SkSurface.h"

// using anonymous namespace because these functions are used as template params.
namespace {
/** convert 0..1 srgb value to 0..1 linear */
float srgb_to_linear(float srgb) {
    if (srgb <= 0.04045f) {
        return srgb / 12.92f;
    } else {
        return powf((srgb + 0.055f) / 1.055f, 2.4f);
    }
}

/** convert 0..1 linear value to 0..1 srgb */
float linear_to_srgb(float linear) {
    if (linear <= 0.0031308) {
        return linear * 12.92f;
    } else {
        return 1.055f * powf(linear, 1.f / 2.4f) - 0.055f;
    }
}
}

static bool check_value(U8CPU value, U8CPU expected, U8CPU error) {
    if (value >= expected) {
        return (value - expected) <= error;
    } else {
        return (expected - value) <= error;
    }
}

void read_and_check_pixels(skiatest::Reporter* reporter, GrTexture* texture, U8CPU expected,
                           U8CPU error, const char* subtestName) {
    int w = texture->width();
    int h = texture->height();
    SkAutoTMalloc<uint32_t> readData(w * h);
    memset(readData.get(), 0, sizeof(uint32_t) * w * h);
    if (!texture->readPixels(0, 0, w, h, texture->config(), readData.get())) {
        ERRORF(reporter, "Could not read pixels for %s.", subtestName);
        return;
    }
    for (int j = 0; j < h; ++j) {
        for (int i = 0; i < w; ++i) {
            uint32_t read = readData[j * w + i];

            bool success =
                check_value(read & 0xff, expected, error) &&
                check_value((read >> 8) & 0xff, expected, error) &&
                check_value((read >> 16) & 0xff, expected, error);

            if (!success) {
                ERRORF(reporter, "Expected 0xff%02x%02x%02x, read back as 0x%08x in %s at %d, %d.",
                       expected, expected, expected, read, subtestName, i, j);
                return;
            }
        }
    }
}

DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(SRGBMipMaps, reporter, ctxInfo) {
    GrContext* context = ctxInfo.grContext();
    if (!context->caps()->srgbSupport()) {
        return;
    }

    const int rtS = 16;
    const int texS = rtS * 2;

    // Fill texture with a dither of black and 60% sRGB (~ 32.5% linear) gray. Although there is
    // only one likely failure mode (doing a direct downsample of the sRGB values), this pattern
    // maximizes the minimum error across all three conceivable failure modes:
    // 1) Likely incorrect:
    //      (A + B) / 2
    // 2) No input decode, decode output:
    //      linear_to_srgb((A + B) / 2)
    // 3) Decode input, no output encode:
    //      (srgb_to_linear(A) + srgb_to_linear(B)) / 2

    const U8CPU srgb60 = sk_float_round2int(0.6f * 255.0f);
    static const SkPMColor colors[2] = {
        SkPackARGB32(0xFF, srgb60, srgb60, srgb60),
        SkPackARGB32(0xFF, 0x00, 0x00, 0x00)
    };
    uint32_t texData[texS * texS];
    for (int y = 0; y < texS; ++y) {
        for (int x = 0; x < texS; ++x) {
            texData[y * texS + x] = colors[(x + y) % 2];
        }
    }

    // We can be pretty generous with the error detection, thanks to the choice of input.
    // The closest likely failure mode is off by > 0.1, so anything that encodes within
    // 10/255 of optimal is more than good enough for this test.
    const U8CPU expectedSRGB = sk_float_round2int(
        linear_to_srgb(srgb_to_linear(srgb60 / 255.0f) / 2.0f) * 255.0f);
    const U8CPU expectedLinear = srgb60 / 2;
    const U8CPU error = 10;

    // Create our test texture
    GrSurfaceDesc desc;
    desc.fFlags = kNone_GrSurfaceFlags;
    desc.fConfig = kSRGBA_8888_GrPixelConfig;
    desc.fWidth = texS;
    desc.fHeight = texS;

    GrResourceProvider* resourceProvider = context->resourceProvider();
    sk_sp<GrTextureProxy> proxy = GrSurfaceProxy::MakeDeferred(*context->caps(), resourceProvider,
                                                               desc, SkBudgeted::kNo,
                                                               texData, 0);

    // Create two render target contexts (L32 and S32)
    sk_sp<SkColorSpace> srgbColorSpace = SkColorSpace::MakeSRGB();
    sk_sp<GrRenderTargetContext> l32RenderTargetContext = context->makeRenderTargetContext(
        SkBackingFit::kExact, rtS, rtS, kRGBA_8888_GrPixelConfig, nullptr);
    sk_sp<GrRenderTargetContext> s32RenderTargetContext = context->makeRenderTargetContext(
        SkBackingFit::kExact, rtS, rtS, kSRGBA_8888_GrPixelConfig, std::move(srgbColorSpace));

    SkRect rect = SkRect::MakeWH(SkIntToScalar(rtS), SkIntToScalar(rtS));
    GrNoClip noClip;
    GrPaint paint;
    paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
    GrSamplerParams mipMapParams(SkShader::kRepeat_TileMode, GrSamplerParams::kMipMap_FilterMode);
    paint.addColorTextureProcessor(context, std::move(proxy),
                                   nullptr, SkMatrix::MakeScale(rtS), mipMapParams);

    // 1) Draw texture to S32 surface (should generate/use sRGB mips)
    paint.setGammaCorrect(true);
    s32RenderTargetContext->drawRect(noClip, GrPaint(paint), GrAA::kNo, SkMatrix::I(), rect);
    read_and_check_pixels(reporter, s32RenderTargetContext->asTexture().get(), expectedSRGB, error,
                          "first render of sRGB");

    // 2) Draw texture to L32 surface (should generate/use linear mips)
    paint.setGammaCorrect(false);
    l32RenderTargetContext->drawRect(noClip, GrPaint(paint), GrAA::kNo, SkMatrix::I(), rect);

    // Right now, this test only runs on GL (because Vulkan doesn't support legacy mip-mapping
    // skbug.com/5048). On GL, we may not have sRGB decode support. In that case, rendering sRGB
    // textures to a legacy surface produces nonsense, so this part of the test is meaningless.
    //
    // We also skip this part of the test on command buffer (via srgbDecodeDisableAffectsMipmaps),
    // because that implementation of the extension doesn't ensure that mips respect the setting.
    //
    // TODO: Once Vulkan supports legacy mip-mapping, we can promote this to GrCaps. Right now,
    // Vulkan has most of the functionality, but not the mip-mapping part that's being tested here.
    GrGLGpu* glGpu = static_cast<GrGLGpu*>(context->getGpu());
    if (glGpu->glCaps().srgbDecodeDisableSupport() &&
        glGpu->glCaps().srgbDecodeDisableAffectsMipmaps()) {
        read_and_check_pixels(reporter, l32RenderTargetContext->asTexture().get(), expectedLinear,
                              error, "re-render as linear");
    }

    // 3) Go back to sRGB
    paint.setGammaCorrect(true);
    s32RenderTargetContext->drawRect(noClip, std::move(paint), GrAA::kNo, SkMatrix::I(), rect);
    read_and_check_pixels(reporter, s32RenderTargetContext->asTexture().get(), expectedSRGB, error,
                          "re-render as sRGB");
}
#endif