1/*------------------------------------------------------------------------
2 * Vulkan Conformance Tests
3 * ------------------------
4 *
5 * Copyright (c) 2019 The Khronos Group Inc.
6 * Copyright (c) 2018 Google 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
22 * \brief Invariant decoration tests.
23 *//*--------------------------------------------------------------------*/
24
25#include "vktShaderRenderInvarianceTests.hpp"
26#include "vktShaderRender.hpp"
27#include "tcuImageCompare.hpp"
28#include "tcuStringTemplate.hpp"
29#include "tcuTextureUtil.hpp"
30#include "tcuTestLog.hpp"
31#include "vktDrawUtil.hpp"
32#include "deMath.h"
33#include "deRandom.hpp"
34
35using namespace vk;
36
37namespace vkt
38{
39using namespace drawutil;
40
41namespace sr
42{
43
44namespace
45{
46
47class FormatArgument
48{
49public:
50						FormatArgument (const char* name, const std::string& value);
51
52private:
53	friend class FormatArgumentList;
54
55	const char* const	m_name;
56	const std::string	m_value;
57};
58
59FormatArgument::FormatArgument (const char* name, const std::string& value)
60	: m_name	(name)
61	, m_value	(value)
62{
63}
64
65class FormatArgumentList
66{
67public:
68												FormatArgumentList	(void);
69
70	FormatArgumentList&							operator<<			(const FormatArgument&);
71	const std::map<std::string, std::string>&	getArguments		(void) const;
72
73private:
74	std::map<std::string, std::string>			m_formatArguments;
75};
76
77FormatArgumentList::FormatArgumentList (void)
78{
79}
80
81FormatArgumentList&	FormatArgumentList::operator<< (const FormatArgument& arg)
82{
83	m_formatArguments[arg.m_name] = arg.m_value;
84	return *this;
85}
86
87const std::map<std::string, std::string>& FormatArgumentList::getArguments (void) const
88{
89	return m_formatArguments;
90}
91
92static std::string formatGLSL(const char* templateString, const FormatArgumentList& args)
93{
94	const std::map<std::string, std::string>& params = args.getArguments();
95
96	return tcu::StringTemplate(std::string(templateString)).specialize(params);
97}
98
99class InvarianceTest : public vkt::TestCase
100{
101public:
102								InvarianceTest(tcu::TestContext& ctx, const char* name, const std::string& vertexShader1, const std::string& vertexShader2, const std::string& fragmentShader = "");
103
104	void						initPrograms	(SourceCollections& sourceCollections) const override;
105	vkt::TestInstance*			createInstance	(vkt::Context& context) const override;
106
107private:
108	const std::string			m_vertexShader1;
109	const std::string			m_vertexShader2;
110	const std::string			m_fragmentShader;
111};
112
113class InvarianceTestInstance : public vkt::TestInstance
114{
115public:
116						InvarianceTestInstance(vkt::Context &context);
117	tcu::TestStatus		iterate(void) override;
118	bool				checkImage(const tcu::ConstPixelBufferAccess& image) const;
119	const int			m_renderSize = 256;
120};
121
122 InvarianceTest::InvarianceTest(tcu::TestContext& ctx, const char* name, const std::string& vertexShader1, const std::string& vertexShader2, const std::string& fragmentShader)
123	: vkt::TestCase(ctx, name)
124	, m_vertexShader1(vertexShader1)
125	, m_vertexShader2(vertexShader2)
126	, m_fragmentShader(fragmentShader)
127
128{
129}
130
131void InvarianceTest::initPrograms(SourceCollections& sourceCollections) const
132{
133	sourceCollections.glslSources.add("vertex1") << glu::VertexSource(m_vertexShader1);
134	sourceCollections.glslSources.add("vertex2") << glu::VertexSource(m_vertexShader2);
135	sourceCollections.glslSources.add("fragment") << glu::FragmentSource(m_fragmentShader);
136}
137
138vkt::TestInstance* InvarianceTest::createInstance(Context& context) const
139{
140	return new InvarianceTestInstance(context);
141}
142
143InvarianceTestInstance::InvarianceTestInstance(vkt::Context &context)
144	: vkt::TestInstance(context)
145{
146}
147
148static tcu::Vec4 genRandomVector(de::Random& rnd)
149{
150	tcu::Vec4 retVal;
151
152	retVal.x() = rnd.getFloat(-1.0f, 1.0f);
153	retVal.y() = rnd.getFloat(-1.0f, 1.0f);
154	retVal.z() = rnd.getFloat(-1.0f, 1.0f);
155	retVal.w() = rnd.getFloat(0.2f, 1.0f);
156
157	return retVal;
158}
159
160struct ColorUniform
161{
162	tcu::Vec4 color;
163};
164
165tcu::TestStatus InvarianceTestInstance::iterate(void)
166{
167	const VkDevice			device			= m_context.getDevice();
168	const DeviceInterface&	vk				= m_context.getDeviceInterface();
169	Allocator&				allocator		= m_context.getDefaultAllocator();
170	tcu::TestLog&			log				= m_context.getTestContext().getLog();
171
172	const int				numTriangles	= 72;
173	de::Random				rnd				(123);
174	std::vector<tcu::Vec4>	vertices		(numTriangles * 3 * 2);
175
176	{
177		// Narrow triangle pattern
178		for (int triNdx = 0; triNdx < numTriangles; ++triNdx)
179		{
180			const tcu::Vec4 vertex1 = genRandomVector(rnd);
181			const tcu::Vec4 vertex2 = genRandomVector(rnd);
182			const tcu::Vec4 vertex3 = vertex2 + genRandomVector(rnd) * 0.01f; // generate narrow triangles
183
184			vertices[triNdx * 3 + 0] = vertex1;
185			vertices[triNdx * 3 + 1] = vertex2;
186			vertices[triNdx * 3 + 2] = vertex3;
187		}
188
189		// Normal triangle pattern
190		for (int triNdx = 0; triNdx < numTriangles; ++triNdx)
191		{
192			vertices[(numTriangles + triNdx) * 3 + 0] = genRandomVector(rnd);
193			vertices[(numTriangles + triNdx) * 3 + 1] = genRandomVector(rnd);
194			vertices[(numTriangles + triNdx) * 3 + 2] = genRandomVector(rnd);
195		}
196	}
197
198	Move<VkDescriptorSetLayout>		descriptorSetLayout;
199	Move<VkDescriptorPool>			descriptorPool;
200	Move<VkBuffer>					uniformBuffer[2];
201	de::MovePtr<Allocation>			uniformBufferAllocation[2];
202	Move<VkDescriptorSet>			descriptorSet[2];
203	const tcu::Vec4					red		= tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f);
204	const tcu::Vec4					green	= tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f);
205
206	// Descriptors
207	{
208		DescriptorSetLayoutBuilder	layoutBuilder;
209		layoutBuilder.addSingleBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT);
210		descriptorSetLayout = layoutBuilder.build(vk, device);
211		descriptorPool = DescriptorPoolBuilder()
212				.addType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2u)
213				.build(vk, device, VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, 2u);
214
215		const VkDescriptorSetAllocateInfo descriptorSetAllocInfo =
216		{
217			VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
218			DE_NULL,
219			*descriptorPool,
220			1u,
221			&descriptorSetLayout.get()
222		};
223
224		const VkBufferCreateInfo uniformBufferCreateInfo =
225		{
226			VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,					// VkStructureType		sType
227			DE_NULL,												// const void*			pNext
228			(VkBufferCreateFlags)0,									// VkBufferCreateFlags	flags
229			sizeof(ColorUniform),									// VkDeviceSize			size
230			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,						// VkBufferUsageFlags	usage
231			VK_SHARING_MODE_EXCLUSIVE,								// VkSharingMode		sharingMode
232			0u,														// deUint32				queueFamilyIndexCount
233			DE_NULL													// pQueueFamilyIndices
234		};
235
236		for (deUint32 passNdx = 0; passNdx < 2; ++passNdx)
237		{
238			uniformBuffer[passNdx]				= createBuffer(vk, device, &uniformBufferCreateInfo, DE_NULL);
239			uniformBufferAllocation[passNdx]	= allocator.allocate(getBufferMemoryRequirements(vk, device, *uniformBuffer[passNdx]), MemoryRequirement::HostVisible);
240			VK_CHECK(vk.bindBufferMemory(device, *uniformBuffer[passNdx], uniformBufferAllocation[passNdx]->getMemory(), uniformBufferAllocation[passNdx]->getOffset()));
241
242			{
243				ColorUniform* bufferData	= (ColorUniform*)(uniformBufferAllocation[passNdx]->getHostPtr());
244				bufferData->color			= (passNdx == 0) ? (red) : (green);
245				flushAlloc(vk, device, *uniformBufferAllocation[passNdx]);
246			}
247			descriptorSet[passNdx] = allocateDescriptorSet(vk, device, &descriptorSetAllocInfo);
248
249			const VkDescriptorBufferInfo bufferInfo =
250			{
251				*uniformBuffer[passNdx],
252				0u,
253				VK_WHOLE_SIZE
254			};
255
256			DescriptorSetUpdateBuilder()
257				.writeSingle(*descriptorSet[passNdx], DescriptorSetUpdateBuilder::Location::binding(0u), VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, &bufferInfo)
258				.update(vk, device);
259		}
260	}
261
262	// pick first available depth buffer format
263	const std::vector<VkFormat>	depthFormats	{ VK_FORMAT_D32_SFLOAT, VK_FORMAT_D24_UNORM_S8_UINT, VK_FORMAT_X8_D24_UNORM_PACK32, VK_FORMAT_D24_UNORM_S8_UINT };
264	VkFormat					depthFormat		= VK_FORMAT_UNDEFINED;
265	const InstanceInterface&	vki				= m_context.getInstanceInterface();
266	const VkPhysicalDevice		vkPhysDevice	= m_context.getPhysicalDevice();
267	for (const auto& df : depthFormats)
268	{
269		const VkFormatProperties	properties = getPhysicalDeviceFormatProperties(vki, vkPhysDevice, df);
270		if ((properties.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) == VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)
271		{
272			depthFormat = df;
273			break;
274		}
275	}
276	if(depthFormat == VK_FORMAT_UNDEFINED)
277		return tcu::TestStatus::fail("There must be at least one depth depth format handled (Vulkan spec 37.3, table 65)");
278
279	FrameBufferState				frameBufferState(m_renderSize, m_renderSize);
280	frameBufferState.depthFormat	= depthFormat;
281	PipelineState					pipelineState(m_context.getDeviceProperties().limits.subPixelPrecisionBits);
282	DrawCallData					drawCallData(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, vertices);
283	VulkanDrawContext				vulkanDrawContext(m_context, frameBufferState);
284
285	const std::vector<std::string>				vertexShaderNames	= { "vertex1", "vertex2" };
286
287	log << tcu::TestLog::Message << "Testing position invariance." << tcu::TestLog::EndMessage;
288
289	for (deUint32 passNdx = 0; passNdx < 2; ++passNdx)
290	{
291		std::vector<VulkanShader>			shaders;
292		shaders.push_back(VulkanShader(VK_SHADER_STAGE_VERTEX_BIT, m_context.getBinaryCollection().get(vertexShaderNames[passNdx])));
293		shaders.push_back(VulkanShader(VK_SHADER_STAGE_FRAGMENT_BIT, m_context.getBinaryCollection().get("fragment")));
294		VulkanProgram						vulkanProgram(shaders);
295		vulkanProgram.descriptorSetLayout	= *descriptorSetLayout;
296		vulkanProgram.descriptorSet			= *descriptorSet[passNdx];
297
298		const char* const			colorStr = (passNdx == 0) ? ("red - purple") : ("green");
299		log << tcu::TestLog::Message << "Drawing position test pattern using shader " << (passNdx + 1) << ". Primitive color: " << colorStr << "." << tcu::TestLog::EndMessage;
300
301		vulkanDrawContext.registerDrawObject(pipelineState, vulkanProgram, drawCallData);
302	}
303	vulkanDrawContext.draw();
304
305	tcu::ConstPixelBufferAccess	resultImage(
306		tcu::TextureFormat(vulkanDrawContext.getColorPixels().getFormat()),
307		vulkanDrawContext.getColorPixels().getWidth(),
308		vulkanDrawContext.getColorPixels().getHeight(),
309		1,
310		vulkanDrawContext.getColorPixels().getDataPtr());
311
312	log << tcu::TestLog::Message << "Verifying output. Expecting only green or background colored pixels." << tcu::TestLog::EndMessage;
313	if( !checkImage(resultImage) )
314		return tcu::TestStatus::fail("Detected variance between two invariant values");
315
316	return tcu::TestStatus::pass("Passed");
317}
318
319bool InvarianceTestInstance::checkImage(const tcu::ConstPixelBufferAccess& image) const
320{
321	const tcu::IVec4	okColor		(0, 255, 0, 255);
322	const tcu::RGBA		errColor	(255, 0, 0, 255);
323	bool				error		= false;
324	tcu::Surface		errorMask	(image.getWidth(), image.getHeight());
325
326	tcu::clear(errorMask.getAccess(), okColor);
327
328	for (int y = 0; y < m_renderSize; ++y)
329		for (int x = 0; x < m_renderSize; ++x)
330		{
331			const tcu::IVec4 col = image.getPixelInt(x, y);
332
333			if (col.x() != 0)
334			{
335				errorMask.setPixel(x, y, errColor);
336				error = true;
337			}
338		}
339
340	// report error
341	if (error)
342	{
343		m_context.getTestContext().getLog() << tcu::TestLog::Message << "Invalid pixels found (fragments from first render pass found). Variance detected." << tcu::TestLog::EndMessage;
344		m_context.getTestContext().getLog()
345			<< tcu::TestLog::ImageSet("Results", "Result verification")
346			<< tcu::TestLog::Image("Result", "Result", image)
347			<< tcu::TestLog::Image("Error mask", "Error mask", errorMask)
348			<< tcu::TestLog::EndImageSet;
349
350		return false;
351	}
352	else
353	{
354		m_context.getTestContext().getLog() << tcu::TestLog::Message << "No variance found." << tcu::TestLog::EndMessage;
355		m_context.getTestContext().getLog()
356			<< tcu::TestLog::ImageSet("Results", "Result verification")
357			<< tcu::TestLog::Image("Result", "Result", image)
358			<< tcu::TestLog::EndImageSet;
359
360		return true;
361	}
362}
363
364} // namespace
365
366tcu::TestCaseGroup* createShaderInvarianceTests (tcu::TestContext& testCtx)
367{
368	de::MovePtr<tcu::TestCaseGroup> invarianceGroup(new tcu::TestCaseGroup(testCtx, "invariance"));
369
370	static const struct PrecisionCase
371	{
372		glu::Precision	prec;
373		const char*		name;
374
375		// set literals in the glsl to be in the representable range
376		const char*		highValue;		// !< highValue < maxValue
377		const char*		invHighValue;
378		const char*		mediumValue;	// !< mediumValue^2 < maxValue
379		const char*		lowValue;		// !< lowValue^4 < maxValue
380		const char*		invlowValue;
381		int				loopIterations;
382		int				loopPartialIterations;
383		int				loopNormalizationExponent;
384		const char*		loopNormalizationConstantLiteral;
385		const char*		loopMultiplier;
386		const char*		sumLoopNormalizationConstantLiteral;
387	} precisions[] =
388	{
389		{ glu::PRECISION_HIGHP,		"highp",	"1.0e20",	"1.0e-20",	"1.0e14",	"1.0e9",	"1.0e-9",	14,	11,	2,	"1.0e4",	"1.9",	"1.0e3"	},
390		{ glu::PRECISION_MEDIUMP,	"mediump",	"1.0e4",	"1.0e-4",	"1.0e2",	"1.0e1",	"1.0e-1",	13,	11,	2,	"1.0e4",	"1.9",	"1.0e3"	},
391		{ glu::PRECISION_LOWP,		"lowp",		"0.9",		"1.1",		"1.1",		"1.15",		"0.87",		6,	2,	0,	"2.0",		"1.1",	"1.0"	},
392	};
393
394	// gl_Position must always be invariant for comparisons on gl_Position to be valid.
395	static const std::string invariantDeclaration[]	= { "invariant gl_Position;", "invariant gl_Position;\nlayout(location = 1) invariant highp out vec4 v_value;" };
396	static const std::string invariantAssignment0[]	= { "gl_Position", "v_value" };
397	static const std::string invariantAssignment1[]	= { "", "gl_Position = v_value;" };
398	static const std::string fragDeclaration[]		= { "", "layout(location = 1) highp in vec4 v_value;" };
399
400	static const char* basicFragmentShader = "${VERSION}"
401		"precision mediump float;\n"
402		"${IN} vec4 v_unrelated;\n"
403		"${FRAG_DECLARATION}\n"
404		"layout(binding = 0) uniform ColorUniform\n"
405		"{\n"
406		"	vec4 u_color;\n"
407		"} ucolor;\n"
408		"layout(location = 0) out vec4 fragColor;\n"
409		"void main ()\n"
410		"{\n"
411		"	float blue = dot(v_unrelated, vec4(1.0, 1.0, 1.0, 1.0));\n"
412		"	fragColor = vec4(ucolor.u_color.r, ucolor.u_color.g, blue, ucolor.u_color.a);\n"
413		"}\n";
414
415	for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); ++precNdx)
416	{
417		const char* const			precisionName = precisions[precNdx].name;
418		const glu::Precision		precision = precisions[precNdx].prec;
419		// Invariance tests using the given precision.
420		tcu::TestCaseGroup* const	group = new tcu::TestCaseGroup(testCtx, precisionName);
421
422		const deUint32 VAR_GROUP_SIZE = 2u;
423		tcu::TestCaseGroup* varGroup[VAR_GROUP_SIZE];
424		varGroup[0] = new tcu::TestCaseGroup(testCtx, "gl_position", "Invariance tests using gl_Position variable");
425		varGroup[1] = new tcu::TestCaseGroup(testCtx, "user_defined", "Invariance tests using user defined variable");
426		FormatArgumentList	args[VAR_GROUP_SIZE];
427		for (deUint32 groupNdx = 0u; groupNdx < VAR_GROUP_SIZE; ++groupNdx)
428		{
429			group->addChild(varGroup[groupNdx]);
430			args[groupNdx] = FormatArgumentList()
431				<< FormatArgument("VERSION",				"#version 450\n")
432				<< FormatArgument("IN",						"layout(location = 0) in")
433				<< FormatArgument("OUT",					"layout(location = 0) out")
434				<< FormatArgument("IN_PREC",				precisionName)
435				<< FormatArgument("INVARIANT_DECLARATION",	invariantDeclaration[groupNdx])
436				<< FormatArgument("INVARIANT_ASSIGN_0",		invariantAssignment0[groupNdx])
437				<< FormatArgument("INVARIANT_ASSIGN_1",		invariantAssignment1[groupNdx])
438				<< FormatArgument("FRAG_DECLARATION",		fragDeclaration[groupNdx])
439				<< FormatArgument("HIGH_VALUE",				de::toString(precisions[precNdx].highValue))
440				<< FormatArgument("HIGH_VALUE_INV",			de::toString(precisions[precNdx].invHighValue))
441				<< FormatArgument("MEDIUM_VALUE",			de::toString(precisions[precNdx].mediumValue))
442				<< FormatArgument("LOW_VALUE",				de::toString(precisions[precNdx].lowValue))
443				<< FormatArgument("LOW_VALUE_INV",			de::toString(precisions[precNdx].invlowValue))
444				<< FormatArgument("LOOP_ITERS",				de::toString(precisions[precNdx].loopIterations))
445				<< FormatArgument("LOOP_ITERS_PARTIAL",		de::toString(precisions[precNdx].loopPartialIterations))
446				<< FormatArgument("LOOP_NORM_FRACT_EXP",	de::toString(precisions[precNdx].loopNormalizationExponent))
447				<< FormatArgument("LOOP_NORM_LITERAL",		precisions[precNdx].loopNormalizationConstantLiteral)
448				<< FormatArgument("LOOP_MULTIPLIER",		precisions[precNdx].loopMultiplier)
449				<< FormatArgument("SUM_LOOP_NORM_LITERAL",	precisions[precNdx].sumLoopNormalizationConstantLiteral);
450		}
451
452		// subexpression cases
453		for (deUint32 groupNdx = 0u; groupNdx < VAR_GROUP_SIZE; ++groupNdx)
454		{
455			// First shader shares "${HIGH_VALUE}*a_input.x*a_input.xxxx + ${HIGH_VALUE}*a_input.y*a_input.yyyy" with unrelated output variable. Reordering might result in accuracy loss
456			// due to the high exponent. In the second shader, the high exponent may be removed during compilation.
457
458			// Shader shares a subexpression with an unrelated variable.
459			varGroup[groupNdx]->addChild(new InvarianceTest(testCtx, "common_subexpression_0",
460				formatGLSL("${VERSION}"
461					"${IN} ${IN_PREC} vec4 a_input;\n"
462					"${OUT} mediump vec4 v_unrelated;\n"
463					"${INVARIANT_DECLARATION}\n"
464					"void main ()\n"
465					"{\n"
466					"	v_unrelated = a_input.xzxz + (${HIGH_VALUE}*a_input.x*a_input.xxxx + ${HIGH_VALUE}*a_input.y*a_input.yyyy) * (1.08 * a_input.zyzy * a_input.xzxz) * ${HIGH_VALUE_INV} * (a_input.z * a_input.zzxz - a_input.z * a_input.zzxz) + (${HIGH_VALUE}*a_input.x*a_input.xxxx + ${HIGH_VALUE}*a_input.y*a_input.yyyy) / ${HIGH_VALUE};\n"
467					"	${INVARIANT_ASSIGN_0} = a_input + (${HIGH_VALUE}*a_input.x*a_input.xxxx + ${HIGH_VALUE}*a_input.y*a_input.yyyy) * ${HIGH_VALUE_INV};\n"
468					"	${INVARIANT_ASSIGN_1}\n"
469					"}\n", args[groupNdx]),
470				formatGLSL("${VERSION}"
471					"${IN} ${IN_PREC} vec4 a_input;\n"
472					"${OUT} mediump vec4 v_unrelated;\n"
473					"${INVARIANT_DECLARATION}\n"
474					"void main ()\n"
475					"{\n"
476					"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
477					"	${INVARIANT_ASSIGN_0} = a_input + (${HIGH_VALUE}*a_input.x*a_input.xxxx + ${HIGH_VALUE}*a_input.y*a_input.yyyy) * ${HIGH_VALUE_INV};\n"
478					"	${INVARIANT_ASSIGN_1}\n"
479					"}\n", args[groupNdx]),
480				formatGLSL(basicFragmentShader, args[groupNdx])));
481
482			// In the first shader, the unrelated variable "d" has mathematically the same expression as "e", but the different
483			// order of calculation might cause different results.
484
485			// Shader shares a subexpression with an unrelated variable.
486			varGroup[groupNdx]->addChild(new InvarianceTest(testCtx, "common_subexpression_1",
487				formatGLSL("${VERSION}"
488					"${IN} ${IN_PREC} vec4 a_input;\n"
489					"${OUT} mediump vec4 v_unrelated;\n"
490					"${INVARIANT_DECLARATION}\n"
491					"void main ()\n"
492					"{\n"
493					"	${IN_PREC} vec4 a = ${HIGH_VALUE} * a_input.zzxx + a_input.xzxy - ${HIGH_VALUE} * a_input.zzxx;\n"
494					"	${IN_PREC} vec4 b = ${HIGH_VALUE} * a_input.zzxx;\n"
495					"	${IN_PREC} vec4 c = b - ${HIGH_VALUE} * a_input.zzxx + a_input.xzxy;\n"
496					"	${IN_PREC} vec4 d = (${LOW_VALUE} * a_input.yzxx) * (${LOW_VALUE} * a_input.yzzw) * (1.1*${LOW_VALUE_INV} * a_input.yzxx) * (${LOW_VALUE_INV} * a_input.xzzy);\n"
497					"	${IN_PREC} vec4 e = ((${LOW_VALUE} * a_input.yzxx) * (1.1*${LOW_VALUE_INV} * a_input.yzxx)) * ((${LOW_VALUE_INV} * a_input.xzzy) * (${LOW_VALUE} * a_input.yzzw));\n"
498					"	v_unrelated = a + b + c + d + e;\n"
499					"	${INVARIANT_ASSIGN_0} = a_input + fract(c) + e;\n"
500					"	${INVARIANT_ASSIGN_1}\n"
501					"}\n", args[groupNdx]),
502				formatGLSL("${VERSION}"
503					"${IN} ${IN_PREC} vec4 a_input;\n"
504					"${OUT} mediump vec4 v_unrelated;\n"
505					"${INVARIANT_DECLARATION}\n"
506					"void main ()\n"
507					"{\n"
508					"	${IN_PREC} vec4 b = ${HIGH_VALUE} * a_input.zzxx;\n"
509					"	${IN_PREC} vec4 c = b - ${HIGH_VALUE} * a_input.zzxx + a_input.xzxy;\n"
510					"	${IN_PREC} vec4 e = ((${LOW_VALUE} * a_input.yzxx) * (1.1*${LOW_VALUE_INV} * a_input.yzxx)) * ((${LOW_VALUE_INV} * a_input.xzzy) * (${LOW_VALUE} * a_input.yzzw));\n"
511					"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
512					"	${INVARIANT_ASSIGN_0} = a_input + fract(c) + e;\n"
513					"	${INVARIANT_ASSIGN_1}\n"
514					"}\n", args[groupNdx]),
515				formatGLSL(basicFragmentShader, args[groupNdx])));
516
517			// Intermediate values used by an unrelated output variable
518
519			// Shader shares a subexpression with an unrelated variable.
520			varGroup[groupNdx]->addChild(new InvarianceTest(testCtx, "common_subexpression_2",
521				formatGLSL("${VERSION}"
522					"${IN} ${IN_PREC} vec4 a_input;\n"
523					"${OUT} mediump vec4 v_unrelated;\n"
524					"${INVARIANT_DECLARATION}\n"
525					"void main ()\n"
526					"{\n"
527					"	${IN_PREC} vec4 a = ${MEDIUM_VALUE} * (a_input.xxxx + a_input.yyyy);\n"
528					"	${IN_PREC} vec4 b = (${MEDIUM_VALUE} * (a_input.xxxx + a_input.yyyy)) * (${MEDIUM_VALUE} * (a_input.xxxx + a_input.yyyy)) / ${MEDIUM_VALUE} / ${MEDIUM_VALUE};\n"
529					"	${IN_PREC} vec4 c = a * a;\n"
530					"	${IN_PREC} vec4 d = c / ${MEDIUM_VALUE} / ${MEDIUM_VALUE};\n"
531					"	v_unrelated = a + b + c + d;\n"
532					"	${INVARIANT_ASSIGN_0} = a_input + d;\n"
533					"	${INVARIANT_ASSIGN_1}\n"
534					"}\n", args[groupNdx]),
535				formatGLSL("${VERSION}"
536					"${IN} ${IN_PREC} vec4 a_input;\n"
537					"${OUT} mediump vec4 v_unrelated;\n"
538					"${INVARIANT_DECLARATION}\n"
539					"void main ()\n"
540					"{\n"
541					"	${IN_PREC} vec4 a = ${MEDIUM_VALUE} * (a_input.xxxx + a_input.yyyy);\n"
542					"	${IN_PREC} vec4 c = a * a;\n"
543					"	${IN_PREC} vec4 d = c / ${MEDIUM_VALUE} / ${MEDIUM_VALUE};\n"
544					"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
545					"	${INVARIANT_ASSIGN_0} = a_input + d;\n"
546					"	${INVARIANT_ASSIGN_1}\n"
547					"}\n", args[groupNdx]),
548				formatGLSL(basicFragmentShader, args[groupNdx])));
549
550			// Invariant value can be calculated using unrelated value
551
552			// Shader shares a subexpression with an unrelated variable.
553			varGroup[groupNdx]->addChild(new InvarianceTest(testCtx, "common_subexpression_3",
554				formatGLSL("${VERSION}"
555					"${IN} ${IN_PREC} vec4 a_input;\n"
556					"${OUT} mediump vec4 v_unrelated;\n"
557					"${INVARIANT_DECLARATION}\n"
558					"void main ()\n"
559					"{\n"
560					"	${IN_PREC} float x = a_input.x * 0.2;\n"
561					"	${IN_PREC} vec4 a = a_input.xxyx * 0.7;\n"
562					"	${IN_PREC} vec4 b = a_input.yxyz * 0.7;\n"
563					"	${IN_PREC} vec4 c = a_input.zxyx * 0.5;\n"
564					"	${IN_PREC} vec4 f = x*a + x*b + x*c;\n"
565					"	v_unrelated = f;\n"
566					"	${IN_PREC} vec4 g = x * (a + b + c);\n"
567					"	${INVARIANT_ASSIGN_0} = a_input + g;\n"
568					"	${INVARIANT_ASSIGN_1}\n"
569					"}\n", args[groupNdx]),
570				formatGLSL("${VERSION}"
571					"${IN} ${IN_PREC} vec4 a_input;\n"
572					"${OUT} mediump vec4 v_unrelated;\n"
573					"${INVARIANT_DECLARATION}\n"
574					"void main ()\n"
575					"{\n"
576					"	${IN_PREC} float x = a_input.x * 0.2;\n"
577					"	${IN_PREC} vec4 a = a_input.xxyx * 0.7;\n"
578					"	${IN_PREC} vec4 b = a_input.yxyz * 0.7;\n"
579					"	${IN_PREC} vec4 c = a_input.zxyx * 0.5;\n"
580					"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
581					"	${IN_PREC} vec4 g = x * (a + b + c);\n"
582					"	${INVARIANT_ASSIGN_0} = a_input + g;\n"
583					"	${INVARIANT_ASSIGN_1}\n"
584					"}\n", args[groupNdx]),
585				formatGLSL(basicFragmentShader, args[groupNdx])));
586		}
587
588		// shared subexpression of different precision
589		for (deUint32 groupNdx = 0u; groupNdx < VAR_GROUP_SIZE; ++groupNdx)
590		{
591			for (int precisionOther = glu::PRECISION_LOWP; precisionOther != glu::PRECISION_LAST; ++precisionOther)
592			{
593				const char* const		unrelatedPrec = glu::getPrecisionName((glu::Precision)precisionOther);
594				const glu::Precision	minPrecision = (precisionOther < (int)precision) ? ((glu::Precision)precisionOther) : (precision);
595				const char* const		multiplierStr = (minPrecision == glu::PRECISION_LOWP) ? ("0.8, 0.4, -0.2, 0.3") : ("1.0e1, 5.0e2, 2.0e2, 1.0");
596				const char* const		normalizationStrUsed = (minPrecision == glu::PRECISION_LOWP) ? ("vec4(fract(used2).xyz, 0.0)") : ("vec4(fract(used2 / 1.0e2).xyz - fract(used2 / 1.0e3).xyz, 0.0)");
597				const char* const		normalizationStrUnrelated = (minPrecision == glu::PRECISION_LOWP) ? ("vec4(fract(unrelated2).xyz, 0.0)") : ("vec4(fract(unrelated2 / 1.0e2).xyz - fract(unrelated2 / 1.0e3).xyz, 0.0)");
598
599				// Shader shares subexpression of different precision with an unrelated variable.
600				varGroup[groupNdx]->addChild(new InvarianceTest(testCtx, ("subexpression_precision_" + std::string(unrelatedPrec)).c_str(),
601					formatGLSL("${VERSION}"
602						"${IN} ${IN_PREC} vec4 a_input;\n"
603						"${OUT} ${UNRELATED_PREC} vec4 v_unrelated;\n"
604						"${INVARIANT_DECLARATION}\n"
605						"void main ()\n"
606						"{\n"
607						"	${UNRELATED_PREC} vec4 unrelated0 = a_input + vec4(0.1, 0.2, 0.3, 0.4);\n"
608						"	${UNRELATED_PREC} vec4 unrelated1 = vec4(${MULTIPLIER}) * unrelated0.xywz + unrelated0;\n"
609						"	${UNRELATED_PREC} vec4 unrelated2 = refract(unrelated1, unrelated0, distance(unrelated0, unrelated1));\n"
610						"	v_unrelated = a_input + 0.02 * ${NORMALIZE_UNRELATED};\n"
611						"	${IN_PREC} vec4 used0 = a_input + vec4(0.1, 0.2, 0.3, 0.4);\n"
612						"	${IN_PREC} vec4 used1 = vec4(${MULTIPLIER}) * used0.xywz + used0;\n"
613						"	${IN_PREC} vec4 used2 = refract(used1, used0, distance(used0, used1));\n"
614						"	${INVARIANT_ASSIGN_0} = a_input + 0.02 * ${NORMALIZE_USED};\n"
615						"	${INVARIANT_ASSIGN_1}\n"
616						"}\n", FormatArgumentList(args[groupNdx])
617						<< FormatArgument("UNRELATED_PREC", unrelatedPrec)
618						<< FormatArgument("MULTIPLIER", multiplierStr)
619						<< FormatArgument("NORMALIZE_USED", normalizationStrUsed)
620						<< FormatArgument("NORMALIZE_UNRELATED", normalizationStrUnrelated)),
621					formatGLSL("${VERSION}"
622						"${IN} ${IN_PREC} vec4 a_input;\n"
623						"${OUT} ${UNRELATED_PREC} vec4 v_unrelated;\n"
624						"${INVARIANT_DECLARATION}\n"
625						"void main ()\n"
626						"{\n"
627						"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
628						"	${IN_PREC} vec4 used0 = a_input + vec4(0.1, 0.2, 0.3, 0.4);\n"
629						"	${IN_PREC} vec4 used1 = vec4(${MULTIPLIER}) * used0.xywz + used0;\n"
630						"	${IN_PREC} vec4 used2 = refract(used1, used0, distance(used0, used1));\n"
631						"	${INVARIANT_ASSIGN_0} = a_input + 0.02 * ${NORMALIZE_USED};\n"
632						"	${INVARIANT_ASSIGN_1}\n"
633						"}\n", FormatArgumentList(args[groupNdx])
634						<< FormatArgument("UNRELATED_PREC", unrelatedPrec)
635						<< FormatArgument("MULTIPLIER", multiplierStr)
636						<< FormatArgument("NORMALIZE_USED", normalizationStrUsed)
637						<< FormatArgument("NORMALIZE_UNRELATED", normalizationStrUnrelated)),
638					formatGLSL("${VERSION}"
639						"precision mediump float;\n"
640						"${IN} ${UNRELATED_PREC} vec4 v_unrelated;\n"
641						"${FRAG_DECLARATION}\n"
642						"layout(binding = 0) uniform ColorUniform\n"
643						"{\n"
644						"	vec4 u_color;\n"
645						"} ucolor;\n"
646						"${OUT} vec4 fragColor;\n"
647						"void main ()\n"
648						"{\n"
649						"	float blue = dot(v_unrelated, vec4(1.0, 1.0, 1.0, 1.0));\n"
650						"	fragColor = vec4(ucolor.u_color.r, ucolor.u_color.g, blue, ucolor.u_color.a);\n"
651						"}\n", FormatArgumentList(args[groupNdx])
652						<< FormatArgument("UNRELATED_PREC", unrelatedPrec)
653						<< FormatArgument("MULTIPLIER", multiplierStr)
654						<< FormatArgument("NORMALIZE_USED", normalizationStrUsed)
655						<< FormatArgument("NORMALIZE_UNRELATED", normalizationStrUnrelated))));
656			}
657		}
658
659		// loops
660		for (deUint32 groupNdx = 0u; groupNdx < VAR_GROUP_SIZE; ++groupNdx)
661		{
662			// Invariant value set using a loop
663			varGroup[groupNdx]->addChild(new InvarianceTest(testCtx, "loop_0",
664				formatGLSL("${VERSION}"
665					"${IN} ${IN_PREC} vec4 a_input;\n"
666					"${OUT} highp vec4 v_unrelated;\n"
667					"${INVARIANT_DECLARATION}\n"
668					"void main ()\n"
669					"{\n"
670					"	${IN_PREC} vec4 value = a_input;\n"
671					"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
672					"	for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n"
673					"	{\n"
674					"		value *= ${LOOP_MULTIPLIER};\n"
675					"		v_unrelated += value;\n"
676					"	}\n"
677					"	${INVARIANT_ASSIGN_0} = vec4(value.xyz / ${LOOP_NORM_LITERAL} + a_input.xyz * 0.1, 1.0);\n"
678					"	${INVARIANT_ASSIGN_1}\n"
679					"}\n", args[groupNdx]),
680				formatGLSL("${VERSION}"
681					"${IN} ${IN_PREC} vec4 a_input;\n"
682					"${OUT} highp vec4 v_unrelated;\n"
683					"${INVARIANT_DECLARATION}\n"
684					"void main ()\n"
685					"{\n"
686					"	${IN_PREC} vec4 value = a_input;\n"
687					"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
688					"	for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n"
689					"	{\n"
690					"		value *= ${LOOP_MULTIPLIER};\n"
691					"	}\n"
692					"	${INVARIANT_ASSIGN_0} = vec4(value.xyz / ${LOOP_NORM_LITERAL} + a_input.xyz * 0.1, 1.0);\n"
693					"	${INVARIANT_ASSIGN_1}\n"
694					"}\n", args[groupNdx]),
695				formatGLSL("${VERSION}"
696					"precision mediump float;\n"
697					"layout(location=0) in highp vec4 v_unrelated;\n"
698					"${FRAG_DECLARATION}\n"
699					"layout(binding = 0) uniform ColorUniform\n"
700					"{\n"
701					"	vec4 u_color;\n"
702					"} ucolor;\n"
703					"layout(location = 0) out vec4 fragColor;\n"
704					"void main ()\n"
705					"{\n"
706					"	float blue = dot(v_unrelated, vec4(1.0, 1.0, 1.0, 1.0));\n"
707					"	fragColor = vec4(ucolor.u_color.r, ucolor.u_color.g, blue, ucolor.u_color.a);\n"
708					"}\n", args[groupNdx])));
709
710			// Invariant value set using a loop
711			varGroup[groupNdx]->addChild(new InvarianceTest(testCtx, "loop_1",
712				formatGLSL("${VERSION}"
713					"${IN} ${IN_PREC} vec4 a_input;\n"
714					"${OUT} mediump vec4 v_unrelated;\n"
715					"${INVARIANT_DECLARATION}\n"
716					"void main ()\n"
717					"{\n"
718					"	${IN_PREC} vec4 value = a_input;\n"
719					"	for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n"
720					"	{\n"
721					"		value *= ${LOOP_MULTIPLIER};\n"
722					"		if (i == ${LOOP_ITERS_PARTIAL})\n"
723					"			v_unrelated = value;\n"
724					"	}\n"
725					"	${INVARIANT_ASSIGN_0} = vec4(value.xyz / ${LOOP_NORM_LITERAL} + a_input.xyz * 0.1, 1.0);\n"
726					"	${INVARIANT_ASSIGN_1}\n"
727					"}\n", args[groupNdx]),
728				formatGLSL("${VERSION}"
729					"${IN} ${IN_PREC} vec4 a_input;\n"
730					"${OUT} mediump vec4 v_unrelated;\n"
731					"${INVARIANT_DECLARATION}\n"
732					"void main ()\n"
733					"{\n"
734					"	${IN_PREC} vec4 value = a_input;\n"
735					"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
736					"	for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n"
737					"	{\n"
738					"		value *= ${LOOP_MULTIPLIER};\n"
739					"	}\n"
740					"	${INVARIANT_ASSIGN_0} = vec4(value.xyz / ${LOOP_NORM_LITERAL} + a_input.xyz * 0.1, 1.0);\n"
741					"	${INVARIANT_ASSIGN_1}\n"
742					"}\n", args[groupNdx]),
743				formatGLSL(basicFragmentShader, args[groupNdx])));
744
745			// Invariant value set using a loop
746			varGroup[groupNdx]->addChild(new InvarianceTest(testCtx, "loop_2",
747				formatGLSL("${VERSION}"
748					"${IN} ${IN_PREC} vec4 a_input;\n"
749					"${OUT} mediump vec4 v_unrelated;\n"
750					"${INVARIANT_DECLARATION}\n"
751					"void main ()\n"
752					"{\n"
753					"	${IN_PREC} vec4 value = a_input;\n"
754					"	v_unrelated = vec4(0.0, 0.0, -1.0, 1.0);\n"
755					"	for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n"
756					"	{\n"
757					"		value *= ${LOOP_MULTIPLIER};\n"
758					"		if (i == ${LOOP_ITERS_PARTIAL})\n"
759					"			${INVARIANT_ASSIGN_0} = a_input + 0.05 * vec4(fract(value.xyz / 1.0e${LOOP_NORM_FRACT_EXP}), 1.0);\n"
760					"		else\n"
761					"			v_unrelated = value + a_input;\n"
762					"	${INVARIANT_ASSIGN_1}\n"
763					"	}\n"
764					"}\n", args[groupNdx]),
765				formatGLSL("${VERSION}"
766					"${IN} ${IN_PREC} vec4 a_input;\n"
767					"${OUT} mediump vec4 v_unrelated;\n"
768					"${INVARIANT_DECLARATION}\n"
769					"void main ()\n"
770					"{\n"
771					"	${IN_PREC} vec4 value = a_input;\n"
772					"	v_unrelated = vec4(0.0, 0.0, -1.0, 1.0);\n"
773					"	for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n"
774					"	{\n"
775					"		value *= ${LOOP_MULTIPLIER};\n"
776					"		if (i == ${LOOP_ITERS_PARTIAL})\n"
777					"			${INVARIANT_ASSIGN_0} = a_input + 0.05 * vec4(fract(value.xyz / 1.0e${LOOP_NORM_FRACT_EXP}), 1.0);\n"
778					"		else\n"
779					"			v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
780					"	${INVARIANT_ASSIGN_1}\n"
781					"	}\n"
782					"}\n", args[groupNdx]),
783				formatGLSL(basicFragmentShader, args[groupNdx])));
784
785			// Invariant value set using a loop
786			varGroup[groupNdx]->addChild(new InvarianceTest(testCtx, "loop_3",
787				formatGLSL("${VERSION}"
788					"${IN} ${IN_PREC} vec4 a_input;\n"
789					"${OUT} mediump vec4 v_unrelated;\n"
790					"${INVARIANT_DECLARATION}\n"
791					"void main ()\n"
792					"{\n"
793					"	${IN_PREC} vec4 value = a_input;\n"
794					"	${INVARIANT_ASSIGN_0} = vec4(0.0, 0.0, 0.0, 0.0);\n"
795					"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
796					"	for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n"
797					"	{\n"
798					"		value *= ${LOOP_MULTIPLIER};\n"
799					"		${INVARIANT_ASSIGN_0} += vec4(value.xyz / ${SUM_LOOP_NORM_LITERAL} + a_input.xyz * 0.1, 1.0);\n"
800					"		v_unrelated = ${INVARIANT_ASSIGN_0}.xyzx * a_input;\n"
801					"	}\n"
802					"	${INVARIANT_ASSIGN_1}\n"
803					"}\n", args[groupNdx]),
804				formatGLSL("${VERSION}"
805					"${IN} ${IN_PREC} vec4 a_input;\n"
806					"${OUT} mediump vec4 v_unrelated;\n"
807					"${INVARIANT_DECLARATION}\n"
808					"void main ()\n"
809					"{\n"
810					"	${IN_PREC} vec4 value = a_input;\n"
811					"	${INVARIANT_ASSIGN_0} = vec4(0.0, 0.0, 0.0, 0.0);\n"
812					"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
813					"	for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n"
814					"	{\n"
815					"		value *= ${LOOP_MULTIPLIER};\n"
816					"		${INVARIANT_ASSIGN_0} += vec4(value.xyz / ${SUM_LOOP_NORM_LITERAL} + a_input.xyz * 0.1, 1.0);\n"
817					"	}\n"
818					"	${INVARIANT_ASSIGN_1}\n"
819					"}\n", args[groupNdx]),
820				formatGLSL(basicFragmentShader, args[groupNdx])));
821
822			// Invariant value set using a loop
823			varGroup[groupNdx]->addChild(new InvarianceTest(testCtx, "loop_4",
824				formatGLSL("${VERSION}"
825					"${IN} ${IN_PREC} vec4 a_input;\n"
826					"${OUT} mediump vec4 v_unrelated;\n"
827					"${INVARIANT_DECLARATION}\n"
828					"void main ()\n"
829					"{\n"
830					"	${IN_PREC} vec4 position = vec4(0.0, 0.0, 0.0, 0.0);\n"
831					"	${IN_PREC} vec4 value1 = a_input;\n"
832					"	${IN_PREC} vec4 value2 = a_input;\n"
833					"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
834					"	for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n"
835					"	{\n"
836					"		value1 *= ${LOOP_MULTIPLIER};\n"
837					"		v_unrelated = v_unrelated*1.3 + a_input.xyzx * value1.xyxw;\n"
838					"	}\n"
839					"	for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n"
840					"	{\n"
841					"		value2 *= ${LOOP_MULTIPLIER};\n"
842					"		position = position*1.3 + a_input.xyzx * value2.xyxw;\n"
843					"	}\n"
844					"	${INVARIANT_ASSIGN_0} = a_input + 0.05 * vec4(fract(position.xyz / 1.0e${LOOP_NORM_FRACT_EXP}), 1.0);\n"
845					"	${INVARIANT_ASSIGN_1}\n"
846					"}\n", args[groupNdx]),
847				formatGLSL("${VERSION}"
848					"${IN} ${IN_PREC} vec4 a_input;\n"
849					"${OUT} mediump vec4 v_unrelated;\n"
850					"${INVARIANT_DECLARATION}\n"
851					"void main ()\n"
852					"{\n"
853					"	${IN_PREC} vec4 position = vec4(0.0, 0.0, 0.0, 0.0);\n"
854					"	${IN_PREC} vec4 value2 = a_input;\n"
855					"	v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n"
856					"	for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n"
857					"	{\n"
858					"		value2 *= ${LOOP_MULTIPLIER};\n"
859					"		position = position*1.3 + a_input.xyzx * value2.xyxw;\n"
860					"	}\n"
861					"	${INVARIANT_ASSIGN_0} = a_input + 0.05 * vec4(fract(position.xyz / 1.0e${LOOP_NORM_FRACT_EXP}), 1.0);\n"
862					"	${INVARIANT_ASSIGN_1}\n"
863					"}\n", args[groupNdx]),
864				formatGLSL(basicFragmentShader, args[groupNdx])));
865		}
866		invarianceGroup->addChild(group);
867	}
868	return invarianceGroup.release();
869}
870
871} // namespace sr
872
873} // namespace vkt
874