1 /*-------------------------------------------------------------------------
2 * OpenGL Conformance Test Suite
3 * -----------------------------
5 * Copyright (c) 2020 Valve Coporation.
6 * Copyright (c) 2020 The Khronos Group Inc.
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
12 * http://www.apache.org/licenses/LICENSE-2.0
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.
21 * \file glcNearestEdgeTests.cpp
23 */ /*-------------------------------------------------------------------*/
25 #include "glcNearestEdgeTests.hpp"
27 #include "gluDefs.hpp"
28 #include "gluTextureUtil.hpp"
29 #include "gluDrawUtil.hpp"
30 #include "gluShaderProgram.hpp"
32 #include "glwDefs.hpp"
33 #include "glwFunctions.hpp"
34 #include "glwEnums.hpp"
36 #include "tcuTestLog.hpp"
37 #include "tcuRenderTarget.hpp"
38 #include "tcuStringTemplate.hpp"
39 #include "tcuTextureUtil.hpp"
52 enum class OffsetDirection
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.
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
68 NearestEdgeTestCase(deqp::Context& context, OffsetDirection direction);
72 tcu::TestNode::IterateResult iterate();
74 static std::string getName (OffsetDirection direction);
75 static std::string getDesc (OffsetDirection direction);
76 static tcu::TextureFormat toTextureFormat (const tcu::PixelFormat& pixelFmt);
79 static const glw::GLenum kTextureType = GL_TEXTURE_2D;
81 void createTexture ();
82 void deleteTexture ();
85 bool verifyResults ();
87 const float m_offsetSign;
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;
100 std::string NearestEdgeTestCase::getName (OffsetDirection direction)
104 case OffsetDirection::LEFT: return "offset_left";
105 case OffsetDirection::RIGHT: return "offset_right";
106 default: DE_ASSERT(false); break;
112 std::string NearestEdgeTestCase::getDesc (OffsetDirection direction)
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;
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)
130 tcu::PixelFormat pixelFmt;
131 tcu::TextureFormat texFmt;
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) }
141 for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(pixelFormatMap); ndx++)
143 if (pixelFormatMap[ndx].pixelFmt == pixelFmt)
144 return pixelFormatMap[ndx].texFmt;
147 TCU_FAIL("Unable to map pixel format to texture format");
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)}
162 void NearestEdgeTestCase::deinit()
166 void NearestEdgeTestCase::init()
168 if (m_width < 2 || m_height < 2)
169 TCU_THROW(NotSupportedError, "Render target size too small");
172 "#version ${VERSION}\n"
174 "in highp vec2 position;\n"
175 "in highp vec2 inTexCoord;\n"
176 "out highp vec2 commonTexCoord;\n"
180 " commonTexCoord = inTexCoord;\n"
181 " gl_Position = vec4(position, 0.0, 1.0);\n"
186 "#version ${VERSION}\n"
188 "in highp vec2 commonTexCoord;\n"
189 "out highp vec4 fragColor;\n"
191 "uniform highp sampler2D texSampler;\n"
195 " fragColor = texture(texSampler, commonTexCoord);\n"
199 tcu::StringTemplate vertShaderTemplate{m_vertShaderText};
200 tcu::StringTemplate fragShaderTemplate{m_fragShaderText};
201 std::map<std::string, std::string> replacements;
203 if (glu::isContextTypeGLCore(m_context.getRenderContext().getType()))
204 replacements["VERSION"] = "130";
206 replacements["VERSION"] = "300 es";
208 m_vertShaderText = vertShaderTemplate.specialize(replacements);
209 m_fragShaderText = fragShaderTemplate.specialize(replacements);
212 void NearestEdgeTestCase::createTexture ()
214 const auto& gl = m_context.getRenderContext().getFunctions();
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");
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");
233 void NearestEdgeTestCase::deleteTexture ()
235 const auto& gl = m_context.getRenderContext().getFunctions();
237 gl.deleteTextures(1, &m_texture);
238 GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteTextures");
241 void NearestEdgeTestCase::fillTexture ()
243 const auto& gl = m_context.getRenderContext().getFunctions();
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()};
248 // Create gradient over the whole texture.
249 DE_ASSERT(m_width > 1);
250 DE_ASSERT(m_height > 1);
252 const float divX = static_cast<float>(m_width - 1);
253 const float divY = static_cast<float>(m_height - 1);
255 for (int x = 0; x < m_width; ++x)
256 for (int y = 0; y < m_height; ++y)
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);
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);
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");
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 ()
278 const auto& renderContext = m_context.getRenderContext();
279 const auto& gl = renderContext.getFunctions();
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);
291 minU += m_offsetSign * offsetWidth;
292 maxU += m_offsetSign * offsetWidth;
293 minV += m_offsetSign * offsetHeight;
294 maxV += m_offsetSign * offsetHeight;
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 };
300 const std::vector<glu::VertexArrayBinding> vertexArrays =
302 glu::va::Float("position", 2, 4, 0, positions.data()),
303 glu::va::Float("inTexCoord", 2, 4, 0, texCoords.data())
306 glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(m_vertShaderText, m_fragShaderText));
308 TCU_FAIL("Shader compilation failed");
310 gl.useProgram(program.getProgram());
311 GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram failed");
313 gl.uniform1i(gl.getUniformLocation(program.getProgram(), "texSampler"), 0);
314 GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i failed");
316 gl.clear(GL_COLOR_BUFFER_BIT);
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()));
323 bool NearestEdgeTestCase::verifyResults ()
325 const auto& gl = m_context.getRenderContext().getFunctions();
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");
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()};
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()};
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};
346 for (int x = 0; x < m_width; ++x)
347 for (int y = 0; y < m_height; ++y)
349 const auto texPixel = texAccess.getPixel(x, y);
350 const auto fbPixel = fbAccess.getPixel(x, y);
352 // Require perfect pixel match.
353 if (texPixel != fbPixel)
356 diffAccess.setPixel(colorRed, x, y);
360 diffAccess.setPixel(colorGreen, x, y);
366 auto& log = m_testCtx.getLog();
368 << tcu::TestLog::Message << "\n"
369 << "Width: " << m_width << "\n"
370 << "Height: " << m_height << "\n"
371 << tcu::TestLog::EndMessage;
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);
381 tcu::TestNode::IterateResult NearestEdgeTestCase::iterate ()
383 // Populate and configure m_texture.
386 // Fill m_texture with data.
389 // Draw full screen quad using the texture and a slight offset left or right.
393 bool pass = verifyResults();
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");
401 m_testCtx.setTestResult(result, desc);
405 } /* anonymous namespace */
407 NearestEdgeCases::NearestEdgeCases(deqp::Context& context)
408 : TestCaseGroup(context, "nearest_edge", "GL_NEAREST edge cases")
412 NearestEdgeCases::~NearestEdgeCases(void)
416 void NearestEdgeCases::init(void)
418 static const std::vector<OffsetDirection> kDirections = { OffsetDirection::LEFT, OffsetDirection::RIGHT };
419 for (const auto direction : kDirections)
420 addChild(new NearestEdgeTestCase{m_context, direction});
423 } /* glcts namespace */