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 lookup simulator that is capable of verifying generic
22 *		  lookup results based on accuracy parameters.
23 *//*--------------------------------------------------------------------*/
24
25#include "tcuTexLookupVerifier.hpp"
26#include "tcuTexVerifierUtil.hpp"
27#include "tcuVectorUtil.hpp"
28#include "tcuTextureUtil.hpp"
29#include "deMath.h"
30
31namespace tcu
32{
33
34using namespace TexVerifierUtil;
35
36// Generic utilities
37
38#if defined(DE_DEBUG)
39static bool isSamplerSupported (const Sampler& sampler)
40{
41	return sampler.compare == Sampler::COMPAREMODE_NONE &&
42		   isWrapModeSupported(sampler.wrapS)			&&
43		   isWrapModeSupported(sampler.wrapT)			&&
44		   isWrapModeSupported(sampler.wrapR);
45}
46#endif // DE_DEBUG
47
48// Color read & compare utilities
49
50static inline bool coordsInBounds (const ConstPixelBufferAccess& access, int x, int y, int z)
51{
52	return de::inBounds(x, 0, access.getWidth()) && de::inBounds(y, 0, access.getHeight()) && de::inBounds(z, 0, access.getDepth());
53}
54
55template<typename ScalarType>
56inline Vector<ScalarType, 4> lookup (const ConstPixelBufferAccess& access, const Sampler& sampler, int i, int j, int k)
57{
58	if (coordsInBounds(access, i, j, k))
59		return access.getPixelT<ScalarType>(i, j, k);
60	else
61		return sampleTextureBorder<ScalarType>(access.getFormat(), sampler);
62}
63
64template<>
65inline Vector<float, 4> lookup (const ConstPixelBufferAccess& access, const Sampler& sampler, int i, int j, int k)
66{
67	// Specialization for float lookups: sRGB conversion is performed as specified in format.
68	if (coordsInBounds(access, i, j, k))
69	{
70		const Vec4 p = access.getPixel(i, j, k);
71		return isSRGB(access.getFormat()) ? sRGBToLinear(p) : p;
72	}
73	else
74		return sampleTextureBorder<float>(access.getFormat(), sampler);
75}
76
77static inline bool isColorValid (const LookupPrecision& prec, const Vec4& ref, const Vec4& result)
78{
79	const Vec4 diff = abs(ref - result);
80	return boolAll(logicalOr(lessThanEqual(diff, prec.colorThreshold), logicalNot(prec.colorMask)));
81}
82
83static inline bool isColorValid (const IntLookupPrecision& prec, const IVec4& ref, const IVec4& result)
84{
85	return boolAll(logicalOr(lessThanEqual(absDiff(ref, result).asUint(), prec.colorThreshold), logicalNot(prec.colorMask)));
86}
87
88static inline bool isColorValid (const IntLookupPrecision& prec, const UVec4& ref, const UVec4& result)
89{
90	return boolAll(logicalOr(lessThanEqual(absDiff(ref, result), prec.colorThreshold), logicalNot(prec.colorMask)));
91}
92
93struct ColorQuad
94{
95	Vec4	p00;		//!< (0, 0)
96	Vec4	p01;		//!< (1, 0)
97	Vec4	p10;		//!< (0, 1)
98	Vec4	p11;		//!< (1, 1)
99};
100
101static void lookupQuad (ColorQuad& dst, const ConstPixelBufferAccess& level, const Sampler& sampler, int x0, int x1, int y0, int y1, int z)
102{
103	dst.p00	= lookup<float>(level, sampler, x0, y0, z);
104	dst.p10	= lookup<float>(level, sampler, x1, y0, z);
105	dst.p01	= lookup<float>(level, sampler, x0, y1, z);
106	dst.p11	= lookup<float>(level, sampler, x1, y1, z);
107}
108
109struct ColorLine
110{
111	Vec4	p0;		//!< 0
112	Vec4	p1;		//!< 1
113};
114
115static void lookupLine (ColorLine& dst, const ConstPixelBufferAccess& level, const Sampler& sampler, int x0, int x1, int y)
116{
117	dst.p0 = lookup<float>(level, sampler, x0, y, 0);
118	dst.p1 = lookup<float>(level, sampler, x1, y, 0);
119}
120
121template<typename T, int Size>
122static T minComp (const Vector<T, Size>& vec)
123{
124	T minVal = vec[0];
125	for (int ndx = 1; ndx < Size; ndx++)
126		minVal = de::min(minVal, vec[ndx]);
127	return minVal;
128}
129
130template<typename T, int Size>
131static T maxComp (const Vector<T, Size>& vec)
132{
133	T maxVal = vec[0];
134	for (int ndx = 1; ndx < Size; ndx++)
135		maxVal = de::max(maxVal, vec[ndx]);
136	return maxVal;
137}
138
139static float computeBilinearSearchStepFromFloatLine (const LookupPrecision&	prec,
140													 const ColorLine&		line)
141{
142	DE_ASSERT(boolAll(greaterThan(prec.colorThreshold, Vec4(0.0f))));
143
144	const int		maxSteps	= 1<<16;
145	const Vec4		d			= abs(line.p1 - line.p0);
146	const Vec4		stepCount	= d / prec.colorThreshold;
147	const Vec4		minStep		= 1.0f / (stepCount + 1.0f);
148	const float		step		= de::max(minComp(minStep), 1.0f / float(maxSteps));
149
150	return step;
151}
152
153static float computeBilinearSearchStepFromFloatQuad (const LookupPrecision&	prec,
154													 const ColorQuad&		quad)
155{
156	DE_ASSERT(boolAll(greaterThan(prec.colorThreshold, Vec4(0.0f))));
157
158	const int		maxSteps	= 1<<16;
159	const Vec4		d0			= abs(quad.p10 - quad.p00);
160	const Vec4		d1			= abs(quad.p01 - quad.p00);
161	const Vec4		d2			= abs(quad.p11 - quad.p10);
162	const Vec4		d3			= abs(quad.p11 - quad.p01);
163	const Vec4		maxD		= max(d0, max(d1, max(d2, d3)));
164	const Vec4		stepCount	= maxD / prec.colorThreshold;
165	const Vec4		minStep		= 1.0f / (stepCount + 1.0f);
166	const float		step		= de::max(minComp(minStep), 1.0f / float(maxSteps));
167
168	return step;
169}
170
171static float computeBilinearSearchStepForUnorm (const LookupPrecision& prec)
172{
173	DE_ASSERT(boolAll(greaterThan(prec.colorThreshold, Vec4(0.0f))));
174
175	const Vec4		stepCount	= 1.0f / prec.colorThreshold;
176	const Vec4		minStep		= 1.0f / (stepCount + 1.0f);
177	const float		step		= minComp(minStep);
178
179	return step;
180}
181
182static float computeBilinearSearchStepForSnorm (const LookupPrecision& prec)
183{
184	DE_ASSERT(boolAll(greaterThan(prec.colorThreshold, Vec4(0.0f))));
185
186	const Vec4		stepCount	= 2.0f / prec.colorThreshold;
187	const Vec4		minStep		= 1.0f / (stepCount + 1.0f);
188	const float		step		= minComp(minStep);
189
190	return step;
191}
192
193static inline Vec4 min (const ColorLine& line)
194{
195	return min(line.p0, line.p1);
196}
197
198static inline Vec4 max (const ColorLine& line)
199{
200	return max(line.p0, line.p1);
201}
202
203static inline Vec4 min (const ColorQuad& quad)
204{
205	return min(quad.p00, min(quad.p10, min(quad.p01, quad.p11)));
206}
207
208static inline Vec4 max (const ColorQuad& quad)
209{
210	return max(quad.p00, max(quad.p10, max(quad.p01, quad.p11)));
211}
212
213static bool isInColorBounds (const LookupPrecision& prec, const ColorQuad& quad, const Vec4& result)
214{
215	const tcu::Vec4 minVal = min(quad) - prec.colorThreshold;
216	const tcu::Vec4 maxVal = max(quad) + prec.colorThreshold;
217	return boolAll(logicalOr(logicalAnd(greaterThanEqual(result, minVal), lessThanEqual(result, maxVal)), logicalNot(prec.colorMask)));
218}
219
220static bool isInColorBounds (const LookupPrecision& prec, const ColorQuad& quad0, const ColorQuad& quad1, const Vec4& result)
221{
222	const tcu::Vec4 minVal = min(min(quad0), min(quad1)) - prec.colorThreshold;
223	const tcu::Vec4 maxVal = max(max(quad0), max(quad1)) + prec.colorThreshold;
224	return boolAll(logicalOr(logicalAnd(greaterThanEqual(result, minVal), lessThanEqual(result, maxVal)), logicalNot(prec.colorMask)));
225}
226
227static bool isInColorBounds (const LookupPrecision& prec, const ColorLine& line0, const ColorLine& line1, const Vec4& result)
228{
229	const tcu::Vec4 minVal = min(min(line0), min(line1)) - prec.colorThreshold;
230	const tcu::Vec4 maxVal = max(max(line0), max(line1)) + prec.colorThreshold;
231	return boolAll(logicalOr(logicalAnd(greaterThanEqual(result, minVal), lessThanEqual(result, maxVal)), logicalNot(prec.colorMask)));
232}
233
234static bool isInColorBounds (const LookupPrecision&		prec,
235							 const ColorQuad&			quad00,
236							 const ColorQuad&			quad01,
237							 const ColorQuad&			quad10,
238							 const ColorQuad&			quad11,
239							 const Vec4&				result)
240{
241	const tcu::Vec4 minVal = min(min(quad00), min(min(quad01), min(min(quad10), min(quad11)))) - prec.colorThreshold;
242	const tcu::Vec4 maxVal = max(max(quad00), max(max(quad01), max(max(quad10), max(quad11)))) + prec.colorThreshold;
243	return boolAll(logicalOr(logicalAnd(greaterThanEqual(result, minVal), lessThanEqual(result, maxVal)), logicalNot(prec.colorMask)));
244}
245
246// Range search utilities
247
248static bool isLinearRangeValid (const LookupPrecision&	prec,
249								const Vec4&				c0,
250								const Vec4&				c1,
251								const Vec2&				fBounds,
252								const Vec4&				result)
253{
254	// This is basically line segment - AABB test. Valid interpolation line is checked
255	// against result AABB constructed by applying threshold.
256
257	const Vec4		i0				= c0*(1.0f - fBounds[0]) + c1*fBounds[0];
258	const Vec4		i1				= c0*(1.0f - fBounds[1]) + c1*fBounds[1];
259	const Vec4		rMin			= result - prec.colorThreshold;
260	const Vec4		rMax			= result + prec.colorThreshold;
261	bool			allIntersect	= true;
262
263	// Algorithm: For each component check whether segment endpoints are inside, or intersect with slab.
264	// If all intersect or are inside, line segment intersects the whole 4D AABB.
265	for (int compNdx = 0; compNdx < 4; compNdx++)
266	{
267		if (!prec.colorMask[compNdx])
268			continue;
269
270		// Signs for both bounds: false = left, true = right.
271		const bool	sMin0	= i0[compNdx] >= rMin[compNdx];
272		const bool	sMin1	= i1[compNdx] >= rMin[compNdx];
273		const bool	sMax0	= i0[compNdx] > rMax[compNdx];
274		const bool	sMax1	= i1[compNdx] > rMax[compNdx];
275
276		// If all signs are equal, line segment is outside bounds.
277		if (sMin0 == sMin1 && sMin1 == sMax0 && sMax0 == sMax1)
278		{
279			allIntersect = false;
280			break;
281		}
282	}
283
284	return allIntersect;
285}
286
287static bool isBilinearRangeValid (const LookupPrecision&	prec,
288								  const ColorQuad&			quad,
289								  const Vec2&				xBounds,
290								  const Vec2&				yBounds,
291								  const float				searchStep,
292								  const Vec4&				result)
293{
294	DE_ASSERT(xBounds.x() <= xBounds.y());
295	DE_ASSERT(yBounds.x() <= yBounds.y());
296
297	if (!isInColorBounds(prec, quad, result))
298		return false;
299
300	for (float x = xBounds.x(); x < xBounds.y()+searchStep; x += searchStep)
301	{
302		const float		a	= de::min(x, xBounds.y());
303		const Vec4		c0	= quad.p00*(1.0f - a) + quad.p10*a;
304		const Vec4		c1	= quad.p01*(1.0f - a) + quad.p11*a;
305
306		if (isLinearRangeValid(prec, c0, c1, yBounds, result))
307			return true;
308	}
309
310	return false;
311}
312
313static bool isTrilinearRangeValid (const LookupPrecision&	prec,
314								   const ColorQuad&			quad0,
315								   const ColorQuad&			quad1,
316								   const Vec2&				xBounds,
317								   const Vec2&				yBounds,
318								   const Vec2&				zBounds,
319								   const float				searchStep,
320								   const Vec4&				result)
321{
322	DE_ASSERT(xBounds.x() <= xBounds.y());
323	DE_ASSERT(yBounds.x() <= yBounds.y());
324	DE_ASSERT(zBounds.x() <= zBounds.y());
325
326	if (!isInColorBounds(prec, quad0, quad1, result))
327		return false;
328
329	for (float x = xBounds.x(); x < xBounds.y()+searchStep; x += searchStep)
330	{
331		for (float y = yBounds.x(); y < yBounds.y()+searchStep; y += searchStep)
332		{
333			const float		a	= de::min(x, xBounds.y());
334			const float		b	= de::min(y, yBounds.y());
335			const Vec4		c0	= quad0.p00*(1.0f-a)*(1.0f-b) + quad0.p10*a*(1.0f-b) + quad0.p01*(1.0f-a)*b + quad0.p11*a*b;
336			const Vec4		c1	= quad1.p00*(1.0f-a)*(1.0f-b) + quad1.p10*a*(1.0f-b) + quad1.p01*(1.0f-a)*b + quad1.p11*a*b;
337
338			if (isLinearRangeValid(prec, c0, c1, zBounds, result))
339				return true;
340		}
341	}
342
343	return false;
344}
345
346static bool is1DTrilinearFilterResultValid (const LookupPrecision&	prec,
347											const ColorLine&		line0,
348											const ColorLine&		line1,
349											const Vec2&				xBounds0,
350											const Vec2&				xBounds1,
351											const Vec2&				zBounds,
352											const float				searchStep,
353											const Vec4&				result)
354{
355	DE_ASSERT(xBounds0.x() <= xBounds0.y());
356	DE_ASSERT(xBounds1.x() <= xBounds1.y());
357
358	if (!isInColorBounds(prec, line0, line1, result))
359		return false;
360
361	for (float x0 = xBounds0.x(); x0 < xBounds0.y()+searchStep; x0 += searchStep)
362	{
363		const float		a0	= de::min(x0, xBounds0.y());
364		const Vec4		c0	= line0.p0*(1.0f-a0) + line0.p1*a0;
365
366		for (float x1 = xBounds1.x(); x1 <= xBounds1.y(); x1 += searchStep)
367		{
368			const float		a1	= de::min(x1, xBounds1.y());
369			const Vec4		c1	= line1.p0*(1.0f-a1) + line1.p1*a1;
370
371			if (isLinearRangeValid(prec, c0, c1, zBounds, result))
372				return true;
373		}
374	}
375
376	return false;
377}
378
379static bool is2DTrilinearFilterResultValid (const LookupPrecision&	prec,
380											const ColorQuad&		quad0,
381											const ColorQuad&		quad1,
382											const Vec2&				xBounds0,
383											const Vec2&				yBounds0,
384											const Vec2&				xBounds1,
385											const Vec2&				yBounds1,
386											const Vec2&				zBounds,
387											const float				searchStep,
388											const Vec4&				result)
389{
390	DE_ASSERT(xBounds0.x() <= xBounds0.y());
391	DE_ASSERT(yBounds0.x() <= yBounds0.y());
392	DE_ASSERT(xBounds1.x() <= xBounds1.y());
393	DE_ASSERT(yBounds1.x() <= yBounds1.y());
394
395	if (!isInColorBounds(prec, quad0, quad1, result))
396		return false;
397
398	for (float x0 = xBounds0.x(); x0 < xBounds0.y()+searchStep; x0 += searchStep)
399	{
400		for (float y0 = yBounds0.x(); y0 < yBounds0.y()+searchStep; y0 += searchStep)
401		{
402			const float		a0	= de::min(x0, xBounds0.y());
403			const float		b0	= de::min(y0, yBounds0.y());
404			const Vec4		c0	= quad0.p00*(1.0f-a0)*(1.0f-b0) + quad0.p10*a0*(1.0f-b0) + quad0.p01*(1.0f-a0)*b0 + quad0.p11*a0*b0;
405
406			for (float x1 = xBounds1.x(); x1 <= xBounds1.y(); x1 += searchStep)
407			{
408				for (float y1 = yBounds1.x(); y1 <= yBounds1.y(); y1 += searchStep)
409				{
410					const float		a1	= de::min(x1, xBounds1.y());
411					const float		b1	= de::min(y1, yBounds1.y());
412					const Vec4		c1	= quad1.p00*(1.0f-a1)*(1.0f-b1) + quad1.p10*a1*(1.0f-b1) + quad1.p01*(1.0f-a1)*b1 + quad1.p11*a1*b1;
413
414					if (isLinearRangeValid(prec, c0, c1, zBounds, result))
415						return true;
416				}
417			}
418		}
419	}
420
421	return false;
422}
423
424static bool is3DTrilinearFilterResultValid (const LookupPrecision&	prec,
425											const ColorQuad&		quad00,
426											const ColorQuad&		quad01,
427											const ColorQuad&		quad10,
428											const ColorQuad&		quad11,
429											const Vec2&				xBounds0,
430											const Vec2&				yBounds0,
431											const Vec2&				zBounds0,
432											const Vec2&				xBounds1,
433											const Vec2&				yBounds1,
434											const Vec2&				zBounds1,
435											const Vec2&				wBounds,
436											const float				searchStep,
437											const Vec4&				result)
438{
439	DE_ASSERT(xBounds0.x() <= xBounds0.y());
440	DE_ASSERT(yBounds0.x() <= yBounds0.y());
441	DE_ASSERT(zBounds0.x() <= zBounds0.y());
442	DE_ASSERT(xBounds1.x() <= xBounds1.y());
443	DE_ASSERT(yBounds1.x() <= yBounds1.y());
444	DE_ASSERT(zBounds1.x() <= zBounds1.y());
445
446	if (!isInColorBounds(prec, quad00, quad01, quad10, quad11, result))
447		return false;
448
449	for (float x0 = xBounds0.x(); x0 < xBounds0.y()+searchStep; x0 += searchStep)
450	{
451		for (float y0 = yBounds0.x(); y0 < yBounds0.y()+searchStep; y0 += searchStep)
452		{
453			const float		a0	= de::min(x0, xBounds0.y());
454			const float		b0	= de::min(y0, yBounds0.y());
455			const Vec4		c00	= quad00.p00*(1.0f-a0)*(1.0f-b0) + quad00.p10*a0*(1.0f-b0) + quad00.p01*(1.0f-a0)*b0 + quad00.p11*a0*b0;
456			const Vec4		c01	= quad01.p00*(1.0f-a0)*(1.0f-b0) + quad01.p10*a0*(1.0f-b0) + quad01.p01*(1.0f-a0)*b0 + quad01.p11*a0*b0;
457
458			for (float z0 = zBounds0.x(); z0 < zBounds0.y()+searchStep; z0 += searchStep)
459			{
460				const float		c0	= de::min(z0, zBounds0.y());
461				const Vec4		cz0	= c00*(1.0f-c0) + c01*c0;
462
463				for (float x1 = xBounds1.x(); x1 < xBounds1.y()+searchStep; x1 += searchStep)
464				{
465					for (float y1 = yBounds1.x(); y1 < yBounds1.y()+searchStep; y1 += searchStep)
466					{
467						const float		a1	= de::min(x1, xBounds1.y());
468						const float		b1	= de::min(y1, yBounds1.y());
469						const Vec4		c10	= quad10.p00*(1.0f-a1)*(1.0f-b1) + quad10.p10*a1*(1.0f-b1) + quad10.p01*(1.0f-a1)*b1 + quad10.p11*a1*b1;
470						const Vec4		c11	= quad11.p00*(1.0f-a1)*(1.0f-b1) + quad11.p10*a1*(1.0f-b1) + quad11.p01*(1.0f-a1)*b1 + quad11.p11*a1*b1;
471
472						for (float z1 = zBounds1.x(); z1 < zBounds1.y()+searchStep; z1 += searchStep)
473						{
474							const float		c1	= de::min(z1, zBounds1.y());
475							const Vec4		cz1	= c10*(1.0f - c1) + c11*c1;
476
477							if (isLinearRangeValid(prec, cz0, cz1, wBounds, result))
478								return true;
479						}
480					}
481				}
482			}
483		}
484	}
485
486	return false;
487}
488
489template<typename PrecType, typename ScalarType>
490static bool isNearestSampleResultValid (const ConstPixelBufferAccess&		level,
491										const Sampler&						sampler,
492										const PrecType&						prec,
493										const float							coordX,
494										const int							coordY,
495										const Vector<ScalarType, 4>&		result)
496{
497	DE_ASSERT(level.getDepth() == 1);
498
499	const Vec2		uBounds			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, level.getWidth(),	coordX, prec.coordBits.x(), prec.uvwBits.x());
500
501	const int		minI			= deFloorFloatToInt32(uBounds.x());
502	const int		maxI			= deFloorFloatToInt32(uBounds.y());
503
504	for (int i = minI; i <= maxI; i++)
505	{
506		const int					x		= wrap(sampler.wrapS, i, level.getWidth());
507		const Vector<ScalarType, 4>	color	= lookup<ScalarType>(level, sampler, x, coordY, 0);
508
509		if (isColorValid(prec, color, result))
510			return true;
511	}
512
513	return false;
514}
515
516template<typename PrecType, typename ScalarType>
517static bool isNearestSampleResultValid (const ConstPixelBufferAccess&		level,
518										const Sampler&						sampler,
519										const PrecType&						prec,
520										const Vec2&							coord,
521										const int							coordZ,
522										const Vector<ScalarType, 4>&		result)
523{
524	const Vec2		uBounds			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, level.getWidth(),	coord.x(), prec.coordBits.x(), prec.uvwBits.x());
525	const Vec2		vBounds			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, level.getHeight(),	coord.y(), prec.coordBits.y(), prec.uvwBits.y());
526
527	// Integer coordinates - without wrap mode
528	const int		minI			= deFloorFloatToInt32(uBounds.x());
529	const int		maxI			= deFloorFloatToInt32(uBounds.y());
530	const int		minJ			= deFloorFloatToInt32(vBounds.x());
531	const int		maxJ			= deFloorFloatToInt32(vBounds.y());
532
533	// \todo [2013-07-03 pyry] This could be optimized by first computing ranges based on wrap mode.
534
535	for (int j = minJ; j <= maxJ; j++)
536	{
537		for (int i = minI; i <= maxI; i++)
538		{
539			const int					x		= wrap(sampler.wrapS, i, level.getWidth());
540			const int					y		= wrap(sampler.wrapT, j, level.getHeight());
541			const Vector<ScalarType, 4>	color	= lookup<ScalarType>(level, sampler, x, y, coordZ);
542
543			if (isColorValid(prec, color, result))
544				return true;
545		}
546	}
547
548	return false;
549}
550
551template<typename PrecType, typename ScalarType>
552static bool isNearestSampleResultValid (const ConstPixelBufferAccess&		level,
553										const Sampler&						sampler,
554										const PrecType&						prec,
555										const Vec3&							coord,
556										const Vector<ScalarType, 4>&		result)
557{
558	const Vec2		uBounds			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, level.getWidth(),	coord.x(), prec.coordBits.x(), prec.uvwBits.x());
559	const Vec2		vBounds			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, level.getHeight(),	coord.y(), prec.coordBits.y(), prec.uvwBits.y());
560	const Vec2		wBounds			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, level.getDepth(),	coord.z(), prec.coordBits.z(), prec.uvwBits.z());
561
562	// Integer coordinates - without wrap mode
563	const int		minI			= deFloorFloatToInt32(uBounds.x());
564	const int		maxI			= deFloorFloatToInt32(uBounds.y());
565	const int		minJ			= deFloorFloatToInt32(vBounds.x());
566	const int		maxJ			= deFloorFloatToInt32(vBounds.y());
567	const int		minK			= deFloorFloatToInt32(wBounds.x());
568	const int		maxK			= deFloorFloatToInt32(wBounds.y());
569
570	// \todo [2013-07-03 pyry] This could be optimized by first computing ranges based on wrap mode.
571
572	for (int k = minK; k <= maxK; k++)
573	{
574		for (int j = minJ; j <= maxJ; j++)
575		{
576			for (int i = minI; i <= maxI; i++)
577			{
578				const int					x		= wrap(sampler.wrapS, i, level.getWidth());
579				const int					y		= wrap(sampler.wrapT, j, level.getHeight());
580				const int					z		= wrap(sampler.wrapR, k, level.getDepth());
581				const Vector<ScalarType, 4>	color	= lookup<ScalarType>(level, sampler, x, y, z);
582
583				if (isColorValid(prec, color, result))
584					return true;
585			}
586		}
587	}
588
589	return false;
590}
591
592bool isLinearSampleResultValid (const ConstPixelBufferAccess&		level,
593								const Sampler&						sampler,
594								const LookupPrecision&				prec,
595								const float							coordX,
596								const int							coordY,
597								const Vec4&							result)
598{
599	const Vec2					uBounds			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, level.getWidth(), coordX, prec.coordBits.x(), prec.uvwBits.x());
600
601	const int					minI			= deFloorFloatToInt32(uBounds.x()-0.5f);
602	const int					maxI			= deFloorFloatToInt32(uBounds.y()-0.5f);
603
604	const int					w				= level.getWidth();
605
606	for (int i = minI; i <= maxI; i++)
607	{
608		// Wrapped coordinates
609		const int	x0		= wrap(sampler.wrapS, i  , w);
610		const int	x1		= wrap(sampler.wrapS, i+1, w);
611
612		// Bounds for filtering factors
613		const float	minA	= de::clamp((uBounds.x()-0.5f)-float(i), 0.0f, 1.0f);
614		const float	maxA	= de::clamp((uBounds.y()-0.5f)-float(i), 0.0f, 1.0f);
615
616		const Vec4	colorA	= lookup<float>(level, sampler, x0, coordY, 0);
617		const Vec4	colorB	= lookup<float>(level, sampler, x1, coordY, 0);
618
619		if (isLinearRangeValid(prec, colorA, colorB, Vec2(minA, maxA), result))
620			return true;
621	}
622
623	return false;
624}
625
626bool isLinearSampleResultValid (const ConstPixelBufferAccess&		level,
627								const Sampler&						sampler,
628								const LookupPrecision&				prec,
629								const Vec2&							coord,
630								const int							coordZ,
631								const Vec4&							result)
632{
633	const Vec2					uBounds			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, level.getWidth(),	coord.x(), prec.coordBits.x(), prec.uvwBits.x());
634	const Vec2					vBounds			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, level.getHeight(),	coord.y(), prec.coordBits.y(), prec.uvwBits.y());
635
636	// Integer coordinate bounds for (x0,y0) - without wrap mode
637	const int					minI			= deFloorFloatToInt32(uBounds.x()-0.5f);
638	const int					maxI			= deFloorFloatToInt32(uBounds.y()-0.5f);
639	const int					minJ			= deFloorFloatToInt32(vBounds.x()-0.5f);
640	const int					maxJ			= deFloorFloatToInt32(vBounds.y()-0.5f);
641
642	const int					w				= level.getWidth();
643	const int					h				= level.getHeight();
644
645	const TextureChannelClass	texClass		= getTextureChannelClass(level.getFormat().type);
646	float						searchStep		= texClass == TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT	? computeBilinearSearchStepForUnorm(prec) :
647												  texClass == TEXTURECHANNELCLASS_SIGNED_FIXED_POINT	? computeBilinearSearchStepForSnorm(prec) :
648												  0.0f; // Step is computed for floating-point quads based on texel values.
649
650	// \todo [2013-07-03 pyry] This could be optimized by first computing ranges based on wrap mode.
651
652	for (int j = minJ; j <= maxJ; j++)
653	{
654		for (int i = minI; i <= maxI; i++)
655		{
656			// Wrapped coordinates
657			const int	x0		= wrap(sampler.wrapS, i  , w);
658			const int	x1		= wrap(sampler.wrapS, i+1, w);
659			const int	y0		= wrap(sampler.wrapT, j  , h);
660			const int	y1		= wrap(sampler.wrapT, j+1, h);
661
662			// Bounds for filtering factors
663			const float	minA	= de::clamp((uBounds.x()-0.5f)-float(i), 0.0f, 1.0f);
664			const float	maxA	= de::clamp((uBounds.y()-0.5f)-float(i), 0.0f, 1.0f);
665			const float	minB	= de::clamp((vBounds.x()-0.5f)-float(j), 0.0f, 1.0f);
666			const float	maxB	= de::clamp((vBounds.y()-0.5f)-float(j), 0.0f, 1.0f);
667
668			ColorQuad quad;
669			lookupQuad(quad, level, sampler, x0, x1, y0, y1, coordZ);
670
671			if (texClass == TEXTURECHANNELCLASS_FLOATING_POINT)
672				searchStep = computeBilinearSearchStepFromFloatQuad(prec, quad);
673
674			if (isBilinearRangeValid(prec, quad, Vec2(minA, maxA), Vec2(minB, maxB), searchStep, result))
675				return true;
676		}
677	}
678
679	return false;
680}
681
682static bool isLinearSampleResultValid (const ConstPixelBufferAccess&		level,
683									   const Sampler&						sampler,
684									   const LookupPrecision&				prec,
685									   const Vec3&							coord,
686									   const Vec4&							result)
687{
688	const Vec2					uBounds			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, level.getWidth(),	coord.x(), prec.coordBits.x(), prec.uvwBits.x());
689	const Vec2					vBounds			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, level.getHeight(),	coord.y(), prec.coordBits.y(), prec.uvwBits.y());
690	const Vec2					wBounds			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, level.getDepth(),	coord.z(), prec.coordBits.z(), prec.uvwBits.z());
691
692	// Integer coordinate bounds for (x0,y0) - without wrap mode
693	const int					minI			= deFloorFloatToInt32(uBounds.x()-0.5f);
694	const int					maxI			= deFloorFloatToInt32(uBounds.y()-0.5f);
695	const int					minJ			= deFloorFloatToInt32(vBounds.x()-0.5f);
696	const int					maxJ			= deFloorFloatToInt32(vBounds.y()-0.5f);
697	const int					minK			= deFloorFloatToInt32(wBounds.x()-0.5f);
698	const int					maxK			= deFloorFloatToInt32(wBounds.y()-0.5f);
699
700	const int					w				= level.getWidth();
701	const int					h				= level.getHeight();
702	const int					d				= level.getDepth();
703
704	const TextureChannelClass	texClass		= getTextureChannelClass(level.getFormat().type);
705	float						searchStep		= texClass == TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT	? computeBilinearSearchStepForUnorm(prec) :
706												  texClass == TEXTURECHANNELCLASS_SIGNED_FIXED_POINT	? computeBilinearSearchStepForSnorm(prec) :
707												  0.0f; // Step is computed for floating-point quads based on texel values.
708
709	// \todo [2013-07-03 pyry] This could be optimized by first computing ranges based on wrap mode.
710
711	for (int k = minK; k <= maxK; k++)
712	{
713		for (int j = minJ; j <= maxJ; j++)
714		{
715			for (int i = minI; i <= maxI; i++)
716			{
717				// Wrapped coordinates
718				const int	x0		= wrap(sampler.wrapS, i  , w);
719				const int	x1		= wrap(sampler.wrapS, i+1, w);
720				const int	y0		= wrap(sampler.wrapT, j  , h);
721				const int	y1		= wrap(sampler.wrapT, j+1, h);
722				const int	z0		= wrap(sampler.wrapR, k  , d);
723				const int	z1		= wrap(sampler.wrapR, k+1, d);
724
725				// Bounds for filtering factors
726				const float	minA	= de::clamp((uBounds.x()-0.5f)-float(i), 0.0f, 1.0f);
727				const float	maxA	= de::clamp((uBounds.y()-0.5f)-float(i), 0.0f, 1.0f);
728				const float	minB	= de::clamp((vBounds.x()-0.5f)-float(j), 0.0f, 1.0f);
729				const float	maxB	= de::clamp((vBounds.y()-0.5f)-float(j), 0.0f, 1.0f);
730				const float	minC	= de::clamp((wBounds.x()-0.5f)-float(k), 0.0f, 1.0f);
731				const float	maxC	= de::clamp((wBounds.y()-0.5f)-float(k), 0.0f, 1.0f);
732
733				ColorQuad quad0, quad1;
734				lookupQuad(quad0, level, sampler, x0, x1, y0, y1, z0);
735				lookupQuad(quad1, level, sampler, x0, x1, y0, y1, z1);
736
737				if (texClass == TEXTURECHANNELCLASS_FLOATING_POINT)
738					searchStep = de::min(computeBilinearSearchStepFromFloatQuad(prec, quad0), computeBilinearSearchStepFromFloatQuad(prec, quad1));
739
740				if (isTrilinearRangeValid(prec, quad0, quad1, Vec2(minA, maxA), Vec2(minB, maxB), Vec2(minC, maxC), searchStep, result))
741					return true;
742			}
743		}
744	}
745
746	return false;
747}
748
749static bool isNearestMipmapLinearSampleResultValid (const ConstPixelBufferAccess&	level0,
750													const ConstPixelBufferAccess&	level1,
751													const Sampler&					sampler,
752													const LookupPrecision&			prec,
753													const float						coord,
754													const int						coordY,
755													const Vec2&						fBounds,
756													const Vec4&						result)
757{
758	const int		w0				= level0.getWidth();
759	const int		w1				= level1.getWidth();
760
761	const Vec2		uBounds0		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, w0,	coord, prec.coordBits.x(), prec.uvwBits.x());
762	const Vec2		uBounds1		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, w1,	coord, prec.coordBits.x(), prec.uvwBits.x());
763
764	// Integer coordinates - without wrap mode
765	const int		minI0			= deFloorFloatToInt32(uBounds0.x());
766	const int		maxI0			= deFloorFloatToInt32(uBounds0.y());
767	const int		minI1			= deFloorFloatToInt32(uBounds1.x());
768	const int		maxI1			= deFloorFloatToInt32(uBounds1.y());
769
770	for (int i0 = minI0; i0 <= maxI0; i0++)
771	{
772		for (int i1 = minI1; i1 <= maxI1; i1++)
773		{
774			const Vec4	c0	= lookup<float>(level0, sampler, wrap(sampler.wrapS, i0, w0), coordY, 0);
775			const Vec4	c1	= lookup<float>(level1, sampler, wrap(sampler.wrapS, i1, w1), coordY, 0);
776
777			if (isLinearRangeValid(prec, c0, c1, fBounds, result))
778				return true;
779		}
780	}
781
782	return false;
783}
784
785static bool isNearestMipmapLinearSampleResultValid (const ConstPixelBufferAccess&	level0,
786													const ConstPixelBufferAccess&	level1,
787													const Sampler&					sampler,
788													const LookupPrecision&			prec,
789													const Vec2&						coord,
790													const int						coordZ,
791													const Vec2&						fBounds,
792													const Vec4&						result)
793{
794	const int		w0				= level0.getWidth();
795	const int		w1				= level1.getWidth();
796	const int		h0				= level0.getHeight();
797	const int		h1				= level1.getHeight();
798
799	const Vec2		uBounds0		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, w0,	coord.x(), prec.coordBits.x(), prec.uvwBits.x());
800	const Vec2		uBounds1		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, w1,	coord.x(), prec.coordBits.x(), prec.uvwBits.x());
801	const Vec2		vBounds0		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, h0,	coord.y(), prec.coordBits.y(), prec.uvwBits.y());
802	const Vec2		vBounds1		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, h1,	coord.y(), prec.coordBits.y(), prec.uvwBits.y());
803
804	// Integer coordinates - without wrap mode
805	const int		minI0			= deFloorFloatToInt32(uBounds0.x());
806	const int		maxI0			= deFloorFloatToInt32(uBounds0.y());
807	const int		minI1			= deFloorFloatToInt32(uBounds1.x());
808	const int		maxI1			= deFloorFloatToInt32(uBounds1.y());
809	const int		minJ0			= deFloorFloatToInt32(vBounds0.x());
810	const int		maxJ0			= deFloorFloatToInt32(vBounds0.y());
811	const int		minJ1			= deFloorFloatToInt32(vBounds1.x());
812	const int		maxJ1			= deFloorFloatToInt32(vBounds1.y());
813
814	for (int j0 = minJ0; j0 <= maxJ0; j0++)
815	{
816		for (int i0 = minI0; i0 <= maxI0; i0++)
817		{
818			for (int j1 = minJ1; j1 <= maxJ1; j1++)
819			{
820				for (int i1 = minI1; i1 <= maxI1; i1++)
821				{
822					const Vec4	c0	= lookup<float>(level0, sampler, wrap(sampler.wrapS, i0, w0), wrap(sampler.wrapT, j0, h0), coordZ);
823					const Vec4	c1	= lookup<float>(level1, sampler, wrap(sampler.wrapS, i1, w1), wrap(sampler.wrapT, j1, h1), coordZ);
824
825					if (isLinearRangeValid(prec, c0, c1, fBounds, result))
826						return true;
827				}
828			}
829		}
830	}
831
832	return false;
833}
834
835static bool isNearestMipmapLinearSampleResultValid (const ConstPixelBufferAccess&	level0,
836													const ConstPixelBufferAccess&	level1,
837													const Sampler&					sampler,
838													const LookupPrecision&			prec,
839													const Vec3&						coord,
840													const Vec2&						fBounds,
841													const Vec4&						result)
842{
843	const int		w0				= level0.getWidth();
844	const int		w1				= level1.getWidth();
845	const int		h0				= level0.getHeight();
846	const int		h1				= level1.getHeight();
847	const int		d0				= level0.getDepth();
848	const int		d1				= level1.getDepth();
849
850	const Vec2		uBounds0		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, w0,	coord.x(), prec.coordBits.x(), prec.uvwBits.x());
851	const Vec2		uBounds1		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, w1,	coord.x(), prec.coordBits.x(), prec.uvwBits.x());
852	const Vec2		vBounds0		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, h0,	coord.y(), prec.coordBits.y(), prec.uvwBits.y());
853	const Vec2		vBounds1		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, h1,	coord.y(), prec.coordBits.y(), prec.uvwBits.y());
854	const Vec2		wBounds0		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, d0,	coord.z(), prec.coordBits.z(), prec.uvwBits.z());
855	const Vec2		wBounds1		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, d1,	coord.z(), prec.coordBits.z(), prec.uvwBits.z());
856
857	// Integer coordinates - without wrap mode
858	const int		minI0			= deFloorFloatToInt32(uBounds0.x());
859	const int		maxI0			= deFloorFloatToInt32(uBounds0.y());
860	const int		minI1			= deFloorFloatToInt32(uBounds1.x());
861	const int		maxI1			= deFloorFloatToInt32(uBounds1.y());
862	const int		minJ0			= deFloorFloatToInt32(vBounds0.x());
863	const int		maxJ0			= deFloorFloatToInt32(vBounds0.y());
864	const int		minJ1			= deFloorFloatToInt32(vBounds1.x());
865	const int		maxJ1			= deFloorFloatToInt32(vBounds1.y());
866	const int		minK0			= deFloorFloatToInt32(wBounds0.x());
867	const int		maxK0			= deFloorFloatToInt32(wBounds0.y());
868	const int		minK1			= deFloorFloatToInt32(wBounds1.x());
869	const int		maxK1			= deFloorFloatToInt32(wBounds1.y());
870
871	for (int k0 = minK0; k0 <= maxK0; k0++)
872	{
873		for (int j0 = minJ0; j0 <= maxJ0; j0++)
874		{
875			for (int i0 = minI0; i0 <= maxI0; i0++)
876			{
877				for (int k1 = minK1; k1 <= maxK1; k1++)
878				{
879					for (int j1 = minJ1; j1 <= maxJ1; j1++)
880					{
881						for (int i1 = minI1; i1 <= maxI1; i1++)
882						{
883							const Vec4	c0	= lookup<float>(level0, sampler, wrap(sampler.wrapS, i0, w0), wrap(sampler.wrapT, j0, h0), wrap(sampler.wrapR, k0, d0));
884							const Vec4	c1	= lookup<float>(level1, sampler, wrap(sampler.wrapS, i1, w1), wrap(sampler.wrapT, j1, h1), wrap(sampler.wrapR, k1, d1));
885
886							if (isLinearRangeValid(prec, c0, c1, fBounds, result))
887								return true;
888						}
889					}
890				}
891			}
892		}
893	}
894
895	return false;
896}
897
898static bool isLinearMipmapLinearSampleResultValid (const ConstPixelBufferAccess&	level0,
899												   const ConstPixelBufferAccess&	level1,
900												   const Sampler&					sampler,
901												   const LookupPrecision&			prec,
902												   const float						coordX,
903												   const int						coordY,
904												   const Vec2&						fBounds,
905												   const Vec4&						result)
906{
907	// \todo [2013-07-04 pyry] This is strictly not correct as coordinates between levels should be dependent.
908	//						   Right now this allows pairing any two valid bilinear quads.
909
910	const int					w0				= level0.getWidth();
911	const int					w1				= level1.getWidth();
912
913	const Vec2					uBounds0		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, w0,	coordX, prec.coordBits.x(), prec.uvwBits.x());
914	const Vec2					uBounds1		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, w1,	coordX, prec.coordBits.x(), prec.uvwBits.x());
915
916	// Integer coordinates - without wrap mode
917	const int					minI0			= deFloorFloatToInt32(uBounds0.x()-0.5f);
918	const int					maxI0			= deFloorFloatToInt32(uBounds0.y()-0.5f);
919	const int					minI1			= deFloorFloatToInt32(uBounds1.x()-0.5f);
920	const int					maxI1			= deFloorFloatToInt32(uBounds1.y()-0.5f);
921
922	const TextureChannelClass	texClass		= getTextureChannelClass(level0.getFormat().type);
923	const float					cSearchStep		= texClass == TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT	? computeBilinearSearchStepForUnorm(prec) :
924												  texClass == TEXTURECHANNELCLASS_SIGNED_FIXED_POINT	? computeBilinearSearchStepForSnorm(prec) :
925												  0.0f; // Step is computed for floating-point quads based on texel values.
926
927	for (int i0 = minI0; i0 <= maxI0; i0++)
928	{
929		ColorLine	line0;
930		float		searchStep0;
931
932		{
933			const int	x0		= wrap(sampler.wrapS, i0  , w0);
934			const int	x1		= wrap(sampler.wrapS, i0+1, w0);
935			lookupLine(line0, level0, sampler, x0, x1, coordY);
936
937			if (texClass == TEXTURECHANNELCLASS_FLOATING_POINT)
938				searchStep0 = computeBilinearSearchStepFromFloatLine(prec, line0);
939			else
940				searchStep0 = cSearchStep;
941		}
942
943		const float	minA0	= de::clamp((uBounds0.x()-0.5f)-float(i0), 0.0f, 1.0f);
944		const float	maxA0	= de::clamp((uBounds0.y()-0.5f)-float(i0), 0.0f, 1.0f);
945
946		for (int i1 = minI1; i1 <= maxI1; i1++)
947		{
948			ColorLine	line1;
949			float		searchStep1;
950
951			{
952				const int	x0		= wrap(sampler.wrapS, i1  , w1);
953				const int	x1		= wrap(sampler.wrapS, i1+1, w1);
954				lookupLine(line1, level1, sampler, x0, x1, coordY);
955
956				if (texClass == TEXTURECHANNELCLASS_FLOATING_POINT)
957					searchStep1 = computeBilinearSearchStepFromFloatLine(prec, line1);
958				else
959					searchStep1 = cSearchStep;
960			}
961
962			const float	minA1	= de::clamp((uBounds1.x()-0.5f)-float(i1), 0.0f, 1.0f);
963			const float	maxA1	= de::clamp((uBounds1.y()-0.5f)-float(i1), 0.0f, 1.0f);
964
965			if (is1DTrilinearFilterResultValid(prec, line0, line1, Vec2(minA0, maxA0), Vec2(minA1, maxA1), fBounds, de::min(searchStep0, searchStep1), result))
966				return true;
967		}
968	}
969
970	return false;
971}
972
973static bool isLinearMipmapLinearSampleResultValid (const ConstPixelBufferAccess&	level0,
974												   const ConstPixelBufferAccess&	level1,
975												   const Sampler&					sampler,
976												   const LookupPrecision&			prec,
977												   const Vec2&						coord,
978												   const int						coordZ,
979												   const Vec2&						fBounds,
980												   const Vec4&						result)
981{
982	// \todo [2013-07-04 pyry] This is strictly not correct as coordinates between levels should be dependent.
983	//						   Right now this allows pairing any two valid bilinear quads.
984
985	const int					w0				= level0.getWidth();
986	const int					w1				= level1.getWidth();
987	const int					h0				= level0.getHeight();
988	const int					h1				= level1.getHeight();
989
990	const Vec2					uBounds0		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, w0,	coord.x(), prec.coordBits.x(), prec.uvwBits.x());
991	const Vec2					uBounds1		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, w1,	coord.x(), prec.coordBits.x(), prec.uvwBits.x());
992	const Vec2					vBounds0		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, h0,	coord.y(), prec.coordBits.y(), prec.uvwBits.y());
993	const Vec2					vBounds1		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, h1,	coord.y(), prec.coordBits.y(), prec.uvwBits.y());
994
995	// Integer coordinates - without wrap mode
996	const int					minI0			= deFloorFloatToInt32(uBounds0.x()-0.5f);
997	const int					maxI0			= deFloorFloatToInt32(uBounds0.y()-0.5f);
998	const int					minI1			= deFloorFloatToInt32(uBounds1.x()-0.5f);
999	const int					maxI1			= deFloorFloatToInt32(uBounds1.y()-0.5f);
1000	const int					minJ0			= deFloorFloatToInt32(vBounds0.x()-0.5f);
1001	const int					maxJ0			= deFloorFloatToInt32(vBounds0.y()-0.5f);
1002	const int					minJ1			= deFloorFloatToInt32(vBounds1.x()-0.5f);
1003	const int					maxJ1			= deFloorFloatToInt32(vBounds1.y()-0.5f);
1004
1005	const TextureChannelClass	texClass		= getTextureChannelClass(level0.getFormat().type);
1006	const float					cSearchStep		= texClass == TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT	? computeBilinearSearchStepForUnorm(prec) :
1007												  texClass == TEXTURECHANNELCLASS_SIGNED_FIXED_POINT	? computeBilinearSearchStepForSnorm(prec) :
1008												  0.0f; // Step is computed for floating-point quads based on texel values.
1009
1010	for (int j0 = minJ0; j0 <= maxJ0; j0++)
1011	{
1012		for (int i0 = minI0; i0 <= maxI0; i0++)
1013		{
1014			ColorQuad	quad0;
1015			float		searchStep0;
1016
1017			{
1018				const int	x0		= wrap(sampler.wrapS, i0  , w0);
1019				const int	x1		= wrap(sampler.wrapS, i0+1, w0);
1020				const int	y0		= wrap(sampler.wrapT, j0  , h0);
1021				const int	y1		= wrap(sampler.wrapT, j0+1, h0);
1022				lookupQuad(quad0, level0, sampler, x0, x1, y0, y1, coordZ);
1023
1024				if (texClass == TEXTURECHANNELCLASS_FLOATING_POINT)
1025					searchStep0 = computeBilinearSearchStepFromFloatQuad(prec, quad0);
1026				else
1027					searchStep0 = cSearchStep;
1028			}
1029
1030			const float	minA0	= de::clamp((uBounds0.x()-0.5f)-float(i0), 0.0f, 1.0f);
1031			const float	maxA0	= de::clamp((uBounds0.y()-0.5f)-float(i0), 0.0f, 1.0f);
1032			const float	minB0	= de::clamp((vBounds0.x()-0.5f)-float(j0), 0.0f, 1.0f);
1033			const float	maxB0	= de::clamp((vBounds0.y()-0.5f)-float(j0), 0.0f, 1.0f);
1034
1035			for (int j1 = minJ1; j1 <= maxJ1; j1++)
1036			{
1037				for (int i1 = minI1; i1 <= maxI1; i1++)
1038				{
1039					ColorQuad	quad1;
1040					float		searchStep1;
1041
1042					{
1043						const int	x0		= wrap(sampler.wrapS, i1  , w1);
1044						const int	x1		= wrap(sampler.wrapS, i1+1, w1);
1045						const int	y0		= wrap(sampler.wrapT, j1  , h1);
1046						const int	y1		= wrap(sampler.wrapT, j1+1, h1);
1047						lookupQuad(quad1, level1, sampler, x0, x1, y0, y1, coordZ);
1048
1049						if (texClass == TEXTURECHANNELCLASS_FLOATING_POINT)
1050							searchStep1 = computeBilinearSearchStepFromFloatQuad(prec, quad1);
1051						else
1052							searchStep1 = cSearchStep;
1053					}
1054
1055					const float	minA1	= de::clamp((uBounds1.x()-0.5f)-float(i1), 0.0f, 1.0f);
1056					const float	maxA1	= de::clamp((uBounds1.y()-0.5f)-float(i1), 0.0f, 1.0f);
1057					const float	minB1	= de::clamp((vBounds1.x()-0.5f)-float(j1), 0.0f, 1.0f);
1058					const float	maxB1	= de::clamp((vBounds1.y()-0.5f)-float(j1), 0.0f, 1.0f);
1059
1060					if (is2DTrilinearFilterResultValid(prec, quad0, quad1, Vec2(minA0, maxA0), Vec2(minB0, maxB0), Vec2(minA1, maxA1), Vec2(minB1, maxB1),
1061													   fBounds, de::min(searchStep0, searchStep1), result))
1062						return true;
1063				}
1064			}
1065		}
1066	}
1067
1068	return false;
1069}
1070
1071static bool isLinearMipmapLinearSampleResultValid (const ConstPixelBufferAccess&	level0,
1072												   const ConstPixelBufferAccess&	level1,
1073												   const Sampler&					sampler,
1074												   const LookupPrecision&			prec,
1075												   const Vec3&						coord,
1076												   const Vec2&						fBounds,
1077												   const Vec4&						result)
1078{
1079	// \todo [2013-07-04 pyry] This is strictly not correct as coordinates between levels should be dependent.
1080	//						   Right now this allows pairing any two valid bilinear quads.
1081
1082	const int					w0				= level0.getWidth();
1083	const int					w1				= level1.getWidth();
1084	const int					h0				= level0.getHeight();
1085	const int					h1				= level1.getHeight();
1086	const int					d0				= level0.getDepth();
1087	const int					d1				= level1.getDepth();
1088
1089	const Vec2					uBounds0		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, w0,	coord.x(), prec.coordBits.x(), prec.uvwBits.x());
1090	const Vec2					uBounds1		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, w1,	coord.x(), prec.coordBits.x(), prec.uvwBits.x());
1091	const Vec2					vBounds0		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, h0,	coord.y(), prec.coordBits.y(), prec.uvwBits.y());
1092	const Vec2					vBounds1		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, h1,	coord.y(), prec.coordBits.y(), prec.uvwBits.y());
1093	const Vec2					wBounds0		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, d0,	coord.z(), prec.coordBits.z(), prec.uvwBits.z());
1094	const Vec2					wBounds1		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, d1,	coord.z(), prec.coordBits.z(), prec.uvwBits.z());
1095
1096	// Integer coordinates - without wrap mode
1097	const int					minI0			= deFloorFloatToInt32(uBounds0.x()-0.5f);
1098	const int					maxI0			= deFloorFloatToInt32(uBounds0.y()-0.5f);
1099	const int					minI1			= deFloorFloatToInt32(uBounds1.x()-0.5f);
1100	const int					maxI1			= deFloorFloatToInt32(uBounds1.y()-0.5f);
1101	const int					minJ0			= deFloorFloatToInt32(vBounds0.x()-0.5f);
1102	const int					maxJ0			= deFloorFloatToInt32(vBounds0.y()-0.5f);
1103	const int					minJ1			= deFloorFloatToInt32(vBounds1.x()-0.5f);
1104	const int					maxJ1			= deFloorFloatToInt32(vBounds1.y()-0.5f);
1105	const int					minK0			= deFloorFloatToInt32(wBounds0.x()-0.5f);
1106	const int					maxK0			= deFloorFloatToInt32(wBounds0.y()-0.5f);
1107	const int					minK1			= deFloorFloatToInt32(wBounds1.x()-0.5f);
1108	const int					maxK1			= deFloorFloatToInt32(wBounds1.y()-0.5f);
1109
1110	const TextureChannelClass	texClass		= getTextureChannelClass(level0.getFormat().type);
1111	const float					cSearchStep		= texClass == TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT	? computeBilinearSearchStepForUnorm(prec) :
1112												  texClass == TEXTURECHANNELCLASS_SIGNED_FIXED_POINT	? computeBilinearSearchStepForSnorm(prec) :
1113												  0.0f; // Step is computed for floating-point quads based on texel values.
1114
1115	for (int k0 = minK0; k0 <= maxK0; k0++)
1116	{
1117		for (int j0 = minJ0; j0 <= maxJ0; j0++)
1118		{
1119			for (int i0 = minI0; i0 <= maxI0; i0++)
1120			{
1121				ColorQuad	quad00, quad01;
1122				float		searchStep0;
1123
1124				{
1125					const int	x0		= wrap(sampler.wrapS, i0  , w0);
1126					const int	x1		= wrap(sampler.wrapS, i0+1, w0);
1127					const int	y0		= wrap(sampler.wrapT, j0  , h0);
1128					const int	y1		= wrap(sampler.wrapT, j0+1, h0);
1129					const int	z0		= wrap(sampler.wrapR, k0  , d0);
1130					const int	z1		= wrap(sampler.wrapR, k0+1, d0);
1131					lookupQuad(quad00, level0, sampler, x0, x1, y0, y1, z0);
1132					lookupQuad(quad01, level0, sampler, x0, x1, y0, y1, z1);
1133
1134					if (texClass == TEXTURECHANNELCLASS_FLOATING_POINT)
1135						searchStep0 = de::min(computeBilinearSearchStepFromFloatQuad(prec, quad00), computeBilinearSearchStepFromFloatQuad(prec, quad01));
1136					else
1137						searchStep0 = cSearchStep;
1138				}
1139
1140				const float	minA0	= de::clamp((uBounds0.x()-0.5f)-float(i0), 0.0f, 1.0f);
1141				const float	maxA0	= de::clamp((uBounds0.y()-0.5f)-float(i0), 0.0f, 1.0f);
1142				const float	minB0	= de::clamp((vBounds0.x()-0.5f)-float(j0), 0.0f, 1.0f);
1143				const float	maxB0	= de::clamp((vBounds0.y()-0.5f)-float(j0), 0.0f, 1.0f);
1144				const float	minC0	= de::clamp((wBounds0.x()-0.5f)-float(k0), 0.0f, 1.0f);
1145				const float	maxC0	= de::clamp((wBounds0.y()-0.5f)-float(k0), 0.0f, 1.0f);
1146
1147				for (int k1 = minK1; k1 <= maxK1; k1++)
1148				{
1149					for (int j1 = minJ1; j1 <= maxJ1; j1++)
1150					{
1151						for (int i1 = minI1; i1 <= maxI1; i1++)
1152						{
1153							ColorQuad	quad10, quad11;
1154							float		searchStep1;
1155
1156							{
1157								const int	x0		= wrap(sampler.wrapS, i1  , w1);
1158								const int	x1		= wrap(sampler.wrapS, i1+1, w1);
1159								const int	y0		= wrap(sampler.wrapT, j1  , h1);
1160								const int	y1		= wrap(sampler.wrapT, j1+1, h1);
1161								const int	z0		= wrap(sampler.wrapR, k1  , d1);
1162								const int	z1		= wrap(sampler.wrapR, k1+1, d1);
1163								lookupQuad(quad10, level1, sampler, x0, x1, y0, y1, z0);
1164								lookupQuad(quad11, level1, sampler, x0, x1, y0, y1, z1);
1165
1166								if (texClass == TEXTURECHANNELCLASS_FLOATING_POINT)
1167									searchStep1 = de::min(computeBilinearSearchStepFromFloatQuad(prec, quad10), computeBilinearSearchStepFromFloatQuad(prec, quad11));
1168								else
1169									searchStep1 = cSearchStep;
1170							}
1171
1172							const float	minA1	= de::clamp((uBounds1.x()-0.5f)-float(i1), 0.0f, 1.0f);
1173							const float	maxA1	= de::clamp((uBounds1.y()-0.5f)-float(i1), 0.0f, 1.0f);
1174							const float	minB1	= de::clamp((vBounds1.x()-0.5f)-float(j1), 0.0f, 1.0f);
1175							const float	maxB1	= de::clamp((vBounds1.y()-0.5f)-float(j1), 0.0f, 1.0f);
1176							const float	minC1	= de::clamp((wBounds1.x()-0.5f)-float(k1), 0.0f, 1.0f);
1177							const float	maxC1	= de::clamp((wBounds1.y()-0.5f)-float(k1), 0.0f, 1.0f);
1178
1179							if (is3DTrilinearFilterResultValid(prec, quad00, quad01, quad10, quad11,
1180															   Vec2(minA0, maxA0), Vec2(minB0, maxB0), Vec2(minC0, maxC0),
1181															   Vec2(minA1, maxA1), Vec2(minB1, maxB1), Vec2(minC1, maxC1),
1182															   fBounds, de::min(searchStep0, searchStep1), result))
1183								return true;
1184						}
1185					}
1186				}
1187			}
1188		}
1189	}
1190
1191	return false;
1192}
1193
1194static bool isLevelSampleResultValid (const ConstPixelBufferAccess&		level,
1195									  const Sampler&					sampler,
1196									  const Sampler::FilterMode			filterMode,
1197									  const LookupPrecision&			prec,
1198									  const float						coordX,
1199									  const int							coordY,
1200									  const Vec4&						result)
1201{
1202	if (filterMode == Sampler::LINEAR)
1203		return isLinearSampleResultValid(level, sampler, prec, coordX, coordY, result);
1204	else
1205		return isNearestSampleResultValid(level, sampler, prec, coordX, coordY, result);
1206}
1207
1208static bool isLevelSampleResultValid (const ConstPixelBufferAccess&		level,
1209									  const Sampler&					sampler,
1210									  const Sampler::FilterMode			filterMode,
1211									  const LookupPrecision&			prec,
1212									  const Vec2&						coord,
1213									  const int							coordZ,
1214									  const Vec4&						result)
1215{
1216	if (filterMode == Sampler::LINEAR)
1217		return isLinearSampleResultValid(level, sampler, prec, coord, coordZ, result);
1218	else
1219		return isNearestSampleResultValid(level, sampler, prec, coord, coordZ, result);
1220}
1221
1222static bool isMipmapLinearSampleResultValid (const ConstPixelBufferAccess&		level0,
1223										     const ConstPixelBufferAccess&		level1,
1224											 const Sampler&						sampler,
1225										     const Sampler::FilterMode			levelFilter,
1226										     const LookupPrecision&				prec,
1227										     const float						coordX,
1228											 const int							coordY,
1229										     const Vec2&						fBounds,
1230										     const Vec4&						result)
1231{
1232	if (levelFilter == Sampler::LINEAR)
1233		return isLinearMipmapLinearSampleResultValid(level0, level1, sampler, prec, coordX, coordY, fBounds, result);
1234	else
1235		return isNearestMipmapLinearSampleResultValid(level0, level1, sampler, prec, coordX, coordY, fBounds, result);
1236}
1237
1238static bool isMipmapLinearSampleResultValid (const ConstPixelBufferAccess&		level0,
1239										     const ConstPixelBufferAccess&		level1,
1240											 const Sampler&						sampler,
1241										     const Sampler::FilterMode			levelFilter,
1242										     const LookupPrecision&				prec,
1243										     const Vec2&						coord,
1244											 const int							coordZ,
1245										     const Vec2&						fBounds,
1246										     const Vec4&						result)
1247{
1248	if (levelFilter == Sampler::LINEAR)
1249		return isLinearMipmapLinearSampleResultValid(level0, level1, sampler, prec, coord, coordZ, fBounds, result);
1250	else
1251		return isNearestMipmapLinearSampleResultValid(level0, level1, sampler, prec, coord, coordZ, fBounds, result);
1252}
1253
1254bool isLookupResultValid (const Texture2DView& texture, const Sampler& sampler, const LookupPrecision& prec, const Vec2& coord, const Vec2& lodBounds, const Vec4& result)
1255{
1256	const float		minLod			= lodBounds.x();
1257	const float		maxLod			= lodBounds.y();
1258	const bool		canBeMagnified	= minLod <= sampler.lodThreshold;
1259	const bool		canBeMinified	= maxLod > sampler.lodThreshold;
1260
1261	DE_ASSERT(isSamplerSupported(sampler));
1262
1263	if (canBeMagnified)
1264	{
1265		if (isLevelSampleResultValid(texture.getLevel(0), sampler, sampler.magFilter, prec, coord, 0, result))
1266			return true;
1267	}
1268
1269	if (canBeMinified)
1270	{
1271		const bool	isNearestMipmap	= isNearestMipmapFilter(sampler.minFilter);
1272		const bool	isLinearMipmap	= isLinearMipmapFilter(sampler.minFilter);
1273		const int	minTexLevel		= 0;
1274		const int	maxTexLevel		= texture.getNumLevels()-1;
1275
1276		DE_ASSERT(minTexLevel <= maxTexLevel);
1277
1278		if (isLinearMipmap && minTexLevel < maxTexLevel)
1279		{
1280			const int		minLevel		= de::clamp((int)deFloatFloor(minLod), minTexLevel, maxTexLevel-1);
1281			const int		maxLevel		= de::clamp((int)deFloatFloor(maxLod), minTexLevel, maxTexLevel-1);
1282
1283			DE_ASSERT(minLevel <= maxLevel);
1284
1285			for (int level = minLevel; level <= maxLevel; level++)
1286			{
1287				const float		minF	= de::clamp(minLod - float(level), 0.0f, 1.0f);
1288				const float		maxF	= de::clamp(maxLod - float(level), 0.0f, 1.0f);
1289
1290				if (isMipmapLinearSampleResultValid(texture.getLevel(level), texture.getLevel(level+1), sampler, getLevelFilter(sampler.minFilter), prec, coord, 0, Vec2(minF, maxF), result))
1291					return true;
1292			}
1293		}
1294		else if (isNearestMipmap)
1295		{
1296			// \note The accurate formula for nearest mipmapping is level = ceil(lod + 0.5) - 1 but Khronos has made
1297			//		 decision to allow floor(lod + 0.5) as well.
1298			const int		minLevel		= de::clamp((int)deFloatCeil(minLod + 0.5f) - 1,	minTexLevel, maxTexLevel);
1299			const int		maxLevel		= de::clamp((int)deFloatFloor(maxLod + 0.5f),		minTexLevel, maxTexLevel);
1300
1301			DE_ASSERT(minLevel <= maxLevel);
1302
1303			for (int level = minLevel; level <= maxLevel; level++)
1304			{
1305				if (isLevelSampleResultValid(texture.getLevel(level), sampler, getLevelFilter(sampler.minFilter), prec, coord, 0, result))
1306					return true;
1307			}
1308		}
1309		else
1310		{
1311			if (isLevelSampleResultValid(texture.getLevel(0), sampler, sampler.minFilter, prec, coord, 0, result))
1312				return true;
1313		}
1314	}
1315
1316	return false;
1317}
1318
1319bool isLookupResultValid (const Texture1DView& texture, const Sampler& sampler, const LookupPrecision& prec, const float coord, const Vec2& lodBounds, const Vec4& result)
1320{
1321	const float		minLod			= lodBounds.x();
1322	const float		maxLod			= lodBounds.y();
1323	const bool		canBeMagnified	= minLod <= sampler.lodThreshold;
1324	const bool		canBeMinified	= maxLod > sampler.lodThreshold;
1325
1326	DE_ASSERT(isSamplerSupported(sampler));
1327
1328	if (canBeMagnified)
1329	{
1330		if (isLevelSampleResultValid(texture.getLevel(0), sampler, sampler.magFilter, prec, coord, 0, result))
1331			return true;
1332	}
1333
1334	if (canBeMinified)
1335	{
1336		const bool	isNearestMipmap	= isNearestMipmapFilter(sampler.minFilter);
1337		const bool	isLinearMipmap	= isLinearMipmapFilter(sampler.minFilter);
1338		const int	minTexLevel		= 0;
1339		const int	maxTexLevel		= texture.getNumLevels()-1;
1340
1341		DE_ASSERT(minTexLevel <= maxTexLevel);
1342
1343		if (isLinearMipmap && minTexLevel < maxTexLevel)
1344		{
1345			const int		minLevel		= de::clamp((int)deFloatFloor(minLod), minTexLevel, maxTexLevel-1);
1346			const int		maxLevel		= de::clamp((int)deFloatFloor(maxLod), minTexLevel, maxTexLevel-1);
1347
1348			DE_ASSERT(minLevel <= maxLevel);
1349
1350			for (int level = minLevel; level <= maxLevel; level++)
1351			{
1352				const float		minF	= de::clamp(minLod - float(level), 0.0f, 1.0f);
1353				const float		maxF	= de::clamp(maxLod - float(level), 0.0f, 1.0f);
1354
1355				if (isMipmapLinearSampleResultValid(texture.getLevel(level), texture.getLevel(level+1), sampler, getLevelFilter(sampler.minFilter), prec, coord, 0, Vec2(minF, maxF), result))
1356					return true;
1357			}
1358		}
1359		else if (isNearestMipmap)
1360		{
1361			// \note The accurate formula for nearest mipmapping is level = ceil(lod + 0.5) - 1 but Khronos has made
1362			//		 decision to allow floor(lod + 0.5) as well.
1363			const int		minLevel		= de::clamp((int)deFloatCeil(minLod + 0.5f) - 1,	minTexLevel, maxTexLevel);
1364			const int		maxLevel		= de::clamp((int)deFloatFloor(maxLod + 0.5f),		minTexLevel, maxTexLevel);
1365
1366			DE_ASSERT(minLevel <= maxLevel);
1367
1368			for (int level = minLevel; level <= maxLevel; level++)
1369			{
1370				if (isLevelSampleResultValid(texture.getLevel(level), sampler, getLevelFilter(sampler.minFilter), prec, coord, 0, result))
1371					return true;
1372			}
1373		}
1374		else
1375		{
1376			if (isLevelSampleResultValid(texture.getLevel(0), sampler, sampler.minFilter, prec, coord, 0, result))
1377				return true;
1378		}
1379	}
1380
1381	return false;
1382}
1383
1384static bool isSeamlessLinearSampleResultValid (const ConstPixelBufferAccess (&faces)[CUBEFACE_LAST],
1385											   const Sampler&				sampler,
1386											   const LookupPrecision&		prec,
1387											   const CubeFaceFloatCoords&	coords,
1388											   const Vec4&					result)
1389{
1390	const int					size			= faces[coords.face].getWidth();
1391
1392	const Vec2					uBounds			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, size, coords.s, prec.coordBits.x(), prec.uvwBits.x());
1393	const Vec2					vBounds			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, size, coords.t, prec.coordBits.y(), prec.uvwBits.y());
1394
1395	// Integer coordinate bounds for (x0,y0) - without wrap mode
1396	const int					minI			= deFloorFloatToInt32(uBounds.x()-0.5f);
1397	const int					maxI			= deFloorFloatToInt32(uBounds.y()-0.5f);
1398	const int					minJ			= deFloorFloatToInt32(vBounds.x()-0.5f);
1399	const int					maxJ			= deFloorFloatToInt32(vBounds.y()-0.5f);
1400
1401	const TextureChannelClass	texClass		= getTextureChannelClass(faces[coords.face].getFormat().type);
1402	float						searchStep		= texClass == TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT	? computeBilinearSearchStepForUnorm(prec) :
1403												  texClass == TEXTURECHANNELCLASS_SIGNED_FIXED_POINT	? computeBilinearSearchStepForSnorm(prec) :
1404												  0.0f; // Step is computed for floating-point quads based on texel values.
1405
1406	for (int j = minJ; j <= maxJ; j++)
1407	{
1408		for (int i = minI; i <= maxI; i++)
1409		{
1410			const CubeFaceIntCoords	c00	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i+0, j+0)), size);
1411			const CubeFaceIntCoords	c10	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i+1, j+0)), size);
1412			const CubeFaceIntCoords	c01	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i+0, j+1)), size);
1413			const CubeFaceIntCoords	c11	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i+1, j+1)), size);
1414
1415			// If any of samples is out of both edges, implementations can do pretty much anything according to spec.
1416			// \todo [2013-07-08 pyry] Test the special case where all corner pixels have exactly the same color.
1417			if (c00.face == CUBEFACE_LAST || c01.face == CUBEFACE_LAST || c10.face == CUBEFACE_LAST || c11.face == CUBEFACE_LAST)
1418				return true;
1419
1420			// Bounds for filtering factors
1421			const float	minA	= de::clamp((uBounds.x()-0.5f)-float(i), 0.0f, 1.0f);
1422			const float	maxA	= de::clamp((uBounds.y()-0.5f)-float(i), 0.0f, 1.0f);
1423			const float	minB	= de::clamp((vBounds.x()-0.5f)-float(j), 0.0f, 1.0f);
1424			const float	maxB	= de::clamp((vBounds.y()-0.5f)-float(j), 0.0f, 1.0f);
1425
1426			ColorQuad quad;
1427			quad.p00 = lookup<float>(faces[c00.face], sampler, c00.s, c00.t, 0);
1428			quad.p10 = lookup<float>(faces[c10.face], sampler, c10.s, c10.t, 0);
1429			quad.p01 = lookup<float>(faces[c01.face], sampler, c01.s, c01.t, 0);
1430			quad.p11 = lookup<float>(faces[c11.face], sampler, c11.s, c11.t, 0);
1431
1432			if (texClass == TEXTURECHANNELCLASS_FLOATING_POINT)
1433				searchStep = computeBilinearSearchStepFromFloatQuad(prec, quad);
1434
1435			if (isBilinearRangeValid(prec, quad, Vec2(minA, maxA), Vec2(minB, maxB), searchStep, result))
1436				return true;
1437		}
1438	}
1439
1440	return false;
1441}
1442
1443static bool isSeamplessLinearMipmapLinearSampleResultValid (const ConstPixelBufferAccess	(&faces0)[CUBEFACE_LAST],
1444															const ConstPixelBufferAccess	(&faces1)[CUBEFACE_LAST],
1445															const Sampler&					sampler,
1446															const LookupPrecision&			prec,
1447															const CubeFaceFloatCoords&		coords,
1448															const Vec2&						fBounds,
1449															const Vec4&						result)
1450{
1451	// \todo [2013-07-04 pyry] This is strictly not correct as coordinates between levels should be dependent.
1452	//						   Right now this allows pairing any two valid bilinear quads.
1453
1454	const int					size0			= faces0[coords.face].getWidth();
1455	const int					size1			= faces1[coords.face].getWidth();
1456
1457	const Vec2					uBounds0		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, size0,	coords.s, prec.coordBits.x(), prec.uvwBits.x());
1458	const Vec2					uBounds1		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, size1,	coords.s, prec.coordBits.x(), prec.uvwBits.x());
1459	const Vec2					vBounds0		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, size0,	coords.t, prec.coordBits.y(), prec.uvwBits.y());
1460	const Vec2					vBounds1		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, size1,	coords.t, prec.coordBits.y(), prec.uvwBits.y());
1461
1462	// Integer coordinates - without wrap mode
1463	const int					minI0			= deFloorFloatToInt32(uBounds0.x()-0.5f);
1464	const int					maxI0			= deFloorFloatToInt32(uBounds0.y()-0.5f);
1465	const int					minI1			= deFloorFloatToInt32(uBounds1.x()-0.5f);
1466	const int					maxI1			= deFloorFloatToInt32(uBounds1.y()-0.5f);
1467	const int					minJ0			= deFloorFloatToInt32(vBounds0.x()-0.5f);
1468	const int					maxJ0			= deFloorFloatToInt32(vBounds0.y()-0.5f);
1469	const int					minJ1			= deFloorFloatToInt32(vBounds1.x()-0.5f);
1470	const int					maxJ1			= deFloorFloatToInt32(vBounds1.y()-0.5f);
1471
1472	const TextureChannelClass	texClass		= getTextureChannelClass(faces0[coords.face].getFormat().type);
1473	const float					cSearchStep		= texClass == TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT	? computeBilinearSearchStepForUnorm(prec) :
1474												  texClass == TEXTURECHANNELCLASS_SIGNED_FIXED_POINT	? computeBilinearSearchStepForSnorm(prec) :
1475												  0.0f; // Step is computed for floating-point quads based on texel values.
1476
1477	for (int j0 = minJ0; j0 <= maxJ0; j0++)
1478	{
1479		for (int i0 = minI0; i0 <= maxI0; i0++)
1480		{
1481			ColorQuad	quad0;
1482			float		searchStep0;
1483
1484			{
1485				const CubeFaceIntCoords	c00	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i0+0, j0+0)), size0);
1486				const CubeFaceIntCoords	c10	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i0+1, j0+0)), size0);
1487				const CubeFaceIntCoords	c01	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i0+0, j0+1)), size0);
1488				const CubeFaceIntCoords	c11	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i0+1, j0+1)), size0);
1489
1490				// If any of samples is out of both edges, implementations can do pretty much anything according to spec.
1491				// \todo [2013-07-08 pyry] Test the special case where all corner pixels have exactly the same color.
1492				if (c00.face == CUBEFACE_LAST || c01.face == CUBEFACE_LAST || c10.face == CUBEFACE_LAST || c11.face == CUBEFACE_LAST)
1493					return true;
1494
1495				quad0.p00 = lookup<float>(faces0[c00.face], sampler, c00.s, c00.t, 0);
1496				quad0.p10 = lookup<float>(faces0[c10.face], sampler, c10.s, c10.t, 0);
1497				quad0.p01 = lookup<float>(faces0[c01.face], sampler, c01.s, c01.t, 0);
1498				quad0.p11 = lookup<float>(faces0[c11.face], sampler, c11.s, c11.t, 0);
1499
1500				if (texClass == TEXTURECHANNELCLASS_FLOATING_POINT)
1501					searchStep0 = computeBilinearSearchStepFromFloatQuad(prec, quad0);
1502				else
1503					searchStep0 = cSearchStep;
1504			}
1505
1506			const float	minA0	= de::clamp((uBounds0.x()-0.5f)-float(i0), 0.0f, 1.0f);
1507			const float	maxA0	= de::clamp((uBounds0.y()-0.5f)-float(i0), 0.0f, 1.0f);
1508			const float	minB0	= de::clamp((vBounds0.x()-0.5f)-float(j0), 0.0f, 1.0f);
1509			const float	maxB0	= de::clamp((vBounds0.y()-0.5f)-float(j0), 0.0f, 1.0f);
1510
1511			for (int j1 = minJ1; j1 <= maxJ1; j1++)
1512			{
1513				for (int i1 = minI1; i1 <= maxI1; i1++)
1514				{
1515					ColorQuad	quad1;
1516					float		searchStep1;
1517
1518					{
1519						const CubeFaceIntCoords	c00	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i1+0, j1+0)), size1);
1520						const CubeFaceIntCoords	c10	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i1+1, j1+0)), size1);
1521						const CubeFaceIntCoords	c01	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i1+0, j1+1)), size1);
1522						const CubeFaceIntCoords	c11	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i1+1, j1+1)), size1);
1523
1524						if (c00.face == CUBEFACE_LAST || c01.face == CUBEFACE_LAST || c10.face == CUBEFACE_LAST || c11.face == CUBEFACE_LAST)
1525							return true;
1526
1527						quad1.p00 = lookup<float>(faces1[c00.face], sampler, c00.s, c00.t, 0);
1528						quad1.p10 = lookup<float>(faces1[c10.face], sampler, c10.s, c10.t, 0);
1529						quad1.p01 = lookup<float>(faces1[c01.face], sampler, c01.s, c01.t, 0);
1530						quad1.p11 = lookup<float>(faces1[c11.face], sampler, c11.s, c11.t, 0);
1531
1532						if (texClass == TEXTURECHANNELCLASS_FLOATING_POINT)
1533							searchStep1 = computeBilinearSearchStepFromFloatQuad(prec, quad1);
1534						else
1535							searchStep1 = cSearchStep;
1536					}
1537
1538					const float	minA1	= de::clamp((uBounds1.x()-0.5f)-float(i1), 0.0f, 1.0f);
1539					const float	maxA1	= de::clamp((uBounds1.y()-0.5f)-float(i1), 0.0f, 1.0f);
1540					const float	minB1	= de::clamp((vBounds1.x()-0.5f)-float(j1), 0.0f, 1.0f);
1541					const float	maxB1	= de::clamp((vBounds1.y()-0.5f)-float(j1), 0.0f, 1.0f);
1542
1543					if (is2DTrilinearFilterResultValid(prec, quad0, quad1, Vec2(minA0, maxA0), Vec2(minB0, maxB0), Vec2(minA1, maxA1), Vec2(minB1, maxB1),
1544													   fBounds, de::min(searchStep0, searchStep1), result))
1545						return true;
1546				}
1547			}
1548		}
1549	}
1550
1551	return false;
1552}
1553
1554static bool isCubeLevelSampleResultValid (const ConstPixelBufferAccess		(&level)[CUBEFACE_LAST],
1555										  const Sampler&					sampler,
1556										  const Sampler::FilterMode			filterMode,
1557										  const LookupPrecision&			prec,
1558										  const CubeFaceFloatCoords&		coords,
1559										  const Vec4&						result)
1560{
1561	if (filterMode == Sampler::LINEAR)
1562	{
1563		if (sampler.seamlessCubeMap)
1564			return isSeamlessLinearSampleResultValid(level, sampler, prec, coords, result);
1565		else
1566			return isLinearSampleResultValid(level[coords.face], sampler, prec, Vec2(coords.s, coords.t), 0, result);
1567	}
1568	else
1569		return isNearestSampleResultValid(level[coords.face], sampler, prec, Vec2(coords.s, coords.t), 0, result);
1570}
1571
1572static bool isCubeMipmapLinearSampleResultValid (const ConstPixelBufferAccess	(&faces0)[CUBEFACE_LAST],
1573												const ConstPixelBufferAccess	(&faces1)[CUBEFACE_LAST],
1574												 const Sampler&					sampler,
1575												 const Sampler::FilterMode		levelFilter,
1576												 const LookupPrecision&			prec,
1577												 const CubeFaceFloatCoords&		coords,
1578												 const Vec2&					fBounds,
1579												 const Vec4&					result)
1580{
1581	if (levelFilter == Sampler::LINEAR)
1582	{
1583		if (sampler.seamlessCubeMap)
1584			return isSeamplessLinearMipmapLinearSampleResultValid(faces0, faces1, sampler, prec, coords, fBounds, result);
1585		else
1586			return isLinearMipmapLinearSampleResultValid(faces0[coords.face], faces1[coords.face], sampler, prec, Vec2(coords.s, coords.t), 0, fBounds, result);
1587	}
1588	else
1589		return isNearestMipmapLinearSampleResultValid(faces0[coords.face], faces1[coords.face], sampler, prec, Vec2(coords.s, coords.t), 0, fBounds, result);
1590}
1591
1592static void getCubeLevelFaces (const TextureCubeView& texture, const int levelNdx, ConstPixelBufferAccess (&out)[CUBEFACE_LAST])
1593{
1594	for (int faceNdx = 0; faceNdx < CUBEFACE_LAST; faceNdx++)
1595		out[faceNdx] = texture.getLevelFace(levelNdx, (CubeFace)faceNdx);
1596}
1597
1598bool isLookupResultValid (const TextureCubeView& texture, const Sampler& sampler, const LookupPrecision& prec, const Vec3& coord, const Vec2& lodBounds, const Vec4& result)
1599{
1600	int			numPossibleFaces				= 0;
1601	CubeFace	possibleFaces[CUBEFACE_LAST];
1602
1603	DE_ASSERT(isSamplerSupported(sampler));
1604
1605	getPossibleCubeFaces(coord, prec.coordBits, &possibleFaces[0], numPossibleFaces);
1606
1607	if (numPossibleFaces == 0)
1608		return true; // Result is undefined.
1609
1610	for (int tryFaceNdx = 0; tryFaceNdx < numPossibleFaces; tryFaceNdx++)
1611	{
1612		const CubeFaceFloatCoords	faceCoords		(possibleFaces[tryFaceNdx], projectToFace(possibleFaces[tryFaceNdx], coord));
1613		const float					minLod			= lodBounds.x();
1614		const float					maxLod			= lodBounds.y();
1615		const bool					canBeMagnified	= minLod <= sampler.lodThreshold;
1616		const bool					canBeMinified	= maxLod > sampler.lodThreshold;
1617
1618		if (canBeMagnified)
1619		{
1620			ConstPixelBufferAccess faces[CUBEFACE_LAST];
1621			getCubeLevelFaces(texture, 0, faces);
1622
1623			if (isCubeLevelSampleResultValid(faces, sampler, sampler.magFilter, prec, faceCoords, result))
1624				return true;
1625		}
1626
1627		if (canBeMinified)
1628		{
1629			const bool	isNearestMipmap	= isNearestMipmapFilter(sampler.minFilter);
1630			const bool	isLinearMipmap	= isLinearMipmapFilter(sampler.minFilter);
1631			const int	minTexLevel		= 0;
1632			const int	maxTexLevel		= texture.getNumLevels()-1;
1633
1634			DE_ASSERT(minTexLevel <= maxTexLevel);
1635
1636			if (isLinearMipmap && minTexLevel < maxTexLevel)
1637			{
1638				const int	minLevel	= de::clamp((int)deFloatFloor(minLod), minTexLevel, maxTexLevel-1);
1639				const int	maxLevel	= de::clamp((int)deFloatFloor(maxLod), minTexLevel, maxTexLevel-1);
1640
1641				DE_ASSERT(minLevel <= maxLevel);
1642
1643				for (int levelNdx = minLevel; levelNdx <= maxLevel; levelNdx++)
1644				{
1645					const float				minF	= de::clamp(minLod - float(levelNdx), 0.0f, 1.0f);
1646					const float				maxF	= de::clamp(maxLod - float(levelNdx), 0.0f, 1.0f);
1647
1648					ConstPixelBufferAccess	faces0[CUBEFACE_LAST];
1649					ConstPixelBufferAccess	faces1[CUBEFACE_LAST];
1650
1651					getCubeLevelFaces(texture, levelNdx,		faces0);
1652					getCubeLevelFaces(texture, levelNdx + 1,	faces1);
1653
1654					if (isCubeMipmapLinearSampleResultValid(faces0, faces1, sampler, getLevelFilter(sampler.minFilter), prec, faceCoords, Vec2(minF, maxF), result))
1655						return true;
1656				}
1657			}
1658			else if (isNearestMipmap)
1659			{
1660				// \note The accurate formula for nearest mipmapping is level = ceil(lod + 0.5) - 1 but Khronos has made
1661				//		 decision to allow floor(lod + 0.5) as well.
1662				const int	minLevel	= de::clamp((int)deFloatCeil(minLod + 0.5f) - 1,	minTexLevel, maxTexLevel);
1663				const int	maxLevel	= de::clamp((int)deFloatFloor(maxLod + 0.5f),		minTexLevel, maxTexLevel);
1664
1665				DE_ASSERT(minLevel <= maxLevel);
1666
1667				for (int levelNdx = minLevel; levelNdx <= maxLevel; levelNdx++)
1668				{
1669					ConstPixelBufferAccess faces[CUBEFACE_LAST];
1670					getCubeLevelFaces(texture, levelNdx, faces);
1671
1672					if (isCubeLevelSampleResultValid(faces, sampler, getLevelFilter(sampler.minFilter), prec, faceCoords, result))
1673						return true;
1674				}
1675			}
1676			else
1677			{
1678				ConstPixelBufferAccess faces[CUBEFACE_LAST];
1679				getCubeLevelFaces(texture, 0, faces);
1680
1681				if (isCubeLevelSampleResultValid(faces, sampler, sampler.minFilter, prec, faceCoords, result))
1682					return true;
1683			}
1684		}
1685	}
1686
1687	return false;
1688}
1689
1690static inline IVec2 computeLayerRange (int numLayers, int numCoordBits, float layerCoord)
1691{
1692	const float	err		= computeFloatingPointError(layerCoord, numCoordBits);
1693	const int	minL	= (int)deFloatFloor(layerCoord - err + 0.5f);		// Round down
1694	const int	maxL	= (int)deFloatCeil(layerCoord + err + 0.5f) - 1;	// Round up
1695
1696	DE_ASSERT(minL <= maxL);
1697
1698	return IVec2(de::clamp(minL, 0, numLayers-1), de::clamp(maxL, 0, numLayers-1));
1699}
1700
1701bool isLookupResultValid (const Texture1DArrayView& texture, const Sampler& sampler, const LookupPrecision& prec, const Vec2& coord, const Vec2& lodBounds, const Vec4& result)
1702{
1703	const IVec2		layerRange		= computeLayerRange(texture.getNumLayers(), prec.coordBits.y(), coord.y());
1704	const float		coordX			= coord.x();
1705	const float		minLod			= lodBounds.x();
1706	const float		maxLod			= lodBounds.y();
1707	const bool		canBeMagnified	= minLod <= sampler.lodThreshold;
1708	const bool		canBeMinified	= maxLod > sampler.lodThreshold;
1709
1710	DE_ASSERT(isSamplerSupported(sampler));
1711
1712	for (int layer = layerRange.x(); layer <= layerRange.y(); layer++)
1713	{
1714		if (canBeMagnified)
1715		{
1716			if (isLevelSampleResultValid(texture.getLevel(0), sampler, sampler.magFilter, prec, coordX, layer, result))
1717				return true;
1718		}
1719
1720		if (canBeMinified)
1721		{
1722			const bool	isNearestMipmap	= isNearestMipmapFilter(sampler.minFilter);
1723			const bool	isLinearMipmap	= isLinearMipmapFilter(sampler.minFilter);
1724			const int	minTexLevel		= 0;
1725			const int	maxTexLevel		= texture.getNumLevels()-1;
1726
1727			DE_ASSERT(minTexLevel <= maxTexLevel);
1728
1729			if (isLinearMipmap && minTexLevel < maxTexLevel)
1730			{
1731				const int		minLevel		= de::clamp((int)deFloatFloor(minLod), minTexLevel, maxTexLevel-1);
1732				const int		maxLevel		= de::clamp((int)deFloatFloor(maxLod), minTexLevel, maxTexLevel-1);
1733
1734				DE_ASSERT(minLevel <= maxLevel);
1735
1736				for (int level = minLevel; level <= maxLevel; level++)
1737				{
1738					const float		minF	= de::clamp(minLod - float(level), 0.0f, 1.0f);
1739					const float		maxF	= de::clamp(maxLod - float(level), 0.0f, 1.0f);
1740
1741					if (isMipmapLinearSampleResultValid(texture.getLevel(level), texture.getLevel(level+1), sampler, getLevelFilter(sampler.minFilter), prec, coordX, layer, Vec2(minF, maxF), result))
1742						return true;
1743				}
1744			}
1745			else if (isNearestMipmap)
1746			{
1747				// \note The accurate formula for nearest mipmapping is level = ceil(lod + 0.5) - 1 but Khronos has made
1748				//		 decision to allow floor(lod + 0.5) as well.
1749				const int		minLevel		= de::clamp((int)deFloatCeil(minLod + 0.5f) - 1,	minTexLevel, maxTexLevel);
1750				const int		maxLevel		= de::clamp((int)deFloatFloor(maxLod + 0.5f),		minTexLevel, maxTexLevel);
1751
1752				DE_ASSERT(minLevel <= maxLevel);
1753
1754				for (int level = minLevel; level <= maxLevel; level++)
1755				{
1756					if (isLevelSampleResultValid(texture.getLevel(level), sampler, getLevelFilter(sampler.minFilter), prec, coordX, layer, result))
1757						return true;
1758				}
1759			}
1760			else
1761			{
1762				if (isLevelSampleResultValid(texture.getLevel(0), sampler, sampler.minFilter, prec, coordX, layer, result))
1763					return true;
1764			}
1765		}
1766	}
1767
1768	return false;
1769}
1770
1771bool isLookupResultValid (const Texture2DArrayView& texture, const Sampler& sampler, const LookupPrecision& prec, const Vec3& coord, const Vec2& lodBounds, const Vec4& result)
1772{
1773	const IVec2		layerRange		= computeLayerRange(texture.getNumLayers(), prec.coordBits.z(), coord.z());
1774	const Vec2		coordXY			= coord.swizzle(0,1);
1775	const float		minLod			= lodBounds.x();
1776	const float		maxLod			= lodBounds.y();
1777	const bool		canBeMagnified	= minLod <= sampler.lodThreshold;
1778	const bool		canBeMinified	= maxLod > sampler.lodThreshold;
1779
1780	DE_ASSERT(isSamplerSupported(sampler));
1781
1782	for (int layer = layerRange.x(); layer <= layerRange.y(); layer++)
1783	{
1784		if (canBeMagnified)
1785		{
1786			if (isLevelSampleResultValid(texture.getLevel(0), sampler, sampler.magFilter, prec, coordXY, layer, result))
1787				return true;
1788		}
1789
1790		if (canBeMinified)
1791		{
1792			const bool	isNearestMipmap	= isNearestMipmapFilter(sampler.minFilter);
1793			const bool	isLinearMipmap	= isLinearMipmapFilter(sampler.minFilter);
1794			const int	minTexLevel		= 0;
1795			const int	maxTexLevel		= texture.getNumLevels()-1;
1796
1797			DE_ASSERT(minTexLevel <= maxTexLevel);
1798
1799			if (isLinearMipmap && minTexLevel < maxTexLevel)
1800			{
1801				const int		minLevel		= de::clamp((int)deFloatFloor(minLod), minTexLevel, maxTexLevel-1);
1802				const int		maxLevel		= de::clamp((int)deFloatFloor(maxLod), minTexLevel, maxTexLevel-1);
1803
1804				DE_ASSERT(minLevel <= maxLevel);
1805
1806				for (int level = minLevel; level <= maxLevel; level++)
1807				{
1808					const float		minF	= de::clamp(minLod - float(level), 0.0f, 1.0f);
1809					const float		maxF	= de::clamp(maxLod - float(level), 0.0f, 1.0f);
1810
1811					if (isMipmapLinearSampleResultValid(texture.getLevel(level), texture.getLevel(level+1), sampler, getLevelFilter(sampler.minFilter), prec, coordXY, layer, Vec2(minF, maxF), result))
1812						return true;
1813				}
1814			}
1815			else if (isNearestMipmap)
1816			{
1817				// \note The accurate formula for nearest mipmapping is level = ceil(lod + 0.5) - 1 but Khronos has made
1818				//		 decision to allow floor(lod + 0.5) as well.
1819				const int		minLevel		= de::clamp((int)deFloatCeil(minLod + 0.5f) - 1,	minTexLevel, maxTexLevel);
1820				const int		maxLevel		= de::clamp((int)deFloatFloor(maxLod + 0.5f),		minTexLevel, maxTexLevel);
1821
1822				DE_ASSERT(minLevel <= maxLevel);
1823
1824				for (int level = minLevel; level <= maxLevel; level++)
1825				{
1826					if (isLevelSampleResultValid(texture.getLevel(level), sampler, getLevelFilter(sampler.minFilter), prec, coordXY, layer, result))
1827						return true;
1828				}
1829			}
1830			else
1831			{
1832				if (isLevelSampleResultValid(texture.getLevel(0), sampler, sampler.minFilter, prec, coordXY, layer, result))
1833					return true;
1834			}
1835		}
1836	}
1837
1838	return false;
1839}
1840
1841static bool isLevelSampleResultValid (const ConstPixelBufferAccess&		level,
1842									  const Sampler&					sampler,
1843									  const Sampler::FilterMode			filterMode,
1844									  const LookupPrecision&			prec,
1845									  const Vec3&						coord,
1846									  const Vec4&						result)
1847{
1848	if (filterMode == Sampler::LINEAR)
1849		return isLinearSampleResultValid(level, sampler, prec, coord, result);
1850	else
1851		return isNearestSampleResultValid(level, sampler, prec, coord, result);
1852}
1853
1854static bool isMipmapLinearSampleResultValid (const ConstPixelBufferAccess&		level0,
1855										     const ConstPixelBufferAccess&		level1,
1856											 const Sampler&						sampler,
1857										     const Sampler::FilterMode			levelFilter,
1858										     const LookupPrecision&				prec,
1859										     const Vec3&						coord,
1860										     const Vec2&						fBounds,
1861										     const Vec4&						result)
1862{
1863	if (levelFilter == Sampler::LINEAR)
1864		return isLinearMipmapLinearSampleResultValid(level0, level1, sampler, prec, coord, fBounds, result);
1865	else
1866		return isNearestMipmapLinearSampleResultValid(level0, level1, sampler, prec, coord, fBounds, result);
1867}
1868
1869bool isLookupResultValid (const Texture3DView& texture, const Sampler& sampler, const LookupPrecision& prec, const Vec3& coord, const Vec2& lodBounds, const Vec4& result)
1870{
1871	const float		minLod			= lodBounds.x();
1872	const float		maxLod			= lodBounds.y();
1873	const bool		canBeMagnified	= minLod <= sampler.lodThreshold;
1874	const bool		canBeMinified	= maxLod > sampler.lodThreshold;
1875
1876	DE_ASSERT(isSamplerSupported(sampler));
1877
1878	if (canBeMagnified)
1879	{
1880		if (isLevelSampleResultValid(texture.getLevel(0), sampler, sampler.magFilter, prec, coord, result))
1881			return true;
1882	}
1883
1884	if (canBeMinified)
1885	{
1886		const bool	isNearestMipmap	= isNearestMipmapFilter(sampler.minFilter);
1887		const bool	isLinearMipmap	= isLinearMipmapFilter(sampler.minFilter);
1888		const int	minTexLevel		= 0;
1889		const int	maxTexLevel		= texture.getNumLevels()-1;
1890
1891		DE_ASSERT(minTexLevel <= maxTexLevel);
1892
1893		if (isLinearMipmap && minTexLevel < maxTexLevel)
1894		{
1895			const int		minLevel		= de::clamp((int)deFloatFloor(minLod), minTexLevel, maxTexLevel-1);
1896			const int		maxLevel		= de::clamp((int)deFloatFloor(maxLod), minTexLevel, maxTexLevel-1);
1897
1898			DE_ASSERT(minLevel <= maxLevel);
1899
1900			for (int level = minLevel; level <= maxLevel; level++)
1901			{
1902				const float		minF	= de::clamp(minLod - float(level), 0.0f, 1.0f);
1903				const float		maxF	= de::clamp(maxLod - float(level), 0.0f, 1.0f);
1904
1905				if (isMipmapLinearSampleResultValid(texture.getLevel(level), texture.getLevel(level+1), sampler, getLevelFilter(sampler.minFilter), prec, coord, Vec2(minF, maxF), result))
1906					return true;
1907			}
1908		}
1909		else if (isNearestMipmap)
1910		{
1911			// \note The accurate formula for nearest mipmapping is level = ceil(lod + 0.5) - 1 but Khronos has made
1912			//		 decision to allow floor(lod + 0.5) as well.
1913			const int		minLevel		= de::clamp((int)deFloatCeil(minLod + 0.5f) - 1,	minTexLevel, maxTexLevel);
1914			const int		maxLevel		= de::clamp((int)deFloatFloor(maxLod + 0.5f),		minTexLevel, maxTexLevel);
1915
1916			DE_ASSERT(minLevel <= maxLevel);
1917
1918			for (int level = minLevel; level <= maxLevel; level++)
1919			{
1920				if (isLevelSampleResultValid(texture.getLevel(level), sampler, getLevelFilter(sampler.minFilter), prec, coord, result))
1921					return true;
1922			}
1923		}
1924		else
1925		{
1926			if (isLevelSampleResultValid(texture.getLevel(0), sampler, sampler.minFilter, prec, coord, result))
1927				return true;
1928		}
1929	}
1930
1931	return false;
1932}
1933
1934static void getCubeArrayLevelFaces (const TextureCubeArrayView& texture, const int levelNdx, const int layerNdx, ConstPixelBufferAccess (&out)[CUBEFACE_LAST])
1935{
1936	const ConstPixelBufferAccess&	level		= texture.getLevel(levelNdx);
1937	const int						layerDepth	= layerNdx * 6;
1938
1939	for (int faceNdx = 0; faceNdx < CUBEFACE_LAST; faceNdx++)
1940	{
1941		const CubeFace face = (CubeFace)faceNdx;
1942		out[faceNdx] = getSubregion(level, 0, 0, layerDepth + getCubeArrayFaceIndex(face), level.getWidth(), level.getHeight(), 1);
1943	}
1944}
1945
1946bool isLookupResultValid (const TextureCubeArrayView& texture, const Sampler& sampler, const LookupPrecision& prec, const IVec4& coordBits, const Vec4& coord, const Vec2& lodBounds, const Vec4& result)
1947{
1948	const IVec2	layerRange						= computeLayerRange(texture.getNumLayers(), coordBits.w(), coord.w());
1949	const Vec3	layerCoord						= coord.toWidth<3>();
1950	int			numPossibleFaces				= 0;
1951	CubeFace	possibleFaces[CUBEFACE_LAST];
1952
1953	DE_ASSERT(isSamplerSupported(sampler));
1954
1955	getPossibleCubeFaces(layerCoord, prec.coordBits, &possibleFaces[0], numPossibleFaces);
1956
1957	if (numPossibleFaces == 0)
1958		return true; // Result is undefined.
1959
1960	for (int layerNdx = layerRange.x(); layerNdx <= layerRange.y(); layerNdx++)
1961	{
1962		for (int tryFaceNdx = 0; tryFaceNdx < numPossibleFaces; tryFaceNdx++)
1963		{
1964			const CubeFaceFloatCoords	faceCoords		(possibleFaces[tryFaceNdx], projectToFace(possibleFaces[tryFaceNdx], layerCoord));
1965			const float					minLod			= lodBounds.x();
1966			const float					maxLod			= lodBounds.y();
1967			const bool					canBeMagnified	= minLod <= sampler.lodThreshold;
1968			const bool					canBeMinified	= maxLod > sampler.lodThreshold;
1969
1970			if (canBeMagnified)
1971			{
1972				ConstPixelBufferAccess faces[CUBEFACE_LAST];
1973				getCubeArrayLevelFaces(texture, 0, layerNdx, faces);
1974
1975				if (isCubeLevelSampleResultValid(faces, sampler, sampler.magFilter, prec, faceCoords, result))
1976					return true;
1977			}
1978
1979			if (canBeMinified)
1980			{
1981				const bool	isNearestMipmap	= isNearestMipmapFilter(sampler.minFilter);
1982				const bool	isLinearMipmap	= isLinearMipmapFilter(sampler.minFilter);
1983				const int	minTexLevel		= 0;
1984				const int	maxTexLevel		= texture.getNumLevels()-1;
1985
1986				DE_ASSERT(minTexLevel <= maxTexLevel);
1987
1988				if (isLinearMipmap && minTexLevel < maxTexLevel)
1989				{
1990					const int	minLevel	= de::clamp((int)deFloatFloor(minLod), minTexLevel, maxTexLevel-1);
1991					const int	maxLevel	= de::clamp((int)deFloatFloor(maxLod), minTexLevel, maxTexLevel-1);
1992
1993					DE_ASSERT(minLevel <= maxLevel);
1994
1995					for (int levelNdx = minLevel; levelNdx <= maxLevel; levelNdx++)
1996					{
1997						const float		minF	= de::clamp(minLod - float(levelNdx), 0.0f, 1.0f);
1998						const float		maxF	= de::clamp(maxLod - float(levelNdx), 0.0f, 1.0f);
1999
2000						ConstPixelBufferAccess	faces0[CUBEFACE_LAST];
2001						ConstPixelBufferAccess	faces1[CUBEFACE_LAST];
2002
2003						getCubeArrayLevelFaces(texture, levelNdx,		layerNdx,	faces0);
2004						getCubeArrayLevelFaces(texture, levelNdx + 1,	layerNdx,	faces1);
2005
2006						if (isCubeMipmapLinearSampleResultValid(faces0, faces1, sampler, getLevelFilter(sampler.minFilter), prec, faceCoords, Vec2(minF, maxF), result))
2007							return true;
2008					}
2009				}
2010				else if (isNearestMipmap)
2011				{
2012					// \note The accurate formula for nearest mipmapping is level = ceil(lod + 0.5) - 1 but Khronos has made
2013					//		 decision to allow floor(lod + 0.5) as well.
2014					const int	minLevel	= de::clamp((int)deFloatCeil(minLod + 0.5f) - 1,	minTexLevel, maxTexLevel);
2015					const int	maxLevel	= de::clamp((int)deFloatFloor(maxLod + 0.5f),		minTexLevel, maxTexLevel);
2016
2017					DE_ASSERT(minLevel <= maxLevel);
2018
2019					for (int levelNdx = minLevel; levelNdx <= maxLevel; levelNdx++)
2020					{
2021						ConstPixelBufferAccess faces[CUBEFACE_LAST];
2022						getCubeArrayLevelFaces(texture, levelNdx, layerNdx, faces);
2023
2024						if (isCubeLevelSampleResultValid(faces, sampler, getLevelFilter(sampler.minFilter), prec, faceCoords, result))
2025							return true;
2026					}
2027				}
2028				else
2029				{
2030					ConstPixelBufferAccess faces[CUBEFACE_LAST];
2031					getCubeArrayLevelFaces(texture, 0, layerNdx, faces);
2032
2033					if (isCubeLevelSampleResultValid(faces, sampler, sampler.minFilter, prec, faceCoords, result))
2034						return true;
2035				}
2036			}
2037		}
2038	}
2039
2040	return false;
2041}
2042
2043Vec4 computeFixedPointThreshold (const IVec4& bits)
2044{
2045	return computeFixedPointError(bits);
2046}
2047
2048Vec4 computeFloatingPointThreshold (const IVec4& bits, const Vec4& value)
2049{
2050	return computeFloatingPointError(value, bits);
2051}
2052
2053Vec2 computeLodBoundsFromDerivates (const float dudx, const float dvdx, const float dwdx, const float dudy, const float dvdy, const float dwdy, const LodPrecision& prec)
2054{
2055	const float		mu			= de::max(deFloatAbs(dudx), deFloatAbs(dudy));
2056	const float		mv			= de::max(deFloatAbs(dvdx), deFloatAbs(dvdy));
2057	const float		mw			= de::max(deFloatAbs(dwdx), deFloatAbs(dwdy));
2058	const float		minDBound	= de::max(de::max(mu, mv), mw);
2059	const float		maxDBound	= mu + mv + mw;
2060	const float		minDErr		= computeFloatingPointError(minDBound, prec.derivateBits);
2061	const float		maxDErr		= computeFloatingPointError(maxDBound, prec.derivateBits);
2062	const float		minLod		= deFloatLog2(minDBound-minDErr);
2063	const float		maxLod		= deFloatLog2(maxDBound+maxDErr);
2064	const float		lodErr		= computeFixedPointError(prec.lodBits);
2065
2066	DE_ASSERT(minLod <= maxLod);
2067	return Vec2(minLod-lodErr, maxLod+lodErr);
2068}
2069
2070Vec2 computeLodBoundsFromDerivates (const float dudx, const float dvdx, const float dudy, const float dvdy, const LodPrecision& prec)
2071{
2072	return computeLodBoundsFromDerivates(dudx, dvdx, 0.0f, dudy, dvdy, 0.0f, prec);
2073}
2074
2075Vec2 computeLodBoundsFromDerivates (const float dudx, const float dudy, const LodPrecision& prec)
2076{
2077	return computeLodBoundsFromDerivates(dudx, 0.0f, 0.0f, dudy, 0.0f, 0.0f, prec);
2078}
2079
2080Vec2 computeCubeLodBoundsFromDerivates (const Vec3& coord, const Vec3& coordDx, const Vec3& coordDy, const int faceSize, const LodPrecision& prec)
2081{
2082	const bool			allowBrokenEdgeDerivate		= false;
2083	const CubeFace		face						= selectCubeFace(coord);
2084	int					maNdx						= 0;
2085	int					sNdx						= 0;
2086	int					tNdx						= 0;
2087
2088	// \note Derivate signs don't matter when computing lod
2089	switch (face)
2090	{
2091		case CUBEFACE_NEGATIVE_X:
2092		case CUBEFACE_POSITIVE_X: maNdx = 0; sNdx = 2; tNdx = 1; break;
2093		case CUBEFACE_NEGATIVE_Y:
2094		case CUBEFACE_POSITIVE_Y: maNdx = 1; sNdx = 0; tNdx = 2; break;
2095		case CUBEFACE_NEGATIVE_Z:
2096		case CUBEFACE_POSITIVE_Z: maNdx = 2; sNdx = 0; tNdx = 1; break;
2097		default:
2098			DE_ASSERT(DE_FALSE);
2099	}
2100
2101	{
2102		const float		sc		= coord[sNdx];
2103		const float		tc		= coord[tNdx];
2104		const float		ma		= de::abs(coord[maNdx]);
2105		const float		scdx	= coordDx[sNdx];
2106		const float		tcdx	= coordDx[tNdx];
2107		const float		madx	= de::abs(coordDx[maNdx]);
2108		const float		scdy	= coordDy[sNdx];
2109		const float		tcdy	= coordDy[tNdx];
2110		const float		mady	= de::abs(coordDy[maNdx]);
2111		const float		dudx	= float(faceSize) * 0.5f * (scdx*ma - sc*madx) / (ma*ma);
2112		const float		dvdx	= float(faceSize) * 0.5f * (tcdx*ma - tc*madx) / (ma*ma);
2113		const float		dudy	= float(faceSize) * 0.5f * (scdy*ma - sc*mady) / (ma*ma);
2114		const float		dvdy	= float(faceSize) * 0.5f * (tcdy*ma - tc*mady) / (ma*ma);
2115		const Vec2		bounds	= computeLodBoundsFromDerivates(dudx, dvdx, dudy, dvdy, prec);
2116
2117		// Implementations may compute derivate from projected (s, t) resulting in incorrect values at edges.
2118		if (allowBrokenEdgeDerivate)
2119		{
2120			const Vec3			dxErr		= computeFloatingPointError(coordDx, IVec3(prec.derivateBits));
2121			const Vec3			dyErr		= computeFloatingPointError(coordDy, IVec3(prec.derivateBits));
2122			const Vec3			xoffs		= abs(coordDx) + dxErr;
2123			const Vec3			yoffs		= abs(coordDy) + dyErr;
2124
2125			if (selectCubeFace(coord + xoffs) != face ||
2126				selectCubeFace(coord - xoffs) != face ||
2127				selectCubeFace(coord + yoffs) != face ||
2128				selectCubeFace(coord - yoffs) != face)
2129			{
2130				return Vec2(bounds.x(), 1000.0f);
2131			}
2132		}
2133
2134		return bounds;
2135	}
2136}
2137
2138Vec2 clampLodBounds (const Vec2& lodBounds, const Vec2& lodMinMax, const LodPrecision& prec)
2139{
2140	const float lodErr	= computeFixedPointError(prec.lodBits);
2141	const float	a		= lodMinMax.x();
2142	const float	b		= lodMinMax.y();
2143	return Vec2(de::clamp(lodBounds.x(), a-lodErr, b-lodErr), de::clamp(lodBounds.y(), a+lodErr, b+lodErr));
2144}
2145
2146bool isLevel1DLookupResultValid (const ConstPixelBufferAccess&	access,
2147								 const Sampler&					sampler,
2148								 TexLookupScaleMode				scaleMode,
2149								 const LookupPrecision&			prec,
2150								 const float					coordX,
2151								 const int						coordY,
2152								 const Vec4&					result)
2153{
2154	const Sampler::FilterMode filterMode = scaleMode == TEX_LOOKUP_SCALE_MAGNIFY ? sampler.magFilter : sampler.minFilter;
2155	return isLevelSampleResultValid(access, sampler, filterMode, prec, coordX, coordY, result);
2156}
2157
2158bool isLevel1DLookupResultValid (const ConstPixelBufferAccess&	access,
2159								 const Sampler&					sampler,
2160								 TexLookupScaleMode				scaleMode,
2161								 const IntLookupPrecision&		prec,
2162								 const float					coordX,
2163								 const int						coordY,
2164								 const IVec4&					result)
2165{
2166	DE_ASSERT(sampler.minFilter == Sampler::NEAREST && sampler.magFilter == Sampler::NEAREST);
2167	DE_UNREF(scaleMode);
2168	return isNearestSampleResultValid(access, sampler, prec, coordX, coordY, result);
2169}
2170
2171bool isLevel1DLookupResultValid (const ConstPixelBufferAccess&	access,
2172								 const Sampler&					sampler,
2173								 TexLookupScaleMode				scaleMode,
2174								 const IntLookupPrecision&		prec,
2175								 const float					coordX,
2176								 const int						coordY,
2177								 const UVec4&					result)
2178{
2179	DE_ASSERT(sampler.minFilter == Sampler::NEAREST && sampler.magFilter == Sampler::NEAREST);
2180	DE_UNREF(scaleMode);
2181	return isNearestSampleResultValid(access, sampler, prec, coordX, coordY, result);
2182}
2183
2184bool isLevel2DLookupResultValid (const ConstPixelBufferAccess&	access,
2185								 const Sampler&					sampler,
2186								 TexLookupScaleMode				scaleMode,
2187								 const LookupPrecision&			prec,
2188								 const Vec2&					coord,
2189								 const int						coordZ,
2190								 const Vec4&					result)
2191{
2192	const Sampler::FilterMode filterMode = scaleMode == TEX_LOOKUP_SCALE_MAGNIFY ? sampler.magFilter : sampler.minFilter;
2193	return isLevelSampleResultValid(access, sampler, filterMode, prec, coord, coordZ, result);
2194}
2195
2196bool isLevel2DLookupResultValid (const ConstPixelBufferAccess&	access,
2197								 const Sampler&					sampler,
2198								 TexLookupScaleMode				scaleMode,
2199								 const IntLookupPrecision&		prec,
2200								 const Vec2&					coord,
2201								 const int						coordZ,
2202								 const IVec4&					result)
2203{
2204	DE_ASSERT(sampler.minFilter == Sampler::NEAREST && sampler.magFilter == Sampler::NEAREST);
2205	DE_UNREF(scaleMode);
2206	return isNearestSampleResultValid(access, sampler, prec, coord, coordZ, result);
2207}
2208
2209bool isLevel2DLookupResultValid (const ConstPixelBufferAccess&	access,
2210								 const Sampler&					sampler,
2211								 TexLookupScaleMode				scaleMode,
2212								 const IntLookupPrecision&		prec,
2213								 const Vec2&					coord,
2214								 const int						coordZ,
2215								 const UVec4&					result)
2216{
2217	DE_ASSERT(sampler.minFilter == Sampler::NEAREST && sampler.magFilter == Sampler::NEAREST);
2218	DE_UNREF(scaleMode);
2219	return isNearestSampleResultValid(access, sampler, prec, coord, coordZ, result);
2220}
2221
2222bool isLevel3DLookupResultValid (const ConstPixelBufferAccess&	access,
2223								 const Sampler&					sampler,
2224								 TexLookupScaleMode				scaleMode,
2225								 const LookupPrecision&			prec,
2226								 const Vec3&					coord,
2227								 const Vec4&					result)
2228{
2229	const tcu::Sampler::FilterMode filterMode = scaleMode == TEX_LOOKUP_SCALE_MAGNIFY ? sampler.magFilter : sampler.minFilter;
2230	return isLevelSampleResultValid(access, sampler, filterMode, prec, coord, result);
2231}
2232
2233bool isLevel3DLookupResultValid (const ConstPixelBufferAccess&	access,
2234								 const Sampler&					sampler,
2235								 TexLookupScaleMode				scaleMode,
2236								 const IntLookupPrecision&		prec,
2237								 const Vec3&					coord,
2238								 const IVec4&					result)
2239{
2240	DE_ASSERT(sampler.minFilter == Sampler::NEAREST && sampler.magFilter == Sampler::NEAREST);
2241	DE_UNREF(scaleMode);
2242	return isNearestSampleResultValid(access, sampler, prec, coord, result);
2243}
2244
2245bool isLevel3DLookupResultValid (const ConstPixelBufferAccess&	access,
2246								 const Sampler&					sampler,
2247								 TexLookupScaleMode				scaleMode,
2248								 const IntLookupPrecision&		prec,
2249								 const Vec3&					coord,
2250								 const UVec4&					result)
2251{
2252	DE_ASSERT(sampler.minFilter == Sampler::NEAREST && sampler.magFilter == Sampler::NEAREST);
2253	DE_UNREF(scaleMode);
2254	return isNearestSampleResultValid(access, sampler, prec, coord, result);
2255}
2256
2257template<typename PrecType, typename ScalarType>
2258static bool isGatherOffsetsResultValid (const ConstPixelBufferAccess&	level,
2259										const Sampler&					sampler,
2260										const PrecType&					prec,
2261										const Vec2&						coord,
2262										int								coordZ,
2263										int								componentNdx,
2264										const IVec2						(&offsets)[4],
2265										const Vector<ScalarType, 4>&	result)
2266{
2267	const Vec2	uBounds		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, level.getWidth(),	coord.x(), prec.coordBits.x(), prec.uvwBits.x());
2268	const Vec2	vBounds		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, level.getHeight(),	coord.y(), prec.coordBits.y(), prec.uvwBits.y());
2269
2270	// Integer coordinate bounds for (x0, y0) - without wrap mode
2271	const int	minI		= deFloorFloatToInt32(uBounds.x()-0.5f);
2272	const int	maxI		= deFloorFloatToInt32(uBounds.y()-0.5f);
2273	const int	minJ		= deFloorFloatToInt32(vBounds.x()-0.5f);
2274	const int	maxJ		= deFloorFloatToInt32(vBounds.y()-0.5f);
2275
2276	const int	w			= level.getWidth();
2277	const int	h			= level.getHeight();
2278
2279	for (int j = minJ; j <= maxJ; j++)
2280	{
2281		for (int i = minI; i <= maxI; i++)
2282		{
2283			Vector<ScalarType, 4> color;
2284			for (int offNdx = 0; offNdx < 4; offNdx++)
2285			{
2286				// offNdx-th coordinate offset and then wrapped.
2287				const int x = wrap(sampler.wrapS, i+offsets[offNdx].x(), w);
2288				const int y = wrap(sampler.wrapT, j+offsets[offNdx].y(), h);
2289				color[offNdx] = lookup<ScalarType>(level, sampler, x, y, coordZ)[componentNdx];
2290			}
2291
2292			if (isColorValid(prec, color, result))
2293				return true;
2294		}
2295	}
2296
2297	return false;
2298}
2299
2300bool isGatherOffsetsResultValid (const Texture2DView&			texture,
2301								 const Sampler&					sampler,
2302								 const LookupPrecision&			prec,
2303								 const Vec2&					coord,
2304								 int							componentNdx,
2305								 const IVec2					(&offsets)[4],
2306								 const Vec4&					result)
2307{
2308	return isGatherOffsetsResultValid(texture.getLevel(0), sampler, prec, coord, 0, componentNdx, offsets, result);
2309}
2310
2311bool isGatherOffsetsResultValid (const Texture2DView&			texture,
2312								 const Sampler&					sampler,
2313								 const IntLookupPrecision&		prec,
2314								 const Vec2&					coord,
2315								 int							componentNdx,
2316								 const IVec2					(&offsets)[4],
2317								 const IVec4&					result)
2318{
2319	return isGatherOffsetsResultValid(texture.getLevel(0), sampler, prec, coord, 0, componentNdx, offsets, result);
2320}
2321
2322bool isGatherOffsetsResultValid (const Texture2DView&			texture,
2323								 const Sampler&					sampler,
2324								 const IntLookupPrecision&		prec,
2325								 const Vec2&					coord,
2326								 int							componentNdx,
2327								 const IVec2					(&offsets)[4],
2328								 const UVec4&					result)
2329{
2330	return isGatherOffsetsResultValid(texture.getLevel(0), sampler, prec, coord, 0, componentNdx, offsets, result);
2331}
2332
2333template <typename PrecType, typename ScalarType>
2334static bool is2DArrayGatherOffsetsResultValid (const Texture2DArrayView&		texture,
2335											   const Sampler&					sampler,
2336											   const PrecType&					prec,
2337											   const Vec3&						coord,
2338											   int								componentNdx,
2339											   const IVec2						(&offsets)[4],
2340											   const Vector<ScalarType, 4>&		result)
2341{
2342	const IVec2 layerRange = computeLayerRange(texture.getNumLayers(), prec.coordBits.z(), coord.z());
2343	for (int layer = layerRange.x(); layer <= layerRange.y(); layer++)
2344	{
2345		if (isGatherOffsetsResultValid(texture.getLevel(0), sampler, prec, coord.swizzle(0,1), layer, componentNdx, offsets, result))
2346			return true;
2347	}
2348	return false;
2349}
2350
2351bool isGatherOffsetsResultValid (const Texture2DArrayView&		texture,
2352								 const Sampler&					sampler,
2353								 const LookupPrecision&			prec,
2354								 const Vec3&					coord,
2355								 int							componentNdx,
2356								 const IVec2					(&offsets)[4],
2357								 const Vec4&					result)
2358{
2359	return is2DArrayGatherOffsetsResultValid(texture, sampler, prec, coord, componentNdx, offsets, result);
2360}
2361
2362bool isGatherOffsetsResultValid (const Texture2DArrayView&		texture,
2363								 const Sampler&					sampler,
2364								 const IntLookupPrecision&		prec,
2365								 const Vec3&					coord,
2366								 int							componentNdx,
2367								 const IVec2					(&offsets)[4],
2368								 const IVec4&					result)
2369{
2370	return is2DArrayGatherOffsetsResultValid(texture, sampler, prec, coord, componentNdx, offsets, result);
2371}
2372
2373bool isGatherOffsetsResultValid (const Texture2DArrayView&		texture,
2374								 const Sampler&					sampler,
2375								 const IntLookupPrecision&		prec,
2376								 const Vec3&					coord,
2377								 int							componentNdx,
2378								 const IVec2					(&offsets)[4],
2379								 const UVec4&					result)
2380{
2381	return is2DArrayGatherOffsetsResultValid(texture, sampler, prec, coord, componentNdx, offsets, result);
2382}
2383
2384template<typename PrecType, typename ScalarType>
2385static bool isGatherResultValid (const TextureCubeView&			texture,
2386								 const Sampler&					sampler,
2387								 const PrecType&				prec,
2388								 const CubeFaceFloatCoords&		coords,
2389								 int							componentNdx,
2390								 const Vector<ScalarType, 4>&	result)
2391{
2392	const int	size		= texture.getLevelFace(0, coords.face).getWidth();
2393
2394	const Vec2	uBounds		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, size, coords.s, prec.coordBits.x(), prec.uvwBits.x());
2395	const Vec2	vBounds		= computeNonNormalizedCoordBounds(sampler.normalizedCoords, size, coords.t, prec.coordBits.y(), prec.uvwBits.y());
2396
2397	// Integer coordinate bounds for (x0,y0) - without wrap mode
2398	const int	minI		= deFloorFloatToInt32(uBounds.x()-0.5f);
2399	const int	maxI		= deFloorFloatToInt32(uBounds.y()-0.5f);
2400	const int	minJ		= deFloorFloatToInt32(vBounds.x()-0.5f);
2401	const int	maxJ		= deFloorFloatToInt32(vBounds.y()-0.5f);
2402
2403	// Face accesses
2404	ConstPixelBufferAccess faces[CUBEFACE_LAST];
2405	for (int face = 0; face < CUBEFACE_LAST; face++)
2406		faces[face] = texture.getLevelFace(0, CubeFace(face));
2407
2408	for (int j = minJ; j <= maxJ; j++)
2409	{
2410		for (int i = minI; i <= maxI; i++)
2411		{
2412			static const IVec2 offsets[4] =
2413			{
2414				IVec2(0, 1),
2415				IVec2(1, 1),
2416				IVec2(1, 0),
2417				IVec2(0, 0)
2418			};
2419
2420			Vector<ScalarType, 4> color;
2421			for (int offNdx = 0; offNdx < 4; offNdx++)
2422			{
2423				const CubeFaceIntCoords c = remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, i+offsets[offNdx].x(), j+offsets[offNdx].y()), size);
2424				// If any of samples is out of both edges, implementations can do pretty much anything according to spec.
2425				// \todo [2014-06-05 nuutti] Test the special case where all corner pixels have exactly the same color.
2426				//							 See also isSeamlessLinearSampleResultValid and similar.
2427				if (c.face == CUBEFACE_LAST)
2428					return true;
2429
2430				color[offNdx] = lookup<ScalarType>(faces[c.face], sampler, c.s, c.t, 0)[componentNdx];
2431			}
2432
2433			if (isColorValid(prec, color, result))
2434				return true;
2435		}
2436	}
2437
2438	return false;
2439}
2440
2441template <typename PrecType, typename ScalarType>
2442static bool isCubeGatherResultValid (const TextureCubeView&			texture,
2443									 const Sampler&					sampler,
2444									 const PrecType&				prec,
2445									 const Vec3&					coord,
2446									 int							componentNdx,
2447									 const Vector<ScalarType, 4>&	result)
2448{
2449	int			numPossibleFaces				= 0;
2450	CubeFace	possibleFaces[CUBEFACE_LAST];
2451
2452	getPossibleCubeFaces(coord, prec.coordBits, &possibleFaces[0], numPossibleFaces);
2453
2454	if (numPossibleFaces == 0)
2455		return true; // Result is undefined.
2456
2457	for (int tryFaceNdx = 0; tryFaceNdx < numPossibleFaces; tryFaceNdx++)
2458	{
2459		const CubeFaceFloatCoords faceCoords(possibleFaces[tryFaceNdx], projectToFace(possibleFaces[tryFaceNdx], coord));
2460
2461		if (isGatherResultValid(texture, sampler, prec, faceCoords, componentNdx, result))
2462			return true;
2463	}
2464
2465	return false;
2466}
2467
2468bool isGatherResultValid (const TextureCubeView&	texture,
2469						  const Sampler&			sampler,
2470						  const LookupPrecision&	prec,
2471						  const Vec3&				coord,
2472						  int						componentNdx,
2473						  const Vec4&				result)
2474{
2475	return isCubeGatherResultValid(texture, sampler, prec, coord, componentNdx, result);
2476}
2477
2478bool isGatherResultValid (const TextureCubeView&		texture,
2479						  const Sampler&				sampler,
2480						  const IntLookupPrecision&		prec,
2481						  const Vec3&					coord,
2482						  int							componentNdx,
2483						  const IVec4&					result)
2484{
2485	return isCubeGatherResultValid(texture, sampler, prec, coord, componentNdx, result);
2486}
2487
2488bool isGatherResultValid (const TextureCubeView&		texture,
2489						  const Sampler&				sampler,
2490						  const IntLookupPrecision&		prec,
2491						  const Vec3&					coord,
2492						  int							componentNdx,
2493						  const UVec4&					result)
2494{
2495	return isCubeGatherResultValid(texture, sampler, prec, coord, componentNdx, result);
2496}
2497
2498} // tcu
2499