1/*-------------------------------------------------------------------------
2 * drawElements Quality Program OpenGL (ES) Module
3 * -----------------------------------------------
4 *
5 * Copyright 2014 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 Utilities for framebuffer objects.
22 *//*--------------------------------------------------------------------*/
23
24#include "glsFboUtil.hpp"
25
26#include "glwEnums.hpp"
27#include "deUniquePtr.hpp"
28#include "gluTextureUtil.hpp"
29#include "gluStrUtil.hpp"
30#include "deStringUtil.hpp"
31#include <sstream>
32
33using namespace glw;
34using tcu::TestLog;
35using tcu::TextureFormat;
36using tcu::NotSupportedError;
37using glu::TransferFormat;
38using glu::mapGLInternalFormat;
39using glu::mapGLTransferFormat;
40using glu::getPixelFormatName;
41using glu::getTypeName;
42using glu::getFramebufferTargetName;
43using glu::getFramebufferAttachmentName;
44using glu::getFramebufferAttachmentTypeName;
45using glu::getTextureTargetName;
46using glu::getTransferFormat;
47using glu::ContextInfo;
48using glu::ContextType;
49using glu::RenderContext;
50using de::UniquePtr;
51using de::toString;
52using std::set;
53using std::vector;
54using std::string;
55using std::istringstream;
56using std::istream_iterator;
57
58namespace deqp
59{
60namespace gls
61{
62
63namespace FboUtil
64{
65
66
67void FormatDB::addFormat (ImageFormat format, FormatFlags newFlags)
68{
69	FormatFlags& flags = m_map[format];
70	flags = FormatFlags(flags | newFlags);
71}
72
73// Not too fast at the moment, might consider indexing?
74Formats FormatDB::getFormats (FormatFlags requirements) const
75{
76	Formats ret;
77	for (FormatMap::const_iterator it = m_map.begin(); it != m_map.end(); it++)
78	{
79		if ((it->second & requirements) == requirements)
80			ret.insert(it->first);
81	}
82	return ret;
83}
84
85FormatFlags FormatDB::getFormatInfo (ImageFormat format, FormatFlags fallback) const
86{
87	return lookupDefault(m_map, format, fallback);
88}
89
90void addFormats (FormatDB& db, FormatEntries stdFmts)
91{
92	for (const FormatEntry* it = stdFmts.begin(); it != stdFmts.end(); it++)
93	{
94		for (const FormatKey* it2 = it->second.begin(); it2 != it->second.end(); it2++)
95			db.addFormat(formatKeyInfo(*it2), it->first);
96	}
97}
98
99void addExtFormats (FormatDB& db, FormatExtEntries extFmts, const RenderContext* ctx)
100{
101	const UniquePtr<ContextInfo> ctxInfo(ctx != DE_NULL ? ContextInfo::create(*ctx) : DE_NULL);
102	for (const FormatExtEntry* it = extFmts.begin(); it != extFmts.end(); it++)
103	{
104		bool supported = true;
105		if (ctxInfo)
106		{
107			istringstream tokenStream(string(it->extensions));
108			istream_iterator<string> tokens((tokenStream)), end;
109
110			while (tokens != end)
111			{
112				if (!ctxInfo->isExtensionSupported(tokens->c_str()))
113				{
114					supported = false;
115					break;
116				}
117				++tokens;
118			}
119		}
120		if (supported)
121			for (const FormatKey* i2 = it->formats.begin(); i2 != it->formats.end(); i2++)
122				db.addFormat(formatKeyInfo(*i2), FormatFlags(it->flags));
123	}
124}
125
126FormatFlags formatFlag (GLenum context)
127{
128	switch (context)
129	{
130		case GL_NONE:
131			return FormatFlags(0);
132		case GL_RENDERBUFFER:
133			return RENDERBUFFER_VALID;
134		case GL_TEXTURE:
135			return TEXTURE_VALID;
136		case GL_STENCIL_ATTACHMENT:
137			return STENCIL_RENDERABLE;
138		case GL_DEPTH_ATTACHMENT:
139			return DEPTH_RENDERABLE;
140		default:
141			DE_ASSERT(context >= GL_COLOR_ATTACHMENT0 && context <= GL_COLOR_ATTACHMENT15);
142			return COLOR_RENDERABLE;
143	}
144}
145
146namespace config {
147
148GLsizei	imageNumSamples	(const Image& img)
149{
150	if (const Renderbuffer* rbo = dynamic_cast<const Renderbuffer*>(&img))
151		return rbo->numSamples;
152	return 0;
153}
154
155static GLenum glTarget (const Image& img)
156{
157	if (dynamic_cast<const Renderbuffer*>(&img) != DE_NULL)
158		return GL_RENDERBUFFER;
159	if (dynamic_cast<const Texture2D*>(&img) != DE_NULL)
160		return GL_TEXTURE_2D;
161	if (dynamic_cast<const TextureCubeMap*>(&img) != DE_NULL)
162		return GL_TEXTURE_CUBE_MAP;
163	if (dynamic_cast<const Texture3D*>(&img) != DE_NULL)
164		return GL_TEXTURE_3D;
165	if (dynamic_cast<const Texture2DArray*>(&img) != DE_NULL)
166		return GL_TEXTURE_2D_ARRAY;
167
168	DE_ASSERT(!"Impossible image type");
169	return GL_NONE;
170}
171
172static void glInitFlat (const TextureFlat& cfg, GLenum target, const glw::Functions& gl)
173{
174	const TransferFormat format = transferImageFormat(cfg.internalFormat);
175	GLint w = cfg.width;
176	GLint h = cfg.height;
177	for (GLint level = 0; level < cfg.numLevels; level++)
178	{
179		gl.texImage2D(target, level, cfg.internalFormat.format, w, h, 0,
180					  format.format, format.dataType, DE_NULL);
181		w = de::max(1, w / 2);
182		h = de::max(1, h / 2);
183	}
184}
185
186static void glInitLayered (const TextureLayered& cfg,
187						   GLint depth_divider, const glw::Functions& gl)
188{
189	const TransferFormat format = transferImageFormat(cfg.internalFormat);
190	GLint w = cfg.width;
191	GLint h = cfg.height;
192	GLint depth = cfg.numLayers;
193	for (GLint level = 0; level < cfg.numLevels; level++)
194	{
195		gl.texImage3D(glTarget(cfg), level, cfg.internalFormat.format, w, h, depth, 0,
196					  format.format, format.dataType, DE_NULL);
197		w = de::max(1, w / 2);
198		h = de::max(1, h / 2);
199		depth = de::max(1, depth / depth_divider);
200	}
201}
202
203static void glInit (const Texture& cfg, const glw::Functions& gl)
204{
205	if (const Texture2D* t2d = dynamic_cast<const Texture2D*>(&cfg))
206		glInitFlat(*t2d, glTarget(*t2d), gl);
207	else if (const TextureCubeMap* tcm = dynamic_cast<const TextureCubeMap*>(&cfg))
208	{
209		// \todo [2013-12-05 lauri]
210		// move this to glu or someplace sensible (this array is already
211		// present in duplicates)
212		static const GLenum s_cubeMapFaces[] =
213			{
214				GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
215				GL_TEXTURE_CUBE_MAP_POSITIVE_X,
216				GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
217				GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
218				GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
219				GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
220			};
221		const Range<GLenum> range = GLS_ARRAY_RANGE(s_cubeMapFaces);
222		for (const GLenum* it = range.begin(); it != range.end(); it++)
223			glInitFlat(*tcm, *it, gl);
224	}
225	else if (const Texture3D* t3d = dynamic_cast<const Texture3D*>(&cfg))
226		glInitLayered(*t3d, 2, gl);
227	else if (const Texture2DArray* t2a = dynamic_cast<const Texture2DArray*>(&cfg))
228		glInitLayered(*t2a, 1, gl);
229}
230
231static GLuint glCreate (const Image& cfg, const glw::Functions& gl)
232{
233	GLuint ret = 0;
234	if (const Renderbuffer* const rbo = dynamic_cast<const Renderbuffer*>(&cfg))
235	{
236		gl.genRenderbuffers(1, &ret);
237		gl.bindRenderbuffer(GL_RENDERBUFFER, ret);
238
239		if (rbo->numSamples == 0)
240			gl.renderbufferStorage(GL_RENDERBUFFER, rbo->internalFormat.format,
241								   rbo->width, rbo->height);
242		else
243			gl.renderbufferStorageMultisample(
244				GL_RENDERBUFFER, rbo->numSamples, rbo->internalFormat.format,
245				rbo->width, rbo->height);
246
247		gl.bindRenderbuffer(GL_RENDERBUFFER, 0);
248	}
249	else if (const Texture* const tex = dynamic_cast<const Texture*>(&cfg))
250	{
251		gl.genTextures(1, &ret);
252		gl.bindTexture(glTarget(*tex), ret);
253		glInit(*tex, gl);
254		gl.bindTexture(glTarget(*tex), 0);
255	}
256	else
257		DE_ASSERT(!"Impossible image type");
258	return ret;
259}
260
261static void glDelete (const Image& cfg, GLuint img, const glw::Functions& gl)
262{
263	if (dynamic_cast<const Renderbuffer*>(&cfg) != DE_NULL)
264		gl.deleteRenderbuffers(1, &img);
265	else if (dynamic_cast<const Texture*>(&cfg) != DE_NULL)
266		gl.deleteTextures(1, &img);
267	else
268		DE_ASSERT(!"Impossible image type");
269}
270
271static void attachAttachment (const Attachment& att, GLenum attPoint,
272							  const glw::Functions& gl)
273{
274	if (const RenderbufferAttachment* const rAtt =
275		dynamic_cast<const RenderbufferAttachment*>(&att))
276		gl.framebufferRenderbuffer(rAtt->target, attPoint,
277								   rAtt->renderbufferTarget, rAtt->imageName);
278	else if (const TextureFlatAttachment* const fAtt =
279			 dynamic_cast<const TextureFlatAttachment*>(&att))
280		gl.framebufferTexture2D(fAtt->target, attPoint,
281								fAtt->texTarget, fAtt->imageName, fAtt->level);
282	else if (const TextureLayerAttachment* const lAtt =
283			 dynamic_cast<const TextureLayerAttachment*>(&att))
284		gl.framebufferTextureLayer(lAtt->target, attPoint,
285								   lAtt->imageName, lAtt->level, lAtt->layer);
286	else
287		DE_ASSERT(!"Impossible attachment type");
288}
289
290GLenum attachmentType (const Attachment& att)
291{
292	if (dynamic_cast<const RenderbufferAttachment*>(&att) != DE_NULL)
293		return GL_RENDERBUFFER;
294	else if (dynamic_cast<const TextureAttachment*>(&att) != DE_NULL)
295		return GL_TEXTURE;
296
297	DE_ASSERT(!"Impossible attachment type");
298	return GL_NONE;
299}
300
301static GLsizei textureLayer (const TextureAttachment& tAtt)
302{
303	if (dynamic_cast<const TextureFlatAttachment*>(&tAtt) != DE_NULL)
304		return 0;
305	else if (const TextureLayerAttachment* const lAtt =
306			 dynamic_cast<const TextureLayerAttachment*>(&tAtt))
307		return lAtt->layer;
308
309	DE_ASSERT(!"Impossible attachment type");
310	return 0;
311}
312
313static void checkAttachmentCompleteness (Checker& cctx, const Attachment& attachment,
314										 GLenum attPoint, const Image* image,
315										 const FormatDB& db)
316{
317	// GLES2 4.4.5 / GLES3 4.4.4, "Framebuffer attachment completeness"
318
319	if (const TextureAttachment* const texAtt =
320		dynamic_cast<const TextureAttachment*>(&attachment))
321		if (const TextureLayered* const ltex = dynamic_cast<const TextureLayered*>(image))
322		{
323			// GLES3: "If the value of FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is
324			// TEXTURE and the value of FRAMEBUFFER_ATTACHMENT_OBJECT_NAME names a
325			// three-dimensional texture, then the value of
326			// FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER must be smaller than the depth
327			// of the texture.
328			//
329			// GLES3: "If the value of FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is
330			// TEXTURE and the value of FRAMEBUFFER_ATTACHMENT_OBJECT_NAME names a
331			// two-dimensional array texture, then the value of
332			// FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER must be smaller than the
333			// number of layers in the texture.
334
335			cctx.require(textureLayer(*texAtt) < ltex->numLayers,
336						 GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT);
337		}
338
339	// "The width and height of image are non-zero."
340	cctx.require(image->width > 0 && image->height > 0, GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT);
341
342	// Check for renderability
343	FormatFlags flags = db.getFormatInfo(image->internalFormat, ANY_FORMAT);
344	// If the format does not have the proper renderability flag, the
345	// completeness check _must_ fail.
346	cctx.require((flags & formatFlag(attPoint)) != 0, GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT);
347	// If the format is only optionally renderable, the completeness check _can_ fail.
348	cctx.canRequire((flags & REQUIRED_RENDERABLE) != 0,
349					GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT);
350}
351
352} // namespace config
353
354using namespace config;
355
356void Checker::require (bool condition, GLenum error)
357{
358	if (!condition)
359	{
360		m_statusCodes.erase(GL_FRAMEBUFFER_COMPLETE);
361		m_statusCodes.insert(error);
362	}
363}
364
365void Checker::canRequire (bool condition, GLenum error)
366{
367	if (!condition)
368		m_statusCodes.insert(error);
369}
370
371FboVerifier::FboVerifier (const FormatDB& formats, CheckerFactory& factory)
372	: m_formats				(formats)
373	, m_factory				(factory)
374{
375}
376
377/*--------------------------------------------------------------------*//*!
378 * \brief Return acceptable framebuffer status codes.
379 *
380 * This function examines the framebuffer configuration descriptor `fboConfig`
381 * and returns the set of status codes that `glCheckFramebufferStatus` is
382 * allowed to return on a conforming implementation when given a framebuffer
383 * whose configuration adheres to `fboConfig`.
384 *
385 * The returned set is guaranteed to be non-empty, but it may contain multiple
386 * INCOMPLETE statuses (if there are multiple errors in the spec), or or a mix
387 * of COMPLETE and INCOMPLETE statuses (if supporting a FBO with this spec is
388 * optional). Furthermore, the statuses may contain GL error codes, which
389 * indicate that trying to create a framebuffer configuration like this could
390 * have failed with an error (if one was checked for) even before
391 * `glCheckFramebufferStatus` was ever called.
392 *
393 *//*--------------------------------------------------------------------*/
394StatusCodes FboVerifier::validStatusCodes (const Framebuffer& fboConfig) const
395{
396	const AttachmentMap& atts = fboConfig.attachments;
397	const UniquePtr<Checker> cctx(m_factory.createChecker());
398
399	for (TextureMap::const_iterator it = fboConfig.textures.begin();
400		 it != fboConfig.textures.end(); it++)
401	{
402		const FormatFlags flags =
403			m_formats.getFormatInfo(it->second->internalFormat, ANY_FORMAT);
404		cctx->require((flags & TEXTURE_VALID) != 0, GL_INVALID_ENUM);
405		cctx->require((flags & TEXTURE_VALID) != 0, GL_INVALID_OPERATION);
406		cctx->require((flags & TEXTURE_VALID) != 0, GL_INVALID_VALUE);
407	}
408
409	for (RboMap::const_iterator it = fboConfig.rbos.begin(); it != fboConfig.rbos.end(); it++)
410	{
411		const FormatFlags flags =
412			m_formats.getFormatInfo(it->second->internalFormat, ANY_FORMAT);
413		cctx->require((flags & RENDERBUFFER_VALID) != 0, GL_INVALID_ENUM);
414	}
415
416	// "There is at least one image attached to the framebuffer."
417	// TODO: support XXX_framebuffer_no_attachments
418	cctx->require(!atts.empty(), GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT);
419
420	for (AttachmentMap::const_iterator it = atts.begin(); it != atts.end(); it++)
421	{
422		const GLenum attPoint = it->first;
423		const Attachment& att = *it->second;
424		const Image* const image = fboConfig.getImage(attachmentType(att), att.imageName);
425		checkAttachmentCompleteness(*cctx, att, attPoint, image, m_formats);
426		cctx->check(it->first, *it->second, image);
427	}
428
429	return cctx->getStatusCodes();
430}
431
432
433void Framebuffer::attach (glw::GLenum attPoint, const Attachment* att)
434{
435	if (att == DE_NULL)
436		attachments.erase(attPoint);
437	else
438		attachments[attPoint] = att;
439}
440
441const Image* Framebuffer::getImage (GLenum type, glw::GLuint imgName) const
442{
443	switch (type)
444	{
445		case GL_TEXTURE:
446			return lookupDefault(textures, imgName, DE_NULL);
447		case GL_RENDERBUFFER:
448			return lookupDefault(rbos, imgName, DE_NULL);
449		default:
450			DE_ASSERT(!"Bad image type");
451	}
452	return DE_NULL; // shut up compiler warning
453}
454
455void Framebuffer::setTexture (glw::GLuint texName, const Texture& texCfg)
456{
457	textures[texName] = &texCfg;
458}
459
460void Framebuffer::setRbo (glw::GLuint rbName, const Renderbuffer& rbCfg)
461{
462	rbos[rbName] = &rbCfg;
463}
464
465static void logField (TestLog& log, const string& field, const string& value)
466{
467	log << TestLog::Message << field << ": " << value << TestLog::EndMessage;
468}
469
470static void logImage (const Image& img, TestLog& log, bool useType)
471{
472	const GLenum type = img.internalFormat.unsizedType;
473	logField(log, "Internal format",	getPixelFormatName(img.internalFormat.format));
474	if (useType && type != GL_NONE)
475		logField(log, "Format type",	getTypeName(type));
476	logField(log, "Width", 				toString(img.width));
477	logField(log, "Height",				toString(img.height));
478}
479
480static void logRenderbuffer (const Renderbuffer& rbo, TestLog& log)
481{
482	logImage(rbo, log, false);
483	logField(log, "Samples",			toString(rbo.numSamples));
484}
485
486static void logTexture (const Texture& tex, TestLog& log)
487{
488	logField(log, "Type",				glu::getTextureTargetName(glTarget(tex)));
489	logImage(tex, log, true);
490	logField(log, "Levels",				toString(tex.numLevels));
491	if (const TextureLayered* const lTex = dynamic_cast<const TextureLayered*>(&tex))
492		logField(log, "Layers",				toString(lTex->numLayers));
493}
494
495static void logAttachment (const Attachment& att, TestLog& log)
496{
497	logField(log, "Target",				getFramebufferTargetName(att.target));
498	logField(log, "Type",				getFramebufferAttachmentTypeName(attachmentType(att)));
499	logField(log, "Image Name",			toString(att.imageName));
500	if (const RenderbufferAttachment* const rAtt
501		= dynamic_cast<const RenderbufferAttachment*>(&att))
502	{
503		DE_UNREF(rAtt); // To shut up compiler during optimized builds.
504		DE_ASSERT(rAtt->renderbufferTarget == GL_RENDERBUFFER);
505		logField(log, "Renderbuffer Target",	"GL_RENDERBUFFER");
506	}
507	else if (const TextureAttachment* const tAtt = dynamic_cast<const TextureAttachment*>(&att))
508	{
509		logField(log, "Mipmap Level",		toString(tAtt->level));
510		if (const TextureFlatAttachment* const fAtt =
511			dynamic_cast<const TextureFlatAttachment*>(tAtt))
512			logField(log, "Texture Target",		getTextureTargetName(fAtt->texTarget));
513		else if (const TextureLayerAttachment* const lAtt =
514			dynamic_cast<const TextureLayerAttachment*>(tAtt))
515			logField(log, "Layer",				toString(lAtt->level));
516	}
517}
518
519void logFramebufferConfig (const Framebuffer& cfg, TestLog& log)
520{
521	log << TestLog::Section("Framebuffer", "Framebuffer configuration");
522
523	const string rboDesc = cfg.rbos.empty()
524		? "No renderbuffers were created"
525		: "Renderbuffers created";
526	log << TestLog::Section("Renderbuffers", rboDesc);
527	for (RboMap::const_iterator it = cfg.rbos.begin(); it != cfg.rbos.end(); ++it)
528	{
529		const string num = toString(it->first);
530		log << TestLog::Section(num, "Renderbuffer " + num);
531		logRenderbuffer(*it->second, log);
532		log << TestLog::EndSection;
533	}
534	log << TestLog::EndSection; // Renderbuffers
535
536	const string texDesc = cfg.textures.empty()
537		? "No textures were created"
538		: "Textures created";
539	log << TestLog::Section("Textures", texDesc);
540	for (TextureMap::const_iterator it = cfg.textures.begin();
541		 it != cfg.textures.end(); ++it)
542	{
543		const string num = toString(it->first);
544		log << TestLog::Section(num, "Texture " + num);
545		logTexture(*it->second, log);
546		log << TestLog::EndSection;
547	}
548	log << TestLog::EndSection; // Textures
549
550	const string attDesc = cfg.attachments.empty()
551		? "Framebuffer has no attachments"
552		: "Framebuffer attachments";
553	log << TestLog::Section("Attachments", attDesc);
554	for (AttachmentMap::const_iterator it = cfg.attachments.begin();
555		 it != cfg.attachments.end(); it++)
556	{
557		const string attPointName = getFramebufferAttachmentName(it->first);
558		log << TestLog::Section(attPointName, "Attachment point " + attPointName);
559		logAttachment(*it->second, log);
560		log << TestLog::EndSection;
561	}
562	log << TestLog::EndSection; // Attachments
563
564	log << TestLog::EndSection; // Framebuffer
565}
566
567FboBuilder::FboBuilder (GLuint fbo, GLenum target, const glw::Functions& gl)
568	: m_error	(GL_NO_ERROR)
569	, m_target	(target)
570	, m_gl		(gl)
571{
572	m_gl.bindFramebuffer(m_target, fbo);
573}
574
575FboBuilder::~FboBuilder (void)
576{
577	for (TextureMap::const_iterator it = textures.begin(); it != textures.end(); it++)
578	{
579		glDelete(*it->second, it->first, m_gl);
580	}
581	for (RboMap::const_iterator it = rbos.begin(); it != rbos.end(); it++)
582	{
583		glDelete(*it->second, it->first, m_gl);
584	}
585	m_gl.bindFramebuffer(m_target, 0);
586	for (Configs::const_iterator it = m_configs.begin(); it != m_configs.end(); it++)
587	{
588		delete *it;
589	}
590}
591
592void FboBuilder::checkError (void)
593{
594	const GLenum error = m_gl.getError();
595	if (error != GL_NO_ERROR && m_error == GL_NO_ERROR)
596		m_error = error;
597}
598
599void FboBuilder::glAttach (GLenum attPoint, const Attachment* att)
600{
601	if (att == NULL)
602		m_gl.framebufferRenderbuffer(m_target, attPoint, GL_RENDERBUFFER, 0);
603	else
604		attachAttachment(*att, attPoint, m_gl);
605	checkError();
606	attach(attPoint, att);
607}
608
609GLuint FboBuilder::glCreateTexture (const Texture& texCfg)
610{
611	const GLuint texName = glCreate(texCfg, m_gl);
612	checkError();
613	setTexture(texName, texCfg);
614	return texName;
615}
616
617GLuint FboBuilder::glCreateRbo (const Renderbuffer& rbCfg)
618{
619	const GLuint rbName = glCreate(rbCfg, m_gl);
620	checkError();
621	setRbo(rbName, rbCfg);
622	return rbName;
623}
624
625TransferFormat transferImageFormat (const ImageFormat& imgFormat)
626{
627	if (imgFormat.unsizedType == GL_NONE)
628		return getTransferFormat(mapGLInternalFormat(imgFormat.format));
629	else
630		return TransferFormat(imgFormat.format, imgFormat.unsizedType);
631}
632
633} // FboUtil
634} // gls
635} // deqp
636