1/*-------------------------------------------------------------------------
2 * drawElements TestLog Library
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 Test case result logging
22 *//*--------------------------------------------------------------------*/
23
24#include "qpTestLog.h"
25#include "qpXmlWriter.h"
26#include "qpInfo.h"
27#include "qpDebugOut.h"
28
29#include "deMemory.h"
30#include "deInt32.h"
31#include "deString.h"
32
33#include "deMutex.h"
34
35#if defined(QP_SUPPORT_PNG)
36#	include <png.h>
37#endif
38
39#include <stdio.h>
40#include <stdlib.h>
41#include <stdarg.h>
42
43#if (DE_OS == DE_OS_WIN32)
44#	include <windows.h>
45#	include <io.h>
46#endif
47
48#if defined(DE_DEBUG)
49
50/* Utils for verifying container (Section, ImageSet, EglConfigSet) usage in debug builds. */
51
52typedef enum ContainerType_e
53{
54	CONTAINERTYPE_SECTION = 0,
55	CONTAINERTYPE_IMAGESET,
56	CONTAINERTYPE_EGLCONFIGSET,
57	CONTAINERTYPE_SHADERPROGRAM,
58	CONTAINERTYPE_SAMPLELIST,
59	CONTAINERTYPE_SAMPLEINFO,
60	CONTAINERTYPE_SAMPLE,
61
62	CONTAINERTYPE_LAST
63} ContainerType;
64
65DE_INLINE deBool childContainersOk (ContainerType type)
66{
67	return type == CONTAINERTYPE_SECTION || type == CONTAINERTYPE_SAMPLELIST;
68}
69
70enum
71{
72	MAX_CONTAINER_STACK_DEPTH		= 32
73};
74
75typedef struct ContainerStack_s
76{
77	int				numElements;
78	ContainerType	elements[MAX_CONTAINER_STACK_DEPTH];
79} ContainerStack;
80
81DE_INLINE void ContainerStack_reset (ContainerStack* stack)
82{
83	deMemset(stack, 0, sizeof(ContainerStack));
84}
85
86DE_INLINE deBool ContainerStack_isEmpty (const ContainerStack* stack)
87{
88	return stack->numElements == 0;
89}
90
91DE_INLINE deBool ContainerStack_push (ContainerStack* stack, ContainerType type)
92{
93	if (stack->numElements == MAX_CONTAINER_STACK_DEPTH)
94		return DE_FALSE;
95
96	if (stack->numElements > 0 && !childContainersOk(stack->elements[stack->numElements-1]))
97		return DE_FALSE;
98
99	stack->elements[stack->numElements]  = type;
100	stack->numElements					+= 1;
101
102	return DE_TRUE;
103}
104
105DE_INLINE ContainerType ContainerStack_pop (ContainerStack* stack)
106{
107	DE_ASSERT(stack->numElements > 0);
108	stack->numElements -= 1;
109	return stack->elements[stack->numElements];
110}
111
112DE_INLINE ContainerType ContainerStack_getTop (const ContainerStack* stack)
113{
114	if (stack->numElements > 0)
115		return stack->elements[stack->numElements-1];
116	else
117		return CONTAINERTYPE_LAST;
118}
119
120#endif
121
122/* qpTestLog instance */
123struct qpTestLog_s
124{
125	deUint32				flags;				/*!< Logging flags.						*/
126
127	deMutex					lock;				/*!< Lock for mutable state below.		*/
128
129	/* State protected by lock. */
130	FILE*					outputFile;
131	qpXmlWriter*			writer;
132	deBool					isSessionOpen;
133	deBool					isCaseOpen;
134
135#if defined(DE_DEBUG)
136	ContainerStack			containerStack;		/*!< For container usage verification.	*/
137#endif
138};
139
140/* Maps integer to string. */
141typedef struct qpKeyStringMap_s
142{
143	int		key;
144	char*	string;
145} qpKeyStringMap;
146
147static const char* LOG_FORMAT_VERSION = "0.3.4";
148
149/* Mapping enum to above strings... */
150static const qpKeyStringMap s_qpTestTypeMap[] =
151{
152	{ QP_TEST_CASE_TYPE_SELF_VALIDATE,		"SelfValidate"	},
153	{ QP_TEST_CASE_TYPE_PERFORMANCE,		"Performance"	},
154	{ QP_TEST_CASE_TYPE_CAPABILITY,			"Capability"	},
155	{ QP_TEST_CASE_TYPE_ACCURACY,			"Accuracy"		},
156
157	{ QP_TEST_CASE_TYPE_LAST,				DE_NULL			}
158};
159
160DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_qpTestTypeMap) == QP_TEST_CASE_TYPE_LAST + 1);
161
162static const qpKeyStringMap s_qpTestResultMap[] =
163{
164	{ QP_TEST_RESULT_PASS,						"Pass"					},
165	{ QP_TEST_RESULT_FAIL,						"Fail"					},
166	{ QP_TEST_RESULT_QUALITY_WARNING,			"QualityWarning"		},
167	{ QP_TEST_RESULT_COMPATIBILITY_WARNING,		"CompatibilityWarning"	},
168	{ QP_TEST_RESULT_PENDING,					"Pending"				},	/* should not be needed here */
169	{ QP_TEST_RESULT_NOT_SUPPORTED,				"NotSupported"			},
170	{ QP_TEST_RESULT_RESOURCE_ERROR,			"ResourceError"			},
171	{ QP_TEST_RESULT_INTERNAL_ERROR,			"InternalError"			},
172	{ QP_TEST_RESULT_CRASH,						"Crash"					},
173	{ QP_TEST_RESULT_TIMEOUT,					"Timeout"				},
174
175	/* Add new values here if needed, remember to update qpTestResult enumeration. */
176
177	{ QP_TEST_RESULT_LAST,						DE_NULL					}
178};
179
180DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_qpTestResultMap) == QP_TEST_RESULT_LAST + 1);
181
182/* Key tag to string mapping. */
183
184static const qpKeyStringMap s_qpTagMap[] =
185{
186	{ QP_KEY_TAG_NONE,			DE_NULL			},
187	{ QP_KEY_TAG_PERFORMANCE,	"Performance"	},
188	{ QP_KEY_TAG_QUALITY,		"Quality"		},
189	{ QP_KEY_TAG_PRECISION,		"Precision"		},
190	{ QP_KEY_TAG_TIME,			"Time"			},
191
192	{ QP_KEY_TAG_LAST,			DE_NULL			}
193};
194
195DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_qpTagMap) == QP_KEY_TAG_LAST + 1);
196
197/* Sample value tag to string mapping. */
198
199static const qpKeyStringMap s_qpSampleValueTagMap[] =
200{
201	{ QP_SAMPLE_VALUE_TAG_PREDICTOR,	"Predictor"	},
202	{ QP_SAMPLE_VALUE_TAG_RESPONSE,		"Response"	},
203
204	{ QP_SAMPLE_VALUE_TAG_LAST,			DE_NULL		}
205};
206
207DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_qpSampleValueTagMap) == QP_SAMPLE_VALUE_TAG_LAST + 1);
208
209/* Image compression mode to string mapping. */
210
211static const qpKeyStringMap s_qpImageCompressionModeMap[] =
212{
213	{ QP_IMAGE_COMPRESSION_MODE_NONE,	"None"	},
214	{ QP_IMAGE_COMPRESSION_MODE_PNG,	"PNG"	},
215
216	{ QP_IMAGE_COMPRESSION_MODE_BEST,	DE_NULL	},	/* not allowed to be written! */
217
218	{ QP_IMAGE_COMPRESSION_MODE_LAST,	DE_NULL	}
219};
220
221DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_qpImageCompressionModeMap) == QP_IMAGE_COMPRESSION_MODE_LAST + 1);
222
223/* Image format to string mapping. */
224
225static const qpKeyStringMap s_qpImageFormatMap[] =
226{
227	{ QP_IMAGE_FORMAT_RGB888,	"RGB888"	},
228	{ QP_IMAGE_FORMAT_RGBA8888,	"RGBA8888"	},
229
230	{ QP_IMAGE_FORMAT_LAST,		DE_NULL		}
231};
232
233DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_qpImageFormatMap) == QP_IMAGE_FORMAT_LAST + 1);
234
235/* Shader type to string mapping. */
236
237static const qpKeyStringMap s_qpShaderTypeMap[] =
238{
239	{ QP_SHADER_TYPE_VERTEX,			"VertexShader"			},
240	{ QP_SHADER_TYPE_FRAGMENT,			"FragmentShader"		},
241	{ QP_SHADER_TYPE_GEOMETRY,			"GeometryShader"		},
242	{ QP_SHADER_TYPE_TESS_CONTROL,		"TessControlShader"		},
243	{ QP_SHADER_TYPE_TESS_EVALUATION,	"TessEvaluationShader"	},
244	{ QP_SHADER_TYPE_COMPUTE,			"ComputeShader"			},
245
246	{ QP_SHADER_TYPE_LAST,				DE_NULL					}
247};
248
249DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_qpShaderTypeMap) == QP_SHADER_TYPE_LAST + 1);
250
251static void qpTestLog_flushFile (qpTestLog* log)
252{
253	DE_ASSERT(log && log->outputFile);
254	fflush(log->outputFile);
255#if (DE_OS == DE_OS_WIN32) && (DE_COMPILER == DE_COMPILER_MSC)
256	/* \todo [petri] Is this really necessary? */
257	FlushFileBuffers((HANDLE)_get_osfhandle(_fileno(log->outputFile)));
258#endif
259}
260
261#define QP_LOOKUP_STRING(KEYMAP, KEY)	qpLookupString(KEYMAP, DE_LENGTH_OF_ARRAY(KEYMAP), (int)(KEY))
262
263static const char* qpLookupString (const qpKeyStringMap* keyMap, int keyMapSize, int key)
264{
265	DE_ASSERT(keyMap);
266	DE_ASSERT(deInBounds32(key, 0, keyMapSize));
267	DE_ASSERT(keyMap[key].key == key);
268	DE_UNREF(keyMapSize); /* for asserting only */
269	return keyMap[key].string;
270}
271
272DE_INLINE void int32ToString (int val, char buf[32])
273{
274	deSprintf(&buf[0], 32, "%d", val);
275}
276
277DE_INLINE void int64ToString (deInt64 val, char buf[32])
278{
279	deSprintf(&buf[0], 32, "%lld", (long long int)val);
280}
281
282DE_INLINE void floatToString (float value, char* buf, size_t bufSize)
283{
284	deSprintf(buf, bufSize, "%f", value);
285}
286
287DE_INLINE void doubleToString (double value, char* buf, size_t bufSize)
288{
289	deSprintf(buf, bufSize, "%f", value);
290}
291
292static deBool beginSession (qpTestLog* log)
293{
294	DE_ASSERT(log && !log->isSessionOpen);
295
296	/* Write session info. */
297	fprintf(log->outputFile, "#sessionInfo releaseName %s\n", qpGetReleaseName());
298	fprintf(log->outputFile, "#sessionInfo releaseId 0x%08x\n", qpGetReleaseId());
299	fprintf(log->outputFile, "#sessionInfo targetName \"%s\"\n", qpGetTargetName());
300
301    /* Write out #beginSession. */
302	fprintf(log->outputFile, "#beginSession\n");
303	qpTestLog_flushFile(log);
304
305	log->isSessionOpen = DE_TRUE;
306
307	return DE_TRUE;
308}
309
310static deBool endSession (qpTestLog* log)
311{
312	DE_ASSERT(log && log->isSessionOpen);
313
314    /* Make sure xml is flushed. */
315    qpXmlWriter_flush(log->writer);
316
317    /* Write out #endSession. */
318	fprintf(log->outputFile, "\n#endSession\n");
319	qpTestLog_flushFile(log);
320
321	log->isSessionOpen = DE_FALSE;
322
323	return DE_TRUE;
324}
325
326/*--------------------------------------------------------------------*//*!
327 * \brief Create a file based logger instance
328 * \param fileName Name of the file where to put logs
329 * \return qpTestLog instance, or DE_NULL if cannot create file
330 *//*--------------------------------------------------------------------*/
331qpTestLog* qpTestLog_createFileLog (const char* fileName, deUint32 flags)
332{
333	qpTestLog* log = (qpTestLog*)deCalloc(sizeof(qpTestLog));
334	if (!log)
335		return DE_NULL;
336
337	DE_ASSERT(fileName && fileName[0]); /* must have filename. */
338
339#if defined(DE_DEBUG)
340	ContainerStack_reset(&log->containerStack);
341#endif
342
343	qpPrintf("Writing test log into %s\n", fileName);
344
345	/* Create output file. */
346	log->outputFile = fopen(fileName, "wb");
347	if (!log->outputFile)
348	{
349		qpPrintf("ERROR: Unable to open test log output file '%s'.\n", fileName);
350		qpTestLog_destroy(log);
351		return DE_NULL;
352	}
353
354	log->flags			= flags;
355	log->writer			= qpXmlWriter_createFileWriter(log->outputFile, 0, !(flags & QP_TEST_LOG_NO_FLUSH));
356	log->lock			= deMutex_create(DE_NULL);
357	log->isSessionOpen	= DE_FALSE;
358	log->isCaseOpen		= DE_FALSE;
359
360	if (!log->writer)
361	{
362		qpPrintf("ERROR: Unable to create output XML writer to file '%s'.\n", fileName);
363		qpTestLog_destroy(log);
364		return DE_NULL;
365	}
366
367	if (!log->lock)
368	{
369		qpPrintf("ERROR: Unable to create mutex.\n");
370		qpTestLog_destroy(log);
371		return DE_NULL;
372	}
373
374	beginSession(log);
375
376	return log;
377}
378
379/*--------------------------------------------------------------------*//*!
380 * \brief Destroy a logger instance
381 * \param a	qpTestLog instance
382 *//*--------------------------------------------------------------------*/
383void qpTestLog_destroy (qpTestLog* log)
384{
385	DE_ASSERT(log);
386
387	if (log->isSessionOpen)
388		endSession(log);
389
390	if (log->writer)
391		qpXmlWriter_destroy(log->writer);
392
393	if (log->outputFile)
394		fclose(log->outputFile);
395
396	if (log->lock)
397		deMutex_destroy(log->lock);
398
399	deFree(log);
400}
401
402/*--------------------------------------------------------------------*//*!
403 * \brief Log start of test case
404 * \param log qpTestLog instance
405 * \param testCasePath	Full test case path (as seen in Candy).
406 * \param testCaseType	Test case type
407 * \return true if ok, false otherwise
408 *//*--------------------------------------------------------------------*/
409deBool qpTestLog_startCase (qpTestLog* log, const char* testCasePath, qpTestCaseType testCaseType)
410{
411	const char*		typeStr				= QP_LOOKUP_STRING(s_qpTestTypeMap, testCaseType);
412	int				numResultAttribs	= 0;
413	qpXmlAttribute	resultAttribs[8];
414
415	DE_ASSERT(log && testCasePath && (testCasePath[0] != 0));
416	deMutex_lock(log->lock);
417
418	DE_ASSERT(!log->isCaseOpen);
419	DE_ASSERT(ContainerStack_isEmpty(&log->containerStack));
420
421	/* Flush XML and write out #beginTestCaseResult. */
422	qpXmlWriter_flush(log->writer);
423	fprintf(log->outputFile, "\n#beginTestCaseResult %s\n", testCasePath);
424	if (!(log->flags & QP_TEST_LOG_NO_FLUSH))
425		qpTestLog_flushFile(log);
426
427	log->isCaseOpen = DE_TRUE;
428
429	/* Fill in attributes. */
430	resultAttribs[numResultAttribs++] = qpSetStringAttrib("Version", LOG_FORMAT_VERSION);
431	resultAttribs[numResultAttribs++] = qpSetStringAttrib("CasePath", testCasePath);
432	resultAttribs[numResultAttribs++] = qpSetStringAttrib("CaseType", typeStr);
433
434	if (!qpXmlWriter_startDocument(log->writer) ||
435		!qpXmlWriter_startElement(log->writer, "TestCaseResult", numResultAttribs, resultAttribs))
436	{
437		qpPrintf("qpTestLog_startCase(): Writing XML failed\n");
438		deMutex_unlock(log->lock);
439		return DE_FALSE;
440	}
441
442	deMutex_unlock(log->lock);
443	return DE_TRUE;
444}
445
446/*--------------------------------------------------------------------*//*!
447 * \brief Log end of test case
448 * \param log qpTestLog instance
449 * \param result Test result
450 * \param description Description of a problem in case of error
451 * \return true if ok, false otherwise
452 *//*--------------------------------------------------------------------*/
453deBool qpTestLog_endCase (qpTestLog* log, qpTestResult result, const char* resultDetails)
454{
455	const char*		statusStr		= QP_LOOKUP_STRING(s_qpTestResultMap, result);
456	qpXmlAttribute	statusAttrib	= qpSetStringAttrib("StatusCode", statusStr);
457
458	deMutex_lock(log->lock);
459
460	DE_ASSERT(log->isCaseOpen);
461	DE_ASSERT(ContainerStack_isEmpty(&log->containerStack));
462
463	/* <Result StatusCode="Pass">Result details</Result>
464	 * </TestCaseResult>
465	 */
466	if (!qpXmlWriter_startElement(log->writer, "Result", 1, &statusAttrib) ||
467		(resultDetails && !qpXmlWriter_writeString(log->writer, resultDetails)) ||
468		!qpXmlWriter_endElement(log->writer, "Result") ||
469		!qpXmlWriter_endElement(log->writer, "TestCaseResult") ||
470		!qpXmlWriter_endDocument(log->writer))		/* Close any XML elements still open */
471	{
472		qpPrintf("qpTestLog_endCase(): Writing XML failed\n");
473		deMutex_unlock(log->lock);
474		return DE_FALSE;
475	}
476
477	/* Flush XML and write #endTestCaseResult. */
478	qpXmlWriter_flush(log->writer);
479	fprintf(log->outputFile, "\n#endTestCaseResult\n");
480	if (!(log->flags & QP_TEST_LOG_NO_FLUSH))
481		qpTestLog_flushFile(log);
482
483	log->isCaseOpen = DE_FALSE;
484
485	deMutex_unlock(log->lock);
486	return DE_TRUE;
487}
488
489/*--------------------------------------------------------------------*//*!
490 * \brief Abrupt termination of logging.
491 * \param log		qpTestLog instance
492 * \param result	Result code, only Crash and Timeout are allowed.
493 * \return true if ok, false otherwise
494 *//*--------------------------------------------------------------------*/
495deBool qpTestLog_terminateCase (qpTestLog* log, qpTestResult result)
496{
497	const char* resultStr = QP_LOOKUP_STRING(s_qpTestResultMap, result);
498
499	DE_ASSERT(log);
500	DE_ASSERT(result == QP_TEST_RESULT_CRASH || result == QP_TEST_RESULT_TIMEOUT);
501
502	deMutex_lock(log->lock);
503
504	if (!log->isCaseOpen)
505	{
506		deMutex_unlock(log->lock);
507		return DE_FALSE; /* Soft error. This is called from error handler. */
508	}
509
510	/* Flush XML and write #terminateTestCaseResult. */
511	qpXmlWriter_flush(log->writer);
512	fprintf(log->outputFile, "\n#terminateTestCaseResult %s\n", resultStr);
513	qpTestLog_flushFile(log);
514
515	log->isCaseOpen = DE_FALSE;
516
517#if defined(DE_DEBUG)
518	ContainerStack_reset(&log->containerStack);
519#endif
520
521	deMutex_unlock(log->lock);
522	return DE_TRUE;
523}
524
525static deBool qpTestLog_writeKeyValuePair (qpTestLog* log, const char* elementName, const char* name, const char* description, const char* unit, qpKeyValueTag tag, const char* text)
526{
527	const char*		tagString = QP_LOOKUP_STRING(s_qpTagMap, tag);
528	qpXmlAttribute	attribs[8];
529	int				numAttribs = 0;
530
531	DE_ASSERT(log && elementName && text);
532	deMutex_lock(log->lock);
533
534	/* Fill in attributes. */
535	if (name)			attribs[numAttribs++] = qpSetStringAttrib("Name", name);
536	if (description)	attribs[numAttribs++] = qpSetStringAttrib("Description", description);
537	if (tagString)		attribs[numAttribs++] = qpSetStringAttrib("Tag", tagString);
538	if (unit)			attribs[numAttribs++] = qpSetStringAttrib("Unit", unit);
539
540	if (!qpXmlWriter_startElement(log->writer, elementName, numAttribs, attribs) ||
541		!qpXmlWriter_writeString(log->writer, text) ||
542		!qpXmlWriter_endElement(log->writer, elementName))
543	{
544		qpPrintf("qpTestLog_writeKeyValuePair(): Writing XML failed\n");
545		deMutex_unlock(log->lock);
546		return DE_FALSE;
547	}
548
549	deMutex_unlock(log->lock);
550	return DE_TRUE;
551}
552
553/*--------------------------------------------------------------------*//*!
554 * \brief Write a message to output log
555 * \param log		qpTestLog instance
556 * \param format	Format string of message
557 * \param ...		Parameters for message
558 * \return true if ok, false otherwise
559 *//*--------------------------------------------------------------------*/
560deBool qpTestLog_writeMessage (qpTestLog* log, const char* format, ...)
561{
562	char	buffer[1024];
563	va_list	args;
564
565	/* \todo [petri] Handle buffer overflows! */
566
567	va_start(args, format);
568	buffer[DE_LENGTH_OF_ARRAY(buffer) - 1] = 0;
569	vsnprintf(buffer, sizeof(buffer), format, args);
570	va_end(args);
571
572	printf("%s\n", buffer);
573
574	/* <Text>text</Text> */
575	return qpTestLog_writeKeyValuePair(log, "Text", DE_NULL, DE_NULL, DE_NULL, QP_KEY_TAG_LAST, buffer);
576}
577
578/*--------------------------------------------------------------------*//*!
579 * \brief Write key-value-pair into log
580 * \param log			qpTestLog instance
581 * \param name			Unique identifier for entry
582 * \param description	Human readable description
583 * \param tag			Optional tag
584 * \param value			Value of the key-value-pair
585 * \return true if ok, false otherwise
586 *//*--------------------------------------------------------------------*/
587deBool qpTestLog_writeText (qpTestLog* log, const char* name, const char* description, qpKeyValueTag tag, const char* text)
588{
589	/* <Text Name="name" Description="description" Tag="tag">text</Text> */
590	return qpTestLog_writeKeyValuePair(log, "Text", name, description, DE_NULL, tag, text);
591}
592
593/*--------------------------------------------------------------------*//*!
594 * \brief Write key-value-pair into log
595 * \param log			qpTestLog instance
596 * \param name			Unique identifier for entry
597 * \param description	Human readable description
598 * \param tag			Optional tag
599 * \param value			Value of the key-value-pair
600 * \return true if ok, false otherwise
601 *//*--------------------------------------------------------------------*/
602deBool qpTestLog_writeInteger (qpTestLog* log, const char* name, const char* description, const char* unit, qpKeyValueTag tag, deInt64 value)
603{
604	char tmpString[64];
605	int64ToString(value, tmpString);
606
607	printf("%s = %lld %s\n", description, (signed long long)value, unit ? unit : "");
608
609	/* <Number Name="name" Description="description" Tag="Performance">15</Number> */
610	return qpTestLog_writeKeyValuePair(log, "Number", name, description, unit, tag, tmpString);
611}
612
613/*--------------------------------------------------------------------*//*!
614 * \brief Write key-value-pair into log
615 * \param log			qpTestLog instance
616 * \param name			Unique identifier for entry
617 * \param description	Human readable description
618 * \param tag			Optional tag
619 * \param value			Value of the key-value-pair
620 * \return true if ok, false otherwise
621 *//*--------------------------------------------------------------------*/
622deBool qpTestLog_writeFloat (qpTestLog* log, const char* name, const char* description, const char* unit, qpKeyValueTag tag, float value)
623{
624	char tmpString[64];
625	floatToString(value, tmpString, sizeof(tmpString));
626
627	printf("%s = %f %s\n", description, value, unit ? unit : "");
628
629	/* <Number Name="name" Description="description" Tag="Performance">15</Number> */
630	return qpTestLog_writeKeyValuePair(log, "Number", name, description, unit, tag, tmpString);
631}
632
633typedef struct Buffer_s
634{
635	size_t		capacity;
636	size_t		size;
637	deUint8*	data;
638} Buffer;
639
640void Buffer_init (Buffer* buffer)
641{
642	buffer->capacity	= 0;
643	buffer->size		= 0;
644	buffer->data		= DE_NULL;
645}
646
647void Buffer_deinit (Buffer* buffer)
648{
649	deFree(buffer->data);
650	Buffer_init(buffer);
651}
652
653deBool Buffer_resize (Buffer* buffer, size_t newSize)
654{
655	/* Grow buffer if necessary. */
656	if (newSize > buffer->capacity)
657	{
658		size_t		newCapacity	= (size_t)deAlign32(deMax32(2*(int)buffer->capacity, (int)newSize), 512);
659		deUint8*	newData		= (deUint8*)deMalloc(newCapacity);
660		if (!newData)
661			return DE_FALSE;
662
663		memcpy(newData, buffer->data, buffer->size);
664		deFree(buffer->data);
665		buffer->data		= newData;
666		buffer->capacity	= newCapacity;
667	}
668
669	buffer->size = newSize;
670	return DE_TRUE;
671}
672
673deBool Buffer_append (Buffer* buffer, const deUint8* data, size_t numBytes)
674{
675	size_t offset = buffer->size;
676
677	if (!Buffer_resize(buffer, buffer->size + numBytes))
678		return DE_FALSE;
679
680	/* Append bytes. */
681	memcpy(&buffer->data[offset], data, numBytes);
682	return DE_TRUE;
683}
684
685#if defined(QP_SUPPORT_PNG)
686void pngWriteData (png_structp png, png_bytep dataPtr, png_size_t numBytes)
687{
688	Buffer* buffer = (Buffer*)png_get_io_ptr(png);
689	if (!Buffer_append(buffer, (const deUint8*)dataPtr, numBytes))
690		png_error(png, "unable to resize PNG write buffer!");
691}
692
693void pngFlushData (png_structp png)
694{
695	DE_UNREF(png);
696	/* nada */
697}
698
699static deBool writeCompressedPNG (png_structp png, png_infop info, png_byte** rowPointers, int width, int height, int colorFormat)
700{
701	if (setjmp(png_jmpbuf(png)) == 0)
702	{
703		/* Write data. */
704		png_set_IHDR(png, info, (png_uint_32)width, (png_uint_32)height,
705			8,
706			colorFormat,
707			PNG_INTERLACE_NONE,
708			PNG_COMPRESSION_TYPE_BASE,
709			PNG_FILTER_TYPE_BASE);
710		png_write_info(png, info);
711		png_write_image(png, rowPointers);
712		png_write_end(png, NULL);
713
714		return DE_TRUE;
715	}
716	else
717		return DE_FALSE;
718}
719
720static deBool compressImagePNG (Buffer* buffer, qpImageFormat imageFormat, int width, int height, int rowStride, const void* data)
721{
722	deBool			compressOk		= DE_FALSE;
723	png_structp		png				= DE_NULL;
724	png_infop		info			= DE_NULL;
725	png_byte**		rowPointers		= DE_NULL;
726	deBool			hasAlpha		= imageFormat == QP_IMAGE_FORMAT_RGBA8888;
727	int				ndx;
728
729	/* Handle format. */
730	DE_ASSERT(imageFormat == QP_IMAGE_FORMAT_RGB888 || imageFormat == QP_IMAGE_FORMAT_RGBA8888);
731
732	/* Allocate & set row pointers. */
733	rowPointers = (png_byte**)deMalloc((size_t)height * sizeof(png_byte*));
734	if (!rowPointers)
735		return DE_FALSE;
736
737	for (ndx = 0; ndx < height; ndx++)
738		rowPointers[ndx] = (png_byte*)((const deUint8*)data + ndx*rowStride);
739
740	/* Initialize PNG compressor. */
741	png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
742	info = png ? png_create_info_struct(png) : DE_NULL;
743	if (png && info)
744	{
745		/* Set our own write function. */
746		png_set_write_fn(png, buffer, pngWriteData, pngFlushData);
747
748		compressOk = writeCompressedPNG(png, info, rowPointers, width, height,
749										hasAlpha ? PNG_COLOR_TYPE_RGBA : PNG_COLOR_TYPE_RGB);
750	}
751
752	/* Cleanup & return. */
753	if (png && info)
754	{
755		png_destroy_info_struct(png, &info);
756		png_destroy_write_struct(&png, DE_NULL);
757	}
758	else if (png)
759		png_destroy_write_struct(&png, &info);
760
761	deFree(rowPointers);
762	return compressOk;
763}
764#endif /* QP_SUPPORT_PNG */
765
766/*--------------------------------------------------------------------*//*!
767 * \brief Start image set
768 * \param log			qpTestLog instance
769 * \param name			Unique identifier for the set
770 * \param description	Human readable description
771 * \return true if ok, false otherwise
772 *//*--------------------------------------------------------------------*/
773deBool qpTestLog_startImageSet (qpTestLog* log, const char* name, const char* description)
774{
775	qpXmlAttribute	attribs[4];
776	int				numAttribs = 0;
777
778	DE_ASSERT(log && name);
779	deMutex_lock(log->lock);
780
781	attribs[numAttribs++] = qpSetStringAttrib("Name", name);
782	if (description)
783		attribs[numAttribs++] = qpSetStringAttrib("Description", description);
784
785	/* <ImageSet Name="<name>"> */
786	if (!qpXmlWriter_startElement(log->writer, "ImageSet", numAttribs, attribs))
787	{
788		qpPrintf("qpTestLog_startImageSet(): Writing XML failed\n");
789		deMutex_unlock(log->lock);
790		return DE_FALSE;
791	}
792
793	DE_ASSERT(ContainerStack_push(&log->containerStack, CONTAINERTYPE_IMAGESET));
794
795	deMutex_unlock(log->lock);
796	return DE_TRUE;
797}
798
799/*--------------------------------------------------------------------*//*!
800 * \brief End image set
801 * \param log			qpTestLog instance
802 * \return true if ok, false otherwise
803 *//*--------------------------------------------------------------------*/
804deBool qpTestLog_endImageSet (qpTestLog* log)
805{
806	DE_ASSERT(log);
807	deMutex_lock(log->lock);
808
809	/* <ImageSet Name="<name>"> */
810	if (!qpXmlWriter_endElement(log->writer, "ImageSet"))
811	{
812		qpPrintf("qpTestLog_endImageSet(): Writing XML failed\n");
813		deMutex_unlock(log->lock);
814		return DE_FALSE;
815	}
816
817	DE_ASSERT(ContainerStack_pop(&log->containerStack) == CONTAINERTYPE_IMAGESET);
818
819	deMutex_unlock(log->lock);
820	return DE_TRUE;
821}
822
823/*--------------------------------------------------------------------*//*!
824 * \brief Write base64 encoded raw image data into log
825 * \param log				qpTestLog instance
826 * \param name				Unique name (matching names can be compared across BatchResults).
827 * \param description		Textual description (shown in Candy).
828 * \param compressionMode	Compression mode
829 * \param imageFormat		Color format
830 * \param width				Width in pixels
831 * \param height			Height in pixels
832 * \param stride			Data stride (offset between rows)
833 * \param data				Pointer to pixel data
834 * \return 0 if OK, otherwise <0
835 *//*--------------------------------------------------------------------*/
836deBool qpTestLog_writeImage	(
837	qpTestLog*				log,
838	const char*				name,
839	const char*				description,
840	qpImageCompressionMode	compressionMode,
841	qpImageFormat			imageFormat,
842	int						width,
843	int						height,
844	int						stride,
845	const void*				data)
846{
847	char			widthStr[32];
848	char			heightStr[32];
849	qpXmlAttribute	attribs[8];
850	int				numAttribs			= 0;
851	Buffer			compressedBuffer;
852	const void*		writeDataPtr		= DE_NULL;
853	size_t			writeDataBytes		= ~(size_t)0;
854
855	DE_ASSERT(log && name);
856	DE_ASSERT(deInRange32(width, 1, 16384));
857	DE_ASSERT(deInRange32(height, 1, 16384));
858	DE_ASSERT(data);
859
860	if (log->flags & QP_TEST_LOG_EXCLUDE_IMAGES)
861		return DE_TRUE; /* Image not logged. */
862
863	Buffer_init(&compressedBuffer);
864
865	/* BEST compression mode defaults to PNG. */
866	if (compressionMode == QP_IMAGE_COMPRESSION_MODE_BEST)
867	{
868#if defined(QP_SUPPORT_PNG)
869		compressionMode = QP_IMAGE_COMPRESSION_MODE_PNG;
870#else
871		compressionMode = QP_IMAGE_COMPRESSION_MODE_NONE;
872#endif
873	}
874
875#if defined(QP_SUPPORT_PNG)
876	/* Try storing with PNG compression. */
877	if (compressionMode == QP_IMAGE_COMPRESSION_MODE_PNG)
878	{
879		deBool compressOk = compressImagePNG(&compressedBuffer, imageFormat, width, height, stride, data);
880		if (compressOk)
881		{
882			writeDataPtr	= compressedBuffer.data;
883			writeDataBytes	= compressedBuffer.size;
884		}
885		else
886		{
887			/* Fall-back to default compression. */
888			qpPrintf("WARNING: PNG compression failed -- storing image uncompressed.\n");
889			compressionMode	= QP_IMAGE_COMPRESSION_MODE_NONE;
890		}
891	}
892#endif
893
894	/* Handle image compression. */
895	switch (compressionMode)
896	{
897		case QP_IMAGE_COMPRESSION_MODE_NONE:
898		{
899			int pixelSize		= imageFormat == QP_IMAGE_FORMAT_RGB888 ? 3 : 4;
900			int packedStride	= pixelSize*width;
901
902			if (packedStride == stride)
903				writeDataPtr = data;
904			else
905			{
906				/* Need to re-pack pixels. */
907				if (Buffer_resize(&compressedBuffer, (size_t)(packedStride*height)))
908				{
909					int row;
910					for (row = 0; row < height; row++)
911						memcpy(&compressedBuffer.data[packedStride*row], &((const deUint8*)data)[row*stride], (size_t)(pixelSize*width));
912				}
913				else
914				{
915					qpPrintf("ERROR: Failed to pack pixels for writing.\n");
916					Buffer_deinit(&compressedBuffer);
917					return DE_FALSE;
918				}
919			}
920
921			writeDataBytes = (size_t)(packedStride*height);
922			break;
923		}
924
925#if defined(QP_SUPPORT_PNG)
926		case QP_IMAGE_COMPRESSION_MODE_PNG:
927			DE_ASSERT(writeDataPtr); /* Already handled. */
928			break;
929#endif
930
931		default:
932			qpPrintf("qpTestLog_writeImage(): Unknown compression mode: %s\n", QP_LOOKUP_STRING(s_qpImageCompressionModeMap, compressionMode));
933			Buffer_deinit(&compressedBuffer);
934			return DE_FALSE;
935	}
936
937	/* Fill in attributes. */
938	int32ToString(width, widthStr);
939	int32ToString(height, heightStr);
940	attribs[numAttribs++] = qpSetStringAttrib("Name", name);
941	attribs[numAttribs++] = qpSetStringAttrib("Width", widthStr);
942	attribs[numAttribs++] = qpSetStringAttrib("Height", heightStr);
943	attribs[numAttribs++] = qpSetStringAttrib("Format", QP_LOOKUP_STRING(s_qpImageFormatMap, imageFormat));
944	attribs[numAttribs++] = qpSetStringAttrib("CompressionMode", QP_LOOKUP_STRING(s_qpImageCompressionModeMap, compressionMode));
945	if (description) attribs[numAttribs++] = qpSetStringAttrib("Description", description);
946
947	/* \note Log lock is acquired after compression! */
948	deMutex_lock(log->lock);
949
950	/* <Image ID="result" Name="Foobar" Width="640" Height="480" Format="RGB888" CompressionMode="None">base64 data</Image> */
951	if (!qpXmlWriter_startElement(log->writer, "Image", numAttribs, attribs) ||
952		!qpXmlWriter_writeBase64(log->writer, (const deUint8*)writeDataPtr, writeDataBytes) ||
953		!qpXmlWriter_endElement(log->writer, "Image"))
954	{
955		qpPrintf("qpTestLog_writeImage(): Writing XML failed\n");
956		deMutex_unlock(log->lock);
957		Buffer_deinit(&compressedBuffer);
958		return DE_FALSE;
959	}
960
961	deMutex_unlock(log->lock);
962
963	/* Free compressed data if allocated. */
964	Buffer_deinit(&compressedBuffer);
965
966	return DE_TRUE;
967}
968
969/*--------------------------------------------------------------------*//*!
970 * \brief Write a OpenGL ES shader program into the log.
971 * \param linkOk			Shader program link result, false on failure
972 * \param linkInfoLog		Implementation provided linkage log
973 *//*--------------------------------------------------------------------*/
974deBool qpTestLog_startShaderProgram (qpTestLog* log, deBool linkOk, const char* linkInfoLog)
975{
976	qpXmlAttribute	programAttribs[4];
977	int				numProgramAttribs = 0;
978
979	DE_ASSERT(log);
980	deMutex_lock(log->lock);
981
982	programAttribs[numProgramAttribs++] = qpSetStringAttrib("LinkStatus", linkOk ? "OK" : "Fail");
983
984	if (!qpXmlWriter_startElement(log->writer, "ShaderProgram", numProgramAttribs, programAttribs) ||
985		!qpXmlWriter_writeStringElement(log->writer, "InfoLog", linkInfoLog))
986	{
987		qpPrintf("qpTestLog_startShaderProgram(): Writing XML failed\n");
988		deMutex_unlock(log->lock);
989		return DE_FALSE;
990	}
991
992	DE_ASSERT(ContainerStack_push(&log->containerStack, CONTAINERTYPE_SHADERPROGRAM));
993
994	deMutex_unlock(log->lock);
995	return DE_TRUE;
996}
997
998/*--------------------------------------------------------------------*//*!
999 * \brief End shader program
1000 * \param log			qpTestLog instance
1001 * \return true if ok, false otherwise
1002 *//*--------------------------------------------------------------------*/
1003deBool qpTestLog_endShaderProgram (qpTestLog* log)
1004{
1005	DE_ASSERT(log);
1006	deMutex_lock(log->lock);
1007
1008	/* </ShaderProgram> */
1009	if (!qpXmlWriter_endElement(log->writer, "ShaderProgram"))
1010	{
1011		qpPrintf("qpTestLog_endShaderProgram(): Writing XML failed\n");
1012		deMutex_unlock(log->lock);
1013		return DE_FALSE;
1014	}
1015
1016	DE_ASSERT(ContainerStack_pop(&log->containerStack) == CONTAINERTYPE_SHADERPROGRAM);
1017
1018	deMutex_unlock(log->lock);
1019	return DE_TRUE;
1020}
1021
1022/*--------------------------------------------------------------------*//*!
1023 * \brief Write a OpenGL ES shader into the log.
1024 * \param type				Shader type
1025 * \param source			Shader source
1026 * \param compileOk			Shader compilation result, false on failure
1027 * \param infoLog			Implementation provided shader compilation log
1028 *//*--------------------------------------------------------------------*/
1029deBool qpTestLog_writeShader (qpTestLog* log, qpShaderType type, const char* source, deBool compileOk, const char* infoLog)
1030{
1031	const char*		tagName				= QP_LOOKUP_STRING(s_qpShaderTypeMap, type);
1032	const char*		sourceStr			= ((log->flags & QP_TEST_LOG_EXCLUDE_SHADER_SOURCES) == 0 || !compileOk) ? source : "";
1033	int				numShaderAttribs	= 0;
1034	qpXmlAttribute	shaderAttribs[4];
1035
1036	deMutex_lock(log->lock);
1037
1038	DE_ASSERT(source);
1039	DE_ASSERT(ContainerStack_getTop(&log->containerStack) == CONTAINERTYPE_SHADERPROGRAM);
1040
1041	shaderAttribs[numShaderAttribs++]	= qpSetStringAttrib("CompileStatus", compileOk ? "OK" : "Fail");
1042
1043	if (!qpXmlWriter_startElement(log->writer, tagName, numShaderAttribs, shaderAttribs) ||
1044		!qpXmlWriter_writeStringElement(log->writer, "ShaderSource", sourceStr) ||
1045		!qpXmlWriter_writeStringElement(log->writer, "InfoLog", infoLog) ||
1046		!qpXmlWriter_endElement(log->writer, tagName))
1047	{
1048		qpPrintf("qpTestLog_writeShader(): Writing XML failed\n");
1049		deMutex_unlock(log->lock);
1050		return DE_FALSE;
1051	}
1052
1053	deMutex_unlock(log->lock);
1054	return DE_TRUE;
1055}
1056
1057/*--------------------------------------------------------------------*//*!
1058 * \brief Start writing a set of EGL configurations into the log.
1059 *//*--------------------------------------------------------------------*/
1060deBool qpTestLog_startEglConfigSet (qpTestLog* log, const char* name, const char* description)
1061{
1062	qpXmlAttribute	attribs[4];
1063	int				numAttribs = 0;
1064
1065	DE_ASSERT(log && name);
1066	deMutex_lock(log->lock);
1067
1068	attribs[numAttribs++] = qpSetStringAttrib("Name", name);
1069	if (description)
1070		attribs[numAttribs++] = qpSetStringAttrib("Description", description);
1071
1072	/* <EglConfigSet Name="<name>"> */
1073	if (!qpXmlWriter_startElement(log->writer, "EglConfigSet", numAttribs, attribs))
1074	{
1075		qpPrintf("qpTestLog_startEglImageSet(): Writing XML failed\n");
1076		deMutex_unlock(log->lock);
1077		return DE_FALSE;
1078	}
1079
1080	DE_ASSERT(ContainerStack_push(&log->containerStack, CONTAINERTYPE_EGLCONFIGSET));
1081
1082	deMutex_unlock(log->lock);
1083	return DE_TRUE;
1084}
1085
1086/*--------------------------------------------------------------------*//*!
1087 * \brief End an EGL config set
1088 *//*--------------------------------------------------------------------*/
1089deBool qpTestLog_endEglConfigSet (qpTestLog* log)
1090{
1091	DE_ASSERT(log);
1092	deMutex_lock(log->lock);
1093
1094	/* <EglConfigSet Name="<name>"> */
1095	if (!qpXmlWriter_endElement(log->writer, "EglConfigSet"))
1096	{
1097		qpPrintf("qpTestLog_endEglImageSet(): Writing XML failed\n");
1098		deMutex_unlock(log->lock);
1099		return DE_FALSE;
1100	}
1101
1102	DE_ASSERT(ContainerStack_pop(&log->containerStack) == CONTAINERTYPE_EGLCONFIGSET);
1103
1104	deMutex_unlock(log->lock);
1105	return DE_TRUE;
1106}
1107
1108/*--------------------------------------------------------------------*//*!
1109 * \brief Write an EGL config inside an EGL config set
1110 * \see   qpElgConfigInfo for details
1111 *//*--------------------------------------------------------------------*/
1112deBool qpTestLog_writeEglConfig (qpTestLog* log, const qpEglConfigInfo* config)
1113{
1114	qpXmlAttribute	attribs[64];
1115	int				numAttribs = 0;
1116
1117	DE_ASSERT(log && config);
1118	deMutex_lock(log->lock);
1119
1120	attribs[numAttribs++] = qpSetIntAttrib		("BufferSize", config->bufferSize);
1121	attribs[numAttribs++] = qpSetIntAttrib		("RedSize", config->redSize);
1122	attribs[numAttribs++] = qpSetIntAttrib		("GreenSize", config->greenSize);
1123	attribs[numAttribs++] = qpSetIntAttrib		("BlueSize", config->blueSize);
1124	attribs[numAttribs++] = qpSetIntAttrib		("LuminanceSize", config->luminanceSize);
1125	attribs[numAttribs++] = qpSetIntAttrib		("AlphaSize", config->alphaSize);
1126	attribs[numAttribs++] = qpSetIntAttrib		("AlphaMaskSize", config->alphaMaskSize);
1127	attribs[numAttribs++] = qpSetBoolAttrib		("BindToTextureRGB", config->bindToTextureRGB);
1128	attribs[numAttribs++] = qpSetBoolAttrib		("BindToTextureRGBA", config->bindToTextureRGBA);
1129	attribs[numAttribs++] = qpSetStringAttrib	("ColorBufferType", config->colorBufferType);
1130	attribs[numAttribs++] = qpSetStringAttrib	("ConfigCaveat", config->configCaveat);
1131	attribs[numAttribs++] = qpSetIntAttrib		("ConfigID", config->configID);
1132	attribs[numAttribs++] = qpSetStringAttrib	("Conformant", config->conformant);
1133	attribs[numAttribs++] = qpSetIntAttrib		("DepthSize", config->depthSize);
1134	attribs[numAttribs++] = qpSetIntAttrib		("Level", config->level);
1135	attribs[numAttribs++] = qpSetIntAttrib		("MaxPBufferWidth", config->maxPBufferWidth);
1136	attribs[numAttribs++] = qpSetIntAttrib		("MaxPBufferHeight", config->maxPBufferHeight);
1137	attribs[numAttribs++] = qpSetIntAttrib		("MaxPBufferPixels", config->maxPBufferPixels);
1138	attribs[numAttribs++] = qpSetIntAttrib		("MaxSwapInterval", config->maxSwapInterval);
1139	attribs[numAttribs++] = qpSetIntAttrib		("MinSwapInterval", config->minSwapInterval);
1140	attribs[numAttribs++] = qpSetBoolAttrib		("NativeRenderable", config->nativeRenderable);
1141	attribs[numAttribs++] = qpSetStringAttrib	("RenderableType", config->renderableType);
1142	attribs[numAttribs++] = qpSetIntAttrib		("SampleBuffers", config->sampleBuffers);
1143	attribs[numAttribs++] = qpSetIntAttrib		("Samples", config->samples);
1144	attribs[numAttribs++] = qpSetIntAttrib		("StencilSize", config->stencilSize);
1145	attribs[numAttribs++] = qpSetStringAttrib	("SurfaceTypes", config->surfaceTypes);
1146	attribs[numAttribs++] = qpSetStringAttrib	("TransparentType", config->transparentType);
1147	attribs[numAttribs++] = qpSetIntAttrib		("TransparentRedValue", config->transparentRedValue);
1148	attribs[numAttribs++] = qpSetIntAttrib		("TransparentGreenValue", config->transparentGreenValue);
1149	attribs[numAttribs++] = qpSetIntAttrib		("TransparentBlueValue", config->transparentBlueValue);
1150	DE_ASSERT(numAttribs <= DE_LENGTH_OF_ARRAY(attribs));
1151
1152	if (!qpXmlWriter_startElement(log->writer, "EglConfig", numAttribs, attribs) ||
1153		!qpXmlWriter_endElement(log->writer, "EglConfig"))
1154	{
1155		qpPrintf("qpTestLog_writeEglConfig(): Writing XML failed\n");
1156		deMutex_unlock(log->lock);
1157		return DE_FALSE;
1158	}
1159
1160	deMutex_unlock(log->lock);
1161	return DE_TRUE;
1162}
1163
1164/*--------------------------------------------------------------------*//*!
1165 * \brief Start section in log.
1166 * \param log			qpTestLog instance
1167 * \param name			Section name
1168 * \param description	Human readable description
1169 * \return true if ok, false otherwise
1170 *//*--------------------------------------------------------------------*/
1171deBool qpTestLog_startSection (qpTestLog* log, const char* name, const char* description)
1172{
1173	qpXmlAttribute	attribs[2];
1174	int				numAttribs = 0;
1175
1176	DE_ASSERT(log && name);
1177	deMutex_lock(log->lock);
1178
1179	attribs[numAttribs++] = qpSetStringAttrib("Name", name);
1180	if (description)
1181		attribs[numAttribs++] = qpSetStringAttrib("Description", description);
1182
1183	/* <Section Name="<name>" Description="<description>"> */
1184	if (!qpXmlWriter_startElement(log->writer, "Section", numAttribs, attribs))
1185	{
1186		qpPrintf("qpTestLog_startSection(): Writing XML failed\n");
1187		deMutex_unlock(log->lock);
1188		return DE_FALSE;
1189	}
1190
1191	DE_ASSERT(ContainerStack_push(&log->containerStack, CONTAINERTYPE_SECTION));
1192
1193	deMutex_unlock(log->lock);
1194	return DE_TRUE;
1195}
1196
1197/*--------------------------------------------------------------------*//*!
1198 * \brief End section in log.
1199 * \param log			qpTestLog instance
1200 * \return true if ok, false otherwise
1201 *//*--------------------------------------------------------------------*/
1202deBool qpTestLog_endSection (qpTestLog* log)
1203{
1204	DE_ASSERT(log);
1205	deMutex_lock(log->lock);
1206
1207	/* </Section> */
1208	if (!qpXmlWriter_endElement(log->writer, "Section"))
1209	{
1210		qpPrintf("qpTestLog_endSection(): Writing XML failed\n");
1211		deMutex_unlock(log->lock);
1212		return DE_FALSE;
1213	}
1214
1215	DE_ASSERT(ContainerStack_pop(&log->containerStack) == CONTAINERTYPE_SECTION);
1216
1217	deMutex_unlock(log->lock);
1218	return DE_TRUE;
1219}
1220
1221/*--------------------------------------------------------------------*//*!
1222 * \brief Write OpenCL compute kernel source into the log.
1223 *//*--------------------------------------------------------------------*/
1224deBool qpTestLog_writeKernelSource (qpTestLog* log, const char* source)
1225{
1226	const char*		sourceStr	= (log->flags & QP_TEST_LOG_EXCLUDE_SHADER_SOURCES) != 0 ? "" : source;
1227
1228	DE_ASSERT(log);
1229	deMutex_lock(log->lock);
1230
1231	if (!qpXmlWriter_writeStringElement(log->writer, "KernelSource", sourceStr))
1232	{
1233		qpPrintf("qpTestLog_writeKernelSource(): Writing XML failed\n");
1234		deMutex_unlock(log->lock);
1235		return DE_FALSE;
1236	}
1237
1238	deMutex_unlock(log->lock);
1239	return DE_TRUE;
1240}
1241
1242/*--------------------------------------------------------------------*//*!
1243 * \brief Write a SPIR-V module assembly source into the log.
1244 *//*--------------------------------------------------------------------*/
1245deBool qpTestLog_writeSpirVAssemblySource (qpTestLog* log, const char* source)
1246{
1247	const char* const	sourceStr	= (log->flags & QP_TEST_LOG_EXCLUDE_SHADER_SOURCES) != 0 ? "" : source;
1248
1249	deMutex_lock(log->lock);
1250
1251	DE_ASSERT(ContainerStack_getTop(&log->containerStack) == CONTAINERTYPE_SHADERPROGRAM);
1252
1253	if (!qpXmlWriter_writeStringElement(log->writer, "SpirVAssemblySource", sourceStr))
1254	{
1255		qpPrintf("qpTestLog_writeSpirVAssemblySource(): Writing XML failed\n");
1256		deMutex_unlock(log->lock);
1257		return DE_FALSE;
1258	}
1259
1260	deMutex_unlock(log->lock);
1261	return DE_TRUE;
1262}
1263
1264/*--------------------------------------------------------------------*//*!
1265 * \brief Write OpenCL kernel compilation results into the log
1266 *//*--------------------------------------------------------------------*/
1267deBool qpTestLog_writeCompileInfo (qpTestLog* log, const char* name, const char* description, deBool compileOk, const char* infoLog)
1268{
1269	int				numAttribs = 0;
1270	qpXmlAttribute	attribs[3];
1271
1272	DE_ASSERT(log && name && description && infoLog);
1273	deMutex_lock(log->lock);
1274
1275	attribs[numAttribs++] = qpSetStringAttrib("Name", name);
1276	attribs[numAttribs++] = qpSetStringAttrib("Description", description);
1277	attribs[numAttribs++] = qpSetStringAttrib("CompileStatus", compileOk ? "OK" : "Fail");
1278
1279	if (!qpXmlWriter_startElement(log->writer, "CompileInfo", numAttribs, attribs) ||
1280		!qpXmlWriter_writeStringElement(log->writer, "InfoLog", infoLog) ||
1281		!qpXmlWriter_endElement(log->writer, "CompileInfo"))
1282	{
1283		qpPrintf("qpTestLog_writeCompileInfo(): Writing XML failed\n");
1284		deMutex_unlock(log->lock);
1285		return DE_FALSE;
1286	}
1287
1288	deMutex_unlock(log->lock);
1289	return DE_TRUE;
1290}
1291
1292deBool qpTestLog_startSampleList (qpTestLog* log, const char* name, const char* description)
1293{
1294	int				numAttribs = 0;
1295	qpXmlAttribute	attribs[2];
1296
1297	DE_ASSERT(log && name && description);
1298	deMutex_lock(log->lock);
1299
1300	attribs[numAttribs++] = qpSetStringAttrib("Name", name);
1301	attribs[numAttribs++] = qpSetStringAttrib("Description", description);
1302
1303	if (!qpXmlWriter_startElement(log->writer, "SampleList", numAttribs, attribs))
1304	{
1305		qpPrintf("qpTestLog_startSampleList(): Writing XML failed\n");
1306		deMutex_unlock(log->lock);
1307		return DE_FALSE;
1308	}
1309
1310	DE_ASSERT(ContainerStack_push(&log->containerStack, CONTAINERTYPE_SAMPLELIST));
1311
1312	deMutex_unlock(log->lock);
1313	return DE_TRUE;
1314}
1315
1316deBool qpTestLog_startSampleInfo (qpTestLog* log)
1317{
1318	DE_ASSERT(log);
1319	deMutex_lock(log->lock);
1320
1321	if (!qpXmlWriter_startElement(log->writer, "SampleInfo", 0, DE_NULL))
1322	{
1323		qpPrintf("qpTestLog_startSampleInfo(): Writing XML failed\n");
1324		deMutex_unlock(log->lock);
1325		return DE_FALSE;
1326	}
1327
1328	DE_ASSERT(ContainerStack_push(&log->containerStack, CONTAINERTYPE_SAMPLEINFO));
1329
1330	deMutex_unlock(log->lock);
1331	return DE_TRUE;
1332}
1333
1334deBool qpTestLog_writeValueInfo (qpTestLog* log, const char* name, const char* description, const char* unit, qpSampleValueTag tag)
1335{
1336	const char*		tagName		= QP_LOOKUP_STRING(s_qpSampleValueTagMap, tag);
1337	int				numAttribs	= 0;
1338	qpXmlAttribute	attribs[4];
1339
1340	DE_ASSERT(log && name && description && tagName);
1341	deMutex_lock(log->lock);
1342
1343	DE_ASSERT(ContainerStack_getTop(&log->containerStack) == CONTAINERTYPE_SAMPLEINFO);
1344
1345	attribs[numAttribs++] = qpSetStringAttrib("Name", name);
1346	attribs[numAttribs++] = qpSetStringAttrib("Description", description);
1347	attribs[numAttribs++] = qpSetStringAttrib("Tag", tagName);
1348
1349	if (unit)
1350		attribs[numAttribs++] = qpSetStringAttrib("Unit", unit);
1351
1352	if (!qpXmlWriter_startElement(log->writer, "ValueInfo", numAttribs, attribs) ||
1353		!qpXmlWriter_endElement(log->writer, "ValueInfo"))
1354	{
1355		qpPrintf("qpTestLog_writeValueInfo(): Writing XML failed\n");
1356		deMutex_unlock(log->lock);
1357		return DE_FALSE;
1358	}
1359
1360	deMutex_unlock(log->lock);
1361	return DE_TRUE;
1362}
1363
1364deBool qpTestLog_endSampleInfo (qpTestLog* log)
1365{
1366	DE_ASSERT(log);
1367	deMutex_lock(log->lock);
1368
1369	if (!qpXmlWriter_endElement(log->writer, "SampleInfo"))
1370	{
1371		qpPrintf("qpTestLog_endSampleInfo(): Writing XML failed\n");
1372		deMutex_unlock(log->lock);
1373		return DE_FALSE;
1374	}
1375
1376	DE_ASSERT(ContainerStack_pop(&log->containerStack) == CONTAINERTYPE_SAMPLEINFO);
1377
1378	deMutex_unlock(log->lock);
1379	return DE_TRUE;
1380}
1381
1382deBool qpTestLog_startSample (qpTestLog* log)
1383{
1384	DE_ASSERT(log);
1385	deMutex_lock(log->lock);
1386
1387	DE_ASSERT(ContainerStack_getTop(&log->containerStack) == CONTAINERTYPE_SAMPLELIST);
1388
1389	if (!qpXmlWriter_startElement(log->writer, "Sample", 0, DE_NULL))
1390	{
1391		qpPrintf("qpTestLog_startSample(): Writing XML failed\n");
1392		deMutex_unlock(log->lock);
1393		return DE_FALSE;
1394	}
1395
1396	DE_ASSERT(ContainerStack_push(&log->containerStack, CONTAINERTYPE_SAMPLE));
1397
1398	deMutex_unlock(log->lock);
1399	return DE_TRUE;
1400}
1401
1402deBool qpTestLog_writeValueFloat (qpTestLog* log, double value)
1403{
1404	char tmpString[512];
1405	doubleToString(value, tmpString, (int)sizeof(tmpString));
1406
1407	deMutex_lock(log->lock);
1408
1409	DE_ASSERT(ContainerStack_getTop(&log->containerStack) == CONTAINERTYPE_SAMPLE);
1410
1411	if (!qpXmlWriter_writeStringElement(log->writer, "Value", &tmpString[0]))
1412	{
1413		qpPrintf("qpTestLog_writeSampleValue(): Writing XML failed\n");
1414		deMutex_unlock(log->lock);
1415		return DE_FALSE;
1416	}
1417
1418	deMutex_unlock(log->lock);
1419	return DE_TRUE;
1420}
1421
1422deBool qpTestLog_writeValueInteger (qpTestLog* log, deInt64 value)
1423{
1424	char tmpString[64];
1425	int64ToString(value, tmpString);
1426
1427	deMutex_lock(log->lock);
1428
1429	DE_ASSERT(ContainerStack_getTop(&log->containerStack) == CONTAINERTYPE_SAMPLE);
1430
1431	if (!qpXmlWriter_writeStringElement(log->writer, "Value", &tmpString[0]))
1432	{
1433		qpPrintf("qpTestLog_writeSampleValue(): Writing XML failed\n");
1434		deMutex_unlock(log->lock);
1435		return DE_FALSE;
1436	}
1437
1438	deMutex_unlock(log->lock);
1439	return DE_TRUE;
1440}
1441
1442deBool qpTestLog_endSample (qpTestLog* log)
1443{
1444	DE_ASSERT(log);
1445	deMutex_lock(log->lock);
1446
1447	if (!qpXmlWriter_endElement(log->writer, "Sample"))
1448	{
1449		qpPrintf("qpTestLog_endSample(): Writing XML failed\n");
1450		deMutex_unlock(log->lock);
1451		return DE_FALSE;
1452	}
1453
1454	DE_ASSERT(ContainerStack_pop(&log->containerStack) == CONTAINERTYPE_SAMPLE);
1455
1456	deMutex_unlock(log->lock);
1457	return DE_TRUE;
1458}
1459
1460deBool qpTestLog_endSampleList (qpTestLog* log)
1461{
1462	DE_ASSERT(log);
1463	deMutex_lock(log->lock);
1464
1465	if (!qpXmlWriter_endElement(log->writer, "SampleList"))
1466	{
1467		qpPrintf("qpTestLog_endSampleList(): Writing XML failed\n");
1468		deMutex_unlock(log->lock);
1469		return DE_FALSE;
1470	}
1471
1472	DE_ASSERT(ContainerStack_pop(&log->containerStack) == CONTAINERTYPE_SAMPLELIST);
1473
1474	deMutex_unlock(log->lock);
1475	return DE_TRUE;
1476}
1477
1478deUint32 qpTestLog_getLogFlags (const qpTestLog* log)
1479{
1480	DE_ASSERT(log);
1481	return log->flags;
1482}
1483
1484const char* qpGetTestResultName (qpTestResult result)
1485{
1486	return QP_LOOKUP_STRING(s_qpTestResultMap, result);
1487}
1488