xref: /aosp_15_r20/external/deqp/external/openglcts/modules/common/glcNearestEdgeTests.cpp (revision 35238bce31c2a825756842865a792f8cf7f89930)
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