• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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