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,uint32_t width,uint32_t)41 static bool checkVertexPipelineStages(std::vector<const void *> datas, uint32_t width, uint32_t)
42 {
43 return glc::subgroups::check(datas, width, 0x7);
44 }
45
checkComputeStage(std::vector<const void * > datas,const uint32_t numWorkgroups[3],const uint32_t localSize[3],uint32_t)46 static bool checkComputeStage(std::vector<const void *> datas, const uint32_t numWorkgroups[3],
47 const uint32_t localSize[3], uint32_t)
48 {
49 return glc::subgroups::checkCompute(datas, numWorkgroups, localSize, 0x7);
50 }
51
52 struct CaseDefinition
53 {
54 glc::subgroups::ShaderStageFlags shaderStage;
55 };
56
initFrameBufferPrograms(SourceCollections & programCollection,CaseDefinition caseDef)57 void initFrameBufferPrograms(SourceCollections &programCollection, CaseDefinition caseDef)
58 {
59 std::ostringstream subgroupSizeStr;
60 subgroupSizeStr << subgroups::maxSupportedSubgroupSize();
61
62 subgroups::setFragmentShaderFrameBuffer(programCollection);
63
64 if (SHADER_STAGE_VERTEX_BIT != caseDef.shaderStage)
65 subgroups::setVertexShaderFrameBuffer(programCollection);
66
67 if (SHADER_STAGE_VERTEX_BIT == caseDef.shaderStage)
68 {
69 const string vertexGLSL = "${VERSION_DECL}\n"
70 "#extension GL_KHR_shader_subgroup_ballot: enable\n"
71 "layout(location = 0) in highp vec4 in_position;\n"
72 "layout(location = 0) out float out_color;\n"
73 "layout(binding = 0, std140) uniform Buffer1\n"
74 "{\n"
75 " uint data[" +
76 subgroupSizeStr.str() +
77 "];\n"
78 "};\n"
79 "\n"
80 "void main (void)\n"
81 "{\n"
82 " uint tempResult = 0u;\n"
83 " tempResult |= !bool(uvec4(0) == subgroupBallot(true)) ? 0x1u : 0u;\n"
84 " bool bData = data[gl_SubgroupInvocationID] != 0u;\n"
85 " tempResult |= !bool(uvec4(0) == subgroupBallot(bData)) ? 0x2u : 0u;\n"
86 " tempResult |= uvec4(0) == subgroupBallot(false) ? 0x4u : 0u;\n"
87 " out_color = float(tempResult);\n"
88 " gl_Position = in_position;\n"
89 " gl_PointSize = 1.0f;\n"
90 "}\n";
91 programCollection.add("vert") << glu::VertexSource(vertexGLSL);
92 }
93 else if (SHADER_STAGE_GEOMETRY_BIT == caseDef.shaderStage)
94 {
95 const string geometryGLSL = "${VERSION_DECL}\n"
96 "#extension GL_KHR_shader_subgroup_ballot: enable\n"
97 "layout(points) in;\n"
98 "layout(points, max_vertices = 1) out;\n"
99 "layout(location = 0) out float out_color;\n"
100 "layout(binding = 0, std140) uniform Buffer1\n"
101 "{\n"
102 " uint data[" +
103 subgroupSizeStr.str() +
104 "];\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 = "${VERSION_DECL}\n"
124 "#extension GL_KHR_shader_subgroup_ballot: enable\n"
125 "layout(vertices = 2) out;\n"
126 "layout(location = 0) out float out_color[];\n"
127 "layout(binding = 0, std140) uniform Buffer1\n"
128 "{\n"
129 " uint data[" +
130 subgroupSizeStr.str() +
131 "];\n"
132 "};\n"
133 "\n"
134 "void main (void)\n"
135 "{\n"
136 " if (gl_InvocationID == 0)\n"
137 " {\n"
138 " gl_TessLevelOuter[0] = 1.0f;\n"
139 " gl_TessLevelOuter[1] = 1.0f;\n"
140 " }\n"
141 " uint tempResult = 0u;\n"
142 " tempResult |= !bool(uvec4(0) == subgroupBallot(true)) ? 0x1u : 0u;\n"
143 " bool bData = data[gl_SubgroupInvocationID] != 0u;\n"
144 " tempResult |= !bool(uvec4(0) == subgroupBallot(bData)) ? 0x2u : 0u;\n"
145 " tempResult |= uvec4(0) == subgroupBallot(false) ? 0x4u : 0u;\n"
146 " out_color[gl_InvocationID] = float(tempResult);\n"
147 " gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
148 "}\n";
149 programCollection.add("tesc") << glu::TessellationControlSource(controlSourceGLSL);
150 subgroups::setTesEvalShaderFrameBuffer(programCollection);
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[" +
162 subgroupSizeStr.str() +
163 "];\n"
164 "};\n"
165 "\n"
166 "void main (void)\n"
167 "{\n"
168 " uint tempResult = 0u;\n"
169 " tempResult |= !bool(uvec4(0) == subgroupBallot(true)) ? 0x1u : 0u;\n"
170 " bool bData = data[gl_SubgroupInvocationID] != 0u;\n"
171 " tempResult |= !bool(uvec4(0) == subgroupBallot(bData)) ? 0x2u : 0u;\n"
172 " tempResult |= uvec4(0) == subgroupBallot(false) ? 0x4u : 0u;\n"
173 " out_color = float(tempResult);\n"
174 " gl_Position = mix(gl_in[0].gl_Position, gl_in[1].gl_Position, gl_TessCoord.x);\n"
175 "}\n";
176 programCollection.add("tese") << glu::TessellationEvaluationSource(evaluationSourceGLSL);
177
178 subgroups::setTesCtrlShaderFrameBuffer(programCollection);
179 }
180 else
181 {
182 DE_FATAL("Unsupported shader stage");
183 }
184 }
185
initPrograms(SourceCollections & programCollection,CaseDefinition caseDef)186 void initPrograms(SourceCollections &programCollection, CaseDefinition caseDef)
187 {
188 if (SHADER_STAGE_COMPUTE_BIT == caseDef.shaderStage)
189 {
190 std::ostringstream src;
191
192 src << "${VERSION_DECL}\n"
193 << "#extension GL_KHR_shader_subgroup_ballot: enable\n"
194 << "layout (${LOCAL_SIZE_X}, ${LOCAL_SIZE_Y}, ${LOCAL_SIZE_Z}) in;\n"
195 << "layout(binding = 0, std430) buffer Buffer1\n"
196 << "{\n"
197 << " uint result[];\n"
198 << "};\n"
199 << "layout(binding = 1, std430) buffer Buffer2\n"
200 << "{\n"
201 << " uint data[];\n"
202 << "};\n"
203 << "\n"
204 << subgroups::getSharedMemoryBallotHelper() << "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 = "${VERSION_DECL}\n"
249 "#extension GL_KHR_shader_subgroup_ballot: enable\n"
250 "layout(vertices=1) out;\n"
251 "layout(binding = 1, std430) buffer Buffer1\n"
252 "{\n"
253 " uint result[];\n"
254 "} b1;\n"
255 "layout(binding = 4, std430) readonly buffer Buffer4\n"
256 "{\n"
257 " uint data[];\n"
258 "};\n"
259 "\n"
260 "void main (void)\n"
261 "{\n"
262 " uint tempResult = 0u;\n"
263 " tempResult |= !bool(uvec4(0) == subgroupBallot(true)) ? 0x1u : 0u;\n"
264 " bool bData = data[gl_SubgroupInvocationID] != 0u;\n"
265 " tempResult |= !bool(uvec4(0) == subgroupBallot(bData)) ? 0x2u : 0u;\n"
266 " tempResult |= uvec4(0) == subgroupBallot(false) ? 0x4u : 0u;\n"
267 " b1.result[gl_PrimitiveID] = tempResult;\n"
268 " if (gl_InvocationID == 0)\n"
269 " {\n"
270 " gl_TessLevelOuter[0] = 1.0f;\n"
271 " gl_TessLevelOuter[1] = 1.0f;\n"
272 " }\n"
273 " gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
274 "}\n";
275
276 const string tese = "${VERSION_DECL}\n"
277 "#extension GL_KHR_shader_subgroup_ballot: enable\n"
278 "layout(isolines) in;\n"
279 "layout(binding = 2, std430) buffer Buffer2\n"
280 "{\n"
281 " uint result[];\n"
282 "} b2;\n"
283 "layout(binding = 4, std430) readonly buffer Buffer4\n"
284 "{\n"
285 " uint data[];\n"
286 "};\n"
287 "\n"
288 "void main (void)\n"
289 "{\n"
290 " uint tempResult = 0u;\n"
291 " tempResult |= !bool(uvec4(0) == subgroupBallot(true)) ? 0x1u : 0u;\n"
292 " bool bData = data[gl_SubgroupInvocationID] != 0u;\n"
293 " tempResult |= !bool(uvec4(0) == subgroupBallot(bData)) ? 0x2u : 0u;\n"
294 " tempResult |= uvec4(0) == subgroupBallot(false) ? 0x4u : 0u;\n"
295 " b2.result[gl_PrimitiveID * 2 + int(gl_TessCoord.x + 0.5)] = tempResult;\n"
296 " float pixelSize = 2.0f/1024.0f;\n"
297 " gl_Position = gl_in[0].gl_Position + gl_TessCoord.x * pixelSize / 2.0f;\n"
298 "}\n";
299
300 const string geometry =
301 // version string added by addGeometryShadersFromTemplate
302 "#extension GL_KHR_shader_subgroup_ballot: enable\n"
303 "layout(${TOPOLOGY}) in;\n"
304 "layout(points, max_vertices = 1) out;\n"
305 "layout(binding = 3, std430) buffer Buffer3\n"
306 "{\n"
307 " uint result[];\n"
308 "} b3;\n"
309 "layout(binding = 4, std430) readonly buffer Buffer4\n"
310 "{\n"
311 " uint data[];\n"
312 "};\n"
313 "\n"
314 "void main (void)\n"
315 "{\n"
316 " uint tempResult = 0u;\n"
317 " tempResult |= !bool(uvec4(0) == subgroupBallot(true)) ? 0x1u : 0u;\n"
318 " bool bData = data[gl_SubgroupInvocationID] != 0u;\n"
319 " tempResult |= !bool(uvec4(0) == subgroupBallot(bData)) ? 0x2u : 0u;\n"
320 " tempResult |= uvec4(0) == subgroupBallot(false) ? 0x4u : 0u;\n"
321 " b3.result[gl_PrimitiveIDIn] = tempResult;\n"
322 " gl_Position = gl_in[0].gl_Position;\n"
323 " EmitVertex();\n"
324 " EndPrimitive();\n"
325 "}\n";
326
327 const string fragment = "${VERSION_DECL}\n"
328 "#extension GL_KHR_shader_subgroup_ballot: enable\n"
329 "precision highp int;\n"
330 "layout(location = 0) out uint result;\n"
331 "layout(binding = 4, std430) readonly buffer Buffer4\n"
332 "{\n"
333 " uint data[];\n"
334 "};\n"
335 "void main (void)\n"
336 "{\n"
337 " uint tempResult = 0u;\n"
338 " tempResult |= !bool(uvec4(0) == subgroupBallot(true)) ? 0x1u : 0u;\n"
339 " bool bData = data[gl_SubgroupInvocationID] != 0u;\n"
340 " tempResult |= !bool(uvec4(0) == subgroupBallot(bData)) ? 0x2u : 0u;\n"
341 " tempResult |= uvec4(0) == subgroupBallot(false) ? 0x4u : 0u;\n"
342 " result = tempResult;\n"
343 "}\n";
344
345 subgroups::addNoSubgroupShader(programCollection);
346
347 programCollection.add("vert") << glu::VertexSource(vertex);
348 programCollection.add("tesc") << glu::TessellationControlSource(tesc);
349 programCollection.add("tese") << glu::TessellationEvaluationSource(tese);
350 subgroups::addGeometryShadersFromTemplate(geometry, programCollection);
351 programCollection.add("fragment") << glu::FragmentSource(fragment);
352 }
353 }
354
supportedCheck(Context & context,CaseDefinition caseDef)355 void supportedCheck(Context &context, CaseDefinition caseDef)
356 {
357 DE_UNREF(caseDef);
358 if (!subgroups::isSubgroupSupported(context))
359 TCU_THROW(NotSupportedError, "Subgroup operations are not supported");
360
361 if (!subgroups::isSubgroupFeatureSupportedForDevice(context, SUBGROUP_FEATURE_BALLOT_BIT))
362 {
363 TCU_THROW(NotSupportedError, "Device does not support subgroup ballot operations");
364 }
365 }
366
noSSBOtest(Context & context,const CaseDefinition caseDef)367 tcu::TestStatus noSSBOtest(Context &context, const CaseDefinition caseDef)
368 {
369 if (!subgroups::areSubgroupOperationsSupportedForStage(context, caseDef.shaderStage))
370 {
371 if (subgroups::areSubgroupOperationsRequiredForStage(caseDef.shaderStage))
372 {
373 return tcu::TestStatus::fail("Shader stage " + subgroups::getShaderStageName(caseDef.shaderStage) +
374 " is required to support subgroup operations!");
375 }
376 else
377 {
378 TCU_THROW(NotSupportedError, "Device does not support subgroup operations for this stage");
379 }
380 }
381
382 subgroups::SSBOData inputData[1];
383 inputData[0].format = FORMAT_R32_UINT;
384 inputData[0].layout = subgroups::SSBOData::LayoutStd140;
385 inputData[0].numElements = subgroups::maxSupportedSubgroupSize();
386 inputData[0].initializeType = subgroups::SSBOData::InitializeNonZero;
387 inputData[0].binding = 0u;
388
389 if (SHADER_STAGE_VERTEX_BIT == caseDef.shaderStage)
390 return subgroups::makeVertexFrameBufferTest(context, FORMAT_R32_UINT, inputData, 1, checkVertexPipelineStages);
391 else if (SHADER_STAGE_GEOMETRY_BIT == caseDef.shaderStage)
392 return subgroups::makeGeometryFrameBufferTest(context, FORMAT_R32_UINT, inputData, 1,
393 checkVertexPipelineStages);
394 else if (SHADER_STAGE_TESS_CONTROL_BIT == caseDef.shaderStage)
395 return subgroups::makeTessellationEvaluationFrameBufferTest(
396 context, FORMAT_R32_UINT, inputData, 1, checkVertexPipelineStages, SHADER_STAGE_TESS_CONTROL_BIT);
397 else if (SHADER_STAGE_TESS_EVALUATION_BIT == caseDef.shaderStage)
398 return subgroups::makeTessellationEvaluationFrameBufferTest(
399 context, FORMAT_R32_UINT, inputData, 1, checkVertexPipelineStages, SHADER_STAGE_TESS_EVALUATION_BIT);
400 else
401 TCU_THROW(InternalError, "Unhandled shader stage");
402 }
403
test(Context & context,const CaseDefinition caseDef)404 tcu::TestStatus test(Context &context, const CaseDefinition caseDef)
405 {
406 if (SHADER_STAGE_COMPUTE_BIT == caseDef.shaderStage)
407 {
408 if (!subgroups::areSubgroupOperationsSupportedForStage(context, caseDef.shaderStage))
409 {
410 return tcu::TestStatus::fail("Shader stage " + subgroups::getShaderStageName(caseDef.shaderStage) +
411 " is required to support subgroup operations!");
412 }
413 subgroups::SSBOData inputData[1];
414 inputData[0].format = FORMAT_R32_UINT;
415 inputData[0].layout = subgroups::SSBOData::LayoutStd430;
416 inputData[0].numElements = subgroups::maxSupportedSubgroupSize();
417 inputData[0].initializeType = subgroups::SSBOData::InitializeNonZero;
418 inputData[0].binding = 1u;
419
420 return subgroups::makeComputeTest(context, FORMAT_R32_UINT, inputData, 1, checkComputeStage);
421 }
422 else
423 {
424 int supportedStages = context.getDeqpContext().getContextInfo().getInt(GL_SUBGROUP_SUPPORTED_STAGES_KHR);
425
426 ShaderStageFlags stages = (ShaderStageFlags)(caseDef.shaderStage & supportedStages);
427
428 if (SHADER_STAGE_FRAGMENT_BIT != stages && !subgroups::isVertexSSBOSupportedForDevice(context))
429 {
430 if ((stages & SHADER_STAGE_FRAGMENT_BIT) == 0)
431 TCU_THROW(NotSupportedError, "Device does not support vertex stage SSBO writes");
432 else
433 stages = SHADER_STAGE_FRAGMENT_BIT;
434 }
435
436 if ((ShaderStageFlags)0u == stages)
437 TCU_THROW(NotSupportedError, "Subgroup operations are not supported for any graphic shader");
438
439 subgroups::SSBOData inputData;
440 inputData.format = FORMAT_R32_UINT;
441 inputData.layout = subgroups::SSBOData::LayoutStd430;
442 inputData.numElements = subgroups::maxSupportedSubgroupSize();
443 inputData.initializeType = subgroups::SSBOData::InitializeNonZero;
444 inputData.binding = 4u;
445 inputData.stages = stages;
446
447 return subgroups::allStages(context, FORMAT_R32_UINT, &inputData, 1, checkVertexPipelineStages, stages);
448 }
449 }
450 } // namespace
451
createSubgroupsBallotTests(deqp::Context & testCtx)452 deqp::TestCaseGroup *createSubgroupsBallotTests(deqp::Context &testCtx)
453 {
454 de::MovePtr<deqp::TestCaseGroup> graphicGroup(
455 new deqp::TestCaseGroup(testCtx, "graphics", "Subgroup ballot category tests: graphics"));
456 de::MovePtr<deqp::TestCaseGroup> computeGroup(
457 new deqp::TestCaseGroup(testCtx, "compute", "Subgroup ballot category tests: compute"));
458 de::MovePtr<deqp::TestCaseGroup> framebufferGroup(
459 new deqp::TestCaseGroup(testCtx, "framebuffer", "Subgroup ballot category tests: framebuffer"));
460
461 const ShaderStageFlags stages[] = {SHADER_STAGE_TESS_EVALUATION_BIT, SHADER_STAGE_TESS_CONTROL_BIT,
462 SHADER_STAGE_GEOMETRY_BIT, SHADER_STAGE_VERTEX_BIT};
463
464 {
465 const CaseDefinition caseDef = {SHADER_STAGE_COMPUTE_BIT};
466 SubgroupFactory<CaseDefinition>::addFunctionCaseWithPrograms(computeGroup.get(),
467 getShaderStageName(caseDef.shaderStage), "",
468 supportedCheck, initPrograms, test, caseDef);
469 }
470
471 {
472 const CaseDefinition caseDef = {SHADER_STAGE_ALL_GRAPHICS};
473 SubgroupFactory<CaseDefinition>::addFunctionCaseWithPrograms(graphicGroup.get(), "graphic", "", supportedCheck,
474 initPrograms, test, caseDef);
475 }
476
477 for (int stageIndex = 0; stageIndex < DE_LENGTH_OF_ARRAY(stages); ++stageIndex)
478 {
479 const CaseDefinition caseDef = {stages[stageIndex]};
480 SubgroupFactory<CaseDefinition>::addFunctionCaseWithPrograms(
481 framebufferGroup.get(), getShaderStageName(caseDef.shaderStage), "", supportedCheck,
482 initFrameBufferPrograms, noSSBOtest, caseDef);
483 }
484
485 de::MovePtr<deqp::TestCaseGroup> group(
486 new deqp::TestCaseGroup(testCtx, "ballot", "Subgroup ballot category tests"));
487
488 group->addChild(graphicGroup.release());
489 group->addChild(computeGroup.release());
490 group->addChild(framebufferGroup.release());
491
492 return group.release();
493 }
494
495 } // namespace subgroups
496 } // namespace glc
497