1 /*-------------------------------------------------------------------------
2 * OpenGL Conformance Test Suite
3 * -----------------------------
4 *
5 * Copyright (c) 2020 Valve Coporation.
6 * Copyright (c) 2020 The Khronos Group Inc.
7 *
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 *
20 */ /*!
21 * \file glcNearestEdgeTests.cpp
22 * \brief
23 */ /*-------------------------------------------------------------------*/
24
25 #include "glcNearestEdgeTests.hpp"
26
27 #include "gluDefs.hpp"
28 #include "gluTextureUtil.hpp"
29 #include "gluDrawUtil.hpp"
30 #include "gluShaderProgram.hpp"
31
32 #include "glwDefs.hpp"
33 #include "glwFunctions.hpp"
34 #include "glwEnums.hpp"
35
36 #include "tcuTestLog.hpp"
37 #include "tcuRenderTarget.hpp"
38 #include "tcuStringTemplate.hpp"
39 #include "tcuTextureUtil.hpp"
40
41 #include <utility>
42 #include <map>
43 #include <algorithm>
44 #include <memory>
45 #include <cmath>
46
47 namespace glcts
48 {
49
50 namespace
51 {
52
53 enum class OffsetDirection
54 {
55 LEFT = 0,
56 RIGHT = 1,
57 };
58
59 // Test sampling at the edge of texels. This test is equivalent to:
60 // 1) Creating a texture using the same format and size as the frame buffer.
61 // 2) Drawing a full screen quad with GL_NEAREST using the texture.
62 // 3) Verifying the frame buffer image and the texture match pixel-by-pixel.
63 //
64 // However, texture coodinates are not located in the exact frame buffer corners. A small offset is applied instead so sampling
65 // happens near a texel border instead of in the middle of the texel.
66 class NearestEdgeTestCase : public deqp::TestCase
67 {
68 public:
69 NearestEdgeTestCase(deqp::Context& context, OffsetDirection direction);
70
71 void deinit();
72 void init();
73 tcu::TestNode::IterateResult iterate();
74
75 static std::string getName (OffsetDirection direction);
76 static std::string getDesc (OffsetDirection direction);
77 static tcu::TextureFormat toTextureFormat (deqp::Context& context, const tcu::PixelFormat& pixelFmt);
78
79 private:
80 static const glw::GLenum kTextureType = GL_TEXTURE_2D;
81
82 void createTexture ();
83 void deleteTexture ();
84 void fillTexture ();
85 void renderQuad ();
86 bool verifyResults ();
87
88 const float m_offsetSign;
89 const int m_width;
90 const int m_height;
91 const tcu::PixelFormat& m_format;
92 const tcu::TextureFormat m_texFormat;
93 const tcu::TextureFormatInfo m_texFormatInfo;
94 const glu::TransferFormat m_transFormat;
95 std::string m_vertShaderText;
96 std::string m_fragShaderText;
97 glw::GLuint m_texture;
98 std::vector<deUint8> m_texData;
99 };
100
getName(OffsetDirection direction)101 std::string NearestEdgeTestCase::getName (OffsetDirection direction)
102 {
103 switch (direction)
104 {
105 case OffsetDirection::LEFT: return "offset_left";
106 case OffsetDirection::RIGHT: return "offset_right";
107 default: DE_ASSERT(false); break;
108 }
109 // Unreachable.
110 return "";
111 }
112
getDesc(OffsetDirection direction)113 std::string NearestEdgeTestCase::getDesc (OffsetDirection direction)
114 {
115 switch (direction)
116 {
117 case OffsetDirection::LEFT: return "Sampling point near the left edge";
118 case OffsetDirection::RIGHT: return "Sampling point near the right edge";
119 default: DE_ASSERT(false); break;
120 }
121 // Unreachable.
122 return "";
123 }
124
125 // Translate pixel format in the frame buffer to texture format.
126 // Copied from sglrReferenceContext.cpp.
toTextureFormat(deqp::Context & context,const tcu::PixelFormat & pixelFmt)127 tcu::TextureFormat NearestEdgeTestCase::toTextureFormat (deqp::Context& context, const tcu::PixelFormat& pixelFmt)
128 {
129 static const struct
130 {
131 tcu::PixelFormat pixelFmt;
132 tcu::TextureFormat texFmt;
133 } pixelFormatMap[] =
134 {
135 { tcu::PixelFormat(8,8,8,8), tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8) },
136 { tcu::PixelFormat(8,8,8,0), tcu::TextureFormat(tcu::TextureFormat::RGB, tcu::TextureFormat::UNORM_INT8) },
137 { tcu::PixelFormat(4,4,4,4), tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_SHORT_4444) },
138 { tcu::PixelFormat(5,5,5,1), tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_SHORT_5551) },
139 { tcu::PixelFormat(5,6,5,0), tcu::TextureFormat(tcu::TextureFormat::RGB, tcu::TextureFormat::UNORM_SHORT_565) },
140 { tcu::PixelFormat(10,10,10,2), tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT_1010102_REV) },
141 { tcu::PixelFormat(16,16,16,16), tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::HALF_FLOAT) },
142 };
143
144 for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(pixelFormatMap); ndx++)
145 {
146 if (pixelFormatMap[ndx].pixelFmt == pixelFmt)
147 {
148 // Some implementations treat GL_RGB8 as GL_RGBA8888,so the test should pass implementation format to ReadPixels.
149 if (pixelFmt == tcu::PixelFormat(8, 8, 8, 0))
150 {
151 const auto& gl = context.getRenderContext().getFunctions();
152
153 glw::GLint implFormat = GL_NONE;
154 glw::GLint implType = GL_NONE;
155 gl.getIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &implFormat);
156 gl.getIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &implType);
157 if (implFormat == GL_RGBA && implType == GL_UNSIGNED_BYTE)
158 return tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8);
159 }
160
161 return pixelFormatMap[ndx].texFmt;
162 }
163 }
164
165 TCU_FAIL("Unable to map pixel format to texture format");
166 }
167
NearestEdgeTestCase(deqp::Context & context,OffsetDirection direction)168 NearestEdgeTestCase::NearestEdgeTestCase (deqp::Context& context, OffsetDirection direction)
169 : TestCase(context, getName(direction).c_str(), getDesc(direction).c_str())
170 , m_offsetSign {(direction == OffsetDirection::LEFT) ? -1.0f : 1.0f}
171 , m_width {context.getRenderTarget().getWidth()}
172 , m_height {context.getRenderTarget().getHeight()}
173 , m_format {context.getRenderTarget().getPixelFormat()}
174 , m_texFormat {toTextureFormat(context, m_format)}
175 , m_texFormatInfo {tcu::getTextureFormatInfo(m_texFormat)}
176 , m_transFormat {glu::getTransferFormat(m_texFormat)}
177 , m_texture (0)
178 {
179 }
180
deinit()181 void NearestEdgeTestCase::deinit()
182 {
183 }
184
init()185 void NearestEdgeTestCase::init()
186 {
187 if (m_width < 2 || m_height < 2)
188 TCU_THROW(NotSupportedError, "Render target size too small");
189
190 m_vertShaderText =
191 "#version ${VERSION}\n"
192 "\n"
193 "in highp vec2 position;\n"
194 "\n"
195 "void main()\n"
196 "{\n"
197 " gl_Position = vec4(position, 0.0, 1.0);\n"
198 "}\n"
199 ;
200 m_fragShaderText =
201 "#version ${VERSION}\n"
202 "\n"
203 "precision highp float;\n"
204 "out highp vec4 fragColor;\n"
205 "\n"
206 "uniform highp sampler2D texSampler;\n"
207 "uniform float texOffset;\n"
208 "uniform float texWidth;\n"
209 "uniform float texHeight;\n"
210 "\n"
211 "void main()\n"
212 "{\n"
213 " float texCoordX;\n"
214 " float texCoordY;\n"
215 " texCoordX = (gl_FragCoord.x + texOffset) / texWidth;\n "
216 " texCoordY = (gl_FragCoord.y + texOffset) / texHeight;\n"
217 " vec2 sampleCoord = vec2(texCoordX, texCoordY);\n"
218 " fragColor = texture(texSampler, sampleCoord);\n"
219 "}\n"
220 "\n";
221
222 tcu::StringTemplate vertShaderTemplate{m_vertShaderText};
223 tcu::StringTemplate fragShaderTemplate{m_fragShaderText};
224 std::map<std::string, std::string> replacements;
225
226 if (glu::isContextTypeGLCore(m_context.getRenderContext().getType()))
227 replacements["VERSION"] = "130";
228 else
229 replacements["VERSION"] = "300 es";
230
231 m_vertShaderText = vertShaderTemplate.specialize(replacements);
232 m_fragShaderText = fragShaderTemplate.specialize(replacements);
233 }
234
createTexture()235 void NearestEdgeTestCase::createTexture ()
236 {
237 const auto& gl = m_context.getRenderContext().getFunctions();
238
239 gl.genTextures(1, &m_texture);
240 GLU_EXPECT_NO_ERROR(gl.getError(), "glGenTextures");
241 gl.bindTexture(kTextureType, m_texture);
242 GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture");
243
244 gl.texParameteri(kTextureType, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
245 GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameteri");
246 gl.texParameteri(kTextureType, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
247 GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameteri");
248 gl.texParameteri(kTextureType, GL_TEXTURE_WRAP_S, GL_REPEAT);
249 GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameteri");
250 gl.texParameteri(kTextureType, GL_TEXTURE_WRAP_T, GL_REPEAT);
251 GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameteri");
252 gl.texParameteri(kTextureType, GL_TEXTURE_MAX_LEVEL, 0);
253 GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameteri");
254 }
255
deleteTexture()256 void NearestEdgeTestCase::deleteTexture ()
257 {
258 const auto& gl = m_context.getRenderContext().getFunctions();
259
260 gl.deleteTextures(1, &m_texture);
261 GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteTextures");
262 }
263
fillTexture()264 void NearestEdgeTestCase::fillTexture ()
265 {
266 const auto& gl = m_context.getRenderContext().getFunctions();
267
268 m_texData.resize(m_width * m_height * tcu::getPixelSize(m_texFormat));
269 tcu::PixelBufferAccess texAccess{m_texFormat, m_width, m_height, 1, m_texData.data()};
270
271 // Create gradient over the whole texture.
272 DE_ASSERT(m_width > 1);
273 DE_ASSERT(m_height > 1);
274
275 const float divX = static_cast<float>(m_width - 1);
276 const float divY = static_cast<float>(m_height - 1);
277
278 for (int x = 0; x < m_width; ++x)
279 for (int y = 0; y < m_height; ++y)
280 {
281 const float colorX = static_cast<float>(x) / divX;
282 const float colorY = static_cast<float>(y) / divY;
283 const float colorZ = std::min(colorX, colorY);
284
285 tcu::Vec4 color{colorX, colorY, colorZ, 1.0f};
286 tcu::Vec4 finalColor = (color - m_texFormatInfo.lookupBias) / m_texFormatInfo.lookupScale;
287 texAccess.setPixel(finalColor, x, y);
288 }
289
290 const auto internalFormat = glu::getInternalFormat(m_texFormat);
291 if (tcu::getPixelSize(m_texFormat) < 4)
292 gl.pixelStorei(GL_UNPACK_ALIGNMENT, 1);
293 gl.texImage2D(kTextureType, 0, internalFormat, m_width, m_height, 0 /* border */, m_transFormat.format, m_transFormat.dataType, m_texData.data());
294 GLU_EXPECT_NO_ERROR(gl.getError(), "glTexImage2D");
295 }
296
297 // Draw full screen quad with the texture and an offset of almost half a texel in one direction, so sampling happens near the texel
298 // border and verifies truncation is happening properly.
renderQuad()299 void NearestEdgeTestCase::renderQuad ()
300 {
301 const auto& renderContext = m_context.getRenderContext();
302 const auto& gl = renderContext.getFunctions();
303
304 float minU = 0.0f;
305 float maxU = 1.0f;
306 float minV = 0.0f;
307 float maxV = 1.0f;
308
309 // Apply offset of almost half a texel to the texture coordinates.
310 DE_ASSERT(m_offsetSign == 1.0f || m_offsetSign == -1.0f);
311
312 const float offset = 0.5f - pow(2.0f, -8.0f);
313 const float offsetWidth = offset / static_cast<float>(m_width);
314 const float offsetHeight = offset / static_cast<float>(m_height);
315
316 minU += m_offsetSign * offsetWidth;
317 maxU += m_offsetSign * offsetWidth;
318 minV += m_offsetSign * offsetHeight;
319 maxV += m_offsetSign * offsetHeight;
320
321 const std::vector<float> positions = { -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f };
322 const std::vector<float> texCoords = { minU, minV, minU, maxV, maxU, minV, maxU, maxV };
323 const std::vector<deUint16> quadIndices = { 0, 1, 2, 2, 1, 3 };
324
325 const std::vector<glu::VertexArrayBinding> vertexArrays =
326 {
327 glu::va::Float("position", 2, 4, 0, positions.data())
328 };
329
330 glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(m_vertShaderText, m_fragShaderText));
331 if (!program.isOk())
332 TCU_FAIL("Shader compilation failed");
333
334 gl.useProgram(program.getProgram());
335 GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram failed");
336
337 gl.uniform1i(gl.getUniformLocation(program.getProgram(), "texSampler"), 0);
338 GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i failed");
339
340 gl.uniform1f(gl.getUniformLocation(program.getProgram(), "texOffset"), m_offsetSign * offset);
341 gl.uniform1f(gl.getUniformLocation(program.getProgram(), "texWidth"), float(m_width));
342 gl.uniform1f(gl.getUniformLocation(program.getProgram(), "texHeight"), float(m_height));
343 GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i failed");
344
345 gl.disable(GL_DITHER);
346 gl.clear(GL_COLOR_BUFFER_BIT);
347
348 glu::draw(renderContext, program.getProgram(),
349 static_cast<int>(vertexArrays.size()), vertexArrays.data(),
350 glu::pr::TriangleStrip(static_cast<int>(quadIndices.size()), quadIndices.data()));
351 }
352
verifyResults()353 bool NearestEdgeTestCase::verifyResults ()
354 {
355 const auto& gl = m_context.getRenderContext().getFunctions();
356
357 std::vector<deUint8> fbData(m_width * m_height * tcu::getPixelSize(m_texFormat));
358 if (tcu::getPixelSize(m_texFormat) < 4)
359 gl.pixelStorei(GL_PACK_ALIGNMENT, 1);
360 gl.readPixels(0, 0, m_width, m_height, m_transFormat.format, m_transFormat.dataType, fbData.data());
361 GLU_EXPECT_NO_ERROR(gl.getError(), "glReadPixels");
362
363 tcu::ConstPixelBufferAccess texAccess {m_texFormat, m_width, m_height, 1, m_texData.data()};
364 tcu::ConstPixelBufferAccess fbAccess {m_texFormat, m_width, m_height, 1, fbData.data()};
365
366 // Difference image to ease spotting problems.
367 const tcu::TextureFormat diffFormat {tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8};
368 const auto diffBytes = tcu::getPixelSize(diffFormat) * m_width * m_height;
369 std::unique_ptr<deUint8[]> diffData {new deUint8[diffBytes]};
370 const tcu::PixelBufferAccess diffAccess {diffFormat, m_width, m_height, 1, diffData.get()};
371
372 const tcu::Vec4 colorRed {1.0f, 0.0f, 0.0f, 1.0f};
373 const tcu::Vec4 colorGreen {0.0f, 1.0f, 0.0f, 1.0f};
374
375 bool pass = true;
376 for (int x = 0; x < m_width; ++x)
377 for (int y = 0; y < m_height; ++y)
378 {
379 const auto texPixel = texAccess.getPixel(x, y);
380 const auto fbPixel = fbAccess.getPixel(x, y);
381
382 // Require perfect pixel match.
383 if (texPixel != fbPixel)
384 {
385 pass = false;
386 diffAccess.setPixel(colorRed, x, y);
387 }
388 else
389 {
390 diffAccess.setPixel(colorGreen, x, y);
391 }
392 }
393
394 if (!pass)
395 {
396 auto& log = m_testCtx.getLog();
397 log
398 << tcu::TestLog::Message << "\n"
399 << "Width: " << m_width << "\n"
400 << "Height: " << m_height << "\n"
401 << tcu::TestLog::EndMessage;
402
403 log << tcu::TestLog::Image("texture", "Generated Texture", texAccess);
404 log << tcu::TestLog::Image("fb", "Frame Buffer Contents", fbAccess);
405 log << tcu::TestLog::Image("diff", "Mismatched pixels in red", diffAccess);
406 }
407
408 return pass;
409 }
410
iterate()411 tcu::TestNode::IterateResult NearestEdgeTestCase::iterate ()
412 {
413 // Populate and configure m_texture.
414 createTexture();
415
416 // Fill m_texture with data.
417 fillTexture();
418
419 // Draw full screen quad using the texture and a slight offset left or right.
420 renderQuad();
421
422 // Verify results.
423 bool pass = verifyResults();
424
425 // Destroy texture.
426 deleteTexture();
427
428 const qpTestResult result = (pass ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL);
429 const char* desc = (pass ? "Pass" : "Pixel mismatch; check the generated images");
430
431 m_testCtx.setTestResult(result, desc);
432 return STOP;
433 }
434
435 } /* anonymous namespace */
436
NearestEdgeCases(deqp::Context & context)437 NearestEdgeCases::NearestEdgeCases(deqp::Context& context)
438 : TestCaseGroup(context, "nearest_edge", "GL_NEAREST edge cases")
439 {
440 }
441
~NearestEdgeCases(void)442 NearestEdgeCases::~NearestEdgeCases(void)
443 {
444 }
445
init(void)446 void NearestEdgeCases::init(void)
447 {
448 static const std::vector<OffsetDirection> kDirections = { OffsetDirection::LEFT, OffsetDirection::RIGHT };
449 for (const auto direction : kDirections)
450 addChild(new NearestEdgeTestCase{m_context, direction});
451 }
452
453 } /* glcts namespace */
454