1 /*
2  * Copyright 2015 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 "GrContext.h"
12 #include "GrContextPriv.h"
13 #include "GrSurfaceContext.h"
14 #include "SkCanvas.h"
15 #include "SkGr.h"
16 #include "SkSurface.h"
17 
18 // using anonymous namespace because these functions are used as template params.
19 namespace {
20 /** convert 0..1 srgb value to 0..1 linear */
21 float srgb_to_linear(float srgb) {
22     if (srgb <= 0.04045f) {
23         return srgb / 12.92f;
24     } else {
25         return powf((srgb + 0.055f) / 1.055f, 2.4f);
26     }
27 }
28 
29 /** convert 0..1 linear value to 0..1 srgb */
30 float linear_to_srgb(float linear) {
31     if (linear <= 0.0031308) {
32         return linear * 12.92f;
33     } else {
34         return 1.055f * powf(linear, 1.f / 2.4f) - 0.055f;
35     }
36 }
37 }
38 
39 /** tests a conversion with an error tolerance */
40 template <float (*CONVERT)(float)> static bool check_conversion(uint32_t input, uint32_t output,
41                                                                 float error) {
42     // alpha should always be exactly preserved.
43     if ((input & 0xff000000) != (output & 0xff000000)) {
44         return false;
45     }
46 
47     for (int c = 0; c < 3; ++c) {
48         uint8_t inputComponent = (uint8_t) ((input & (0xff << (c*8))) >> (c*8));
49         float lower = SkTMax(0.f, (float) inputComponent - error);
50         float upper = SkTMin(255.f, (float) inputComponent + error);
51         lower = CONVERT(lower / 255.f);
52         upper = CONVERT(upper / 255.f);
53         SkASSERT(lower >= 0.f && lower <= 255.f);
54         SkASSERT(upper >= 0.f && upper <= 255.f);
55         uint8_t outputComponent = (output & (0xff << (c*8))) >> (c*8);
56         if (outputComponent < SkScalarFloorToInt(lower * 255.f) ||
57             outputComponent > SkScalarCeilToInt(upper * 255.f)) {
58             return false;
59         }
60     }
61     return true;
62 }
63 
64 /** tests a forward and backward conversion with an error tolerance */
65 template <float (*FORWARD)(float), float (*BACKWARD)(float)>
66 static bool check_double_conversion(uint32_t input, uint32_t output, float error) {
67     // alpha should always be exactly preserved.
68     if ((input & 0xff000000) != (output & 0xff000000)) {
69         return false;
70     }
71 
72     for (int c = 0; c < 3; ++c) {
73         uint8_t inputComponent = (uint8_t) ((input & (0xff << (c*8))) >> (c*8));
74         float lower = SkTMax(0.f, (float) inputComponent - error);
75         float upper = SkTMin(255.f, (float) inputComponent + error);
76         lower = FORWARD(lower / 255.f);
77         upper = FORWARD(upper / 255.f);
78         SkASSERT(lower >= 0.f && lower <= 255.f);
79         SkASSERT(upper >= 0.f && upper <= 255.f);
80         uint8_t upperComponent = SkScalarCeilToInt(upper * 255.f);
81         uint8_t lowerComponent = SkScalarFloorToInt(lower * 255.f);
82         lower = SkTMax(0.f, (float) lowerComponent - error);
83         upper = SkTMin(255.f, (float) upperComponent + error);
84         lower = BACKWARD(lowerComponent / 255.f);
85         upper = BACKWARD(upperComponent / 255.f);
86         SkASSERT(lower >= 0.f && lower <= 255.f);
87         SkASSERT(upper >= 0.f && upper <= 255.f);
88         upperComponent = SkScalarCeilToInt(upper * 255.f);
89         lowerComponent = SkScalarFloorToInt(lower * 255.f);
90 
91         uint8_t outputComponent = (output & (0xff << (c*8))) >> (c*8);
92         if (outputComponent < lowerComponent || outputComponent > upperComponent) {
93             return false;
94         }
95     }
96     return true;
97 }
98 
99 static bool check_srgb_to_linear_conversion(uint32_t srgb, uint32_t linear, float error) {
100     return check_conversion<srgb_to_linear>(srgb, linear, error);
101 }
102 
103 static bool check_linear_to_srgb_conversion(uint32_t linear, uint32_t srgb, float error) {
104     return check_conversion<linear_to_srgb>(linear, srgb, error);
105 }
106 
107 static bool check_linear_to_srgb_to_linear_conversion(uint32_t input, uint32_t output, float error) {
108     return check_double_conversion<linear_to_srgb, srgb_to_linear>(input, output, error);
109 }
110 
111 static bool check_srgb_to_linear_to_srgb_conversion(uint32_t input, uint32_t output, float error) {
112     return check_double_conversion<srgb_to_linear, linear_to_srgb>(input, output, error);
113 }
114 
115 typedef bool (*CheckFn) (uint32_t orig, uint32_t actual, float error);
116 
117 void read_and_check_pixels(skiatest::Reporter* reporter, GrSurfaceContext* context,
118                            uint32_t* origData,
119                            const SkImageInfo& dstInfo, CheckFn checker, float error,
120                            const char* subtestName) {
121     int w = dstInfo.width();
122     int h = dstInfo.height();
123     SkAutoTMalloc<uint32_t> readData(w * h);
124     memset(readData.get(), 0, sizeof(uint32_t) * w * h);
125 
126     if (!context->readPixels(dstInfo, readData.get(), 0, 0, 0)) {
127         ERRORF(reporter, "Could not read pixels for %s.", subtestName);
128         return;
129     }
130 
131     for (int j = 0; j < h; ++j) {
132         for (int i = 0; i < w; ++i) {
133             uint32_t orig = origData[j * w + i];
134             uint32_t read = readData[j * w + i];
135 
136             if (!checker(orig, read, error)) {
137                 ERRORF(reporter, "Expected 0x%08x, read back as 0x%08x in %s at %d, %d).",
138                        orig, read, subtestName, i, j);
139                 return;
140             }
141         }
142     }
143 }
144 
145 // TODO: Add tests for copySurface between srgb/linear textures. Add tests for unpremul/premul
146 // conversion during read/write along with srgb/linear conversions.
147 DEF_GPUTEST_FOR_RENDERING_CONTEXTS(SRGBReadWritePixels, reporter, ctxInfo) {
148     GrContext* context = ctxInfo.grContext();
149 #if defined(SK_BUILD_FOR_GOOGLE3)
150     // Stack frame size is limited in SK_BUILD_FOR_GOOGLE3.
151     static const int kW = 63;
152     static const int kH = 63;
153 #else
154     static const int kW = 255;
155     static const int kH = 255;
156 #endif
157     uint32_t origData[kW * kH];
158     for (int j = 0; j < kH; ++j) {
159         for (int i = 0; i < kW; ++i) {
160             origData[j * kW + i] = (j << 24) | (i << 16) | (i << 8) | i;
161         }
162     }
163 
164     const SkImageInfo iiSRGBA = SkImageInfo::Make(kW, kH, kRGBA_8888_SkColorType,
165                                                   kPremul_SkAlphaType,
166                                                   SkColorSpace::MakeSRGB());
167     const SkImageInfo iiRGBA = SkImageInfo::Make(kW, kH, kRGBA_8888_SkColorType,
168                                                  kPremul_SkAlphaType);
169     GrSurfaceDesc desc;
170     desc.fFlags = kRenderTarget_GrSurfaceFlag;
171     desc.fOrigin = kBottomLeft_GrSurfaceOrigin;
172     desc.fWidth = kW;
173     desc.fHeight = kH;
174     desc.fConfig = kSRGBA_8888_GrPixelConfig;
175     if (context->caps()->isConfigRenderable(desc.fConfig, false) &&
176         context->caps()->isConfigTexturable(desc.fConfig)) {
177 
178         sk_sp<GrSurfaceContext> sContext = context->contextPriv().makeDeferredSurfaceContext(
179                                                                              desc, GrMipMapped::kNo,
180                                                                              SkBackingFit::kExact,
181                                                                              SkBudgeted::kNo);
182         if (!sContext) {
183             ERRORF(reporter, "Could not create SRGBA surface context.");
184             return;
185         }
186 
187         float error = context->caps()->shaderCaps()->halfIs32Bits() ? 0.5f : 1.2f;
188 
189         // Write srgba data and read as srgba and then as rgba
190         if (sContext->writePixels(iiSRGBA, origData, 0, 0, 0)) {
191             // For the all-srgba case, we allow a small error only for devices that have
192             // precision variation because the srgba data gets converted to linear and back in
193             // the shader.
194             float smallError = context->caps()->shaderCaps()->halfIs32Bits() ? 0.0f : 1.f;
195             read_and_check_pixels(reporter, sContext.get(), origData, iiSRGBA,
196                                   check_srgb_to_linear_to_srgb_conversion, smallError,
197                                   "write/read srgba to srgba texture");
198             read_and_check_pixels(reporter, sContext.get(), origData, iiRGBA,
199                                   check_srgb_to_linear_conversion, error,
200                                   "write srgba/read rgba with srgba texture");
201         } else {
202             ERRORF(reporter, "Could not write srgba data to srgba texture.");
203         }
204 
205         // Now verify that we can write linear data
206         if (sContext->writePixels(iiRGBA, origData, 0, 0, 0)) {
207             // We allow more error on GPUs with lower precision shader variables.
208             read_and_check_pixels(reporter, sContext.get(), origData, iiSRGBA,
209                                   check_linear_to_srgb_conversion, error,
210                                   "write rgba/read srgba with srgba texture");
211             read_and_check_pixels(reporter, sContext.get(), origData, iiRGBA,
212                                   check_linear_to_srgb_to_linear_conversion, error,
213                                   "write/read rgba with srgba texture");
214         } else {
215             ERRORF(reporter, "Could not write rgba data to srgba texture.");
216         }
217 
218         desc.fConfig = kRGBA_8888_GrPixelConfig;
219         sContext = context->contextPriv().makeDeferredSurfaceContext(desc,
220                                                                      GrMipMapped::kNo,
221                                                                      SkBackingFit::kExact,
222                                                                      SkBudgeted::kNo);
223         if (!sContext) {
224             ERRORF(reporter, "Could not create RGBA surface context.");
225             return;
226         }
227 
228         // Write srgba data to a rgba texture and read back as srgba and rgba
229         if (sContext->writePixels(iiSRGBA, origData, 0, 0, 0)) {
230 #if 0
231             // We don't support this conversion (read from untagged source into tagged destination.
232             // If we decide there is a meaningful way to implement this, restore this test.
233             read_and_check_pixels(reporter, sContext.get(), origData, iiSRGBA,
234                                   check_srgb_to_linear_to_srgb_conversion, error,
235                                   "write/read srgba to rgba texture");
236 #endif
237             // We expect the sRGB -> linear write to do no sRGB conversion (to match the behavior of
238             // drawing tagged sources). skbug.com/6547. So the data we read should still contain
239             // sRGB encoded values.
240             //
241             // srgb_to_linear_to_srgb is a proxy for the expected identity transform.
242             read_and_check_pixels(reporter, sContext.get(), origData, iiRGBA,
243                                   check_srgb_to_linear_to_srgb_conversion, error,
244                                   "write srgba/read rgba to rgba texture");
245         } else {
246             ERRORF(reporter, "Could not write srgba data to rgba texture.");
247         }
248 
249         // Write rgba data to a rgba texture and read back as srgba
250         if (sContext->writePixels(iiRGBA, origData, 0, 0, 0)) {
251             read_and_check_pixels(reporter, sContext.get(), origData, iiSRGBA,
252                                   check_linear_to_srgb_conversion, 1.2f,
253                                   "write rgba/read srgba to rgba texture");
254         } else {
255             ERRORF(reporter, "Could not write rgba data to rgba texture.");
256         }
257     }
258 }
259 #endif
260