| /*------------------------------------------------------------------------- |
| * drawElements TestLog Library |
| * ---------------------------- |
| * |
| * Copyright 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| *//*! |
| * \file |
| * \brief Test case result logging |
| *//*--------------------------------------------------------------------*/ |
| |
| #include "qpTestLog.h" |
| #include "qpXmlWriter.h" |
| #include "qpInfo.h" |
| #include "qpDebugOut.h" |
| |
| #include "deMemory.h" |
| #include "deInt32.h" |
| #include "deString.h" |
| |
| #include "deMutex.h" |
| |
| #if defined(QP_SUPPORT_PNG) |
| # include <png.h> |
| #endif |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <stdarg.h> |
| |
| #if (DE_OS == DE_OS_WIN32) |
| # include <windows.h> |
| # include <io.h> |
| #endif |
| |
| #if defined(DE_DEBUG) |
| |
| /* Utils for verifying container (Section, ImageSet, EglConfigSet) usage in debug builds. */ |
| |
| typedef enum ContainerType_e |
| { |
| CONTAINERTYPE_SECTION = 0, |
| CONTAINERTYPE_IMAGESET, |
| CONTAINERTYPE_EGLCONFIGSET, |
| CONTAINERTYPE_SHADERPROGRAM, |
| CONTAINERTYPE_SAMPLELIST, |
| CONTAINERTYPE_SAMPLEINFO, |
| CONTAINERTYPE_SAMPLE, |
| |
| CONTAINERTYPE_LAST |
| } ContainerType; |
| |
| DE_INLINE deBool childContainersOk (ContainerType type) |
| { |
| return type == CONTAINERTYPE_SECTION || type == CONTAINERTYPE_SAMPLELIST; |
| } |
| |
| enum |
| { |
| MAX_CONTAINER_STACK_DEPTH = 32 |
| }; |
| |
| typedef struct ContainerStack_s |
| { |
| int numElements; |
| ContainerType elements[MAX_CONTAINER_STACK_DEPTH]; |
| } ContainerStack; |
| |
| DE_INLINE void ContainerStack_reset (ContainerStack* stack) |
| { |
| deMemset(stack, 0, sizeof(ContainerStack)); |
| } |
| |
| DE_INLINE deBool ContainerStack_isEmpty (const ContainerStack* stack) |
| { |
| return stack->numElements == 0; |
| } |
| |
| DE_INLINE deBool ContainerStack_push (ContainerStack* stack, ContainerType type) |
| { |
| if (stack->numElements == MAX_CONTAINER_STACK_DEPTH) |
| return DE_FALSE; |
| |
| if (stack->numElements > 0 && !childContainersOk(stack->elements[stack->numElements-1])) |
| return DE_FALSE; |
| |
| stack->elements[stack->numElements] = type; |
| stack->numElements += 1; |
| |
| return DE_TRUE; |
| } |
| |
| DE_INLINE ContainerType ContainerStack_pop (ContainerStack* stack) |
| { |
| DE_ASSERT(stack->numElements > 0); |
| stack->numElements -= 1; |
| return stack->elements[stack->numElements]; |
| } |
| |
| DE_INLINE ContainerType ContainerStack_getTop (const ContainerStack* stack) |
| { |
| if (stack->numElements > 0) |
| return stack->elements[stack->numElements-1]; |
| else |
| return CONTAINERTYPE_LAST; |
| } |
| |
| #endif |
| |
| /* qpTestLog instance */ |
| struct qpTestLog_s |
| { |
| deUint32 flags; /*!< Logging flags. */ |
| |
| deMutex lock; /*!< Lock for mutable state below. */ |
| |
| /* State protected by lock. */ |
| FILE* outputFile; |
| qpXmlWriter* writer; |
| deBool isSessionOpen; |
| deBool isCaseOpen; |
| |
| #if defined(DE_DEBUG) |
| ContainerStack containerStack; /*!< For container usage verification. */ |
| #endif |
| }; |
| |
| /* Maps integer to string. */ |
| typedef struct qpKeyStringMap_s |
| { |
| int key; |
| char* string; |
| } qpKeyStringMap; |
| |
| static const char* LOG_FORMAT_VERSION = "0.3.4"; |
| |
| /* Mapping enum to above strings... */ |
| static const qpKeyStringMap s_qpTestTypeMap[] = |
| { |
| { QP_TEST_CASE_TYPE_SELF_VALIDATE, "SelfValidate" }, |
| { QP_TEST_CASE_TYPE_PERFORMANCE, "Performance" }, |
| { QP_TEST_CASE_TYPE_CAPABILITY, "Capability" }, |
| { QP_TEST_CASE_TYPE_ACCURACY, "Accuracy" }, |
| |
| { QP_TEST_CASE_TYPE_LAST, DE_NULL } |
| }; |
| |
| DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_qpTestTypeMap) == QP_TEST_CASE_TYPE_LAST + 1); |
| |
| static const qpKeyStringMap s_qpTestResultMap[] = |
| { |
| { QP_TEST_RESULT_PASS, "Pass" }, |
| { QP_TEST_RESULT_FAIL, "Fail" }, |
| { QP_TEST_RESULT_QUALITY_WARNING, "QualityWarning" }, |
| { QP_TEST_RESULT_COMPATIBILITY_WARNING, "CompatibilityWarning" }, |
| { QP_TEST_RESULT_PENDING, "Pending" }, /* should not be needed here */ |
| { QP_TEST_RESULT_NOT_SUPPORTED, "NotSupported" }, |
| { QP_TEST_RESULT_RESOURCE_ERROR, "ResourceError" }, |
| { QP_TEST_RESULT_INTERNAL_ERROR, "InternalError" }, |
| { QP_TEST_RESULT_CRASH, "Crash" }, |
| { QP_TEST_RESULT_TIMEOUT, "Timeout" }, |
| |
| /* Add new values here if needed, remember to update qpTestResult enumeration. */ |
| |
| { QP_TEST_RESULT_LAST, DE_NULL } |
| }; |
| |
| DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_qpTestResultMap) == QP_TEST_RESULT_LAST + 1); |
| |
| /* Key tag to string mapping. */ |
| |
| static const qpKeyStringMap s_qpTagMap[] = |
| { |
| { QP_KEY_TAG_NONE, DE_NULL }, |
| { QP_KEY_TAG_PERFORMANCE, "Performance" }, |
| { QP_KEY_TAG_QUALITY, "Quality" }, |
| { QP_KEY_TAG_PRECISION, "Precision" }, |
| { QP_KEY_TAG_TIME, "Time" }, |
| |
| { QP_KEY_TAG_LAST, DE_NULL } |
| }; |
| |
| DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_qpTagMap) == QP_KEY_TAG_LAST + 1); |
| |
| /* Sample value tag to string mapping. */ |
| |
| static const qpKeyStringMap s_qpSampleValueTagMap[] = |
| { |
| { QP_SAMPLE_VALUE_TAG_PREDICTOR, "Predictor" }, |
| { QP_SAMPLE_VALUE_TAG_RESPONSE, "Response" }, |
| |
| { QP_SAMPLE_VALUE_TAG_LAST, DE_NULL } |
| }; |
| |
| DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_qpSampleValueTagMap) == QP_SAMPLE_VALUE_TAG_LAST + 1); |
| |
| /* Image compression mode to string mapping. */ |
| |
| static const qpKeyStringMap s_qpImageCompressionModeMap[] = |
| { |
| { QP_IMAGE_COMPRESSION_MODE_NONE, "None" }, |
| { QP_IMAGE_COMPRESSION_MODE_PNG, "PNG" }, |
| |
| { QP_IMAGE_COMPRESSION_MODE_BEST, DE_NULL }, /* not allowed to be written! */ |
| |
| { QP_IMAGE_COMPRESSION_MODE_LAST, DE_NULL } |
| }; |
| |
| DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_qpImageCompressionModeMap) == QP_IMAGE_COMPRESSION_MODE_LAST + 1); |
| |
| /* Image format to string mapping. */ |
| |
| static const qpKeyStringMap s_qpImageFormatMap[] = |
| { |
| { QP_IMAGE_FORMAT_RGB888, "RGB888" }, |
| { QP_IMAGE_FORMAT_RGBA8888, "RGBA8888" }, |
| |
| { QP_IMAGE_FORMAT_LAST, DE_NULL } |
| }; |
| |
| DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_qpImageFormatMap) == QP_IMAGE_FORMAT_LAST + 1); |
| |
| /* Shader type to string mapping. */ |
| |
| static const qpKeyStringMap s_qpShaderTypeMap[] = |
| { |
| { QP_SHADER_TYPE_VERTEX, "VertexShader" }, |
| { QP_SHADER_TYPE_FRAGMENT, "FragmentShader" }, |
| { QP_SHADER_TYPE_GEOMETRY, "GeometryShader" }, |
| { QP_SHADER_TYPE_TESS_CONTROL, "TessControlShader" }, |
| { QP_SHADER_TYPE_TESS_EVALUATION, "TessEvaluationShader" }, |
| { QP_SHADER_TYPE_COMPUTE, "ComputeShader" }, |
| |
| { QP_SHADER_TYPE_LAST, DE_NULL } |
| }; |
| |
| DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_qpShaderTypeMap) == QP_SHADER_TYPE_LAST + 1); |
| |
| static void qpTestLog_flushFile (qpTestLog* log) |
| { |
| DE_ASSERT(log && log->outputFile); |
| fflush(log->outputFile); |
| #if (DE_OS == DE_OS_WIN32) && (DE_COMPILER == DE_COMPILER_MSC) |
| /* \todo [petri] Is this really necessary? */ |
| FlushFileBuffers((HANDLE)_get_osfhandle(_fileno(log->outputFile))); |
| #endif |
| } |
| |
| #define QP_LOOKUP_STRING(KEYMAP, KEY) qpLookupString(KEYMAP, DE_LENGTH_OF_ARRAY(KEYMAP), (int)(KEY)) |
| |
| static const char* qpLookupString (const qpKeyStringMap* keyMap, int keyMapSize, int key) |
| { |
| DE_ASSERT(keyMap); |
| DE_ASSERT(deInBounds32(key, 0, keyMapSize)); |
| DE_ASSERT(keyMap[key].key == key); |
| DE_UNREF(keyMapSize); /* for asserting only */ |
| return keyMap[key].string; |
| } |
| |
| DE_INLINE void int32ToString (int val, char buf[32]) |
| { |
| deSprintf(&buf[0], 32, "%d", val); |
| } |
| |
| DE_INLINE void int64ToString (deInt64 val, char buf[32]) |
| { |
| deSprintf(&buf[0], 32, "%lld", (long long int)val); |
| } |
| |
| DE_INLINE void floatToString (float value, char* buf, size_t bufSize) |
| { |
| deSprintf(buf, bufSize, "%f", value); |
| } |
| |
| DE_INLINE void doubleToString (double value, char* buf, size_t bufSize) |
| { |
| deSprintf(buf, bufSize, "%f", value); |
| } |
| |
| static deBool beginSession (qpTestLog* log) |
| { |
| DE_ASSERT(log && !log->isSessionOpen); |
| |
| /* Write session info. */ |
| fprintf(log->outputFile, "#sessionInfo releaseName %s\n", qpGetReleaseName()); |
| fprintf(log->outputFile, "#sessionInfo releaseId 0x%08x\n", qpGetReleaseId()); |
| fprintf(log->outputFile, "#sessionInfo targetName \"%s\"\n", qpGetTargetName()); |
| |
| /* Write out #beginSession. */ |
| fprintf(log->outputFile, "#beginSession\n"); |
| qpTestLog_flushFile(log); |
| |
| log->isSessionOpen = DE_TRUE; |
| |
| return DE_TRUE; |
| } |
| |
| static deBool endSession (qpTestLog* log) |
| { |
| DE_ASSERT(log && log->isSessionOpen); |
| |
| /* Make sure xml is flushed. */ |
| qpXmlWriter_flush(log->writer); |
| |
| /* Write out #endSession. */ |
| fprintf(log->outputFile, "\n#endSession\n"); |
| qpTestLog_flushFile(log); |
| |
| log->isSessionOpen = DE_FALSE; |
| |
| return DE_TRUE; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Create a file based logger instance |
| * \param fileName Name of the file where to put logs |
| * \return qpTestLog instance, or DE_NULL if cannot create file |
| *//*--------------------------------------------------------------------*/ |
| qpTestLog* qpTestLog_createFileLog (const char* fileName, deUint32 flags) |
| { |
| qpTestLog* log = (qpTestLog*)deCalloc(sizeof(qpTestLog)); |
| if (!log) |
| return DE_NULL; |
| |
| DE_ASSERT(fileName && fileName[0]); /* must have filename. */ |
| |
| #if defined(DE_DEBUG) |
| ContainerStack_reset(&log->containerStack); |
| #endif |
| |
| /* Create output file. */ |
| log->outputFile = fopen(fileName, "wb"); |
| if (!log->outputFile) |
| { |
| qpPrintf("ERROR: Unable to open test log output file '%s'.\n", fileName); |
| qpTestLog_destroy(log); |
| return DE_NULL; |
| } |
| |
| log->flags = flags; |
| log->writer = qpXmlWriter_createFileWriter(log->outputFile, 0, !(flags & QP_TEST_LOG_NO_FLUSH)); |
| log->lock = deMutex_create(DE_NULL); |
| log->isSessionOpen = DE_FALSE; |
| log->isCaseOpen = DE_FALSE; |
| |
| if (!log->writer) |
| { |
| qpPrintf("ERROR: Unable to create output XML writer to file '%s'.\n", fileName); |
| qpTestLog_destroy(log); |
| return DE_NULL; |
| } |
| |
| if (!log->lock) |
| { |
| qpPrintf("ERROR: Unable to create mutex.\n"); |
| qpTestLog_destroy(log); |
| return DE_NULL; |
| } |
| |
| beginSession(log); |
| |
| return log; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Destroy a logger instance |
| * \param a qpTestLog instance |
| *//*--------------------------------------------------------------------*/ |
| void qpTestLog_destroy (qpTestLog* log) |
| { |
| DE_ASSERT(log); |
| |
| if (log->isSessionOpen) |
| endSession(log); |
| |
| if (log->writer) |
| qpXmlWriter_destroy(log->writer); |
| |
| if (log->outputFile) |
| fclose(log->outputFile); |
| |
| if (log->lock) |
| deMutex_destroy(log->lock); |
| |
| deFree(log); |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Log start of test case |
| * \param log qpTestLog instance |
| * \param testCasePath Full test case path (as seen in Candy). |
| * \param testCaseType Test case type |
| * \return true if ok, false otherwise |
| *//*--------------------------------------------------------------------*/ |
| deBool qpTestLog_startCase (qpTestLog* log, const char* testCasePath, qpTestCaseType testCaseType) |
| { |
| const char* typeStr = QP_LOOKUP_STRING(s_qpTestTypeMap, testCaseType); |
| int numResultAttribs = 0; |
| qpXmlAttribute resultAttribs[8]; |
| |
| DE_ASSERT(log && testCasePath && (testCasePath[0] != 0)); |
| deMutex_lock(log->lock); |
| |
| DE_ASSERT(!log->isCaseOpen); |
| DE_ASSERT(ContainerStack_isEmpty(&log->containerStack)); |
| |
| /* Flush XML and write out #beginTestCaseResult. */ |
| qpXmlWriter_flush(log->writer); |
| fprintf(log->outputFile, "\n#beginTestCaseResult %s\n", testCasePath); |
| if (!(log->flags & QP_TEST_LOG_NO_FLUSH)) |
| qpTestLog_flushFile(log); |
| |
| log->isCaseOpen = DE_TRUE; |
| |
| /* Fill in attributes. */ |
| resultAttribs[numResultAttribs++] = qpSetStringAttrib("Version", LOG_FORMAT_VERSION); |
| resultAttribs[numResultAttribs++] = qpSetStringAttrib("CasePath", testCasePath); |
| resultAttribs[numResultAttribs++] = qpSetStringAttrib("CaseType", typeStr); |
| |
| if (!qpXmlWriter_startDocument(log->writer) || |
| !qpXmlWriter_startElement(log->writer, "TestCaseResult", numResultAttribs, resultAttribs)) |
| { |
| qpPrintf("qpTestLog_startCase(): Writing XML failed\n"); |
| deMutex_unlock(log->lock); |
| return DE_FALSE; |
| } |
| |
| deMutex_unlock(log->lock); |
| return DE_TRUE; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Log end of test case |
| * \param log qpTestLog instance |
| * \param result Test result |
| * \param description Description of a problem in case of error |
| * \return true if ok, false otherwise |
| *//*--------------------------------------------------------------------*/ |
| deBool qpTestLog_endCase (qpTestLog* log, qpTestResult result, const char* resultDetails) |
| { |
| const char* statusStr = QP_LOOKUP_STRING(s_qpTestResultMap, result); |
| qpXmlAttribute statusAttrib = qpSetStringAttrib("StatusCode", statusStr); |
| |
| DE_ASSERT(log && log->isCaseOpen); |
| DE_ASSERT(ContainerStack_isEmpty(&log->containerStack)); |
| deMutex_lock(log->lock); |
| |
| /* <Result StatusCode="Pass">Result details</Result> |
| * </TestCaseResult> |
| */ |
| if (!qpXmlWriter_startElement(log->writer, "Result", 1, &statusAttrib) || |
| (resultDetails && !qpXmlWriter_writeString(log->writer, resultDetails)) || |
| !qpXmlWriter_endElement(log->writer, "Result") || |
| !qpXmlWriter_endElement(log->writer, "TestCaseResult") || |
| !qpXmlWriter_endDocument(log->writer)) /* Close any XML elements still open */ |
| { |
| qpPrintf("qpTestLog_endCase(): Writing XML failed\n"); |
| deMutex_unlock(log->lock); |
| return DE_FALSE; |
| } |
| |
| /* Flush XML and write #endTestCaseResult. */ |
| qpXmlWriter_flush(log->writer); |
| fprintf(log->outputFile, "\n#endTestCaseResult\n"); |
| if (!(log->flags & QP_TEST_LOG_NO_FLUSH)) |
| qpTestLog_flushFile(log); |
| |
| log->isCaseOpen = DE_FALSE; |
| |
| deMutex_unlock(log->lock); |
| return DE_TRUE; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Abrupt termination of logging. |
| * \param log qpTestLog instance |
| * \param result Result code, only Crash and Timeout are allowed. |
| * \return true if ok, false otherwise |
| *//*--------------------------------------------------------------------*/ |
| deBool qpTestLog_terminateCase (qpTestLog* log, qpTestResult result) |
| { |
| const char* resultStr = QP_LOOKUP_STRING(s_qpTestResultMap, result); |
| |
| DE_ASSERT(log); |
| DE_ASSERT(result == QP_TEST_RESULT_CRASH || result == QP_TEST_RESULT_TIMEOUT); |
| |
| deMutex_lock(log->lock); |
| |
| if (!log->isCaseOpen) |
| { |
| deMutex_unlock(log->lock); |
| return DE_FALSE; /* Soft error. This is called from error handler. */ |
| } |
| |
| /* Flush XML and write #terminateTestCaseResult. */ |
| qpXmlWriter_flush(log->writer); |
| fprintf(log->outputFile, "\n#terminateTestCaseResult %s\n", resultStr); |
| qpTestLog_flushFile(log); |
| |
| log->isCaseOpen = DE_FALSE; |
| |
| #if defined(DE_DEBUG) |
| ContainerStack_reset(&log->containerStack); |
| #endif |
| |
| deMutex_unlock(log->lock); |
| return DE_TRUE; |
| } |
| |
| static deBool qpTestLog_writeKeyValuePair (qpTestLog* log, const char* elementName, const char* name, const char* description, const char* unit, qpKeyValueTag tag, const char* text) |
| { |
| const char* tagString = QP_LOOKUP_STRING(s_qpTagMap, tag); |
| qpXmlAttribute attribs[8]; |
| int numAttribs = 0; |
| |
| DE_ASSERT(log && elementName && text); |
| deMutex_lock(log->lock); |
| |
| /* Fill in attributes. */ |
| if (name) attribs[numAttribs++] = qpSetStringAttrib("Name", name); |
| if (description) attribs[numAttribs++] = qpSetStringAttrib("Description", description); |
| if (tagString) attribs[numAttribs++] = qpSetStringAttrib("Tag", tagString); |
| if (unit) attribs[numAttribs++] = qpSetStringAttrib("Unit", unit); |
| |
| if (!qpXmlWriter_startElement(log->writer, elementName, numAttribs, attribs) || |
| !qpXmlWriter_writeString(log->writer, text) || |
| !qpXmlWriter_endElement(log->writer, elementName)) |
| { |
| qpPrintf("qpTestLog_writeKeyValuePair(): Writing XML failed\n"); |
| deMutex_unlock(log->lock); |
| return DE_FALSE; |
| } |
| |
| deMutex_unlock(log->lock); |
| return DE_TRUE; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Write a message to output log |
| * \param log qpTestLog instance |
| * \param format Format string of message |
| * \param ... Parameters for message |
| * \return true if ok, false otherwise |
| *//*--------------------------------------------------------------------*/ |
| deBool qpTestLog_writeMessage (qpTestLog* log, const char* format, ...) |
| { |
| char buffer[1024]; |
| va_list args; |
| |
| /* \todo [petri] Handle buffer overflows! */ |
| |
| va_start(args, format); |
| buffer[DE_LENGTH_OF_ARRAY(buffer) - 1] = 0; |
| vsnprintf(buffer, sizeof(buffer), format, args); |
| va_end(args); |
| |
| printf("%s\n", buffer); |
| |
| /* <Text>text</Text> */ |
| return qpTestLog_writeKeyValuePair(log, "Text", DE_NULL, DE_NULL, DE_NULL, QP_KEY_TAG_LAST, buffer); |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Write key-value-pair into log |
| * \param log qpTestLog instance |
| * \param name Unique identifier for entry |
| * \param description Human readable description |
| * \param tag Optional tag |
| * \param value Value of the key-value-pair |
| * \return true if ok, false otherwise |
| *//*--------------------------------------------------------------------*/ |
| deBool qpTestLog_writeText (qpTestLog* log, const char* name, const char* description, qpKeyValueTag tag, const char* text) |
| { |
| /* <Text Name="name" Description="description" Tag="tag">text</Text> */ |
| return qpTestLog_writeKeyValuePair(log, "Text", name, description, DE_NULL, tag, text); |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Write key-value-pair into log |
| * \param log qpTestLog instance |
| * \param name Unique identifier for entry |
| * \param description Human readable description |
| * \param tag Optional tag |
| * \param value Value of the key-value-pair |
| * \return true if ok, false otherwise |
| *//*--------------------------------------------------------------------*/ |
| deBool qpTestLog_writeInteger (qpTestLog* log, const char* name, const char* description, const char* unit, qpKeyValueTag tag, deInt64 value) |
| { |
| char tmpString[64]; |
| int64ToString(value, tmpString); |
| |
| printf("%s = %lld %s\n", description, (signed long long)value, unit ? unit : ""); |
| |
| /* <Number Name="name" Description="description" Tag="Performance">15</Number> */ |
| return qpTestLog_writeKeyValuePair(log, "Number", name, description, unit, tag, tmpString); |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Write key-value-pair into log |
| * \param log qpTestLog instance |
| * \param name Unique identifier for entry |
| * \param description Human readable description |
| * \param tag Optional tag |
| * \param value Value of the key-value-pair |
| * \return true if ok, false otherwise |
| *//*--------------------------------------------------------------------*/ |
| deBool qpTestLog_writeFloat (qpTestLog* log, const char* name, const char* description, const char* unit, qpKeyValueTag tag, float value) |
| { |
| char tmpString[64]; |
| floatToString(value, tmpString, sizeof(tmpString)); |
| |
| printf("%s = %f %s\n", description, value, unit ? unit : ""); |
| |
| /* <Number Name="name" Description="description" Tag="Performance">15</Number> */ |
| return qpTestLog_writeKeyValuePair(log, "Number", name, description, unit, tag, tmpString); |
| } |
| |
| typedef struct Buffer_s |
| { |
| size_t capacity; |
| size_t size; |
| deUint8* data; |
| } Buffer; |
| |
| void Buffer_init (Buffer* buffer) |
| { |
| buffer->capacity = 0; |
| buffer->size = 0; |
| buffer->data = DE_NULL; |
| } |
| |
| void Buffer_deinit (Buffer* buffer) |
| { |
| deFree(buffer->data); |
| Buffer_init(buffer); |
| } |
| |
| deBool Buffer_resize (Buffer* buffer, size_t newSize) |
| { |
| /* Grow buffer if necessary. */ |
| if (newSize > buffer->capacity) |
| { |
| size_t newCapacity = (size_t)deAlign32(deMax32(2*(int)buffer->capacity, (int)newSize), 512); |
| deUint8* newData = (deUint8*)deMalloc(newCapacity); |
| if (!newData) |
| return DE_FALSE; |
| |
| memcpy(newData, buffer->data, buffer->size); |
| deFree(buffer->data); |
| buffer->data = newData; |
| buffer->capacity = newCapacity; |
| } |
| |
| buffer->size = newSize; |
| return DE_TRUE; |
| } |
| |
| deBool Buffer_append (Buffer* buffer, const deUint8* data, size_t numBytes) |
| { |
| size_t offset = buffer->size; |
| |
| if (!Buffer_resize(buffer, buffer->size + numBytes)) |
| return DE_FALSE; |
| |
| /* Append bytes. */ |
| memcpy(&buffer->data[offset], data, numBytes); |
| return DE_TRUE; |
| } |
| |
| #if defined(QP_SUPPORT_PNG) |
| void pngWriteData (png_structp png, png_bytep dataPtr, png_size_t numBytes) |
| { |
| Buffer* buffer = (Buffer*)png_get_io_ptr(png); |
| if (!Buffer_append(buffer, (const deUint8*)dataPtr, numBytes)) |
| png_error(png, "unable to resize PNG write buffer!"); |
| } |
| |
| void pngFlushData (png_structp png) |
| { |
| DE_UNREF(png); |
| /* nada */ |
| } |
| |
| static deBool writeCompressedPNG (png_structp png, png_infop info, png_byte** rowPointers, int width, int height, int colorFormat) |
| { |
| if (setjmp(png_jmpbuf(png)) == 0) |
| { |
| /* Write data. */ |
| png_set_IHDR(png, info, (png_uint_32)width, (png_uint_32)height, |
| 8, |
| colorFormat, |
| PNG_INTERLACE_NONE, |
| PNG_COMPRESSION_TYPE_BASE, |
| PNG_FILTER_TYPE_BASE); |
| png_write_info(png, info); |
| png_write_image(png, rowPointers); |
| png_write_end(png, NULL); |
| |
| return DE_TRUE; |
| } |
| else |
| return DE_FALSE; |
| } |
| |
| static deBool compressImagePNG (Buffer* buffer, qpImageFormat imageFormat, int width, int height, int rowStride, const void* data) |
| { |
| deBool compressOk = DE_FALSE; |
| png_structp png = DE_NULL; |
| png_infop info = DE_NULL; |
| png_byte** rowPointers = DE_NULL; |
| deBool hasAlpha = imageFormat == QP_IMAGE_FORMAT_RGBA8888; |
| int ndx; |
| |
| /* Handle format. */ |
| DE_ASSERT(imageFormat == QP_IMAGE_FORMAT_RGB888 || imageFormat == QP_IMAGE_FORMAT_RGBA8888); |
| |
| /* Allocate & set row pointers. */ |
| rowPointers = (png_byte**)deMalloc((size_t)height * sizeof(png_byte*)); |
| if (!rowPointers) |
| return DE_FALSE; |
| |
| for (ndx = 0; ndx < height; ndx++) |
| rowPointers[ndx] = (png_byte*)((const deUint8*)data + ndx*rowStride); |
| |
| /* Initialize PNG compressor. */ |
| png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); |
| info = png ? png_create_info_struct(png) : DE_NULL; |
| if (png && info) |
| { |
| /* Set our own write function. */ |
| png_set_write_fn(png, buffer, pngWriteData, pngFlushData); |
| |
| compressOk = writeCompressedPNG(png, info, rowPointers, width, height, |
| hasAlpha ? PNG_COLOR_TYPE_RGBA : PNG_COLOR_TYPE_RGB); |
| } |
| |
| /* Cleanup & return. */ |
| if (png && info) |
| { |
| png_destroy_info_struct(png, &info); |
| png_destroy_write_struct(&png, DE_NULL); |
| } |
| else if (png) |
| png_destroy_write_struct(&png, &info); |
| |
| deFree(rowPointers); |
| return compressOk; |
| } |
| #endif /* QP_SUPPORT_PNG */ |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Start image set |
| * \param log qpTestLog instance |
| * \param name Unique identifier for the set |
| * \param description Human readable description |
| * \return true if ok, false otherwise |
| *//*--------------------------------------------------------------------*/ |
| deBool qpTestLog_startImageSet (qpTestLog* log, const char* name, const char* description) |
| { |
| qpXmlAttribute attribs[4]; |
| int numAttribs = 0; |
| |
| DE_ASSERT(log && name); |
| deMutex_lock(log->lock); |
| |
| attribs[numAttribs++] = qpSetStringAttrib("Name", name); |
| if (description) |
| attribs[numAttribs++] = qpSetStringAttrib("Description", description); |
| |
| /* <ImageSet Name="<name>"> */ |
| if (!qpXmlWriter_startElement(log->writer, "ImageSet", numAttribs, attribs)) |
| { |
| qpPrintf("qpTestLog_startImageSet(): Writing XML failed\n"); |
| deMutex_unlock(log->lock); |
| return DE_FALSE; |
| } |
| |
| DE_ASSERT(ContainerStack_push(&log->containerStack, CONTAINERTYPE_IMAGESET)); |
| |
| deMutex_unlock(log->lock); |
| return DE_TRUE; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief End image set |
| * \param log qpTestLog instance |
| * \return true if ok, false otherwise |
| *//*--------------------------------------------------------------------*/ |
| deBool qpTestLog_endImageSet (qpTestLog* log) |
| { |
| DE_ASSERT(log); |
| deMutex_lock(log->lock); |
| |
| /* <ImageSet Name="<name>"> */ |
| if (!qpXmlWriter_endElement(log->writer, "ImageSet")) |
| { |
| qpPrintf("qpTestLog_endImageSet(): Writing XML failed\n"); |
| deMutex_unlock(log->lock); |
| return DE_FALSE; |
| } |
| |
| DE_ASSERT(ContainerStack_pop(&log->containerStack) == CONTAINERTYPE_IMAGESET); |
| |
| deMutex_unlock(log->lock); |
| return DE_TRUE; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Write base64 encoded raw image data into log |
| * \param log qpTestLog instance |
| * \param name Unique name (matching names can be compared across BatchResults). |
| * \param description Textual description (shown in Candy). |
| * \param compressionMode Compression mode |
| * \param imageFormat Color format |
| * \param width Width in pixels |
| * \param height Height in pixels |
| * \param stride Data stride (offset between rows) |
| * \param data Pointer to pixel data |
| * \return 0 if OK, otherwise <0 |
| *//*--------------------------------------------------------------------*/ |
| deBool qpTestLog_writeImage ( |
| qpTestLog* log, |
| const char* name, |
| const char* description, |
| qpImageCompressionMode compressionMode, |
| qpImageFormat imageFormat, |
| int width, |
| int height, |
| int stride, |
| const void* data) |
| { |
| char widthStr[32]; |
| char heightStr[32]; |
| qpXmlAttribute attribs[8]; |
| int numAttribs = 0; |
| Buffer compressedBuffer; |
| const void* writeDataPtr = DE_NULL; |
| size_t writeDataBytes = ~(size_t)0; |
| |
| DE_ASSERT(log && name); |
| DE_ASSERT(deInRange32(width, 1, 16384)); |
| DE_ASSERT(deInRange32(height, 1, 16384)); |
| DE_ASSERT(data); |
| |
| if (log->flags & QP_TEST_LOG_EXCLUDE_IMAGES) |
| return DE_TRUE; /* Image not logged. */ |
| |
| Buffer_init(&compressedBuffer); |
| |
| /* BEST compression mode defaults to PNG. */ |
| if (compressionMode == QP_IMAGE_COMPRESSION_MODE_BEST) |
| { |
| #if defined(QP_SUPPORT_PNG) |
| compressionMode = QP_IMAGE_COMPRESSION_MODE_PNG; |
| #else |
| compressionMode = QP_IMAGE_COMPRESSION_MODE_NONE; |
| #endif |
| } |
| |
| #if defined(QP_SUPPORT_PNG) |
| /* Try storing with PNG compression. */ |
| if (compressionMode == QP_IMAGE_COMPRESSION_MODE_PNG) |
| { |
| deBool compressOk = compressImagePNG(&compressedBuffer, imageFormat, width, height, stride, data); |
| if (compressOk) |
| { |
| writeDataPtr = compressedBuffer.data; |
| writeDataBytes = compressedBuffer.size; |
| } |
| else |
| { |
| /* Fall-back to default compression. */ |
| qpPrintf("WARNING: PNG compression failed -- storing image uncompressed.\n"); |
| compressionMode = QP_IMAGE_COMPRESSION_MODE_NONE; |
| } |
| } |
| #endif |
| |
| /* Handle image compression. */ |
| switch (compressionMode) |
| { |
| case QP_IMAGE_COMPRESSION_MODE_NONE: |
| { |
| int pixelSize = imageFormat == QP_IMAGE_FORMAT_RGB888 ? 3 : 4; |
| int packedStride = pixelSize*width; |
| |
| if (packedStride == stride) |
| writeDataPtr = data; |
| else |
| { |
| /* Need to re-pack pixels. */ |
| if (Buffer_resize(&compressedBuffer, (size_t)(packedStride*height))) |
| { |
| int row; |
| for (row = 0; row < height; row++) |
| memcpy(&compressedBuffer.data[packedStride*row], &((const deUint8*)data)[row*stride], (size_t)(pixelSize*width)); |
| } |
| else |
| { |
| qpPrintf("ERROR: Failed to pack pixels for writing.\n"); |
| Buffer_deinit(&compressedBuffer); |
| return DE_FALSE; |
| } |
| } |
| |
| writeDataBytes = (size_t)(packedStride*height); |
| break; |
| } |
| |
| #if defined(QP_SUPPORT_PNG) |
| case QP_IMAGE_COMPRESSION_MODE_PNG: |
| DE_ASSERT(writeDataPtr); /* Already handled. */ |
| break; |
| #endif |
| |
| default: |
| qpPrintf("qpTestLog_writeImage(): Unknown compression mode: %s\n", QP_LOOKUP_STRING(s_qpImageCompressionModeMap, compressionMode)); |
| Buffer_deinit(&compressedBuffer); |
| return DE_FALSE; |
| } |
| |
| /* Fill in attributes. */ |
| int32ToString(width, widthStr); |
| int32ToString(height, heightStr); |
| attribs[numAttribs++] = qpSetStringAttrib("Name", name); |
| attribs[numAttribs++] = qpSetStringAttrib("Width", widthStr); |
| attribs[numAttribs++] = qpSetStringAttrib("Height", heightStr); |
| attribs[numAttribs++] = qpSetStringAttrib("Format", QP_LOOKUP_STRING(s_qpImageFormatMap, imageFormat)); |
| attribs[numAttribs++] = qpSetStringAttrib("CompressionMode", QP_LOOKUP_STRING(s_qpImageCompressionModeMap, compressionMode)); |
| if (description) attribs[numAttribs++] = qpSetStringAttrib("Description", description); |
| |
| /* \note Log lock is acquired after compression! */ |
| deMutex_lock(log->lock); |
| |
| /* <Image ID="result" Name="Foobar" Width="640" Height="480" Format="RGB888" CompressionMode="None">base64 data</Image> */ |
| if (!qpXmlWriter_startElement(log->writer, "Image", numAttribs, attribs) || |
| !qpXmlWriter_writeBase64(log->writer, (const deUint8*)writeDataPtr, writeDataBytes) || |
| !qpXmlWriter_endElement(log->writer, "Image")) |
| { |
| qpPrintf("qpTestLog_writeImage(): Writing XML failed\n"); |
| deMutex_unlock(log->lock); |
| Buffer_deinit(&compressedBuffer); |
| return DE_FALSE; |
| } |
| |
| deMutex_unlock(log->lock); |
| |
| /* Free compressed data if allocated. */ |
| Buffer_deinit(&compressedBuffer); |
| |
| return DE_TRUE; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Write a OpenGL ES shader program into the log. |
| * \param linkOk Shader program link result, false on failure |
| * \param linkInfoLog Implementation provided linkage log |
| *//*--------------------------------------------------------------------*/ |
| deBool qpTestLog_startShaderProgram (qpTestLog* log, deBool linkOk, const char* linkInfoLog) |
| { |
| qpXmlAttribute programAttribs[4]; |
| int numProgramAttribs = 0; |
| |
| DE_ASSERT(log); |
| deMutex_lock(log->lock); |
| |
| programAttribs[numProgramAttribs++] = qpSetStringAttrib("LinkStatus", linkOk ? "OK" : "Fail"); |
| |
| if (!qpXmlWriter_startElement(log->writer, "ShaderProgram", numProgramAttribs, programAttribs) || |
| !qpXmlWriter_writeStringElement(log->writer, "InfoLog", linkInfoLog)) |
| { |
| qpPrintf("qpTestLog_startShaderProgram(): Writing XML failed\n"); |
| deMutex_unlock(log->lock); |
| return DE_FALSE; |
| } |
| |
| DE_ASSERT(ContainerStack_push(&log->containerStack, CONTAINERTYPE_SHADERPROGRAM)); |
| |
| deMutex_unlock(log->lock); |
| return DE_TRUE; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief End shader program |
| * \param log qpTestLog instance |
| * \return true if ok, false otherwise |
| *//*--------------------------------------------------------------------*/ |
| deBool qpTestLog_endShaderProgram (qpTestLog* log) |
| { |
| DE_ASSERT(log); |
| deMutex_lock(log->lock); |
| |
| /* </ShaderProgram> */ |
| if (!qpXmlWriter_endElement(log->writer, "ShaderProgram")) |
| { |
| qpPrintf("qpTestLog_endShaderProgram(): Writing XML failed\n"); |
| deMutex_unlock(log->lock); |
| return DE_FALSE; |
| } |
| |
| DE_ASSERT(ContainerStack_pop(&log->containerStack) == CONTAINERTYPE_SHADERPROGRAM); |
| |
| deMutex_unlock(log->lock); |
| return DE_TRUE; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Write a OpenGL ES shader into the log. |
| * \param type Shader type |
| * \param source Shader source |
| * \param compileOk Shader compilation result, false on failure |
| * \param infoLog Implementation provided shader compilation log |
| *//*--------------------------------------------------------------------*/ |
| deBool qpTestLog_writeShader (qpTestLog* log, qpShaderType type, const char* source, deBool compileOk, const char* infoLog) |
| { |
| const char* tagName = QP_LOOKUP_STRING(s_qpShaderTypeMap, type); |
| const char* sourceStr = ((log->flags & QP_TEST_LOG_EXCLUDE_SHADER_SOURCES) == 0 || !compileOk) ? source : ""; |
| int numShaderAttribs = 0; |
| qpXmlAttribute shaderAttribs[4]; |
| |
| DE_ASSERT(log && source); |
| DE_ASSERT(ContainerStack_getTop(&log->containerStack) == CONTAINERTYPE_SHADERPROGRAM); |
| deMutex_lock(log->lock); |
| |
| shaderAttribs[numShaderAttribs++] = qpSetStringAttrib("CompileStatus", compileOk ? "OK" : "Fail"); |
| |
| if (!qpXmlWriter_startElement(log->writer, tagName, numShaderAttribs, shaderAttribs) || |
| !qpXmlWriter_writeStringElement(log->writer, "ShaderSource", sourceStr) || |
| !qpXmlWriter_writeStringElement(log->writer, "InfoLog", infoLog) || |
| !qpXmlWriter_endElement(log->writer, tagName)) |
| { |
| qpPrintf("qpTestLog_writeShader(): Writing XML failed\n"); |
| deMutex_unlock(log->lock); |
| return DE_FALSE; |
| } |
| |
| deMutex_unlock(log->lock); |
| return DE_TRUE; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Start writing a set of EGL configurations into the log. |
| *//*--------------------------------------------------------------------*/ |
| deBool qpTestLog_startEglConfigSet (qpTestLog* log, const char* name, const char* description) |
| { |
| qpXmlAttribute attribs[4]; |
| int numAttribs = 0; |
| |
| DE_ASSERT(log && name); |
| deMutex_lock(log->lock); |
| |
| attribs[numAttribs++] = qpSetStringAttrib("Name", name); |
| if (description) |
| attribs[numAttribs++] = qpSetStringAttrib("Description", description); |
| |
| /* <EglConfigSet Name="<name>"> */ |
| if (!qpXmlWriter_startElement(log->writer, "EglConfigSet", numAttribs, attribs)) |
| { |
| qpPrintf("qpTestLog_startEglImageSet(): Writing XML failed\n"); |
| deMutex_unlock(log->lock); |
| return DE_FALSE; |
| } |
| |
| DE_ASSERT(ContainerStack_push(&log->containerStack, CONTAINERTYPE_EGLCONFIGSET)); |
| |
| deMutex_unlock(log->lock); |
| return DE_TRUE; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief End an EGL config set |
| *//*--------------------------------------------------------------------*/ |
| deBool qpTestLog_endEglConfigSet (qpTestLog* log) |
| { |
| DE_ASSERT(log); |
| deMutex_lock(log->lock); |
| |
| /* <EglConfigSet Name="<name>"> */ |
| if (!qpXmlWriter_endElement(log->writer, "EglConfigSet")) |
| { |
| qpPrintf("qpTestLog_endEglImageSet(): Writing XML failed\n"); |
| deMutex_unlock(log->lock); |
| return DE_FALSE; |
| } |
| |
| DE_ASSERT(ContainerStack_pop(&log->containerStack) == CONTAINERTYPE_EGLCONFIGSET); |
| |
| deMutex_unlock(log->lock); |
| return DE_TRUE; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Write an EGL config inside an EGL config set |
| * \see qpElgConfigInfo for details |
| *//*--------------------------------------------------------------------*/ |
| deBool qpTestLog_writeEglConfig (qpTestLog* log, const qpEglConfigInfo* config) |
| { |
| qpXmlAttribute attribs[64]; |
| int numAttribs = 0; |
| |
| DE_ASSERT(log && config); |
| deMutex_lock(log->lock); |
| |
| attribs[numAttribs++] = qpSetIntAttrib ("BufferSize", config->bufferSize); |
| attribs[numAttribs++] = qpSetIntAttrib ("RedSize", config->redSize); |
| attribs[numAttribs++] = qpSetIntAttrib ("GreenSize", config->greenSize); |
| attribs[numAttribs++] = qpSetIntAttrib ("BlueSize", config->blueSize); |
| attribs[numAttribs++] = qpSetIntAttrib ("LuminanceSize", config->luminanceSize); |
| attribs[numAttribs++] = qpSetIntAttrib ("AlphaSize", config->alphaSize); |
| attribs[numAttribs++] = qpSetIntAttrib ("AlphaMaskSize", config->alphaMaskSize); |
| attribs[numAttribs++] = qpSetBoolAttrib ("BindToTextureRGB", config->bindToTextureRGB); |
| attribs[numAttribs++] = qpSetBoolAttrib ("BindToTextureRGBA", config->bindToTextureRGBA); |
| attribs[numAttribs++] = qpSetStringAttrib ("ColorBufferType", config->colorBufferType); |
| attribs[numAttribs++] = qpSetStringAttrib ("ConfigCaveat", config->configCaveat); |
| attribs[numAttribs++] = qpSetIntAttrib ("ConfigID", config->configID); |
| attribs[numAttribs++] = qpSetStringAttrib ("Conformant", config->conformant); |
| attribs[numAttribs++] = qpSetIntAttrib ("DepthSize", config->depthSize); |
| attribs[numAttribs++] = qpSetIntAttrib ("Level", config->level); |
| attribs[numAttribs++] = qpSetIntAttrib ("MaxPBufferWidth", config->maxPBufferWidth); |
| attribs[numAttribs++] = qpSetIntAttrib ("MaxPBufferHeight", config->maxPBufferHeight); |
| attribs[numAttribs++] = qpSetIntAttrib ("MaxPBufferPixels", config->maxPBufferPixels); |
| attribs[numAttribs++] = qpSetIntAttrib ("MaxSwapInterval", config->maxSwapInterval); |
| attribs[numAttribs++] = qpSetIntAttrib ("MinSwapInterval", config->minSwapInterval); |
| attribs[numAttribs++] = qpSetBoolAttrib ("NativeRenderable", config->nativeRenderable); |
| attribs[numAttribs++] = qpSetStringAttrib ("RenderableType", config->renderableType); |
| attribs[numAttribs++] = qpSetIntAttrib ("SampleBuffers", config->sampleBuffers); |
| attribs[numAttribs++] = qpSetIntAttrib ("Samples", config->samples); |
| attribs[numAttribs++] = qpSetIntAttrib ("StencilSize", config->stencilSize); |
| attribs[numAttribs++] = qpSetStringAttrib ("SurfaceTypes", config->surfaceTypes); |
| attribs[numAttribs++] = qpSetStringAttrib ("TransparentType", config->transparentType); |
| attribs[numAttribs++] = qpSetIntAttrib ("TransparentRedValue", config->transparentRedValue); |
| attribs[numAttribs++] = qpSetIntAttrib ("TransparentGreenValue", config->transparentGreenValue); |
| attribs[numAttribs++] = qpSetIntAttrib ("TransparentBlueValue", config->transparentBlueValue); |
| DE_ASSERT(numAttribs <= DE_LENGTH_OF_ARRAY(attribs)); |
| |
| if (!qpXmlWriter_startElement(log->writer, "EglConfig", numAttribs, attribs) || |
| !qpXmlWriter_endElement(log->writer, "EglConfig")) |
| { |
| qpPrintf("qpTestLog_writeEglConfig(): Writing XML failed\n"); |
| deMutex_unlock(log->lock); |
| return DE_FALSE; |
| } |
| |
| deMutex_unlock(log->lock); |
| return DE_TRUE; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Start section in log. |
| * \param log qpTestLog instance |
| * \param name Section name |
| * \param description Human readable description |
| * \return true if ok, false otherwise |
| *//*--------------------------------------------------------------------*/ |
| deBool qpTestLog_startSection (qpTestLog* log, const char* name, const char* description) |
| { |
| qpXmlAttribute attribs[2]; |
| int numAttribs = 0; |
| |
| DE_ASSERT(log && name); |
| deMutex_lock(log->lock); |
| |
| attribs[numAttribs++] = qpSetStringAttrib("Name", name); |
| if (description) |
| attribs[numAttribs++] = qpSetStringAttrib("Description", description); |
| |
| /* <Section Name="<name>" Description="<description>"> */ |
| if (!qpXmlWriter_startElement(log->writer, "Section", numAttribs, attribs)) |
| { |
| qpPrintf("qpTestLog_startSection(): Writing XML failed\n"); |
| deMutex_unlock(log->lock); |
| return DE_FALSE; |
| } |
| |
| DE_ASSERT(ContainerStack_push(&log->containerStack, CONTAINERTYPE_SECTION)); |
| |
| deMutex_unlock(log->lock); |
| return DE_TRUE; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief End section in log. |
| * \param log qpTestLog instance |
| * \return true if ok, false otherwise |
| *//*--------------------------------------------------------------------*/ |
| deBool qpTestLog_endSection (qpTestLog* log) |
| { |
| DE_ASSERT(log); |
| deMutex_lock(log->lock); |
| |
| /* </Section> */ |
| if (!qpXmlWriter_endElement(log->writer, "Section")) |
| { |
| qpPrintf("qpTestLog_endSection(): Writing XML failed\n"); |
| deMutex_unlock(log->lock); |
| return DE_FALSE; |
| } |
| |
| DE_ASSERT(ContainerStack_pop(&log->containerStack) == CONTAINERTYPE_SECTION); |
| |
| deMutex_unlock(log->lock); |
| return DE_TRUE; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Write OpenCL compute kernel source into the log. |
| *//*--------------------------------------------------------------------*/ |
| deBool qpTestLog_writeKernelSource (qpTestLog* log, const char* source) |
| { |
| const char* sourceStr = (log->flags & QP_TEST_LOG_EXCLUDE_SHADER_SOURCES) != 0 ? "" : source; |
| |
| DE_ASSERT(log); |
| deMutex_lock(log->lock); |
| |
| if (!qpXmlWriter_writeStringElement(log->writer, "KernelSource", sourceStr)) |
| { |
| qpPrintf("qpTestLog_writeKernelSource(): Writing XML failed\n"); |
| deMutex_unlock(log->lock); |
| return DE_FALSE; |
| } |
| |
| deMutex_unlock(log->lock); |
| return DE_TRUE; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Write a SPIR-V module assembly source into the log. |
| *//*--------------------------------------------------------------------*/ |
| deBool qpTestLog_writeSpirVAssemblySource (qpTestLog* log, const char* source) |
| { |
| DE_ASSERT(log); |
| DE_ASSERT(ContainerStack_getTop(&log->containerStack) == CONTAINERTYPE_SHADERPROGRAM); |
| deMutex_lock(log->lock); |
| |
| if (!qpXmlWriter_writeStringElement(log->writer, "SpirVAssemblySource", source)) |
| { |
| qpPrintf("qpTestLog_writeSpirVAssemblySource(): Writing XML failed\n"); |
| deMutex_unlock(log->lock); |
| return DE_FALSE; |
| } |
| |
| deMutex_unlock(log->lock); |
| return DE_TRUE; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Write OpenCL kernel compilation results into the log |
| *//*--------------------------------------------------------------------*/ |
| deBool qpTestLog_writeCompileInfo (qpTestLog* log, const char* name, const char* description, deBool compileOk, const char* infoLog) |
| { |
| int numAttribs = 0; |
| qpXmlAttribute attribs[3]; |
| |
| DE_ASSERT(log && name && description && infoLog); |
| deMutex_lock(log->lock); |
| |
| attribs[numAttribs++] = qpSetStringAttrib("Name", name); |
| attribs[numAttribs++] = qpSetStringAttrib("Description", description); |
| attribs[numAttribs++] = qpSetStringAttrib("CompileStatus", compileOk ? "OK" : "Fail"); |
| |
| if (!qpXmlWriter_startElement(log->writer, "CompileInfo", numAttribs, attribs) || |
| !qpXmlWriter_writeStringElement(log->writer, "InfoLog", infoLog) || |
| !qpXmlWriter_endElement(log->writer, "CompileInfo")) |
| { |
| qpPrintf("qpTestLog_writeCompileInfo(): Writing XML failed\n"); |
| deMutex_unlock(log->lock); |
| return DE_FALSE; |
| } |
| |
| deMutex_unlock(log->lock); |
| return DE_TRUE; |
| } |
| |
| deBool qpTestLog_startSampleList (qpTestLog* log, const char* name, const char* description) |
| { |
| int numAttribs = 0; |
| qpXmlAttribute attribs[2]; |
| |
| DE_ASSERT(log && name && description); |
| deMutex_lock(log->lock); |
| |
| attribs[numAttribs++] = qpSetStringAttrib("Name", name); |
| attribs[numAttribs++] = qpSetStringAttrib("Description", description); |
| |
| if (!qpXmlWriter_startElement(log->writer, "SampleList", numAttribs, attribs)) |
| { |
| qpPrintf("qpTestLog_startSampleList(): Writing XML failed\n"); |
| deMutex_unlock(log->lock); |
| return DE_FALSE; |
| } |
| |
| DE_ASSERT(ContainerStack_push(&log->containerStack, CONTAINERTYPE_SAMPLELIST)); |
| |
| deMutex_unlock(log->lock); |
| return DE_TRUE; |
| } |
| |
| deBool qpTestLog_startSampleInfo (qpTestLog* log) |
| { |
| DE_ASSERT(log); |
| deMutex_lock(log->lock); |
| |
| if (!qpXmlWriter_startElement(log->writer, "SampleInfo", 0, DE_NULL)) |
| { |
| qpPrintf("qpTestLog_startSampleInfo(): Writing XML failed\n"); |
| deMutex_unlock(log->lock); |
| return DE_FALSE; |
| } |
| |
| DE_ASSERT(ContainerStack_push(&log->containerStack, CONTAINERTYPE_SAMPLEINFO)); |
| |
| deMutex_unlock(log->lock); |
| return DE_TRUE; |
| } |
| |
| deBool qpTestLog_writeValueInfo (qpTestLog* log, const char* name, const char* description, const char* unit, qpSampleValueTag tag) |
| { |
| const char* tagName = QP_LOOKUP_STRING(s_qpSampleValueTagMap, tag); |
| int numAttribs = 0; |
| qpXmlAttribute attribs[4]; |
| |
| DE_ASSERT(log && name && description && tagName); |
| deMutex_lock(log->lock); |
| |
| DE_ASSERT(ContainerStack_getTop(&log->containerStack) == CONTAINERTYPE_SAMPLEINFO); |
| |
| attribs[numAttribs++] = qpSetStringAttrib("Name", name); |
| attribs[numAttribs++] = qpSetStringAttrib("Description", description); |
| attribs[numAttribs++] = qpSetStringAttrib("Tag", tagName); |
| |
| if (unit) |
| attribs[numAttribs++] = qpSetStringAttrib("Unit", unit); |
| |
| if (!qpXmlWriter_startElement(log->writer, "ValueInfo", numAttribs, attribs) || |
| !qpXmlWriter_endElement(log->writer, "ValueInfo")) |
| { |
| qpPrintf("qpTestLog_writeValueInfo(): Writing XML failed\n"); |
| deMutex_unlock(log->lock); |
| return DE_FALSE; |
| } |
| |
| deMutex_unlock(log->lock); |
| return DE_TRUE; |
| } |
| |
| deBool qpTestLog_endSampleInfo (qpTestLog* log) |
| { |
| DE_ASSERT(log); |
| deMutex_lock(log->lock); |
| |
| if (!qpXmlWriter_endElement(log->writer, "SampleInfo")) |
| { |
| qpPrintf("qpTestLog_endSampleInfo(): Writing XML failed\n"); |
| deMutex_unlock(log->lock); |
| return DE_FALSE; |
| } |
| |
| DE_ASSERT(ContainerStack_pop(&log->containerStack) == CONTAINERTYPE_SAMPLEINFO); |
| |
| deMutex_unlock(log->lock); |
| return DE_TRUE; |
| } |
| |
| deBool qpTestLog_startSample (qpTestLog* log) |
| { |
| DE_ASSERT(log); |
| deMutex_lock(log->lock); |
| |
| DE_ASSERT(ContainerStack_getTop(&log->containerStack) == CONTAINERTYPE_SAMPLELIST); |
| |
| if (!qpXmlWriter_startElement(log->writer, "Sample", 0, DE_NULL)) |
| { |
| qpPrintf("qpTestLog_startSample(): Writing XML failed\n"); |
| deMutex_unlock(log->lock); |
| return DE_FALSE; |
| } |
| |
| DE_ASSERT(ContainerStack_push(&log->containerStack, CONTAINERTYPE_SAMPLE)); |
| |
| deMutex_unlock(log->lock); |
| return DE_TRUE; |
| } |
| |
| deBool qpTestLog_writeValueFloat (qpTestLog* log, double value) |
| { |
| char tmpString[512]; |
| doubleToString(value, tmpString, (int)sizeof(tmpString)); |
| |
| deMutex_lock(log->lock); |
| |
| DE_ASSERT(ContainerStack_getTop(&log->containerStack) == CONTAINERTYPE_SAMPLE); |
| |
| if (!qpXmlWriter_writeStringElement(log->writer, "Value", &tmpString[0])) |
| { |
| qpPrintf("qpTestLog_writeSampleValue(): Writing XML failed\n"); |
| deMutex_unlock(log->lock); |
| return DE_FALSE; |
| } |
| |
| deMutex_unlock(log->lock); |
| return DE_TRUE; |
| } |
| |
| deBool qpTestLog_writeValueInteger (qpTestLog* log, deInt64 value) |
| { |
| char tmpString[64]; |
| int64ToString(value, tmpString); |
| |
| deMutex_lock(log->lock); |
| |
| DE_ASSERT(ContainerStack_getTop(&log->containerStack) == CONTAINERTYPE_SAMPLE); |
| |
| if (!qpXmlWriter_writeStringElement(log->writer, "Value", &tmpString[0])) |
| { |
| qpPrintf("qpTestLog_writeSampleValue(): Writing XML failed\n"); |
| deMutex_unlock(log->lock); |
| return DE_FALSE; |
| } |
| |
| deMutex_unlock(log->lock); |
| return DE_TRUE; |
| } |
| |
| deBool qpTestLog_endSample (qpTestLog* log) |
| { |
| DE_ASSERT(log); |
| deMutex_lock(log->lock); |
| |
| if (!qpXmlWriter_endElement(log->writer, "Sample")) |
| { |
| qpPrintf("qpTestLog_endSample(): Writing XML failed\n"); |
| deMutex_unlock(log->lock); |
| return DE_FALSE; |
| } |
| |
| DE_ASSERT(ContainerStack_pop(&log->containerStack) == CONTAINERTYPE_SAMPLE); |
| |
| deMutex_unlock(log->lock); |
| return DE_TRUE; |
| } |
| |
| deBool qpTestLog_endSampleList (qpTestLog* log) |
| { |
| DE_ASSERT(log); |
| deMutex_lock(log->lock); |
| |
| if (!qpXmlWriter_endElement(log->writer, "SampleList")) |
| { |
| qpPrintf("qpTestLog_endSampleList(): Writing XML failed\n"); |
| deMutex_unlock(log->lock); |
| return DE_FALSE; |
| } |
| |
| DE_ASSERT(ContainerStack_pop(&log->containerStack) == CONTAINERTYPE_SAMPLELIST); |
| |
| deMutex_unlock(log->lock); |
| return DE_TRUE; |
| } |
| |
| deUint32 qpTestLog_getLogFlags (const qpTestLog* log) |
| { |
| DE_ASSERT(log); |
| return log->flags; |
| } |
| |
| const char* qpGetTestResultName (qpTestResult result) |
| { |
| return QP_LOOKUP_STRING(s_qpTestResultMap, result); |
| } |