Fix pixel pack/unpack in nearest edge tests
[platform/upstream/VK-GL-CTS.git] / external / openglcts / modules / common / glcNearestEdgeTests.cpp
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
46 namespace glcts
47 {
48
49 namespace
50 {
51
52 enum class OffsetDirection
53 {
54         LEFT    = 0,
55         RIGHT   = 1,
56 };
57
58 // Test sampling at the edge of texels. This test is equivalent to:
59 //  1) Creating a texture using the same format and size as the frame buffer.
60 //  2) Drawing a full screen quad with GL_NEAREST using the texture.
61 //  3) Verifying the frame buffer image and the texture match pixel-by-pixel.
62 //
63 // However, texture coodinates are not located in the exact frame buffer corners. A small offset is applied instead so sampling
64 // happens near a texel border instead of in the middle of the texel.
65 class NearestEdgeTestCase : public deqp::TestCase
66 {
67 public:
68         NearestEdgeTestCase(deqp::Context& context, OffsetDirection direction);
69
70         void                                                    deinit();
71         void                                                    init();
72         tcu::TestNode::IterateResult    iterate();
73
74         static std::string                              getName                 (OffsetDirection direction);
75         static std::string                              getDesc                 (OffsetDirection direction);
76         static tcu::TextureFormat               toTextureFormat (const tcu::PixelFormat& pixelFmt);
77
78 private:
79         static const glw::GLenum kTextureType   = GL_TEXTURE_2D;
80
81         void createTexture      ();
82         void deleteTexture      ();
83         void fillTexture        ();
84         void renderQuad         ();
85         bool verifyResults      ();
86
87         const float                                             m_offsetSign;
88         const int                                               m_width;
89         const int                                               m_height;
90         const tcu::PixelFormat&                 m_format;
91         const tcu::TextureFormat                m_texFormat;
92         const tcu::TextureFormatInfo    m_texFormatInfo;
93         const glu::TransferFormat               m_transFormat;
94         std::string                                             m_vertShaderText;
95         std::string                                             m_fragShaderText;
96         glw::GLuint                                             m_texture;
97         std::vector<deUint8>                    m_texData;
98 };
99
100 std::string NearestEdgeTestCase::getName (OffsetDirection direction)
101 {
102         switch (direction)
103         {
104         case OffsetDirection::LEFT:             return "offset_left";
105         case OffsetDirection::RIGHT:    return "offset_right";
106         default: DE_ASSERT(false); break;
107         }
108         // Unreachable.
109         return "";
110 }
111
112 std::string NearestEdgeTestCase::getDesc (OffsetDirection direction)
113 {
114         switch (direction)
115         {
116         case OffsetDirection::LEFT:             return "Sampling point near the left edge";
117         case OffsetDirection::RIGHT:    return "Sampling point near the right edge";
118         default: DE_ASSERT(false); break;
119         }
120         // Unreachable.
121         return "";
122 }
123
124 // Translate pixel format in the frame buffer to texture format.
125 // Copied from sglrReferenceContext.cpp.
126 tcu::TextureFormat NearestEdgeTestCase::toTextureFormat (const tcu::PixelFormat& pixelFmt)
127 {
128         static const struct
129         {
130                 tcu::PixelFormat        pixelFmt;
131                 tcu::TextureFormat      texFmt;
132         } pixelFormatMap[] =
133         {
134                 { tcu::PixelFormat(8,8,8,8),    tcu::TextureFormat(tcu::TextureFormat::RGBA,    tcu::TextureFormat::UNORM_INT8)                 },
135                 { tcu::PixelFormat(8,8,8,0),    tcu::TextureFormat(tcu::TextureFormat::RGB,             tcu::TextureFormat::UNORM_INT8)                 },
136                 { tcu::PixelFormat(4,4,4,4),    tcu::TextureFormat(tcu::TextureFormat::RGBA,    tcu::TextureFormat::UNORM_SHORT_4444)   },
137                 { tcu::PixelFormat(5,5,5,1),    tcu::TextureFormat(tcu::TextureFormat::RGBA,    tcu::TextureFormat::UNORM_SHORT_5551)   },
138                 { tcu::PixelFormat(5,6,5,0),    tcu::TextureFormat(tcu::TextureFormat::RGB,             tcu::TextureFormat::UNORM_SHORT_565)    }
139         };
140
141         for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(pixelFormatMap); ndx++)
142         {
143                 if (pixelFormatMap[ndx].pixelFmt == pixelFmt)
144                         return pixelFormatMap[ndx].texFmt;
145         }
146
147         TCU_FAIL("Unable to map pixel format to texture format");
148 }
149
150 NearestEdgeTestCase::NearestEdgeTestCase (deqp::Context& context, OffsetDirection direction)
151         : TestCase(context, getName(direction).c_str(), getDesc(direction).c_str())
152         , m_offsetSign          {(direction == OffsetDirection::LEFT) ? -1.0f : 1.0f}
153         , m_width                       {context.getRenderTarget().getWidth()}
154         , m_height                      {context.getRenderTarget().getHeight()}
155         , m_format                      {context.getRenderTarget().getPixelFormat()}
156         , m_texFormat           {toTextureFormat(m_format)}
157         , m_texFormatInfo       {tcu::getTextureFormatInfo(m_texFormat)}
158         , m_transFormat         {glu::getTransferFormat(m_texFormat)}
159 {
160 }
161
162 void NearestEdgeTestCase::deinit()
163 {
164 }
165
166 void NearestEdgeTestCase::init()
167 {
168         if (m_width < 2 || m_height < 2)
169                 TCU_THROW(NotSupportedError, "Render target size too small");
170
171         m_vertShaderText =
172                 "#version ${VERSION}\n"
173                 "\n"
174                 "in highp vec2 position;\n"
175                 "in highp vec2 inTexCoord;\n"
176                 "out highp vec2 commonTexCoord;\n"
177                 "\n"
178                 "void main()\n"
179                 "{\n"
180                 "    commonTexCoord = inTexCoord;\n"
181                 "    gl_Position = vec4(position, 0.0, 1.0);\n"
182                 "}\n"
183                 ;
184
185         m_fragShaderText =
186                 "#version ${VERSION}\n"
187                 "\n"
188                 "in highp vec2 commonTexCoord;\n"
189                 "out highp vec4 fragColor;\n"
190                 "\n"
191                 "uniform highp sampler2D texSampler;\n"
192                 "\n"
193                 "void main()\n"
194                 "{\n"
195                 "    fragColor = texture(texSampler, commonTexCoord);\n"
196                 "}\n"
197                 "\n";
198
199         tcu::StringTemplate vertShaderTemplate{m_vertShaderText};
200         tcu::StringTemplate fragShaderTemplate{m_fragShaderText};
201         std::map<std::string, std::string> replacements;
202
203         if (glu::isContextTypeGLCore(m_context.getRenderContext().getType()))
204                 replacements["VERSION"] = "130";
205         else
206                 replacements["VERSION"] = "300 es";
207
208         m_vertShaderText = vertShaderTemplate.specialize(replacements);
209         m_fragShaderText = fragShaderTemplate.specialize(replacements);
210 }
211
212 void NearestEdgeTestCase::createTexture ()
213 {
214         const auto& gl = m_context.getRenderContext().getFunctions();
215
216         gl.genTextures(1, &m_texture);
217         GLU_EXPECT_NO_ERROR(gl.getError(), "glGenTextures");
218         gl.bindTexture(kTextureType, m_texture);
219         GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture");
220
221         gl.texParameteri(kTextureType, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
222         GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameteri");
223         gl.texParameteri(kTextureType, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
224         GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameteri");
225         gl.texParameteri(kTextureType, GL_TEXTURE_WRAP_S, GL_REPEAT);
226         GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameteri");
227         gl.texParameteri(kTextureType, GL_TEXTURE_WRAP_T, GL_REPEAT);
228         GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameteri");
229         gl.texParameteri(kTextureType, GL_TEXTURE_MAX_LEVEL, 0);
230         GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameteri");
231 }
232
233 void NearestEdgeTestCase::deleteTexture ()
234 {
235         const auto& gl = m_context.getRenderContext().getFunctions();
236
237         gl.deleteTextures(1, &m_texture);
238         GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteTextures");
239 }
240
241 void NearestEdgeTestCase::fillTexture ()
242 {
243         const auto& gl = m_context.getRenderContext().getFunctions();
244
245         m_texData.resize(m_width * m_height * tcu::getPixelSize(m_texFormat));
246         tcu::PixelBufferAccess texAccess{m_texFormat, m_width, m_height, 1, m_texData.data()};
247
248         // Create gradient over the whole texture.
249         DE_ASSERT(m_width > 1);
250         DE_ASSERT(m_height > 1);
251
252         const float divX = static_cast<float>(m_width - 1);
253         const float divY = static_cast<float>(m_height - 1);
254
255         for (int x = 0; x < m_width; ++x)
256         for (int y = 0; y < m_height; ++y)
257         {
258                 const float colorX = static_cast<float>(x) / divX;
259                 const float colorY = static_cast<float>(y) / divY;
260                 const float colorZ = std::min(colorX, colorY);
261
262                 tcu::Vec4 color{colorX, colorY, colorZ, 1.0f};
263                 tcu::Vec4 finalColor = (color - m_texFormatInfo.lookupBias) / m_texFormatInfo.lookupScale;
264                 texAccess.setPixel(finalColor, x, y);
265         }
266
267         const auto internalFormat = glu::getInternalFormat(m_texFormat);
268         if (tcu::getPixelSize(m_texFormat) < 4)
269                 gl.pixelStorei(GL_UNPACK_ALIGNMENT, 1);
270         gl.texImage2D(kTextureType, 0, internalFormat, m_width,  m_height, 0 /* border */, m_transFormat.format, m_transFormat.dataType, m_texData.data());
271         GLU_EXPECT_NO_ERROR(gl.getError(), "glTexImage2D");
272 }
273
274 // Draw full screen quad with the texture and an offset of almost half a texel in one direction, so sampling happens near the texel
275 // border and verifies truncation is happening properly.
276 void NearestEdgeTestCase::renderQuad ()
277 {
278         const auto& renderContext       = m_context.getRenderContext();
279         const auto& gl                          = renderContext.getFunctions();
280
281         float minU = 0.0f;
282         float maxU = 1.0f;
283         float minV = 0.0f;
284         float maxV = 1.0f;
285
286         // Apply offset of almost half a texel to the texture coordinates.
287         DE_ASSERT(m_offsetSign == 1.0f || m_offsetSign == -1.0f);
288         const float offsetWidth         = 0.499f / static_cast<float>(m_width);
289         const float offsetHeight        = 0.499f / static_cast<float>(m_height);
290
291         minU += m_offsetSign * offsetWidth;
292         maxU += m_offsetSign * offsetWidth;
293         minV += m_offsetSign * offsetHeight;
294         maxV += m_offsetSign * offsetHeight;
295
296         const std::vector<float>        positions       = { -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f };
297         const std::vector<float>        texCoords       = { minU, minV, minU, maxV, maxU, minV, maxU, maxV };
298         const std::vector<deUint16>     quadIndices     = { 0, 1, 2, 2, 1, 3 };
299
300         const std::vector<glu::VertexArrayBinding> vertexArrays =
301         {
302                 glu::va::Float("position", 2, 4, 0, positions.data()),
303                 glu::va::Float("inTexCoord", 2, 4, 0, texCoords.data())
304         };
305
306         glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(m_vertShaderText, m_fragShaderText));
307         if (!program.isOk())
308                 TCU_FAIL("Shader compilation failed");
309
310         gl.useProgram(program.getProgram());
311         GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram failed");
312
313         gl.uniform1i(gl.getUniformLocation(program.getProgram(), "texSampler"), 0);
314         GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i failed");
315
316         gl.clear(GL_COLOR_BUFFER_BIT);
317
318         glu::draw(renderContext, program.getProgram(),
319                           static_cast<int>(vertexArrays.size()), vertexArrays.data(),
320                           glu::pr::TriangleStrip(static_cast<int>(quadIndices.size()), quadIndices.data()));
321 }
322
323 bool NearestEdgeTestCase::verifyResults ()
324 {
325         const auto& gl = m_context.getRenderContext().getFunctions();
326
327         std::vector<deUint8> fbData(m_width * m_height * tcu::getPixelSize(m_texFormat));
328         if (tcu::getPixelSize(m_texFormat) < 4)
329                 gl.pixelStorei(GL_PACK_ALIGNMENT, 1);
330         gl.readPixels(0, 0, m_width, m_height, m_transFormat.format, m_transFormat.dataType, fbData.data());
331         GLU_EXPECT_NO_ERROR(gl.getError(), "glReadPixels");
332
333         tcu::ConstPixelBufferAccess texAccess   {m_texFormat, m_width, m_height, 1, m_texData.data()};
334         tcu::ConstPixelBufferAccess fbAccess    {m_texFormat, m_width, m_height, 1, fbData.data()};
335
336         // Difference image to ease spotting problems.
337         const tcu::TextureFormat                diffFormat      {tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8};
338         const auto                                              diffBytes       = tcu::getPixelSize(diffFormat) * m_width * m_height;
339         std::unique_ptr<deUint8[]>              diffData        {new deUint8[diffBytes]};
340         const tcu::PixelBufferAccess    diffAccess      {diffFormat, m_width, m_height, 1, diffData.get()};
341
342         const tcu::Vec4                                 colorRed        {1.0f, 0.0f, 0.0f, 1.0f};
343         const tcu::Vec4                                 colorGreen      {0.0f, 1.0f, 0.0f, 1.0f};
344
345         bool pass = true;
346         for (int x = 0; x < m_width; ++x)
347         for (int y = 0; y < m_height; ++y)
348         {
349                 const auto texPixel     = texAccess.getPixel(x, y);
350                 const auto fbPixel      = fbAccess.getPixel(x, y);
351
352                 // Require perfect pixel match.
353                 if (texPixel != fbPixel)
354                 {
355                         pass = false;
356                         diffAccess.setPixel(colorRed, x, y);
357                 }
358                 else
359                 {
360                         diffAccess.setPixel(colorGreen, x, y);
361                 }
362         }
363
364         if (!pass)
365         {
366                 auto& log = m_testCtx.getLog();
367                 log
368                         << tcu::TestLog::Message << "\n"
369                         << "Width:       " << m_width << "\n"
370                         << "Height:      " << m_height << "\n"
371                         << tcu::TestLog::EndMessage;
372
373                 log << tcu::TestLog::Image("texture", "Generated Texture", texAccess);
374                 log << tcu::TestLog::Image("fb", "Frame Buffer Contents", fbAccess);
375                 log << tcu::TestLog::Image("diff", "Mismatched pixels in red", diffAccess);
376         }
377
378         return pass;
379 }
380
381 tcu::TestNode::IterateResult NearestEdgeTestCase::iterate ()
382 {
383         // Populate and configure m_texture.
384         createTexture();
385
386         // Fill m_texture with data.
387         fillTexture();
388
389         // Draw full screen quad using the texture and a slight offset left or right.
390         renderQuad();
391
392         // Verify results.
393         bool pass = verifyResults();
394
395         // Destroy texture.
396         deleteTexture();
397
398         const qpTestResult      result  = (pass ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL);
399         const char*                     desc    = (pass ? "Pass" : "Pixel mismatch; check the generated images");
400
401         m_testCtx.setTestResult(result, desc);
402         return STOP;
403 }
404
405 } /* anonymous namespace */
406
407 NearestEdgeCases::NearestEdgeCases(deqp::Context& context)
408         : TestCaseGroup(context, "nearest_edge", "GL_NEAREST edge cases")
409 {
410 }
411
412 NearestEdgeCases::~NearestEdgeCases(void)
413 {
414 }
415
416 void NearestEdgeCases::init(void)
417 {
418         static const std::vector<OffsetDirection> kDirections = { OffsetDirection::LEFT, OffsetDirection::RIGHT };
419         for (const auto direction : kDirections)
420                 addChild(new NearestEdgeTestCase{m_context, direction});
421 }
422
423 } /* glcts namespace */