es31fProgramStateQueryTests.cpp revision c423ce6164cdd88c8c3e47bec4ec34476743042a
1/*-------------------------------------------------------------------------
2 * drawElements Quality Program OpenGL ES 3.1 Module
3 * -------------------------------------------------
4 *
5 * Copyright 2015 The Android Open Source Project
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 *      http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 *
19 *//*!
20 * \file
21 * \brief Program State Query tests.
22 *//*--------------------------------------------------------------------*/
23
24#include "es31fProgramStateQueryTests.hpp"
25#include "es31fInfoLogQueryShared.hpp"
26#include "glsStateQueryUtil.hpp"
27#include "gluRenderContext.hpp"
28#include "gluCallLogWrapper.hpp"
29#include "gluContextInfo.hpp"
30#include "gluObjectWrapper.hpp"
31#include "gluShaderProgram.hpp"
32#include "glwFunctions.hpp"
33#include "glwEnums.hpp"
34
35namespace deqp
36{
37namespace gles31
38{
39namespace Functional
40{
41namespace
42{
43
44using namespace gls::StateQueryUtil;
45
46static const char* getVerifierSuffix (QueryType type)
47{
48	switch (type)
49	{
50		case QUERY_PROGRAM_INTEGER_VEC3:
51		case QUERY_PROGRAM_INTEGER:
52			return "get_programiv";
53
54		default:
55			DE_ASSERT(DE_FALSE);
56			return DE_NULL;
57	}
58}
59
60class ProgramSeparableCase : public TestCase
61{
62public:
63						ProgramSeparableCase	(Context& context, QueryType verifier, const char* name, const char* desc);
64	IterateResult		iterate					(void);
65
66private:
67	const QueryType		m_verifier;
68};
69
70ProgramSeparableCase::ProgramSeparableCase (Context& context, QueryType verifier, const char* name, const char* desc)
71	: TestCase		(context, name, desc)
72	, m_verifier	(verifier)
73{
74}
75
76ProgramSeparableCase::IterateResult ProgramSeparableCase::iterate (void)
77{
78	static const char* const s_vertexSource = 	"#version 310 es\n"
79												"out highp vec4 v_color;\n"
80												"void main()\n"
81												"{\n"
82												"	gl_Position = vec4(float(gl_VertexID) * 0.5, float(gl_VertexID+1) * 0.5, 0.0, 1.0);\n"
83												"	v_color = vec4(float(gl_VertexID), 1.0, 0.0, 1.0);\n"
84												"}\n";
85	static const char* const s_fragmentSource = "#version 310 es\n"
86												"in highp vec4 v_color;\n"
87												"layout(location=0) out highp vec4 o_color;\n"
88												"void main()\n"
89												"{\n"
90												"	o_color = v_color;\n"
91												"}\n";
92
93	glu::CallLogWrapper		gl			(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
94	tcu::ResultCollector	result		(m_testCtx.getLog(), " // ERROR: ");
95	glu::Shader				vtxShader	(m_context.getRenderContext(), glu::SHADERTYPE_VERTEX);
96	glu::Shader				frgShader	(m_context.getRenderContext(), glu::SHADERTYPE_FRAGMENT);
97
98	vtxShader.setSources(1, &s_vertexSource, DE_NULL);
99	frgShader.setSources(1, &s_fragmentSource, DE_NULL);
100
101	vtxShader.compile();
102	frgShader.compile();
103
104	{
105		const tcu::ScopedLogSection section(m_testCtx.getLog(), "VtxShader", "Vertex shader");
106		m_testCtx.getLog() << vtxShader;
107	}
108
109	{
110		const tcu::ScopedLogSection section(m_testCtx.getLog(), "FrgShader", "Fragment shader");
111		m_testCtx.getLog() << frgShader;
112	}
113
114	if (!vtxShader.getCompileStatus() || !frgShader.getCompileStatus())
115		throw tcu::TestError("failed to build shaders");
116
117	gl.enableLogging(true);
118
119	{
120		const tcu::ScopedLogSection	section	(m_testCtx.getLog(), "Initial", "Initial");
121		glu::Program				program	(m_context.getRenderContext());
122
123		verifyStateProgramInteger(result, gl, program.getProgram(), GL_PROGRAM_SEPARABLE, 0, m_verifier);
124	}
125
126	{
127		const tcu::ScopedLogSection section		(m_testCtx.getLog(), "SetFalse", "SetFalse");
128		glu::Program				program		(m_context.getRenderContext());
129		int							linkStatus	= 0;
130
131		gl.glAttachShader(program.getProgram(), vtxShader.getShader());
132		gl.glAttachShader(program.getProgram(), frgShader.getShader());
133		gl.glProgramParameteri(program.getProgram(), GL_PROGRAM_SEPARABLE, GL_FALSE);
134		gl.glLinkProgram(program.getProgram());
135		GLU_EXPECT_NO_ERROR(gl.glGetError(), "setup program");
136
137		gl.glGetProgramiv(program.getProgram(), GL_LINK_STATUS, &linkStatus);
138		GLU_EXPECT_NO_ERROR(gl.glGetError(), "query link status");
139
140		if (linkStatus == GL_FALSE)
141			throw tcu::TestError("failed to link program");
142
143		verifyStateProgramInteger(result, gl, program.getProgram(), GL_PROGRAM_SEPARABLE, 0, m_verifier);
144	}
145
146	{
147		const tcu::ScopedLogSection section		(m_testCtx.getLog(), "SetTrue", "SetTrue");
148		glu::Program				program		(m_context.getRenderContext());
149		int							linkStatus	= 0;
150
151		gl.glAttachShader(program.getProgram(), vtxShader.getShader());
152		gl.glAttachShader(program.getProgram(), frgShader.getShader());
153		gl.glProgramParameteri(program.getProgram(), GL_PROGRAM_SEPARABLE, GL_TRUE);
154		gl.glLinkProgram(program.getProgram());
155		GLU_EXPECT_NO_ERROR(gl.glGetError(), "setup program");
156
157		gl.glGetProgramiv(program.getProgram(), GL_LINK_STATUS, &linkStatus);
158		GLU_EXPECT_NO_ERROR(gl.glGetError(), "query link status");
159
160		if (linkStatus == GL_FALSE)
161			throw tcu::TestError("failed to link program");
162
163		verifyStateProgramInteger(result, gl, program.getProgram(), GL_PROGRAM_SEPARABLE, GL_TRUE, m_verifier);
164	}
165
166	result.setTestContextResult(m_testCtx);
167	return STOP;
168}
169
170class ComputeWorkGroupSizeCase : public TestCase
171{
172public:
173						ComputeWorkGroupSizeCase	(Context& context, QueryType verifier, const char* name, const char* desc);
174	IterateResult		iterate						(void);
175
176private:
177	const QueryType		m_verifier;
178};
179
180ComputeWorkGroupSizeCase::ComputeWorkGroupSizeCase (Context& context, QueryType verifier, const char* name, const char* desc)
181	: TestCase		(context, name, desc)
182	, m_verifier	(verifier)
183{
184}
185
186ComputeWorkGroupSizeCase::IterateResult ComputeWorkGroupSizeCase::iterate (void)
187{
188	static const char* const s_computeSource1D =	"#version 310 es\n"
189													"layout (local_size_x = 3) in;\n"
190													"layout(binding = 0) buffer Output\n"
191													"{\n"
192													"	highp float val;\n"
193													"} sb_out;\n"
194													"\n"
195													"void main (void)\n"
196													"{\n"
197													"	sb_out.val = 1.0;\n"
198													"}\n";
199	static const char* const s_computeSource2D =	"#version 310 es\n"
200													"layout (local_size_x = 3, local_size_y = 2) in;\n"
201													"layout(binding = 0) buffer Output\n"
202													"{\n"
203													"	highp float val;\n"
204													"} sb_out;\n"
205													"\n"
206													"void main (void)\n"
207													"{\n"
208													"	sb_out.val = 1.0;\n"
209													"}\n";
210	static const char* const s_computeSource3D =	"#version 310 es\n"
211													"layout (local_size_x = 3, local_size_y = 2, local_size_z = 4) in;\n"
212													"layout(binding = 0) buffer Output\n"
213													"{\n"
214													"	highp float val;\n"
215													"} sb_out;\n"
216													"\n"
217													"void main (void)\n"
218													"{\n"
219													"	sb_out.val = 1.0;\n"
220													"}\n";
221
222	glu::CallLogWrapper		gl		(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
223	tcu::ResultCollector	result	(m_testCtx.getLog(), " // ERROR: ");
224
225	gl.enableLogging(true);
226
227	{
228		const tcu::ScopedLogSection section		(m_testCtx.getLog(), "OneDimensional", "1D");
229		glu::ShaderProgram			program		(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(s_computeSource1D));
230
231		m_testCtx.getLog() << program;
232		if (!program.isOk())
233			throw tcu::TestError("failed to build program");
234
235		verifyStateProgramIntegerVec3(result, gl, program.getProgram(), GL_COMPUTE_WORK_GROUP_SIZE, tcu::IVec3(3, 1, 1), m_verifier);
236	}
237
238	{
239		const tcu::ScopedLogSection section		(m_testCtx.getLog(), "TwoDimensional", "2D");
240		glu::ShaderProgram			program		(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(s_computeSource2D));
241
242		m_testCtx.getLog() << program;
243		if (!program.isOk())
244			throw tcu::TestError("failed to build program");
245
246		verifyStateProgramIntegerVec3(result, gl, program.getProgram(), GL_COMPUTE_WORK_GROUP_SIZE, tcu::IVec3(3, 2, 1), m_verifier);
247	}
248
249	{
250		const tcu::ScopedLogSection section		(m_testCtx.getLog(), "TreeDimensional", "3D");
251		glu::ShaderProgram			program		(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(s_computeSource3D));
252
253		m_testCtx.getLog() << program;
254		if (!program.isOk())
255			throw tcu::TestError("failed to build program");
256
257		verifyStateProgramIntegerVec3(result, gl, program.getProgram(), GL_COMPUTE_WORK_GROUP_SIZE, tcu::IVec3(3, 2, 4), m_verifier);
258	}
259
260	result.setTestContextResult(m_testCtx);
261	return STOP;
262}
263
264class ActiveAtomicCounterBuffersCase : public TestCase
265{
266public:
267						ActiveAtomicCounterBuffersCase	(Context& context, QueryType verifier, const char* name, const char* desc);
268	IterateResult		iterate							(void);
269
270private:
271	const QueryType		m_verifier;
272};
273
274ActiveAtomicCounterBuffersCase::ActiveAtomicCounterBuffersCase (Context& context, QueryType verifier, const char* name, const char* desc)
275	: TestCase		(context, name, desc)
276	, m_verifier	(verifier)
277{
278}
279
280ActiveAtomicCounterBuffersCase::IterateResult ActiveAtomicCounterBuffersCase::iterate (void)
281{
282	static const char* const s_computeSource0 =	"#version 310 es\n"
283												"layout (local_size_x = 3) in;\n"
284												"layout(binding = 0) buffer Output\n"
285												"{\n"
286												"	highp float val;\n"
287												"} sb_out;\n"
288												"\n"
289												"void main (void)\n"
290												"{\n"
291												"	sb_out.val = 1.0;\n"
292												"}\n";
293	static const char* const s_computeSource1 =	"#version 310 es\n"
294												"layout (local_size_x = 3) in;\n"
295												"layout(binding = 0) uniform highp atomic_uint u_counters[2];\n"
296												"layout(binding = 0) buffer Output\n"
297												"{\n"
298												"	highp float val;\n"
299												"} sb_out;\n"
300												"\n"
301												"void main (void)\n"
302												"{\n"
303												"	sb_out.val = float(atomicCounterIncrement(u_counters[0])) + float(atomicCounterIncrement(u_counters[1]));\n"
304												"}\n";
305
306	glu::CallLogWrapper		gl		(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
307	tcu::ResultCollector	result	(m_testCtx.getLog(), " // ERROR: ");
308
309	gl.enableLogging(true);
310
311	{
312		const tcu::ScopedLogSection	section		(m_testCtx.getLog(), "Initial", "Initial");
313		glu::Program				program		(m_context.getRenderContext());
314
315		verifyStateProgramInteger(result, gl, program.getProgram(), GL_ACTIVE_ATOMIC_COUNTER_BUFFERS, 0, m_verifier);
316	}
317
318	{
319		const tcu::ScopedLogSection	section		(m_testCtx.getLog(), "NoBuffers", "No buffers");
320		glu::ShaderProgram			program		(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(s_computeSource0));
321
322		m_testCtx.getLog() << program;
323		if (!program.isOk())
324			throw tcu::TestError("failed to build program");
325
326		verifyStateProgramInteger(result, gl, program.getProgram(), GL_ACTIVE_ATOMIC_COUNTER_BUFFERS, 0, m_verifier);
327	}
328
329	{
330		const tcu::ScopedLogSection	section		(m_testCtx.getLog(), "OneBuffer", "One buffer");
331		glu::ShaderProgram			program		(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(s_computeSource1));
332
333		m_testCtx.getLog() << program;
334		if (!program.isOk())
335			throw tcu::TestError("failed to build program");
336
337		verifyStateProgramInteger(result, gl, program.getProgram(), GL_ACTIVE_ATOMIC_COUNTER_BUFFERS, 1, m_verifier);
338	}
339
340	result.setTestContextResult(m_testCtx);
341	return STOP;
342}
343
344class ProgramLogCase : public TestCase
345{
346public:
347	enum BuildErrorType
348	{
349		BUILDERROR_VERTEX_FRAGMENT = 0,
350		BUILDERROR_COMPUTE,
351		BUILDERROR_GEOMETRY,
352		BUILDERROR_TESSELLATION,
353	};
354
355							ProgramLogCase		(Context& ctx, const char* name, const char* desc, BuildErrorType errorType);
356
357private:
358	void					init				(void);
359	IterateResult			iterate				(void);
360	glu::ProgramSources		getProgramSources	(void) const;
361
362	const BuildErrorType	m_buildErrorType;
363};
364
365ProgramLogCase::ProgramLogCase (Context& ctx, const char* name, const char* desc, BuildErrorType errorType)
366	: TestCase			(ctx, name, desc)
367	, m_buildErrorType	(errorType)
368{
369}
370
371void ProgramLogCase::init (void)
372{
373	switch (m_buildErrorType)
374	{
375		case BUILDERROR_VERTEX_FRAGMENT:
376		case BUILDERROR_COMPUTE:
377			break;
378
379		case BUILDERROR_GEOMETRY:
380			if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"))
381				throw tcu::NotSupportedError("Test requires GL_EXT_geometry_shader extension");
382			break;
383
384		case BUILDERROR_TESSELLATION:
385			if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader"))
386				throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_shader extension");
387			break;
388
389		default:
390			DE_ASSERT(false);
391			break;
392	}
393}
394
395ProgramLogCase::IterateResult ProgramLogCase::iterate (void)
396{
397	using gls::StateQueryUtil::StateQueryMemoryWriteGuard;
398
399	tcu::ResultCollector					result		(m_testCtx.getLog());
400	glu::CallLogWrapper						gl			(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
401	glu::ShaderProgram						program		(m_context.getRenderContext(), getProgramSources());
402	StateQueryMemoryWriteGuard<glw::GLint>	logLen;
403
404	gl.enableLogging(true);
405
406	m_testCtx.getLog() << tcu::TestLog::Message << "Trying to link a broken program." << tcu::TestLog::EndMessage;
407
408	gl.glGetProgramiv(program.getProgram(), GL_INFO_LOG_LENGTH, &logLen);
409	logLen.verifyValidity(result);
410
411	if (logLen.verifyValidity(result))
412		verifyInfoLogQuery(result, gl, logLen, program.getProgram(), &glu::CallLogWrapper::glGetProgramInfoLog, "glGetProgramInfoLog");
413
414	result.setTestContextResult(m_testCtx);
415	return STOP;
416}
417
418glu::ProgramSources ProgramLogCase::getProgramSources (void) const
419{
420	switch (m_buildErrorType)
421	{
422		case BUILDERROR_VERTEX_FRAGMENT:
423			return glu::ProgramSources()
424					<< glu::VertexSource("#version 310 es\n"
425										 "in highp vec4 a_pos;\n"
426										 "uniform highp vec4 u_uniform;\n"
427										 "void main()\n"
428										 "{\n"
429										 "	gl_Position = a_pos + u_uniform;\n"
430										 "}\n")
431					<< glu::FragmentSource("#version 310 es\n"
432										   "in highp vec4 v_missingVar;\n"
433										   "uniform highp int u_uniform;\n"
434										   "layout(location = 0) out mediump vec4 fragColor;\n"
435										   "void main()\n"
436										   "{\n"
437										   "	fragColor = v_missingVar + vec4(float(u_uniform));\n"
438										   "}\n");
439
440		case BUILDERROR_COMPUTE:
441			return glu::ProgramSources()
442					<< glu::ComputeSource("#version 310 es\n"
443										 "layout (binding = 0) buffer IOBuffer { highp float buf_var; };\n"
444										 "uniform highp vec4 u_uniform;\n"
445										 "void main()\n"
446										 "{\n"
447										 "	buf_var = u_uniform.x;\n"
448										 "}\n");
449
450		case BUILDERROR_GEOMETRY:
451			return glu::ProgramSources()
452					<< glu::VertexSource("#version 310 es\n"
453										 "in highp vec4 a_pos;\n"
454										 "uniform highp vec4 u_uniform;\n"
455										 "void main()\n"
456										 "{\n"
457										 "	gl_Position = a_pos + u_uniform;\n"
458										 "}\n")
459					<< glu::GeometrySource("#version 310 es\n"
460										   "#extension GL_EXT_geometry_shader : require\n"
461										   "layout(triangles) in;\n"
462										   "layout(max_vertices=1, points) out;\n"
463										   "in highp vec4 v_missingVar[];\n"
464										   "uniform highp int u_uniform;\n"
465										   "void main()\n"
466										   "{\n"
467										   "	gl_Position = gl_in[0].gl_Position + v_missingVar[2] + vec4(float(u_uniform));\n"
468										   "	EmitVertex();\n"
469										   "}\n")
470					<< glu::FragmentSource("#version 310 es\n"
471										   "layout(location = 0) out mediump vec4 fragColor;\n"
472										   "void main()\n"
473										   "{\n"
474										   "	fragColor = vec4(1.0);\n"
475										   "}\n");
476
477		case BUILDERROR_TESSELLATION:
478			return glu::ProgramSources()
479					<< glu::VertexSource("#version 310 es\n"
480										 "in highp vec4 a_pos;\n"
481										 "void main()\n"
482										 "{\n"
483										 "	gl_Position = a_pos;\n"
484										 "}\n")
485					<< glu::TessellationControlSource("#version 310 es\n"
486													  "#extension GL_EXT_tessellation_shader : require\n"
487													  "layout(vertices=2) out;"
488													  "patch out highp vec2 vp_var;\n"
489													  "void main()\n"
490													  "{\n"
491													  "	gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position\n"
492													  "	gl_TessLevelOuter[0] = 0.8;\n"
493													  "	gl_TessLevelOuter[1] = 0.8;\n"
494													  "	if (gl_InvocationID == 0)\n"
495													  "		vp_var = gl_in[gl_InvocationID].gl_Position.xy;\n"
496													  "}\n")
497					<< glu::TessellationEvaluationSource("#version 310 es\n"
498														 "#extension GL_EXT_tessellation_shader : require\n"
499														 "layout(isolines) in;"
500														 "in highp float vp_var[];\n"
501														 "void main()\n"
502														 "{\n"
503														 "	gl_Position = gl_in[gl_InvocationID].gl_Position + vec4(vp_var[1]);\n"
504														 "}\n")
505					<< glu::FragmentSource("#version 310 es\n"
506										   "layout(location = 0) out mediump vec4 fragColor;\n"
507										   "void main()\n"
508										   "{\n"
509										   "	fragColor = vec4(1.0);\n"
510										   "}\n");
511
512		default:
513			DE_ASSERT(false);
514			return glu::ProgramSources();
515	}
516}
517
518} // anonymous
519
520ProgramStateQueryTests::ProgramStateQueryTests (Context& context)
521	: TestCaseGroup(context, "program", "Program State Query tests")
522{
523}
524
525ProgramStateQueryTests::~ProgramStateQueryTests (void)
526{
527}
528
529void ProgramStateQueryTests::init (void)
530{
531	static const QueryType intVerifiers[] =
532	{
533		QUERY_PROGRAM_INTEGER,
534	};
535	static const QueryType intVec3Verifiers[] =
536	{
537		QUERY_PROGRAM_INTEGER_VEC3,
538	};
539
540#define FOR_EACH_INT_VERIFIER(X) \
541	for (int verifierNdx = 0; verifierNdx < DE_LENGTH_OF_ARRAY(intVerifiers); ++verifierNdx)	\
542	{																							\
543		const char* verifierSuffix = getVerifierSuffix(intVerifiers[verifierNdx]);				\
544		const QueryType verifier = intVerifiers[verifierNdx];									\
545		this->addChild(X);																		\
546	}
547
548#define FOR_EACH_VEC_VERIFIER(X) \
549	for (int verifierNdx = 0; verifierNdx < DE_LENGTH_OF_ARRAY(intVec3Verifiers); ++verifierNdx)	\
550	{																								\
551		const char* verifierSuffix = getVerifierSuffix(intVec3Verifiers[verifierNdx]);				\
552		const QueryType verifier = intVec3Verifiers[verifierNdx];									\
553		this->addChild(X);																			\
554	}
555
556	FOR_EACH_INT_VERIFIER(new ProgramSeparableCase				(m_context, verifier, (std::string("program_separable_") + verifierSuffix).c_str(),				"Test PROGRAM_SEPARABLE"));
557	FOR_EACH_VEC_VERIFIER(new ComputeWorkGroupSizeCase			(m_context, verifier, (std::string("compute_work_group_size_") + verifierSuffix).c_str(),		"Test COMPUTE_WORK_GROUP_SIZE"));
558	FOR_EACH_INT_VERIFIER(new ActiveAtomicCounterBuffersCase	(m_context, verifier, (std::string("active_atomic_counter_buffers_") + verifierSuffix).c_str(),	"Test ACTIVE_ATOMIC_COUNTER_BUFFERS"));
559
560#undef FOR_EACH_INT_VERIFIER
561#undef FOR_EACH_VEC_VERIFIER
562
563	// program info log tests
564	// \note, there exists similar tests in gles3 module. However, the gles31 could use a different
565	//        shader compiler with different INFO_LOG bugs.
566	{
567		static const struct
568		{
569			const char*						caseName;
570			ProgramLogCase::BuildErrorType	caseType;
571		} shaderTypes[] =
572		{
573			{ "info_log_vertex_fragment_link_fail",		ProgramLogCase::BUILDERROR_VERTEX_FRAGMENT	},
574			{ "info_log_compute_link_fail",				ProgramLogCase::BUILDERROR_COMPUTE			},
575			{ "info_log_geometry_link_fail",			ProgramLogCase::BUILDERROR_GEOMETRY			},
576			{ "info_log_tessellation_link_fail",		ProgramLogCase::BUILDERROR_TESSELLATION		},
577		};
578
579		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(shaderTypes); ++ndx)
580			addChild(new ProgramLogCase(m_context, shaderTypes[ndx].caseName, "", shaderTypes[ndx].caseType));
581	}
582}
583
584} // Functional
585} // gles31
586} // deqp
587