1 //
2 // Copyright 2015 The ANGLE Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 //
6
7 #include "test_utils/ANGLETest.h"
8 #include "test_utils/gl_raii.h"
9
10 namespace angle
11 {
12
13 // These two colors are equivelent in different colorspaces
14 constexpr GLColor kLinearColor(64, 127, 191, 255);
15 constexpr GLColor kNonlinearColor(13, 54, 133, 255);
16
17 class SRGBTextureTest : public ANGLETest
18 {
19 protected:
SRGBTextureTest()20 SRGBTextureTest()
21 {
22 setWindowWidth(128);
23 setWindowHeight(128);
24 setConfigRedBits(8);
25 setConfigGreenBits(8);
26 setConfigBlueBits(8);
27 setConfigAlphaBits(8);
28 }
29
testSetUp()30 void testSetUp() override
31 {
32 constexpr char kVS[] =
33 "precision highp float;\n"
34 "attribute vec4 position;\n"
35 "varying vec2 texcoord;\n"
36 "\n"
37 "void main()\n"
38 "{\n"
39 " gl_Position = vec4(position.xy, 0.0, 1.0);\n"
40 " texcoord = (position.xy * 0.5) + 0.5;\n"
41 "}\n";
42
43 constexpr char kFS[] =
44 "precision highp float;\n"
45 "uniform sampler2D tex;\n"
46 "varying vec2 texcoord;\n"
47 "\n"
48 "void main()\n"
49 "{\n"
50 " gl_FragColor = texture2D(tex, texcoord);\n"
51 "}\n";
52
53 mProgram = CompileProgram(kVS, kFS);
54 ASSERT_NE(0u, mProgram);
55
56 mTextureLocation = glGetUniformLocation(mProgram, "tex");
57 ASSERT_NE(-1, mTextureLocation);
58 }
59
testTearDown()60 void testTearDown() override { glDeleteProgram(mProgram); }
61
getSRGBA8TextureInternalFormat() const62 GLenum getSRGBA8TextureInternalFormat() const
63 {
64 return getClientMajorVersion() >= 3 ? GL_SRGB8_ALPHA8 : GL_SRGB_ALPHA_EXT;
65 }
66
getSRGBA8TextureFormat() const67 GLenum getSRGBA8TextureFormat() const
68 {
69 return getClientMajorVersion() >= 3 ? GL_RGBA : GL_SRGB_ALPHA_EXT;
70 }
71
getSRGB8TextureInternalFormat() const72 GLenum getSRGB8TextureInternalFormat() const
73 {
74 return getClientMajorVersion() >= 3 ? GL_SRGB8 : GL_SRGB_EXT;
75 }
76
getSRGB8TextureFormat() const77 GLenum getSRGB8TextureFormat() const
78 {
79 return getClientMajorVersion() >= 3 ? GL_RGB : GL_SRGB_EXT;
80 }
81
82 GLuint mProgram = 0;
83 GLint mTextureLocation = -1;
84 };
85
86 // GenerateMipmaps should generate INVALID_OPERATION in ES 2.0 / WebGL 1.0 with EXT_sRGB.
87 // https://bugs.chromium.org/p/chromium/issues/detail?id=769989
TEST_P(SRGBTextureTest,SRGBValidation)88 TEST_P(SRGBTextureTest, SRGBValidation)
89 {
90 // TODO(fjhenigman): Figure out why this fails on Ozone Intel.
91 ANGLE_SKIP_TEST_IF(IsOzone() && IsIntel() && IsOpenGLES());
92
93 bool supported = IsGLExtensionEnabled("GL_EXT_sRGB") || getClientMajorVersion() == 3;
94
95 GLuint tex = 0;
96 glGenTextures(1, &tex);
97 glBindTexture(GL_TEXTURE_2D, tex);
98
99 GLubyte pixel[3] = {0};
100 glTexImage2D(GL_TEXTURE_2D, 0, getSRGB8TextureInternalFormat(), 1, 1, 0,
101 getSRGB8TextureFormat(), GL_UNSIGNED_BYTE, pixel);
102 if (supported)
103 {
104 EXPECT_GL_NO_ERROR();
105
106 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, getSRGB8TextureFormat(), GL_UNSIGNED_BYTE,
107 pixel);
108 EXPECT_GL_NO_ERROR();
109
110 // Mipmap generation always generates errors for SRGB unsized in ES2 or SRGB8 sized in ES3.
111 glGenerateMipmap(GL_TEXTURE_2D);
112 EXPECT_GL_ERROR(GL_INVALID_OPERATION);
113 }
114 else
115 {
116 EXPECT_GL_ERROR(GL_INVALID_ENUM);
117 }
118
119 glDeleteTextures(1, &tex);
120 }
121
TEST_P(SRGBTextureTest,SRGBAValidation)122 TEST_P(SRGBTextureTest, SRGBAValidation)
123 {
124 // TODO(fjhenigman): Figure out why this fails on Ozone Intel.
125 ANGLE_SKIP_TEST_IF(IsOzone() && IsIntel() && IsOpenGLES());
126
127 bool supported = IsGLExtensionEnabled("GL_EXT_sRGB") || getClientMajorVersion() == 3;
128
129 GLuint tex = 0;
130 glGenTextures(1, &tex);
131 glBindTexture(GL_TEXTURE_2D, tex);
132
133 GLubyte pixel[4] = {0};
134 glTexImage2D(GL_TEXTURE_2D, 0, getSRGBA8TextureInternalFormat(), 1, 1, 0,
135 getSRGBA8TextureFormat(), GL_UNSIGNED_BYTE, pixel);
136 if (supported)
137 {
138 EXPECT_GL_NO_ERROR();
139
140 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, getSRGBA8TextureFormat(), GL_UNSIGNED_BYTE,
141 pixel);
142 EXPECT_GL_NO_ERROR();
143
144 glGenerateMipmap(GL_TEXTURE_2D);
145 if (getClientMajorVersion() < 3)
146 {
147 EXPECT_GL_ERROR(GL_INVALID_OPERATION);
148 }
149 else
150 {
151 EXPECT_GL_NO_ERROR();
152 }
153 }
154 else
155 {
156 EXPECT_GL_ERROR(GL_INVALID_ENUM);
157 }
158
159 glDeleteTextures(1, &tex);
160 }
161
162 // Test that sized SRGBA formats allow generating mipmaps
TEST_P(SRGBTextureTest,SRGBASizedValidation)163 TEST_P(SRGBTextureTest, SRGBASizedValidation)
164 {
165 // TODO(fjhenigman): Figure out why this fails on Ozone Intel.
166 ANGLE_SKIP_TEST_IF(IsOzone() && IsIntel() && IsOpenGLES());
167
168 // ES3 required for sized SRGB textures
169 ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3);
170
171 GLTexture tex;
172 glBindTexture(GL_TEXTURE_2D, tex);
173
174 GLubyte pixel[4] = {0};
175 glTexImage2D(GL_TEXTURE_2D, 0, getSRGBA8TextureInternalFormat(), 1, 1, 0,
176 getSRGBA8TextureFormat(), GL_UNSIGNED_BYTE, pixel);
177
178 EXPECT_GL_NO_ERROR();
179
180 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
181 EXPECT_GL_NO_ERROR();
182
183 glGenerateMipmap(GL_TEXTURE_2D);
184 EXPECT_GL_NO_ERROR();
185 }
186
TEST_P(SRGBTextureTest,SRGBARenderbuffer)187 TEST_P(SRGBTextureTest, SRGBARenderbuffer)
188 {
189 bool supported = IsGLExtensionEnabled("GL_EXT_sRGB") || getClientMajorVersion() == 3;
190
191 GLuint rbo = 0;
192 glGenRenderbuffers(1, &rbo);
193 glBindRenderbuffer(GL_RENDERBUFFER, rbo);
194
195 glRenderbufferStorage(GL_RENDERBUFFER, GL_SRGB8_ALPHA8_EXT, 1, 1);
196 if (supported)
197 {
198 EXPECT_GL_NO_ERROR();
199 }
200 else
201 {
202 EXPECT_GL_ERROR(GL_INVALID_ENUM);
203
204 // Make sure the rbo has a size for future tests
205 glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8_OES, 1, 1);
206 EXPECT_GL_NO_ERROR();
207 }
208
209 GLuint fbo = 0;
210 glGenFramebuffers(1, &fbo);
211 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
212 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo);
213 EXPECT_GL_NO_ERROR();
214
215 GLint colorEncoding = 0;
216 glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
217 GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING_EXT,
218 &colorEncoding);
219 if (supported)
220 {
221 EXPECT_GL_NO_ERROR();
222 EXPECT_EQ(GL_SRGB_EXT, colorEncoding);
223 }
224 else
225 {
226 EXPECT_GL_ERROR(GL_INVALID_ENUM);
227 }
228
229 glDeleteFramebuffers(1, &fbo);
230 glDeleteRenderbuffers(1, &rbo);
231 }
232
233 // Verify that if the srgb decode extension is available, srgb textures are too
TEST_P(SRGBTextureTest,SRGBDecodeExtensionAvailability)234 TEST_P(SRGBTextureTest, SRGBDecodeExtensionAvailability)
235 {
236 bool hasSRGBDecode = IsGLExtensionEnabled("GL_EXT_texture_sRGB_decode");
237 if (hasSRGBDecode)
238 {
239 bool hasSRGBTextures = IsGLExtensionEnabled("GL_EXT_sRGB") || getClientMajorVersion() >= 3;
240 EXPECT_TRUE(hasSRGBTextures);
241 }
242 }
243
244 // Test basic functionality of SRGB decode using the texture parameter
TEST_P(SRGBTextureTest,SRGBDecodeTextureParameter)245 TEST_P(SRGBTextureTest, SRGBDecodeTextureParameter)
246 {
247 // TODO(fjhenigman): Figure out why this fails on Ozone Intel.
248 ANGLE_SKIP_TEST_IF(IsOzone() && IsIntel() && IsOpenGLES());
249
250 ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_sRGB_decode"));
251
252 GLColor linearColor = kLinearColor;
253 GLColor srgbColor = kNonlinearColor;
254
255 GLTexture tex;
256 glBindTexture(GL_TEXTURE_2D, tex.get());
257 glTexImage2D(GL_TEXTURE_2D, 0, getSRGBA8TextureInternalFormat(), 1, 1, 0,
258 getSRGBA8TextureFormat(), GL_UNSIGNED_BYTE, &linearColor);
259 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SRGB_DECODE_EXT, GL_DECODE_EXT);
260 ASSERT_GL_NO_ERROR();
261
262 glUseProgram(mProgram);
263 glUniform1i(mTextureLocation, 0);
264
265 glDisable(GL_DEPTH_TEST);
266 drawQuad(mProgram, "position", 0.5f);
267
268 EXPECT_PIXEL_COLOR_NEAR(0, 0, srgbColor, 1.0);
269
270 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SRGB_DECODE_EXT, GL_SKIP_DECODE_EXT);
271 drawQuad(mProgram, "position", 0.5f);
272
273 EXPECT_PIXEL_COLOR_NEAR(0, 0, linearColor, 1.0);
274 }
275
276 // Test basic functionality of SRGB override using the texture parameter
TEST_P(SRGBTextureTest,SRGBOverrideTextureParameter)277 TEST_P(SRGBTextureTest, SRGBOverrideTextureParameter)
278 {
279 ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_sRGB_override"));
280
281 GLColor linearColor = kLinearColor;
282 GLColor srgbColor = kNonlinearColor;
283
284 GLenum internalFormat = getClientMajorVersion() >= 3 ? GL_RGBA8 : GL_RGBA;
285
286 GLTexture tex;
287 glBindTexture(GL_TEXTURE_2D, tex.get());
288 glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
289 &linearColor);
290 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_FORMAT_SRGB_OVERRIDE_EXT, GL_NONE);
291 ASSERT_GL_NO_ERROR();
292
293 glUseProgram(mProgram);
294 glUniform1i(mTextureLocation, 0);
295
296 glDisable(GL_DEPTH_TEST);
297 drawQuad(mProgram, "position", 0.5f);
298
299 EXPECT_PIXEL_COLOR_NEAR(0, 0, linearColor, 1.0);
300
301 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_FORMAT_SRGB_OVERRIDE_EXT, GL_SRGB);
302 drawQuad(mProgram, "position", 0.5f);
303
304 EXPECT_PIXEL_COLOR_NEAR(0, 0, srgbColor, 1.0);
305 }
306
307 // Test that SRGB override is a noop when used on a nonlinear texture format
308 // EXT_texture_format_sRGB_override spec says:
309 // "If the internal format is not one of the above formats, then
310 // the value of TEXTURE_FORMAT_SRGB_OVERRIDE_EXT is ignored."
TEST_P(SRGBTextureTest,SRGBOverrideTextureParameterNoop)311 TEST_P(SRGBTextureTest, SRGBOverrideTextureParameterNoop)
312 {
313 ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_sRGB_override"));
314 ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_sRGB") || getClientMajorVersion() < 3);
315
316 GLColor linearColor = kLinearColor;
317 GLColor srgbColor = kNonlinearColor;
318
319 GLTexture tex;
320 glBindTexture(GL_TEXTURE_2D, tex.get());
321 glTexImage2D(GL_TEXTURE_2D, 0, getSRGBA8TextureInternalFormat(), 1, 1, 0,
322 getSRGBA8TextureFormat(), GL_UNSIGNED_BYTE, &linearColor);
323 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_FORMAT_SRGB_OVERRIDE_EXT, GL_NONE);
324 ASSERT_GL_NO_ERROR();
325
326 glUseProgram(mProgram);
327 glUniform1i(mTextureLocation, 0);
328
329 glDisable(GL_DEPTH_TEST);
330 drawQuad(mProgram, "position", 0.5f);
331
332 EXPECT_PIXEL_COLOR_NEAR(0, 0, srgbColor, 1.0);
333
334 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_FORMAT_SRGB_OVERRIDE_EXT, GL_SRGB);
335 drawQuad(mProgram, "position", 0.5f);
336
337 EXPECT_PIXEL_COLOR_NEAR(0, 0, srgbColor, 1.0);
338 }
339
340 // Test basic functionality of SRGB decode using the sampler parameter
TEST_P(SRGBTextureTest,SRGBDecodeSamplerParameter)341 TEST_P(SRGBTextureTest, SRGBDecodeSamplerParameter)
342 {
343 ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_sRGB_decode") ||
344 getClientMajorVersion() < 3);
345
346 GLColor linearColor = kLinearColor;
347 GLColor srgbColor = kNonlinearColor;
348
349 GLTexture tex;
350 glBindTexture(GL_TEXTURE_2D, tex.get());
351 glTexImage2D(GL_TEXTURE_2D, 0, getSRGBA8TextureInternalFormat(), 1, 1, 0,
352 getSRGBA8TextureFormat(), GL_UNSIGNED_BYTE, &linearColor);
353 ASSERT_GL_NO_ERROR();
354
355 GLSampler sampler;
356 glBindSampler(0, sampler.get());
357 glSamplerParameteri(sampler.get(), GL_TEXTURE_SRGB_DECODE_EXT, GL_DECODE_EXT);
358
359 glUseProgram(mProgram);
360 glUniform1i(mTextureLocation, 0);
361
362 glDisable(GL_DEPTH_TEST);
363 drawQuad(mProgram, "position", 0.5f);
364
365 EXPECT_PIXEL_COLOR_NEAR(0, 0, srgbColor, 1.0);
366
367 glSamplerParameteri(sampler.get(), GL_TEXTURE_SRGB_DECODE_EXT, GL_SKIP_DECODE_EXT);
368 drawQuad(mProgram, "position", 0.5f);
369
370 EXPECT_PIXEL_COLOR_NEAR(0, 0, linearColor, 1.0);
371 }
372
373 // Test that mipmaps are generated correctly for sRGB textures
TEST_P(SRGBTextureTest,GenerateMipmaps)374 TEST_P(SRGBTextureTest, GenerateMipmaps)
375 {
376 ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3);
377
378 ANGLE_SKIP_TEST_IF(IsOpenGL() && ((IsIntel() && IsOSX()) || IsAMD()));
379
380 auto createAndReadBackTexture = [this](GLenum internalFormat, const GLColor &color) {
381 constexpr GLsizei width = 128;
382 constexpr GLsizei height = 128;
383
384 std::array<GLColor, width * height> buf;
385 std::fill(buf.begin(), buf.end(), color);
386
387 // Set up-left region of the texture as red color.
388 // In order to make sure bi-linear interpolation operates on different colors, red region
389 // is 1 pixel smaller than a quarter of the full texture on each side.
390 constexpr GLsizei redWidth = width / 2 - 1;
391 constexpr GLsizei redHeight = height / 2 - 1;
392 std::array<GLColor, redWidth * redHeight> redBuf;
393 std::fill(redBuf.begin(), redBuf.end(), GLColor::red);
394
395 GLTexture tex;
396 glBindTexture(GL_TEXTURE_2D, tex.get());
397 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
398 glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE,
399 buf.data());
400 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, redWidth, redHeight, GL_RGBA, GL_UNSIGNED_BYTE,
401 redBuf.data());
402 glGenerateMipmap(GL_TEXTURE_2D);
403
404 constexpr GLsizei drawWidth = 32;
405 constexpr GLsizei drawHeight = 32;
406 glViewport(0, 0, drawWidth, drawHeight);
407
408 drawQuad(mProgram, "position", 0.5f);
409
410 std::array<GLColor, drawWidth * drawHeight> result;
411 glReadPixels(0, 0, drawWidth, drawHeight, GL_RGBA, GL_UNSIGNED_BYTE, result.data());
412
413 EXPECT_GL_NO_ERROR();
414
415 return result;
416 };
417
418 GLColor srgbaColor(0, 63, 127, 255);
419 auto srgbaReadback = createAndReadBackTexture(GL_SRGB8_ALPHA8, srgbaColor);
420
421 GLColor linearColor(0, 13, 54, 255);
422 auto rgbaReadback = createAndReadBackTexture(GL_RGBA8, linearColor);
423
424 ASSERT_EQ(srgbaReadback.size(), rgbaReadback.size());
425 for (size_t i = 0; i < srgbaReadback.size(); i++)
426 {
427 constexpr double tolerence = 7.0;
428 EXPECT_COLOR_NEAR(srgbaReadback[i], rgbaReadback[i], tolerence);
429 }
430 }
431
432 // Use this to select which configurations (e.g. which renderer, which GLES major version) these
433 // tests should be run against.
434 ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(SRGBTextureTest);
435
436 } // namespace angle
437