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