tcuTextureUtil.cpp revision 6c307165131fb7249bb044fc79ff0c2747263b3d
1/*-------------------------------------------------------------------------
2 * drawElements Quality Program Tester Core
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 Texture utilities.
22 *//*--------------------------------------------------------------------*/
23
24#include "tcuTextureUtil.hpp"
25#include "tcuVectorUtil.hpp"
26#include "deRandom.hpp"
27#include "deMath.h"
28#include "deMemory.h"
29
30#include <limits>
31
32namespace tcu
33{
34
35static inline float sRGBChannelToLinear (float cs)
36{
37	if (cs <= 0.04045)
38		return cs / 12.92f;
39	else
40		return deFloatPow((cs + 0.055f) / 1.055f, 2.4f);
41}
42
43static inline float linearChannelToSRGB (float cl)
44{
45	if (cl <= 0.0f)
46		return 0.0f;
47	else if (cl < 0.0031308f)
48		return 12.92f*cl;
49	else if (cl < 1.0f)
50		return 1.055f*deFloatPow(cl, 0.41666f) - 0.055f;
51	else
52		return 1.0f;
53}
54
55//! Convert sRGB to linear colorspace
56Vec4 sRGBToLinear (const Vec4& cs)
57{
58	return Vec4(sRGBChannelToLinear(cs[0]),
59				sRGBChannelToLinear(cs[1]),
60				sRGBChannelToLinear(cs[2]),
61				cs[3]);
62}
63
64//! Convert from linear to sRGB colorspace
65Vec4 linearToSRGB (const Vec4& cl)
66{
67	return Vec4(linearChannelToSRGB(cl[0]),
68				linearChannelToSRGB(cl[1]),
69				linearChannelToSRGB(cl[2]),
70				cl[3]);
71}
72
73bool isSRGB (TextureFormat format)
74{
75	return	format.order == TextureFormat::sR	||
76			format.order == TextureFormat::sRG	||
77			format.order == TextureFormat::sRGB	||
78			format.order == TextureFormat::sRGBA;
79}
80
81//! Get texture channel class for format
82TextureChannelClass getTextureChannelClass (TextureFormat::ChannelType channelType)
83{
84	// make sure this table is updated if format table is updated
85	DE_STATIC_ASSERT(TextureFormat::CHANNELTYPE_LAST == 26);
86
87	switch (channelType)
88	{
89		case TextureFormat::SNORM_INT8:						return TEXTURECHANNELCLASS_SIGNED_FIXED_POINT;
90		case TextureFormat::SNORM_INT16:					return TEXTURECHANNELCLASS_SIGNED_FIXED_POINT;
91		case TextureFormat::SNORM_INT32:					return TEXTURECHANNELCLASS_SIGNED_FIXED_POINT;
92		case TextureFormat::UNORM_INT8:						return TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT;
93		case TextureFormat::UNORM_INT16:					return TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT;
94		case TextureFormat::UNORM_INT24:					return TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT;
95		case TextureFormat::UNORM_INT32:					return TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT;
96		case TextureFormat::UNORM_SHORT_565:				return TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT;
97		case TextureFormat::UNORM_SHORT_555:				return TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT;
98		case TextureFormat::UNORM_SHORT_4444:				return TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT;
99		case TextureFormat::UNORM_SHORT_5551:				return TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT;
100		case TextureFormat::UNORM_INT_101010:				return TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT;
101		case TextureFormat::UNORM_INT_1010102_REV:			return TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT;
102		case TextureFormat::UNSIGNED_INT_1010102_REV:		return TEXTURECHANNELCLASS_UNSIGNED_INTEGER;
103		case TextureFormat::UNSIGNED_INT_11F_11F_10F_REV:	return TEXTURECHANNELCLASS_FLOATING_POINT;
104		case TextureFormat::UNSIGNED_INT_999_E5_REV:		return TEXTURECHANNELCLASS_FLOATING_POINT;
105		case TextureFormat::UNSIGNED_INT_24_8:				return TEXTURECHANNELCLASS_LAST;					//!< packed unorm24-uint8
106		case TextureFormat::SIGNED_INT8:					return TEXTURECHANNELCLASS_SIGNED_INTEGER;
107		case TextureFormat::SIGNED_INT16:					return TEXTURECHANNELCLASS_SIGNED_INTEGER;
108		case TextureFormat::SIGNED_INT32:					return TEXTURECHANNELCLASS_SIGNED_INTEGER;
109		case TextureFormat::UNSIGNED_INT8:					return TEXTURECHANNELCLASS_UNSIGNED_INTEGER;
110		case TextureFormat::UNSIGNED_INT16:					return TEXTURECHANNELCLASS_UNSIGNED_INTEGER;
111		case TextureFormat::UNSIGNED_INT32:					return TEXTURECHANNELCLASS_UNSIGNED_INTEGER;
112		case TextureFormat::HALF_FLOAT:						return TEXTURECHANNELCLASS_FLOATING_POINT;
113		case TextureFormat::FLOAT:							return TEXTURECHANNELCLASS_FLOATING_POINT;
114		case TextureFormat::FLOAT_UNSIGNED_INT_24_8_REV:	return TEXTURECHANNELCLASS_LAST;					//!< packed float32-pad24-uint8
115		default:											return TEXTURECHANNELCLASS_LAST;
116	}
117}
118
119/*--------------------------------------------------------------------*//*!
120 * \brief Get access to subregion of pixel buffer
121 * \param access	Parent access object
122 * \param x			X offset
123 * \param y			Y offset
124 * \param z			Z offset
125 * \param width		Width
126 * \param height	Height
127 * \param depth		Depth
128 * \return Access object that targets given subregion of parent access object
129 *//*--------------------------------------------------------------------*/
130ConstPixelBufferAccess getSubregion (const ConstPixelBufferAccess& access, int x, int y, int z, int width, int height, int depth)
131{
132	DE_ASSERT(de::inBounds(x, 0, access.getWidth()));
133	DE_ASSERT(de::inRange(x+width, x+1, access.getWidth()));
134
135	DE_ASSERT(de::inBounds(y, 0, access.getHeight()));
136	DE_ASSERT(de::inRange(y+height, y+1, access.getHeight()));
137
138	DE_ASSERT(de::inBounds(z, 0, access.getDepth()));
139	DE_ASSERT(de::inRange(z+depth, z+1, access.getDepth()));
140
141	return ConstPixelBufferAccess(access.getFormat(), tcu::IVec3(width, height, depth), access.getPitch(),
142								  (const deUint8*)access.getDataPtr() + access.getPixelPitch()*x + access.getRowPitch()*y + access.getSlicePitch()*z);
143}
144
145/*--------------------------------------------------------------------*//*!
146 * \brief Get access to subregion of pixel buffer
147 * \param access	Parent access object
148 * \param x			X offset
149 * \param y			Y offset
150 * \param z			Z offset
151 * \param width		Width
152 * \param height	Height
153 * \param depth		Depth
154 * \return Access object that targets given subregion of parent access object
155 *//*--------------------------------------------------------------------*/
156PixelBufferAccess getSubregion (const PixelBufferAccess& access, int x, int y, int z, int width, int height, int depth)
157{
158	DE_ASSERT(de::inBounds(x, 0, access.getWidth()));
159	DE_ASSERT(de::inRange(x+width, x+1, access.getWidth()));
160
161	DE_ASSERT(de::inBounds(y, 0, access.getHeight()));
162	DE_ASSERT(de::inRange(y+height, y+1, access.getHeight()));
163
164	DE_ASSERT(de::inBounds(z, 0, access.getDepth()));
165	DE_ASSERT(de::inRange(z+depth, z+1, access.getDepth()));
166
167	return PixelBufferAccess(access.getFormat(), tcu::IVec3(width, height, depth), access.getPitch(),
168							 (deUint8*)access.getDataPtr() + access.getPixelPitch()*x + access.getRowPitch()*y + access.getSlicePitch()*z);
169}
170
171/*--------------------------------------------------------------------*//*!
172 * \brief Get access to subregion of pixel buffer
173 * \param access	Parent access object
174 * \param x			X offset
175 * \param y			Y offset
176 * \param width		Width
177 * \param height	Height
178 * \return Access object that targets given subregion of parent access object
179 *//*--------------------------------------------------------------------*/
180PixelBufferAccess getSubregion (const PixelBufferAccess& access, int x, int y, int width, int height)
181{
182	return getSubregion(access, x, y, 0, width, height, 1);
183}
184
185/*--------------------------------------------------------------------*//*!
186 * \brief Get access to subregion of pixel buffer
187 * \param access	Parent access object
188 * \param x			X offset
189 * \param y			Y offset
190 * \param width		Width
191 * \param height	Height
192 * \return Access object that targets given subregion of parent access object
193 *//*--------------------------------------------------------------------*/
194ConstPixelBufferAccess getSubregion (const ConstPixelBufferAccess& access, int x, int y, int width, int height)
195{
196	return getSubregion(access, x, y, 0, width, height, 1);
197}
198
199/*--------------------------------------------------------------------*//*!
200 * \brief Flip rows in Y direction
201 * \param access Access object
202 * \return Modified access object where Y coordinates are reversed
203 *//*--------------------------------------------------------------------*/
204PixelBufferAccess flipYAccess (const PixelBufferAccess& access)
205{
206	const int			rowPitch		= access.getRowPitch();
207	const int			offsetToLast	= rowPitch*(access.getHeight()-1);
208	const tcu::IVec3	pitch			(access.getPixelPitch(), -rowPitch, access.getSlicePitch());
209
210	return PixelBufferAccess(access.getFormat(), access.getSize(), pitch, (deUint8*)access.getDataPtr() + offsetToLast);
211}
212
213/*--------------------------------------------------------------------*//*!
214 * \brief Flip rows in Y direction
215 * \param access Access object
216 * \return Modified access object where Y coordinates are reversed
217 *//*--------------------------------------------------------------------*/
218ConstPixelBufferAccess flipYAccess (const ConstPixelBufferAccess& access)
219{
220	const int			rowPitch		= access.getRowPitch();
221	const int			offsetToLast	= rowPitch*(access.getHeight()-1);
222	const tcu::IVec3	pitch			(access.getPixelPitch(), -rowPitch, access.getSlicePitch());
223
224	return ConstPixelBufferAccess(access.getFormat(), access.getSize(), pitch, (deUint8*)access.getDataPtr() + offsetToLast);
225}
226
227static Vec2 getChannelValueRange (TextureFormat::ChannelType channelType)
228{
229	// make sure this table is updated if format table is updated
230	DE_STATIC_ASSERT(TextureFormat::CHANNELTYPE_LAST == 26);
231
232	float cMin = 0.0f;
233	float cMax = 0.0f;
234
235	switch (channelType)
236	{
237		// Signed normalized formats.
238		case TextureFormat::SNORM_INT8:
239		case TextureFormat::SNORM_INT16:
240		case TextureFormat::SNORM_INT32:					cMin = -1.0f;			cMax = 1.0f;			break;
241
242		// Unsigned normalized formats.
243		case TextureFormat::UNORM_INT8:
244		case TextureFormat::UNORM_INT16:
245		case TextureFormat::UNORM_INT24:
246		case TextureFormat::UNORM_INT32:
247		case TextureFormat::UNORM_SHORT_565:
248		case TextureFormat::UNORM_SHORT_4444:
249		case TextureFormat::UNORM_INT_101010:
250		case TextureFormat::UNORM_INT_1010102_REV:			cMin = 0.0f;			cMax = 1.0f;			break;
251
252		// Misc formats.
253		case TextureFormat::SIGNED_INT8:					cMin = -128.0f;			cMax = 127.0f;			break;
254		case TextureFormat::SIGNED_INT16:					cMin = -32768.0f;		cMax = 32767.0f;		break;
255		case TextureFormat::SIGNED_INT32:					cMin = -2147483648.0f;	cMax = 2147483647.0f;	break;
256		case TextureFormat::UNSIGNED_INT8:					cMin = 0.0f;			cMax = 255.0f;			break;
257		case TextureFormat::UNSIGNED_INT16:					cMin = 0.0f;			cMax = 65535.0f;		break;
258		case TextureFormat::UNSIGNED_INT32:					cMin = 0.0f;			cMax = 4294967295.f;	break;
259		case TextureFormat::HALF_FLOAT:						cMin = -1e3f;			cMax = 1e3f;			break;
260		case TextureFormat::FLOAT:							cMin = -1e5f;			cMax = 1e5f;			break;
261		case TextureFormat::UNSIGNED_INT_11F_11F_10F_REV:	cMin = 0.0f;			cMax = 1e4f;			break;
262		case TextureFormat::UNSIGNED_INT_999_E5_REV:		cMin = 0.0f;			cMax = 1e5f;			break;
263
264		default:
265			DE_ASSERT(false);
266	}
267
268	return Vec2(cMin, cMax);
269}
270
271/*--------------------------------------------------------------------*//*!
272 * \brief Get standard parameters for testing texture format
273 *
274 * Returns TextureFormatInfo that describes good parameters for exercising
275 * given TextureFormat. Parameters include value ranges per channel and
276 * suitable lookup scaling and bias in order to reduce result back to
277 * 0..1 range.
278 *//*--------------------------------------------------------------------*/
279TextureFormatInfo getTextureFormatInfo (const TextureFormat& format)
280{
281	// Special cases.
282	if (format == TextureFormat(TextureFormat::RGBA, TextureFormat::UNSIGNED_INT_1010102_REV))
283		return TextureFormatInfo(Vec4(	    0.0f,		    0.0f,		    0.0f,		 0.0f),
284								 Vec4(	 1023.0f,		 1023.0f,		 1023.0f,		 3.0f),
285								 Vec4(1.0f/1023.f,	1.0f/1023.0f,	1.0f/1023.0f,	1.0f/3.0f),
286								 Vec4(	    0.0f,		    0.0f,		    0.0f,		 0.0f));
287	else if (format.order == TextureFormat::D || format.order == TextureFormat::DS)
288		return TextureFormatInfo(Vec4(0.0f,	0.0f,	0.0f,	0.0f),
289								 Vec4(1.0f,	1.0f,	1.0f,	0.0f),
290								 Vec4(1.0f,	1.0f,	1.0f,	1.0f),
291								 Vec4(0.0f,	0.0f,	0.0f,	0.0f)); // Depth / stencil formats.
292	else if (format == TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_SHORT_5551))
293		return TextureFormatInfo(Vec4(0.0f, 0.0f, 0.0f, 0.5f),
294								 Vec4(1.0f, 1.0f, 1.0f, 1.5f),
295								 Vec4(1.0f, 1.0f, 1.0f, 1.0f),
296								 Vec4(0.0f, 0.0f, 0.0f, 0.0f));
297
298	const Vec2						cRange		= getChannelValueRange(format.type);
299	const TextureSwizzle::Channel*	map			= getChannelReadSwizzle(format.order).components;
300	const BVec4						chnMask		= BVec4(deInRange32(map[0], TextureSwizzle::CHANNEL_0, TextureSwizzle::CHANNEL_3) == DE_TRUE,
301														deInRange32(map[1], TextureSwizzle::CHANNEL_0, TextureSwizzle::CHANNEL_3) == DE_TRUE,
302														deInRange32(map[2], TextureSwizzle::CHANNEL_0, TextureSwizzle::CHANNEL_3) == DE_TRUE,
303														deInRange32(map[3], TextureSwizzle::CHANNEL_0, TextureSwizzle::CHANNEL_3) == DE_TRUE);
304	const float						scale		= 1.0f / (cRange[1] - cRange[0]);
305	const float						bias		= -cRange[0] * scale;
306
307	return TextureFormatInfo(select(cRange[0],	0.0f, chnMask),
308							 select(cRange[1],	0.0f, chnMask),
309							 select(scale,		1.0f, chnMask),
310							 select(bias,		0.0f, chnMask));
311}
312
313static IVec4 getChannelBitDepth (TextureFormat::ChannelType channelType)
314{
315	// make sure this table is updated if format table is updated
316	DE_STATIC_ASSERT(TextureFormat::CHANNELTYPE_LAST == 26);
317
318	switch (channelType)
319	{
320		case TextureFormat::SNORM_INT8:						return IVec4(8);
321		case TextureFormat::SNORM_INT16:					return IVec4(16);
322		case TextureFormat::SNORM_INT32:					return IVec4(32);
323		case TextureFormat::UNORM_INT8:						return IVec4(8);
324		case TextureFormat::UNORM_INT16:					return IVec4(16);
325		case TextureFormat::UNORM_INT24:					return IVec4(24);
326		case TextureFormat::UNORM_INT32:					return IVec4(32);
327		case TextureFormat::UNORM_SHORT_565:				return IVec4(5,6,5,0);
328		case TextureFormat::UNORM_SHORT_4444:				return IVec4(4);
329		case TextureFormat::UNORM_SHORT_555:				return IVec4(5,5,5,0);
330		case TextureFormat::UNORM_SHORT_5551:				return IVec4(5,5,5,1);
331		case TextureFormat::UNORM_INT_101010:				return IVec4(10,10,10,0);
332		case TextureFormat::UNORM_INT_1010102_REV:			return IVec4(10,10,10,2);
333		case TextureFormat::SIGNED_INT8:					return IVec4(8);
334		case TextureFormat::SIGNED_INT16:					return IVec4(16);
335		case TextureFormat::SIGNED_INT32:					return IVec4(32);
336		case TextureFormat::UNSIGNED_INT8:					return IVec4(8);
337		case TextureFormat::UNSIGNED_INT16:					return IVec4(16);
338		case TextureFormat::UNSIGNED_INT32:					return IVec4(32);
339		case TextureFormat::UNSIGNED_INT_1010102_REV:		return IVec4(10,10,10,2);
340		case TextureFormat::UNSIGNED_INT_24_8:				return IVec4(24,8,0,0);
341		case TextureFormat::HALF_FLOAT:						return IVec4(16);
342		case TextureFormat::FLOAT:							return IVec4(32);
343		case TextureFormat::UNSIGNED_INT_11F_11F_10F_REV:	return IVec4(11,11,10,0);
344		case TextureFormat::UNSIGNED_INT_999_E5_REV:		return IVec4(9,9,9,0);
345		case TextureFormat::FLOAT_UNSIGNED_INT_24_8_REV:	return IVec4(32,8,0,0);
346		default:
347			DE_ASSERT(false);
348			return IVec4(0);
349	}
350}
351
352IVec4 getTextureFormatBitDepth (const TextureFormat& format)
353{
354	const IVec4						chnBits		= getChannelBitDepth(format.type);
355	const TextureSwizzle::Channel*	map			= getChannelReadSwizzle(format.order).components;
356	const BVec4						chnMask		= BVec4(deInRange32(map[0], TextureSwizzle::CHANNEL_0, TextureSwizzle::CHANNEL_3) == DE_TRUE,
357														deInRange32(map[1], TextureSwizzle::CHANNEL_0, TextureSwizzle::CHANNEL_3) == DE_TRUE,
358														deInRange32(map[2], TextureSwizzle::CHANNEL_0, TextureSwizzle::CHANNEL_3) == DE_TRUE,
359														deInRange32(map[3], TextureSwizzle::CHANNEL_0, TextureSwizzle::CHANNEL_3) == DE_TRUE);
360	const IVec4						chnSwz		= IVec4((chnMask[0]) ? ((int)map[0]) : (0),
361														(chnMask[1]) ? ((int)map[1]) : (0),
362														(chnMask[2]) ? ((int)map[2]) : (0),
363														(chnMask[3]) ? ((int)map[3]) : (0));
364
365	return select(chnBits.swizzle(chnSwz.x(), chnSwz.y(), chnSwz.z(), chnSwz.w()), IVec4(0), chnMask);
366}
367
368static IVec4 getChannelMantissaBitDepth (TextureFormat::ChannelType channelType)
369{
370	// make sure this table is updated if format table is updated
371	DE_STATIC_ASSERT(TextureFormat::CHANNELTYPE_LAST == 26);
372
373	switch (channelType)
374	{
375		case TextureFormat::SNORM_INT8:
376		case TextureFormat::SNORM_INT16:
377		case TextureFormat::SNORM_INT32:
378		case TextureFormat::UNORM_INT8:
379		case TextureFormat::UNORM_INT16:
380		case TextureFormat::UNORM_INT24:
381		case TextureFormat::UNORM_INT32:
382		case TextureFormat::UNORM_SHORT_565:
383		case TextureFormat::UNORM_SHORT_4444:
384		case TextureFormat::UNORM_SHORT_555:
385		case TextureFormat::UNORM_SHORT_5551:
386		case TextureFormat::UNORM_INT_101010:
387		case TextureFormat::UNORM_INT_1010102_REV:
388		case TextureFormat::SIGNED_INT8:
389		case TextureFormat::SIGNED_INT16:
390		case TextureFormat::SIGNED_INT32:
391		case TextureFormat::UNSIGNED_INT8:
392		case TextureFormat::UNSIGNED_INT16:
393		case TextureFormat::UNSIGNED_INT32:
394		case TextureFormat::UNSIGNED_INT_1010102_REV:
395		case TextureFormat::UNSIGNED_INT_24_8:
396		case TextureFormat::UNSIGNED_INT_999_E5_REV:
397			return getChannelBitDepth(channelType);
398
399		case TextureFormat::HALF_FLOAT:						return IVec4(10);
400		case TextureFormat::FLOAT:							return IVec4(23);
401		case TextureFormat::UNSIGNED_INT_11F_11F_10F_REV:	return IVec4(6,6,5,0);
402		case TextureFormat::FLOAT_UNSIGNED_INT_24_8_REV:	return IVec4(23,8,0,0);
403		default:
404			DE_ASSERT(false);
405			return IVec4(0);
406	}
407}
408
409IVec4 getTextureFormatMantissaBitDepth (const TextureFormat& format)
410{
411	const IVec4						chnBits		= getChannelMantissaBitDepth(format.type);
412	const TextureSwizzle::Channel*	map			= getChannelReadSwizzle(format.order).components;
413	const BVec4						chnMask		= BVec4(deInRange32(map[0], TextureSwizzle::CHANNEL_0, TextureSwizzle::CHANNEL_3) == DE_TRUE,
414														deInRange32(map[1], TextureSwizzle::CHANNEL_0, TextureSwizzle::CHANNEL_3) == DE_TRUE,
415														deInRange32(map[2], TextureSwizzle::CHANNEL_0, TextureSwizzle::CHANNEL_3) == DE_TRUE,
416														deInRange32(map[3], TextureSwizzle::CHANNEL_0, TextureSwizzle::CHANNEL_3) == DE_TRUE);
417	const IVec4						chnSwz		= IVec4((chnMask[0]) ? ((int)map[0]) : (0),
418														(chnMask[1]) ? ((int)map[1]) : (0),
419														(chnMask[2]) ? ((int)map[2]) : (0),
420														(chnMask[3]) ? ((int)map[3]) : (0));
421
422	return select(chnBits.swizzle(chnSwz.x(), chnSwz.y(), chnSwz.z(), chnSwz.w()), IVec4(0), chnMask);
423}
424
425static inline float linearInterpolate (float t, float minVal, float maxVal)
426{
427	return minVal + (maxVal - minVal) * t;
428}
429
430static inline Vec4 linearInterpolate (float t, const Vec4& a, const Vec4& b)
431{
432	return a + (b - a) * t;
433}
434
435enum
436{
437	CLEAR_OPTIMIZE_THRESHOLD		= 128,
438	CLEAR_OPTIMIZE_MAX_PIXEL_SIZE	= 8
439};
440
441inline void fillRow (const PixelBufferAccess& dst, int y, int z, int pixelSize, const deUint8* pixel)
442{
443	deUint8*	dstPtr	= (deUint8*)dst.getDataPtr() + z*dst.getSlicePitch() + y*dst.getRowPitch();
444	int			width	= dst.getWidth();
445
446	if (pixelSize == 8 && deIsAlignedPtr(dstPtr, pixelSize) && deIsAlignedPtr(dstPtr, pixelSize))
447	{
448		deUint64 val;
449		memcpy(&val, pixel, sizeof(val));
450
451		for (int i = 0; i < width; i++)
452			((deUint64*)dstPtr)[i] = val;
453	}
454	else if (pixelSize == 4 && deIsAlignedPtr(dstPtr, pixelSize) && deIsAlignedPtr(dstPtr, pixelSize))
455	{
456		deUint32 val;
457		memcpy(&val, pixel, sizeof(val));
458
459		for (int i = 0; i < width; i++)
460			((deUint32*)dstPtr)[i] = val;
461	}
462	else
463	{
464		for (int i = 0; i < width; i++)
465			for (int j = 0; j < pixelSize; j++)
466				dstPtr[i*pixelSize+j] = pixel[j];
467	}
468}
469
470void clear (const PixelBufferAccess& access, const Vec4& color)
471{
472	const int	pixelSize				= access.getFormat().getPixelSize();
473	const int	pixelPitch				= access.getPixelPitch();
474	const bool	rowPixelsTightlyPacked	= (pixelSize == pixelPitch);
475
476	if (access.getWidth()*access.getHeight()*access.getDepth() >= CLEAR_OPTIMIZE_THRESHOLD &&
477		pixelSize < CLEAR_OPTIMIZE_MAX_PIXEL_SIZE && rowPixelsTightlyPacked)
478	{
479		// Convert to destination format.
480		union
481		{
482			deUint8		u8[CLEAR_OPTIMIZE_MAX_PIXEL_SIZE];
483			deUint64	u64; // Forces 64-bit alignment.
484		} pixel;
485		DE_STATIC_ASSERT(sizeof(pixel) == CLEAR_OPTIMIZE_MAX_PIXEL_SIZE);
486		PixelBufferAccess(access.getFormat(), 1, 1, 1, 0, 0, &pixel.u8[0]).setPixel(color, 0, 0);
487
488		for (int z = 0; z < access.getDepth(); z++)
489			for (int y = 0; y < access.getHeight(); y++)
490				fillRow(access, y, z, pixelSize, &pixel.u8[0]);
491	}
492	else
493	{
494		for (int z = 0; z < access.getDepth(); z++)
495			for (int y = 0; y < access.getHeight(); y++)
496				for (int x = 0; x < access.getWidth(); x++)
497					access.setPixel(color, x, y, z);
498	}
499}
500
501void clear (const PixelBufferAccess& access, const IVec4& color)
502{
503	const int	pixelSize				= access.getFormat().getPixelSize();
504	const int	pixelPitch				= access.getPixelPitch();
505	const bool	rowPixelsTightlyPacked	= (pixelSize == pixelPitch);
506
507	if (access.getWidth()*access.getHeight()*access.getDepth() >= CLEAR_OPTIMIZE_THRESHOLD &&
508		pixelSize < CLEAR_OPTIMIZE_MAX_PIXEL_SIZE && rowPixelsTightlyPacked)
509	{
510		// Convert to destination format.
511		union
512		{
513			deUint8		u8[CLEAR_OPTIMIZE_MAX_PIXEL_SIZE];
514			deUint64	u64; // Forces 64-bit alignment.
515		} pixel;
516		DE_STATIC_ASSERT(sizeof(pixel) == CLEAR_OPTIMIZE_MAX_PIXEL_SIZE);
517		PixelBufferAccess(access.getFormat(), 1, 1, 1, 0, 0, &pixel.u8[0]).setPixel(color, 0, 0);
518
519		for (int z = 0; z < access.getDepth(); z++)
520			for (int y = 0; y < access.getHeight(); y++)
521				fillRow(access, y, z, pixelSize, &pixel.u8[0]);
522	}
523	else
524	{
525		for (int z = 0; z < access.getDepth(); z++)
526			for (int y = 0; y < access.getHeight(); y++)
527				for (int x = 0; x < access.getWidth(); x++)
528					access.setPixel(color, x, y, z);
529	}
530}
531
532void clear (const PixelBufferAccess& access, const UVec4& color)
533{
534	clear(access, color.cast<deInt32>());
535}
536
537void clearDepth (const PixelBufferAccess& access, float depth)
538{
539	const int	pixelSize				= access.getFormat().getPixelSize();
540	const int	pixelPitch				= access.getPixelPitch();
541	const bool	rowPixelsTightlyPacked	= (pixelSize == pixelPitch);
542
543	if (access.getWidth()*access.getHeight()*access.getDepth() >= CLEAR_OPTIMIZE_THRESHOLD &&
544		pixelSize < CLEAR_OPTIMIZE_MAX_PIXEL_SIZE && rowPixelsTightlyPacked)
545	{
546		// Convert to destination format.
547		union
548		{
549			deUint8		u8[CLEAR_OPTIMIZE_MAX_PIXEL_SIZE];
550			deUint64	u64; // Forces 64-bit alignment.
551		} pixel;
552		DE_STATIC_ASSERT(sizeof(pixel) == CLEAR_OPTIMIZE_MAX_PIXEL_SIZE);
553		PixelBufferAccess(access.getFormat(), 1, 1, 1, 0, 0, &pixel.u8[0]).setPixDepth(depth, 0, 0);
554
555		for (int z = 0; z < access.getDepth(); z++)
556			for (int y = 0; y < access.getHeight(); y++)
557				fillRow(access, y, z, pixelSize, &pixel.u8[0]);
558	}
559	else
560	{
561		for (int z = 0; z < access.getDepth(); z++)
562			for (int y = 0; y < access.getHeight(); y++)
563				for (int x = 0; x < access.getWidth(); x++)
564					access.setPixDepth(depth, x, y, z);
565	}
566}
567
568void clearStencil (const PixelBufferAccess& access, int stencil)
569{
570	const int	pixelSize				= access.getFormat().getPixelSize();
571	const int	pixelPitch				= access.getPixelPitch();
572	const bool	rowPixelsTightlyPacked	= (pixelSize == pixelPitch);
573
574	if (access.getWidth()*access.getHeight()*access.getDepth() >= CLEAR_OPTIMIZE_THRESHOLD &&
575		pixelSize < CLEAR_OPTIMIZE_MAX_PIXEL_SIZE && rowPixelsTightlyPacked)
576	{
577		// Convert to destination format.
578		union
579		{
580			deUint8		u8[CLEAR_OPTIMIZE_MAX_PIXEL_SIZE];
581			deUint64	u64; // Forces 64-bit alignment.
582		} pixel;
583		DE_STATIC_ASSERT(sizeof(pixel) == CLEAR_OPTIMIZE_MAX_PIXEL_SIZE);
584		PixelBufferAccess(access.getFormat(), 1, 1, 1, 0, 0, &pixel.u8[0]).setPixStencil(stencil, 0, 0);
585
586		for (int z = 0; z < access.getDepth(); z++)
587			for (int y = 0; y < access.getHeight(); y++)
588				fillRow(access, y, z, pixelSize, &pixel.u8[0]);
589	}
590	else
591	{
592		for (int z = 0; z < access.getDepth(); z++)
593			for (int y = 0; y < access.getHeight(); y++)
594				for (int x = 0; x < access.getWidth(); x++)
595					access.setPixStencil(stencil, x, y, z);
596	}
597}
598
599static void fillWithComponentGradients1D (const PixelBufferAccess& access, const Vec4& minVal, const Vec4& maxVal)
600{
601	DE_ASSERT(access.getHeight() == 1);
602	for (int x = 0; x < access.getWidth(); x++)
603	{
604		float s	= ((float)x + 0.5f) / (float)access.getWidth();
605
606		float r	= linearInterpolate(s, minVal.x(), maxVal.x());
607		float g = linearInterpolate(s, minVal.y(), maxVal.y());
608		float b = linearInterpolate(s, minVal.z(), maxVal.z());
609		float a = linearInterpolate(s, minVal.w(), maxVal.w());
610
611		access.setPixel(tcu::Vec4(r, g, b, a), x, 0);
612	}
613}
614
615static void fillWithComponentGradients2D (const PixelBufferAccess& access, const Vec4& minVal, const Vec4& maxVal)
616{
617	for (int y = 0; y < access.getHeight(); y++)
618	{
619		for (int x = 0; x < access.getWidth(); x++)
620		{
621			float s	= ((float)x + 0.5f) / (float)access.getWidth();
622			float t	= ((float)y + 0.5f) / (float)access.getHeight();
623
624			float r	= linearInterpolate((      s  +       t) *0.5f, minVal.x(), maxVal.x());
625			float g = linearInterpolate((      s  + (1.0f-t))*0.5f, minVal.y(), maxVal.y());
626			float b = linearInterpolate(((1.0f-s) +       t) *0.5f, minVal.z(), maxVal.z());
627			float a = linearInterpolate(((1.0f-s) + (1.0f-t))*0.5f, minVal.w(), maxVal.w());
628
629			access.setPixel(tcu::Vec4(r, g, b, a), x, y);
630		}
631	}
632}
633
634static void fillWithComponentGradients3D (const PixelBufferAccess& dst, const Vec4& minVal, const Vec4& maxVal)
635{
636	for (int z = 0; z < dst.getDepth(); z++)
637	{
638		for (int y = 0; y < dst.getHeight(); y++)
639		{
640			for (int x = 0; x < dst.getWidth(); x++)
641			{
642				float s = ((float)x + 0.5f) / (float)dst.getWidth();
643				float t = ((float)y + 0.5f) / (float)dst.getHeight();
644				float p = ((float)z + 0.5f) / (float)dst.getDepth();
645
646				float r = linearInterpolate(s,						minVal.x(), maxVal.x());
647				float g = linearInterpolate(t,						minVal.y(), maxVal.y());
648				float b = linearInterpolate(p,						minVal.z(), maxVal.z());
649				float a = linearInterpolate(1.0f - (s+t+p)/3.0f,	minVal.w(), maxVal.w());
650
651				dst.setPixel(tcu::Vec4(r, g, b, a), x, y, z);
652			}
653		}
654	}
655}
656
657void fillWithComponentGradients (const PixelBufferAccess& access, const Vec4& minVal, const Vec4& maxVal)
658{
659	if (access.getHeight() == 1 && access.getDepth() == 1)
660		fillWithComponentGradients1D(access, minVal, maxVal);
661	else if (access.getDepth() == 1)
662		fillWithComponentGradients2D(access, minVal, maxVal);
663	else
664		fillWithComponentGradients3D(access, minVal, maxVal);
665}
666
667void fillWithGrid1D (const PixelBufferAccess& access, int cellSize, const Vec4& colorA, const Vec4& colorB)
668{
669	for (int x = 0; x < access.getWidth(); x++)
670	{
671		int mx = (x / cellSize) % 2;
672
673		if (mx)
674			access.setPixel(colorB, x, 0);
675		else
676			access.setPixel(colorA, x, 0);
677	}
678}
679
680void fillWithGrid2D (const PixelBufferAccess& access, int cellSize, const Vec4& colorA, const Vec4& colorB)
681{
682	for (int y = 0; y < access.getHeight(); y++)
683	{
684		for (int x = 0; x < access.getWidth(); x++)
685		{
686			int mx = (x / cellSize) % 2;
687			int my = (y / cellSize) % 2;
688
689			if (mx ^ my)
690				access.setPixel(colorB, x, y);
691			else
692				access.setPixel(colorA, x, y);
693		}
694	}
695}
696
697void fillWithGrid3D (const PixelBufferAccess& access, int cellSize, const Vec4& colorA, const Vec4& colorB)
698{
699	for (int z = 0; z < access.getDepth(); z++)
700	{
701		for (int y = 0; y < access.getHeight(); y++)
702		{
703			for (int x = 0; x < access.getWidth(); x++)
704			{
705				int mx = (x / cellSize) % 2;
706				int my = (y / cellSize) % 2;
707				int mz = (z / cellSize) % 2;
708
709				if (mx ^ my ^ mz)
710					access.setPixel(colorB, x, y, z);
711				else
712					access.setPixel(colorA, x, y, z);
713			}
714		}
715	}
716}
717
718void fillWithGrid (const PixelBufferAccess& access, int cellSize, const Vec4& colorA, const Vec4& colorB)
719{
720	if (access.getHeight() == 1 && access.getDepth() == 1)
721		fillWithGrid1D(access, cellSize, colorA, colorB);
722	else if (access.getDepth() == 1)
723		fillWithGrid2D(access, cellSize, colorA, colorB);
724	else
725		fillWithGrid3D(access, cellSize, colorA, colorB);
726}
727
728void fillWithRepeatableGradient (const PixelBufferAccess& access, const Vec4& colorA, const Vec4& colorB)
729{
730	for (int y = 0; y < access.getHeight(); y++)
731	{
732		for (int x = 0; x < access.getWidth(); x++)
733		{
734			float s = ((float)x + 0.5f) / (float)access.getWidth();
735			float t = ((float)y + 0.5f) / (float)access.getHeight();
736
737			float a = s > 0.5f ? (2.0f - 2.0f*s) : 2.0f*s;
738			float b = t > 0.5f ? (2.0f - 2.0f*t) : 2.0f*t;
739
740			float p = deFloatClamp(deFloatSqrt(a*a + b*b), 0.0f, 1.0f);
741			access.setPixel(linearInterpolate(p, colorA, colorB), x, y);
742		}
743	}
744}
745
746void fillWithRGBAQuads (const PixelBufferAccess& dst)
747{
748	TCU_CHECK_INTERNAL(dst.getDepth() == 1);
749	int width	= dst.getWidth();
750	int height	= dst.getHeight();
751	int	left	= width/2;
752	int top		= height/2;
753
754	clear(getSubregion(dst, 0,		0,		0, left,		top,		1),	Vec4(1.0f, 0.0f, 0.0f, 1.0f));
755	clear(getSubregion(dst, left,	0,		0, width-left,	top,		1),	Vec4(0.0f, 1.0f, 0.0f, 1.0f));
756	clear(getSubregion(dst, 0,		top,	0, left,		height-top,	1), Vec4(0.0f, 0.0f, 1.0f, 0.0f));
757	clear(getSubregion(dst, left,	top,	0, width-left,	height-top, 1), Vec4(0.5f, 0.5f, 0.5f, 1.0f));
758}
759
760// \todo [2012-11-13 pyry] There is much better metaballs code in CL SIR value generators.
761void fillWithMetaballs (const PixelBufferAccess& dst, int numBalls, deUint32 seed)
762{
763	TCU_CHECK_INTERNAL(dst.getDepth() == 1);
764	std::vector<Vec2>	points(numBalls);
765	de::Random			rnd(seed);
766
767	for (int i = 0; i < numBalls; i++)
768	{
769		float x = rnd.getFloat();
770		float y = rnd.getFloat();
771		points[i] = (Vec2(x, y));
772	}
773
774	for (int y = 0; y < dst.getHeight(); y++)
775	for (int x = 0; x < dst.getWidth(); x++)
776	{
777		Vec2 p((float)x/(float)dst.getWidth(), (float)y/(float)dst.getHeight());
778
779		float sum = 0.0f;
780		for (std::vector<Vec2>::const_iterator i = points.begin(); i != points.end(); i++)
781		{
782			Vec2	d = p - *i;
783			float	f = 0.01f / (d.x()*d.x() + d.y()*d.y());
784
785			sum += f;
786		}
787
788		dst.setPixel(Vec4(sum), x, y);
789	}
790}
791
792void copy (const PixelBufferAccess& dst, const ConstPixelBufferAccess& src)
793{
794	DE_ASSERT(src.getSize() == dst.getSize());
795
796	const int	width				= dst.getWidth();
797	const int	height				= dst.getHeight();
798	const int	depth				= dst.getDepth();
799
800	const int	srcPixelSize		= src.getFormat().getPixelSize();
801	const int	dstPixelSize		= dst.getFormat().getPixelSize();
802	const int	srcPixelPitch		= src.getPixelPitch();
803	const int	dstPixelPitch		= dst.getPixelPitch();
804	const bool	srcTightlyPacked	= (srcPixelSize == srcPixelPitch);
805	const bool	dstTightlyPacked	= (dstPixelSize == dstPixelPitch);
806
807	if (src.getFormat() == dst.getFormat() && srcTightlyPacked && dstTightlyPacked)
808	{
809		// Fast-path for matching formats.
810		for (int z = 0; z < depth; z++)
811		for (int y = 0; y < height; y++)
812			deMemcpy(dst.getPixelPtr(0, y, z), src.getPixelPtr(0, y, z), srcPixelSize*width);
813	}
814	else if (src.getFormat() == dst.getFormat())
815	{
816		// Bit-exact copy for matching formats.
817		for (int z = 0; z < depth; z++)
818		for (int y = 0; y < height; y++)
819		for (int x = 0; x < width; x++)
820			deMemcpy(dst.getPixelPtr(x, y, z), src.getPixelPtr(x, y, z), srcPixelSize);
821	}
822	else
823	{
824		TextureChannelClass		srcClass	= getTextureChannelClass(src.getFormat().type);
825		TextureChannelClass		dstClass	= getTextureChannelClass(dst.getFormat().type);
826		bool					srcIsInt	= srcClass == TEXTURECHANNELCLASS_SIGNED_INTEGER || srcClass == TEXTURECHANNELCLASS_UNSIGNED_INTEGER;
827		bool					dstIsInt	= dstClass == TEXTURECHANNELCLASS_SIGNED_INTEGER || dstClass == TEXTURECHANNELCLASS_UNSIGNED_INTEGER;
828
829		if (srcIsInt && dstIsInt)
830		{
831			for (int z = 0; z < depth; z++)
832			for (int y = 0; y < height; y++)
833			for (int x = 0; x < width; x++)
834				dst.setPixel(src.getPixelInt(x, y, z), x, y, z);
835		}
836		else
837		{
838			for (int z = 0; z < depth; z++)
839			for (int y = 0; y < height; y++)
840			for (int x = 0; x < width; x++)
841				dst.setPixel(src.getPixel(x, y, z), x, y, z);
842		}
843	}
844}
845
846void scale (const PixelBufferAccess& dst, const ConstPixelBufferAccess& src, Sampler::FilterMode filter)
847{
848	DE_ASSERT(filter == Sampler::NEAREST || filter == Sampler::LINEAR);
849
850	Sampler sampler(Sampler::CLAMP_TO_EDGE, Sampler::CLAMP_TO_EDGE, Sampler::CLAMP_TO_EDGE,
851					filter, filter, 0.0f, false);
852
853	float sX = (float)src.getWidth() / (float)dst.getWidth();
854	float sY = (float)src.getHeight() / (float)dst.getHeight();
855	float sZ = (float)src.getDepth() / (float)dst.getDepth();
856
857	if (dst.getDepth() == 1 && src.getDepth() == 1)
858	{
859		for (int y = 0; y < dst.getHeight(); y++)
860		for (int x = 0; x < dst.getWidth(); x++)
861			dst.setPixel(src.sample2D(sampler, filter, (x+0.5f)*sX, (y+0.5f)*sY, 0), x, y);
862	}
863	else
864	{
865		for (int z = 0; z < dst.getDepth(); z++)
866		for (int y = 0; y < dst.getHeight(); y++)
867		for (int x = 0; x < dst.getWidth(); x++)
868			dst.setPixel(src.sample3D(sampler, filter, (x+0.5f)*sX, (y+0.5f)*sY, (z+0.5f)*sZ), x, y, z);
869	}
870}
871
872void estimatePixelValueRange (const ConstPixelBufferAccess& access, Vec4& minVal, Vec4& maxVal)
873{
874	const TextureFormat& format = access.getFormat();
875
876	switch (format.type)
877	{
878		case TextureFormat::UNORM_INT8:
879		case TextureFormat::UNORM_INT16:
880			// Normalized unsigned formats.
881			minVal = Vec4(0.0f);
882			maxVal = Vec4(1.0f);
883			break;
884
885		case TextureFormat::SNORM_INT8:
886		case TextureFormat::SNORM_INT16:
887			// Normalized signed formats.
888			minVal = Vec4(-1.0f);
889			maxVal = Vec4(+1.0f);
890			break;
891
892		default:
893			// \note Samples every 4/8th pixel.
894			minVal = Vec4(std::numeric_limits<float>::max());
895			maxVal = Vec4(std::numeric_limits<float>::min());
896
897			for (int z = 0; z < access.getDepth(); z += 2)
898			{
899				for (int y = 0; y < access.getHeight(); y += 2)
900				{
901					for (int x = 0; x < access.getWidth(); x += 2)
902					{
903						Vec4 p = access.getPixel(x, y, z);
904
905						minVal[0] = (deFloatIsNaN(p[0]) ? minVal[0] : de::min(minVal[0], p[0]));
906						minVal[1] = (deFloatIsNaN(p[1]) ? minVal[1] : de::min(minVal[1], p[1]));
907						minVal[2] = (deFloatIsNaN(p[2]) ? minVal[2] : de::min(minVal[2], p[2]));
908						minVal[3] = (deFloatIsNaN(p[3]) ? minVal[3] : de::min(minVal[3], p[3]));
909
910						maxVal[0] = (deFloatIsNaN(p[0]) ? maxVal[0] : de::max(maxVal[0], p[0]));
911						maxVal[1] = (deFloatIsNaN(p[1]) ? maxVal[1] : de::max(maxVal[1], p[1]));
912						maxVal[2] = (deFloatIsNaN(p[2]) ? maxVal[2] : de::max(maxVal[2], p[2]));
913						maxVal[3] = (deFloatIsNaN(p[3]) ? maxVal[3] : de::max(maxVal[3], p[3]));
914					}
915				}
916			}
917			break;
918	}
919}
920
921void computePixelScaleBias (const ConstPixelBufferAccess& access, Vec4& scale, Vec4& bias)
922{
923	Vec4 minVal, maxVal;
924	estimatePixelValueRange(access, minVal, maxVal);
925
926	const float eps = 0.0001f;
927
928	for (int c = 0; c < 4; c++)
929	{
930		if (maxVal[c] - minVal[c] < eps)
931		{
932			scale[c]	= (maxVal[c] < eps) ? 1.0f : (1.0f / maxVal[c]);
933			bias[c]		= (c == 3) ? (1.0f - maxVal[c]*scale[c]) : (0.0f - minVal[c]*scale[c]);
934		}
935		else
936		{
937			scale[c]	= 1.0f / (maxVal[c] - minVal[c]);
938			bias[c]		= 0.0f - minVal[c]*scale[c];
939		}
940	}
941}
942
943int getCubeArrayFaceIndex (CubeFace face)
944{
945	DE_ASSERT((int)face >= 0 && face < CUBEFACE_LAST);
946
947	switch (face)
948	{
949		case CUBEFACE_POSITIVE_X:	return 0;
950		case CUBEFACE_NEGATIVE_X:	return 1;
951		case CUBEFACE_POSITIVE_Y:	return 2;
952		case CUBEFACE_NEGATIVE_Y:	return 3;
953		case CUBEFACE_POSITIVE_Z:	return 4;
954		case CUBEFACE_NEGATIVE_Z:	return 5;
955
956		default:
957			return -1;
958	}
959}
960
961deUint32 packRGB999E5 (const tcu::Vec4& color)
962{
963	const int	mBits	= 9;
964	const int	eBits	= 5;
965	const int	eBias	= 15;
966	const int	eMax	= (1<<eBits)-1;
967	const float	maxVal	= (float)(((1<<mBits) - 1) * (1<<(eMax-eBias))) / (float)(1<<mBits);
968
969	float	rc		= deFloatClamp(color[0], 0.0f, maxVal);
970	float	gc		= deFloatClamp(color[1], 0.0f, maxVal);
971	float	bc		= deFloatClamp(color[2], 0.0f, maxVal);
972	float	maxc	= de::max(rc, de::max(gc, bc));
973	int		expp	= de::max(-eBias - 1, deFloorFloatToInt32(deFloatLog2(maxc))) + 1 + eBias;
974	float	e		= deFloatPow(2.0f, (float)(expp-eBias-mBits));
975	int		maxs	= deFloorFloatToInt32(maxc / e + 0.5f);
976
977	deUint32	exps	= maxs == (1<<mBits) ? expp+1 : expp;
978	deUint32	rs		= (deUint32)deClamp32(deFloorFloatToInt32(rc / e + 0.5f), 0, (1<<9)-1);
979	deUint32	gs		= (deUint32)deClamp32(deFloorFloatToInt32(gc / e + 0.5f), 0, (1<<9)-1);
980	deUint32	bs		= (deUint32)deClamp32(deFloorFloatToInt32(bc / e + 0.5f), 0, (1<<9)-1);
981
982	DE_ASSERT((exps & ~((1<<5)-1)) == 0);
983	DE_ASSERT((rs & ~((1<<9)-1)) == 0);
984	DE_ASSERT((gs & ~((1<<9)-1)) == 0);
985	DE_ASSERT((bs & ~((1<<9)-1)) == 0);
986
987	return rs | (gs << 9) | (bs << 18) | (exps << 27);
988}
989
990} // tcu
991