1 /*------------------------------------------------------------------------
2 * OpenGL Conformance Tests
3 * ------------------------
4 *
5 * Copyright (c) 2017-2019 The Khronos Group Inc.
6 * Copyright (c) 2017 Codeplay Software Ltd.
7 * Copyright (c) 2019 NVIDIA Corporation.
8 *
9 * Licensed under the Apache License, Version 2.0 (the "License");
10 * you may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
20 *
21 */ /*!
22 * \file
23 * \brief Subgroups Tests
24 */ /*--------------------------------------------------------------------*/
25
26 #include "glcSubgroupsBallotTests.hpp"
27 #include "glcSubgroupsTestsUtils.hpp"
28
29 #include <string>
30 #include <vector>
31
32 using namespace tcu;
33 using namespace std;
34
35 namespace glc
36 {
37 namespace subgroups
38 {
39 namespace
40 {
checkVertexPipelineStages(std::vector<const void * > datas,deUint32 width,deUint32)41 static bool checkVertexPipelineStages(std::vector<const void*> datas,
42 deUint32 width, deUint32)
43 {
44 return glc::subgroups::check(datas, width, 0x7);
45 }
46
checkComputeStage(std::vector<const void * > datas,const deUint32 numWorkgroups[3],const deUint32 localSize[3],deUint32)47 static bool checkComputeStage(std::vector<const void*> datas,
48 const deUint32 numWorkgroups[3], const deUint32 localSize[3],
49 deUint32)
50 {
51 return glc::subgroups::checkCompute(datas, numWorkgroups, localSize, 0x7);
52 }
53
54 struct CaseDefinition
55 {
56 glc::subgroups::ShaderStageFlags shaderStage;
57 };
58
initFrameBufferPrograms(SourceCollections & programCollection,CaseDefinition caseDef)59 void initFrameBufferPrograms(SourceCollections& programCollection, CaseDefinition caseDef)
60 {
61 std::ostringstream subgroupSizeStr;
62 subgroupSizeStr << subgroups::maxSupportedSubgroupSize();
63
64 subgroups::setFragmentShaderFrameBuffer(programCollection);
65
66 if (SHADER_STAGE_VERTEX_BIT != caseDef.shaderStage)
67 subgroups::setVertexShaderFrameBuffer(programCollection);
68
69 if (SHADER_STAGE_VERTEX_BIT == caseDef.shaderStage)
70 {
71 const string vertexGLSL =
72 "${VERSION_DECL}\n"
73 "#extension GL_KHR_shader_subgroup_ballot: enable\n"
74 "layout(location = 0) in highp vec4 in_position;\n"
75 "layout(location = 0) out float out_color;\n"
76 "layout(binding = 0, std140) uniform Buffer1\n"
77 "{\n"
78 " uint data[" + subgroupSizeStr.str() + "];\n"
79 "};\n"
80 "\n"
81 "void main (void)\n"
82 "{\n"
83 " uint tempResult = 0u;\n"
84 " tempResult |= !bool(uvec4(0) == subgroupBallot(true)) ? 0x1u : 0u;\n"
85 " bool bData = data[gl_SubgroupInvocationID] != 0u;\n"
86 " tempResult |= !bool(uvec4(0) == subgroupBallot(bData)) ? 0x2u : 0u;\n"
87 " tempResult |= uvec4(0) == subgroupBallot(false) ? 0x4u : 0u;\n"
88 " out_color = float(tempResult);\n"
89 " gl_Position = in_position;\n"
90 " gl_PointSize = 1.0f;\n"
91 "}\n";
92 programCollection.add("vert") << glu::VertexSource(vertexGLSL);
93 }
94 else if (SHADER_STAGE_GEOMETRY_BIT == caseDef.shaderStage)
95 {
96 const string geometryGLSL =
97 "${VERSION_DECL}\n"
98 "#extension GL_KHR_shader_subgroup_ballot: enable\n"
99 "layout(points) in;\n"
100 "layout(points, max_vertices = 1) out;\n"
101 "layout(location = 0) out float out_color;\n"
102 "layout(binding = 0, std140) uniform Buffer1\n"
103 "{\n"
104 " uint data[" + subgroupSizeStr.str() + "];\n"
105 "};\n"
106 "\n"
107 "void main (void)\n"
108 "{\n"
109 " uint tempResult = 0u;\n"
110 " tempResult |= !bool(uvec4(0) == subgroupBallot(true)) ? 0x1u : 0u;\n"
111 " bool bData = data[gl_SubgroupInvocationID] != 0u;\n"
112 " tempResult |= !bool(uvec4(0) == subgroupBallot(bData)) ? 0x2u : 0u;\n"
113 " tempResult |= uvec4(0) == subgroupBallot(false) ? 0x4u : 0u;\n"
114 " out_color = float(tempResult);\n"
115 " gl_Position = gl_in[0].gl_Position;\n"
116 " EmitVertex();\n"
117 " EndPrimitive();\n"
118 "}\n";
119 programCollection.add("geometry") << glu::GeometrySource(geometryGLSL);
120 }
121 else if (SHADER_STAGE_TESS_CONTROL_BIT == caseDef.shaderStage)
122 {
123 const string controlSourceGLSL =
124 "${VERSION_DECL}\n"
125 "#extension GL_KHR_shader_subgroup_ballot: enable\n"
126 "layout(vertices = 2) out;\n"
127 "layout(location = 0) out float out_color[];\n"
128 "layout(binding = 0, std140) uniform Buffer1\n"
129 "{\n"
130 " uint data[" + subgroupSizeStr.str() + "];\n"
131 "};\n"
132 "\n"
133 "void main (void)\n"
134 "{\n"
135 " if (gl_InvocationID == 0)\n"
136 " {\n"
137 " gl_TessLevelOuter[0] = 1.0f;\n"
138 " gl_TessLevelOuter[1] = 1.0f;\n"
139 " }\n"
140 " uint tempResult = 0u;\n"
141 " tempResult |= !bool(uvec4(0) == subgroupBallot(true)) ? 0x1u : 0u;\n"
142 " bool bData = data[gl_SubgroupInvocationID] != 0u;\n"
143 " tempResult |= !bool(uvec4(0) == subgroupBallot(bData)) ? 0x2u : 0u;\n"
144 " tempResult |= uvec4(0) == subgroupBallot(false) ? 0x4u : 0u;\n"
145 " out_color[gl_InvocationID] = float(tempResult);\n"
146 " gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
147 "}\n";
148 programCollection.add("tesc") << glu::TessellationControlSource(controlSourceGLSL);
149 subgroups::setTesEvalShaderFrameBuffer(programCollection);
150
151 }
152 else if (SHADER_STAGE_TESS_EVALUATION_BIT == caseDef.shaderStage)
153 {
154 const string evaluationSourceGLSL =
155 "${VERSION_DECL}\n"
156 "#extension GL_KHR_shader_subgroup_ballot: enable\n"
157 "layout(isolines, equal_spacing, ccw ) in;\n"
158 "layout(location = 0) out float out_color;\n"
159 "layout(binding = 0, std140) uniform Buffer1\n"
160 "{\n"
161 " uint data[" + subgroupSizeStr.str() + "];\n"
162 "};\n"
163 "\n"
164 "void main (void)\n"
165 "{\n"
166 " uint tempResult = 0u;\n"
167 " tempResult |= !bool(uvec4(0) == subgroupBallot(true)) ? 0x1u : 0u;\n"
168 " bool bData = data[gl_SubgroupInvocationID] != 0u;\n"
169 " tempResult |= !bool(uvec4(0) == subgroupBallot(bData)) ? 0x2u : 0u;\n"
170 " tempResult |= uvec4(0) == subgroupBallot(false) ? 0x4u : 0u;\n"
171 " out_color = float(tempResult);\n"
172 " gl_Position = mix(gl_in[0].gl_Position, gl_in[1].gl_Position, gl_TessCoord.x);\n"
173 "}\n";
174 programCollection.add("tese") << glu::TessellationEvaluationSource(evaluationSourceGLSL);
175
176 subgroups::setTesCtrlShaderFrameBuffer(programCollection);
177 }
178 else
179 {
180 DE_FATAL("Unsupported shader stage");
181 }
182 }
183
184
initPrograms(SourceCollections & programCollection,CaseDefinition caseDef)185 void initPrograms(SourceCollections& programCollection, CaseDefinition caseDef)
186 {
187 if (SHADER_STAGE_COMPUTE_BIT == caseDef.shaderStage)
188 {
189 std::ostringstream src;
190
191 src << "${VERSION_DECL}\n"
192 << "#extension GL_KHR_shader_subgroup_ballot: enable\n"
193 << "layout (${LOCAL_SIZE_X}, ${LOCAL_SIZE_Y}, ${LOCAL_SIZE_Z}) in;\n"
194 << "layout(binding = 0, std430) buffer Buffer1\n"
195 << "{\n"
196 << " uint result[];\n"
197 << "};\n"
198 << "layout(binding = 1, std430) buffer Buffer2\n"
199 << "{\n"
200 << " uint data[];\n"
201 << "};\n"
202 << "\n"
203 << subgroups::getSharedMemoryBallotHelper()
204 << "void main (void)\n"
205 << "{\n"
206 << " uvec3 globalSize = gl_NumWorkGroups * gl_WorkGroupSize;\n"
207 << " highp uint offset = globalSize.x * ((globalSize.y * "
208 "gl_GlobalInvocationID.z) + gl_GlobalInvocationID.y) + "
209 "gl_GlobalInvocationID.x;\n"
210 << " uint tempResult = 0u;\n"
211 << " tempResult |= sharedMemoryBallot(true) == subgroupBallot(true) ? 0x1u : 0u;\n"
212 << " bool bData = data[gl_SubgroupInvocationID] != 0u;\n"
213 << " tempResult |= sharedMemoryBallot(bData) == subgroupBallot(bData) ? 0x2u : 0u;\n"
214 << " tempResult |= uvec4(0) == subgroupBallot(false) ? 0x4u : 0u;\n"
215 << " result[offset] = tempResult;\n"
216 << "}\n";
217
218 programCollection.add("comp") << glu::ComputeSource(src.str());
219 }
220 else
221 {
222 const string vertex =
223 "${VERSION_DECL}\n"
224 "#extension GL_KHR_shader_subgroup_ballot: enable\n"
225 "layout(binding = 0, std430) buffer Buffer0\n"
226 "{\n"
227 " uint result[];\n"
228 "} b0;\n"
229 "layout(binding = 4, std430) readonly buffer Buffer4\n"
230 "{\n"
231 " uint data[];\n"
232 "};\n"
233 "\n"
234 "void main (void)\n"
235 "{\n"
236 " uint tempResult = 0u;\n"
237 " tempResult |= !bool(uvec4(0) == subgroupBallot(true)) ? 0x1u : 0u;\n"
238 " bool bData = data[gl_SubgroupInvocationID] != 0u;\n"
239 " tempResult |= !bool(uvec4(0) == subgroupBallot(bData)) ? 0x2u : 0u;\n"
240 " tempResult |= uvec4(0) == subgroupBallot(false) ? 0x4u : 0u;\n"
241 " b0.result[gl_VertexID] = tempResult;\n"
242 " float pixelSize = 2.0f/1024.0f;\n"
243 " float pixelPosition = pixelSize/2.0f - 1.0f;\n"
244 " gl_Position = vec4(float(gl_VertexID) * pixelSize + pixelPosition, 0.0f, 0.0f, 1.0f);\n"
245 " gl_PointSize = 1.0f;\n"
246 "}\n";
247
248 const string tesc =
249 "${VERSION_DECL}\n"
250 "#extension GL_KHR_shader_subgroup_ballot: enable\n"
251 "layout(vertices=1) out;\n"
252 "layout(binding = 1, std430) buffer Buffer1\n"
253 "{\n"
254 " uint result[];\n"
255 "} b1;\n"
256 "layout(binding = 4, std430) readonly buffer Buffer4\n"
257 "{\n"
258 " uint data[];\n"
259 "};\n"
260 "\n"
261 "void main (void)\n"
262 "{\n"
263 " uint tempResult = 0u;\n"
264 " tempResult |= !bool(uvec4(0) == subgroupBallot(true)) ? 0x1u : 0u;\n"
265 " bool bData = data[gl_SubgroupInvocationID] != 0u;\n"
266 " tempResult |= !bool(uvec4(0) == subgroupBallot(bData)) ? 0x2u : 0u;\n"
267 " tempResult |= uvec4(0) == subgroupBallot(false) ? 0x4u : 0u;\n"
268 " b1.result[gl_PrimitiveID] = tempResult;\n"
269 " if (gl_InvocationID == 0)\n"
270 " {\n"
271 " gl_TessLevelOuter[0] = 1.0f;\n"
272 " gl_TessLevelOuter[1] = 1.0f;\n"
273 " }\n"
274 " gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
275 "}\n";
276
277 const string tese =
278 "${VERSION_DECL}\n"
279 "#extension GL_KHR_shader_subgroup_ballot: enable\n"
280 "layout(isolines) in;\n"
281 "layout(binding = 2, std430) buffer Buffer2\n"
282 "{\n"
283 " uint result[];\n"
284 "} b2;\n"
285 "layout(binding = 4, std430) readonly buffer Buffer4\n"
286 "{\n"
287 " uint data[];\n"
288 "};\n"
289 "\n"
290 "void main (void)\n"
291 "{\n"
292 " uint tempResult = 0u;\n"
293 " tempResult |= !bool(uvec4(0) == subgroupBallot(true)) ? 0x1u : 0u;\n"
294 " bool bData = data[gl_SubgroupInvocationID] != 0u;\n"
295 " tempResult |= !bool(uvec4(0) == subgroupBallot(bData)) ? 0x2u : 0u;\n"
296 " tempResult |= uvec4(0) == subgroupBallot(false) ? 0x4u : 0u;\n"
297 " b2.result[gl_PrimitiveID * 2 + int(gl_TessCoord.x + 0.5)] = tempResult;\n"
298 " float pixelSize = 2.0f/1024.0f;\n"
299 " gl_Position = gl_in[0].gl_Position + gl_TessCoord.x * pixelSize / 2.0f;\n"
300 "}\n";
301
302 const string geometry =
303 // version string added by addGeometryShadersFromTemplate
304 "#extension GL_KHR_shader_subgroup_ballot: enable\n"
305 "layout(${TOPOLOGY}) in;\n"
306 "layout(points, max_vertices = 1) out;\n"
307 "layout(binding = 3, std430) buffer Buffer3\n"
308 "{\n"
309 " uint result[];\n"
310 "} b3;\n"
311 "layout(binding = 4, std430) readonly buffer Buffer4\n"
312 "{\n"
313 " uint data[];\n"
314 "};\n"
315 "\n"
316 "void main (void)\n"
317 "{\n"
318 " uint tempResult = 0u;\n"
319 " tempResult |= !bool(uvec4(0) == subgroupBallot(true)) ? 0x1u : 0u;\n"
320 " bool bData = data[gl_SubgroupInvocationID] != 0u;\n"
321 " tempResult |= !bool(uvec4(0) == subgroupBallot(bData)) ? 0x2u : 0u;\n"
322 " tempResult |= uvec4(0) == subgroupBallot(false) ? 0x4u : 0u;\n"
323 " b3.result[gl_PrimitiveIDIn] = tempResult;\n"
324 " gl_Position = gl_in[0].gl_Position;\n"
325 " EmitVertex();\n"
326 " EndPrimitive();\n"
327 "}\n";
328
329 const string fragment =
330 "${VERSION_DECL}\n"
331 "#extension GL_KHR_shader_subgroup_ballot: enable\n"
332 "precision highp int;\n"
333 "layout(location = 0) out uint result;\n"
334 "layout(binding = 4, std430) readonly buffer Buffer4\n"
335 "{\n"
336 " uint data[];\n"
337 "};\n"
338 "void main (void)\n"
339 "{\n"
340 " uint tempResult = 0u;\n"
341 " tempResult |= !bool(uvec4(0) == subgroupBallot(true)) ? 0x1u : 0u;\n"
342 " bool bData = data[gl_SubgroupInvocationID] != 0u;\n"
343 " tempResult |= !bool(uvec4(0) == subgroupBallot(bData)) ? 0x2u : 0u;\n"
344 " tempResult |= uvec4(0) == subgroupBallot(false) ? 0x4u : 0u;\n"
345 " result = tempResult;\n"
346 "}\n";
347
348 subgroups::addNoSubgroupShader(programCollection);
349
350 programCollection.add("vert") << glu::VertexSource(vertex);
351 programCollection.add("tesc") << glu::TessellationControlSource(tesc);
352 programCollection.add("tese") << glu::TessellationEvaluationSource(tese);
353 subgroups::addGeometryShadersFromTemplate(geometry, programCollection);
354 programCollection.add("fragment") << glu::FragmentSource(fragment);
355 }
356 }
357
supportedCheck(Context & context,CaseDefinition caseDef)358 void supportedCheck (Context& context, CaseDefinition caseDef)
359 {
360 DE_UNREF(caseDef);
361 if (!subgroups::isSubgroupSupported(context))
362 TCU_THROW(NotSupportedError, "Subgroup operations are not supported");
363
364 if (!subgroups::isSubgroupFeatureSupportedForDevice(context, SUBGROUP_FEATURE_BALLOT_BIT))
365 {
366 TCU_THROW(NotSupportedError, "Device does not support subgroup ballot operations");
367 }
368 }
369
noSSBOtest(Context & context,const CaseDefinition caseDef)370 tcu::TestStatus noSSBOtest (Context& context, const CaseDefinition caseDef)
371 {
372 if (!subgroups::areSubgroupOperationsSupportedForStage(
373 context, caseDef.shaderStage))
374 {
375 if (subgroups::areSubgroupOperationsRequiredForStage(caseDef.shaderStage))
376 {
377 return tcu::TestStatus::fail(
378 "Shader stage " +
379 subgroups::getShaderStageName(caseDef.shaderStage) +
380 " is required to support subgroup operations!");
381 }
382 else
383 {
384 TCU_THROW(NotSupportedError, "Device does not support subgroup operations for this stage");
385 }
386 }
387
388 subgroups::SSBOData inputData[1];
389 inputData[0].format = FORMAT_R32_UINT;
390 inputData[0].layout = subgroups::SSBOData::LayoutStd140;
391 inputData[0].numElements = subgroups::maxSupportedSubgroupSize();
392 inputData[0].initializeType = subgroups::SSBOData::InitializeNonZero;
393 inputData[0].binding = 0u;
394
395 if (SHADER_STAGE_VERTEX_BIT == caseDef.shaderStage)
396 return subgroups::makeVertexFrameBufferTest(context, FORMAT_R32_UINT, inputData, 1, checkVertexPipelineStages);
397 else if (SHADER_STAGE_GEOMETRY_BIT == caseDef.shaderStage)
398 return subgroups::makeGeometryFrameBufferTest(context, FORMAT_R32_UINT, inputData, 1, checkVertexPipelineStages);
399 else if (SHADER_STAGE_TESS_CONTROL_BIT == caseDef.shaderStage)
400 return subgroups::makeTessellationEvaluationFrameBufferTest(context, FORMAT_R32_UINT, inputData, 1, checkVertexPipelineStages, SHADER_STAGE_TESS_CONTROL_BIT);
401 else if (SHADER_STAGE_TESS_EVALUATION_BIT == caseDef.shaderStage)
402 return subgroups::makeTessellationEvaluationFrameBufferTest(context, FORMAT_R32_UINT, inputData, 1, checkVertexPipelineStages, SHADER_STAGE_TESS_EVALUATION_BIT);
403 else
404 TCU_THROW(InternalError, "Unhandled shader stage");
405 }
406
test(Context & context,const CaseDefinition caseDef)407 tcu::TestStatus test(Context& context, const CaseDefinition caseDef)
408 {
409 if (SHADER_STAGE_COMPUTE_BIT == caseDef.shaderStage)
410 {
411 if (!subgroups::areSubgroupOperationsSupportedForStage(context, caseDef.shaderStage))
412 {
413 return tcu::TestStatus::fail(
414 "Shader stage " +
415 subgroups::getShaderStageName(caseDef.shaderStage) +
416 " is required to support subgroup operations!");
417 }
418 subgroups::SSBOData inputData[1];
419 inputData[0].format = FORMAT_R32_UINT;
420 inputData[0].layout = subgroups::SSBOData::LayoutStd430;
421 inputData[0].numElements = subgroups::maxSupportedSubgroupSize();
422 inputData[0].initializeType = subgroups::SSBOData::InitializeNonZero;
423 inputData[0].binding = 1u;
424
425 return subgroups::makeComputeTest(context, FORMAT_R32_UINT, inputData, 1, checkComputeStage);
426 }
427 else
428 {
429 int supportedStages = context.getDeqpContext().getContextInfo().getInt(GL_SUBGROUP_SUPPORTED_STAGES_KHR);
430
431 ShaderStageFlags stages = (ShaderStageFlags)(caseDef.shaderStage & supportedStages);
432
433 if ( SHADER_STAGE_FRAGMENT_BIT != stages && !subgroups::isVertexSSBOSupportedForDevice(context))
434 {
435 if ( (stages & SHADER_STAGE_FRAGMENT_BIT) == 0)
436 TCU_THROW(NotSupportedError, "Device does not support vertex stage SSBO writes");
437 else
438 stages = SHADER_STAGE_FRAGMENT_BIT;
439 }
440
441 if ((ShaderStageFlags)0u == stages)
442 TCU_THROW(NotSupportedError, "Subgroup operations are not supported for any graphic shader");
443
444 subgroups::SSBOData inputData;
445 inputData.format = FORMAT_R32_UINT;
446 inputData.layout = subgroups::SSBOData::LayoutStd430;
447 inputData.numElements = subgroups::maxSupportedSubgroupSize();
448 inputData.initializeType = subgroups::SSBOData::InitializeNonZero;
449 inputData.binding = 4u;
450 inputData.stages = stages;
451
452 return subgroups::allStages(context, FORMAT_R32_UINT, &inputData, 1, checkVertexPipelineStages, stages);
453 }
454 }
455 }
456
createSubgroupsBallotTests(deqp::Context & testCtx)457 deqp::TestCaseGroup* createSubgroupsBallotTests(deqp::Context& testCtx)
458 {
459 de::MovePtr<deqp::TestCaseGroup> graphicGroup(new deqp::TestCaseGroup(
460 testCtx, "graphics", "Subgroup ballot category tests: graphics"));
461 de::MovePtr<deqp::TestCaseGroup> computeGroup(new deqp::TestCaseGroup(
462 testCtx, "compute", "Subgroup ballot category tests: compute"));
463 de::MovePtr<deqp::TestCaseGroup> framebufferGroup(new deqp::TestCaseGroup(
464 testCtx, "framebuffer", "Subgroup ballot category tests: framebuffer"));
465
466 const ShaderStageFlags stages[] =
467 {
468 SHADER_STAGE_TESS_EVALUATION_BIT,
469 SHADER_STAGE_TESS_CONTROL_BIT,
470 SHADER_STAGE_GEOMETRY_BIT,
471 SHADER_STAGE_VERTEX_BIT
472 };
473
474 {
475 const CaseDefinition caseDef = {SHADER_STAGE_COMPUTE_BIT};
476 SubgroupFactory<CaseDefinition>::addFunctionCaseWithPrograms(computeGroup.get(),
477 getShaderStageName(caseDef.shaderStage), "",
478 supportedCheck, initPrograms, test, caseDef);
479 }
480
481 {
482 const CaseDefinition caseDef = {SHADER_STAGE_ALL_GRAPHICS};
483 SubgroupFactory<CaseDefinition>::addFunctionCaseWithPrograms(graphicGroup.get(), "graphic", "", supportedCheck, initPrograms, test, caseDef);
484 }
485
486 for (int stageIndex = 0; stageIndex < DE_LENGTH_OF_ARRAY(stages); ++stageIndex)
487 {
488 const CaseDefinition caseDef = {stages[stageIndex]};
489 SubgroupFactory<CaseDefinition>::addFunctionCaseWithPrograms(framebufferGroup.get(), getShaderStageName(caseDef.shaderStage), "",
490 supportedCheck, initFrameBufferPrograms, noSSBOtest, caseDef);
491 }
492
493 de::MovePtr<deqp::TestCaseGroup> group(new deqp::TestCaseGroup(
494 testCtx, "ballot", "Subgroup ballot category tests"));
495
496 group->addChild(graphicGroup.release());
497 group->addChild(computeGroup.release());
498 group->addChild(framebufferGroup.release());
499
500 return group.release();
501 }
502
503 } // subgroups
504 } // glc
505