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<uint8_t> m_texData;
99 };
100
getName(OffsetDirection direction)101 std::string NearestEdgeTestCase::getName(OffsetDirection direction)
102 {
103 switch (direction)
104 {
105 case OffsetDirection::LEFT:
106 return "offset_left";
107 case OffsetDirection::RIGHT:
108 return "offset_right";
109 default:
110 DE_ASSERT(false);
111 break;
112 }
113 // Unreachable.
114 return "";
115 }
116
getDesc(OffsetDirection direction)117 std::string NearestEdgeTestCase::getDesc(OffsetDirection direction)
118 {
119 switch (direction)
120 {
121 case OffsetDirection::LEFT:
122 return "Sampling point near the left edge";
123 case OffsetDirection::RIGHT:
124 return "Sampling point near the right edge";
125 default:
126 DE_ASSERT(false);
127 break;
128 }
129 // Unreachable.
130 return "";
131 }
132
133 // Translate pixel format in the frame buffer to texture format.
134 // Copied from sglrReferenceContext.cpp.
toTextureFormat(deqp::Context & context,const tcu::PixelFormat & pixelFmt)135 tcu::TextureFormat NearestEdgeTestCase::toTextureFormat(deqp::Context &context, const tcu::PixelFormat &pixelFmt)
136 {
137 static const struct
138 {
139 tcu::PixelFormat pixelFmt;
140 tcu::TextureFormat texFmt;
141 } pixelFormatMap[] = {
142 {tcu::PixelFormat(8, 8, 8, 8), tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8)},
143 {tcu::PixelFormat(8, 8, 8, 0), tcu::TextureFormat(tcu::TextureFormat::RGB, tcu::TextureFormat::UNORM_INT8)},
144 {tcu::PixelFormat(4, 4, 4, 4),
145 tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_SHORT_4444)},
146 {tcu::PixelFormat(5, 5, 5, 1),
147 tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_SHORT_5551)},
148 {tcu::PixelFormat(5, 6, 5, 0),
149 tcu::TextureFormat(tcu::TextureFormat::RGB, tcu::TextureFormat::UNORM_SHORT_565)},
150 {tcu::PixelFormat(10, 10, 10, 2),
151 tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT_1010102_REV)},
152 {tcu::PixelFormat(10, 10, 10, 0),
153 tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT_1010102_REV)},
154 {tcu::PixelFormat(16, 16, 16, 16),
155 tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::HALF_FLOAT)},
156 };
157
158 for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(pixelFormatMap); ndx++)
159 {
160 if (pixelFormatMap[ndx].pixelFmt == pixelFmt)
161 {
162 // Some implementations treat GL_RGB8 as GL_RGBA8888,so the test should pass implementation format to ReadPixels.
163 if (pixelFmt == tcu::PixelFormat(8, 8, 8, 0))
164 {
165 const auto &gl = context.getRenderContext().getFunctions();
166
167 glw::GLint implFormat = GL_NONE;
168 glw::GLint implType = GL_NONE;
169 gl.getIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &implFormat);
170 gl.getIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &implType);
171 if (implFormat == GL_RGBA && implType == GL_UNSIGNED_BYTE)
172 return tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8);
173 }
174
175 return pixelFormatMap[ndx].texFmt;
176 }
177 }
178
179 TCU_FAIL("Unable to map pixel format to texture format");
180 }
181
NearestEdgeTestCase(deqp::Context & context,OffsetDirection direction)182 NearestEdgeTestCase::NearestEdgeTestCase(deqp::Context &context, OffsetDirection direction)
183 : TestCase(context, getName(direction).c_str(), getDesc(direction).c_str())
184 , m_offsetSign{(direction == OffsetDirection::LEFT) ? -1.0f : 1.0f}
185 , m_width{context.getRenderTarget().getWidth()}
186 , m_height{context.getRenderTarget().getHeight()}
187 , m_format{context.getRenderTarget().getPixelFormat()}
188 , m_texFormat{toTextureFormat(context, m_format)}
189 , m_texFormatInfo{tcu::getTextureFormatInfo(m_texFormat)}
190 , m_transFormat{glu::getTransferFormat(m_texFormat)}
191 , m_texture(0)
192 {
193 }
194
deinit()195 void NearestEdgeTestCase::deinit()
196 {
197 }
198
init()199 void NearestEdgeTestCase::init()
200 {
201 if (m_width < 2 || m_height < 2)
202 TCU_THROW(NotSupportedError, "Render target size too small");
203
204 m_vertShaderText = "#version ${VERSION}\n"
205 "\n"
206 "in highp vec2 position;\n"
207 "\n"
208 "void main()\n"
209 "{\n"
210 " gl_Position = vec4(position, 0.0, 1.0);\n"
211 "}\n";
212 m_fragShaderText = "#version ${VERSION}\n"
213 "\n"
214 "precision highp float;\n"
215 "out highp vec4 fragColor;\n"
216 "\n"
217 "uniform highp sampler2D texSampler;\n"
218 "uniform float texOffset;\n"
219 "uniform float texWidth;\n"
220 "uniform float texHeight;\n"
221 "\n"
222 "void main()\n"
223 "{\n"
224 " float texCoordX;\n"
225 " float texCoordY;\n"
226 " texCoordX = (gl_FragCoord.x + texOffset) / texWidth;\n "
227 " texCoordY = (gl_FragCoord.y + texOffset) / texHeight;\n"
228 " vec2 sampleCoord = vec2(texCoordX, texCoordY);\n"
229 " fragColor = texture(texSampler, sampleCoord);\n"
230 "}\n"
231 "\n";
232
233 tcu::StringTemplate vertShaderTemplate{m_vertShaderText};
234 tcu::StringTemplate fragShaderTemplate{m_fragShaderText};
235 std::map<std::string, std::string> replacements;
236
237 if (glu::isContextTypeGLCore(m_context.getRenderContext().getType()))
238 replacements["VERSION"] = "130";
239 else
240 replacements["VERSION"] = "300 es";
241
242 m_vertShaderText = vertShaderTemplate.specialize(replacements);
243 m_fragShaderText = fragShaderTemplate.specialize(replacements);
244 }
245
createTexture()246 void NearestEdgeTestCase::createTexture()
247 {
248 const auto &gl = m_context.getRenderContext().getFunctions();
249
250 gl.genTextures(1, &m_texture);
251 GLU_EXPECT_NO_ERROR(gl.getError(), "glGenTextures");
252 gl.bindTexture(kTextureType, m_texture);
253 GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture");
254
255 gl.texParameteri(kTextureType, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
256 GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameteri");
257 gl.texParameteri(kTextureType, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
258 GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameteri");
259 gl.texParameteri(kTextureType, GL_TEXTURE_WRAP_S, GL_REPEAT);
260 GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameteri");
261 gl.texParameteri(kTextureType, GL_TEXTURE_WRAP_T, GL_REPEAT);
262 GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameteri");
263 gl.texParameteri(kTextureType, GL_TEXTURE_MAX_LEVEL, 0);
264 GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameteri");
265 }
266
deleteTexture()267 void NearestEdgeTestCase::deleteTexture()
268 {
269 const auto &gl = m_context.getRenderContext().getFunctions();
270
271 gl.deleteTextures(1, &m_texture);
272 GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteTextures");
273 }
274
fillTexture()275 void NearestEdgeTestCase::fillTexture()
276 {
277 const auto &gl = m_context.getRenderContext().getFunctions();
278
279 m_texData.resize(m_width * m_height * tcu::getPixelSize(m_texFormat));
280 tcu::PixelBufferAccess texAccess{m_texFormat, m_width, m_height, 1, m_texData.data()};
281
282 // Create gradient over the whole texture.
283 DE_ASSERT(m_width > 1);
284 DE_ASSERT(m_height > 1);
285
286 const float divX = static_cast<float>(m_width - 1);
287 const float divY = static_cast<float>(m_height - 1);
288
289 for (int x = 0; x < m_width; ++x)
290 for (int y = 0; y < m_height; ++y)
291 {
292 const float colorX = static_cast<float>(x) / divX;
293 const float colorY = static_cast<float>(y) / divY;
294 const float colorZ = std::min(colorX, colorY);
295
296 tcu::Vec4 color{colorX, colorY, colorZ, 1.0f};
297 tcu::Vec4 finalColor = (color - m_texFormatInfo.lookupBias) / m_texFormatInfo.lookupScale;
298 texAccess.setPixel(finalColor, x, y);
299 }
300
301 const auto internalFormat = glu::getInternalFormat(m_texFormat);
302 if (tcu::getPixelSize(m_texFormat) < 4)
303 gl.pixelStorei(GL_UNPACK_ALIGNMENT, 1);
304 gl.texImage2D(kTextureType, 0, internalFormat, m_width, m_height, 0 /* border */, m_transFormat.format,
305 m_transFormat.dataType, m_texData.data());
306 GLU_EXPECT_NO_ERROR(gl.getError(), "glTexImage2D");
307 }
308
309 // Draw full screen quad with the texture and an offset of almost half a texel in one direction, so sampling happens near the texel
310 // border and verifies truncation is happening properly.
renderQuad()311 void NearestEdgeTestCase::renderQuad()
312 {
313 const auto &renderContext = m_context.getRenderContext();
314 const auto &gl = renderContext.getFunctions();
315
316 float minU = 0.0f;
317 float maxU = 1.0f;
318 float minV = 0.0f;
319 float maxV = 1.0f;
320
321 // Apply offset of almost half a texel to the texture coordinates.
322 DE_ASSERT(m_offsetSign == 1.0f || m_offsetSign == -1.0f);
323
324 const float offset = 0.5f - pow(2.0f, -8.0f);
325 const float offsetWidth = offset / static_cast<float>(m_width);
326 const float offsetHeight = offset / static_cast<float>(m_height);
327
328 minU += m_offsetSign * offsetWidth;
329 maxU += m_offsetSign * offsetWidth;
330 minV += m_offsetSign * offsetHeight;
331 maxV += m_offsetSign * offsetHeight;
332
333 const std::vector<float> positions = {-1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f};
334 const std::vector<float> texCoords = {minU, minV, minU, maxV, maxU, minV, maxU, maxV};
335 const std::vector<uint16_t> quadIndices = {0, 1, 2, 2, 1, 3};
336
337 const std::vector<glu::VertexArrayBinding> vertexArrays = {glu::va::Float("position", 2, 4, 0, positions.data())};
338
339 glu::ShaderProgram program(m_context.getRenderContext(),
340 glu::makeVtxFragSources(m_vertShaderText, m_fragShaderText));
341 if (!program.isOk())
342 TCU_FAIL("Shader compilation failed");
343
344 gl.useProgram(program.getProgram());
345 GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram failed");
346
347 gl.uniform1i(gl.getUniformLocation(program.getProgram(), "texSampler"), 0);
348 GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i failed");
349
350 gl.uniform1f(gl.getUniformLocation(program.getProgram(), "texOffset"), m_offsetSign * offset);
351 gl.uniform1f(gl.getUniformLocation(program.getProgram(), "texWidth"), float(m_width));
352 gl.uniform1f(gl.getUniformLocation(program.getProgram(), "texHeight"), float(m_height));
353 GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i failed");
354
355 gl.disable(GL_DITHER);
356 gl.clear(GL_COLOR_BUFFER_BIT);
357
358 glu::draw(renderContext, program.getProgram(), static_cast<int>(vertexArrays.size()), vertexArrays.data(),
359 glu::pr::TriangleStrip(static_cast<int>(quadIndices.size()), quadIndices.data()));
360 }
361
verifyResults()362 bool NearestEdgeTestCase::verifyResults()
363 {
364 const auto &gl = m_context.getRenderContext().getFunctions();
365
366 std::vector<uint8_t> fbData(m_width * m_height * tcu::getPixelSize(m_texFormat));
367 if (tcu::getPixelSize(m_texFormat) < 4)
368 gl.pixelStorei(GL_PACK_ALIGNMENT, 1);
369 gl.readPixels(0, 0, m_width, m_height, m_transFormat.format, m_transFormat.dataType, fbData.data());
370 GLU_EXPECT_NO_ERROR(gl.getError(), "glReadPixels");
371
372 tcu::ConstPixelBufferAccess texAccess{m_texFormat, m_width, m_height, 1, m_texData.data()};
373 tcu::ConstPixelBufferAccess fbAccess{m_texFormat, m_width, m_height, 1, fbData.data()};
374
375 // Difference image to ease spotting problems.
376 const tcu::TextureFormat diffFormat{tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8};
377 const auto diffBytes = tcu::getPixelSize(diffFormat) * m_width * m_height;
378 std::unique_ptr<uint8_t[]> diffData{new uint8_t[diffBytes]};
379 const tcu::PixelBufferAccess diffAccess{diffFormat, m_width, m_height, 1, diffData.get()};
380
381 const tcu::Vec4 colorRed{1.0f, 0.0f, 0.0f, 1.0f};
382 const tcu::Vec4 colorGreen{0.0f, 1.0f, 0.0f, 1.0f};
383
384 bool pass = true;
385 for (int x = 0; x < m_width; ++x)
386 for (int y = 0; y < m_height; ++y)
387 {
388 const auto texPixel = texAccess.getPixel(x, y);
389 const auto fbPixel = fbAccess.getPixel(x, y);
390
391 // Require perfect pixel match.
392 if (texPixel != fbPixel)
393 {
394 pass = false;
395 diffAccess.setPixel(colorRed, x, y);
396 }
397 else
398 {
399 diffAccess.setPixel(colorGreen, x, y);
400 }
401 }
402
403 if (!pass)
404 {
405 auto &log = m_testCtx.getLog();
406 log << tcu::TestLog::Message << "\n"
407 << "Width: " << m_width << "\n"
408 << "Height: " << m_height << "\n"
409 << tcu::TestLog::EndMessage;
410
411 log << tcu::TestLog::Image("texture", "Generated Texture", texAccess);
412 log << tcu::TestLog::Image("fb", "Frame Buffer Contents", fbAccess);
413 log << tcu::TestLog::Image("diff", "Mismatched pixels in red", diffAccess);
414 }
415
416 return pass;
417 }
418
iterate()419 tcu::TestNode::IterateResult NearestEdgeTestCase::iterate()
420 {
421 // Populate and configure m_texture.
422 createTexture();
423
424 // Fill m_texture with data.
425 fillTexture();
426
427 // Draw full screen quad using the texture and a slight offset left or right.
428 renderQuad();
429
430 // Verify results.
431 bool pass = verifyResults();
432
433 // Destroy texture.
434 deleteTexture();
435
436 const qpTestResult result = (pass ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL);
437 const char *desc = (pass ? "Pass" : "Pixel mismatch; check the generated images");
438
439 m_testCtx.setTestResult(result, desc);
440 return STOP;
441 }
442
443 } /* anonymous namespace */
444
NearestEdgeCases(deqp::Context & context)445 NearestEdgeCases::NearestEdgeCases(deqp::Context &context)
446 : TestCaseGroup(context, "nearest_edge", "GL_NEAREST edge cases")
447 {
448 }
449
~NearestEdgeCases(void)450 NearestEdgeCases::~NearestEdgeCases(void)
451 {
452 }
453
init(void)454 void NearestEdgeCases::init(void)
455 {
456 static const std::vector<OffsetDirection> kDirections = {OffsetDirection::LEFT, OffsetDirection::RIGHT};
457 for (const auto direction : kDirections)
458 addChild(new NearestEdgeTestCase{m_context, direction});
459 }
460
461 } // namespace glcts
462