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