/* * Copyright (c) 2019 The Khronos Group Inc. * Copyright (c) 2019 Valve Corporation * Copyright (c) 2019 LunarG, Inc. * Copyright (c) 2023 RasterGrid Kft. * * 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. * * Author: Charles Giessen * */ #pragma once #include #include #include #include #include #include #include #include "vulkaninfo.h" std::string insert_quotes(std::string s) { return "\"" + s + "\""; } std::string to_string(const std::array &uid) { std::stringstream ss; ss << std::hex << std::setfill('0'); for (int i = 0; i < 16; ++i) { if (i == 4 || i == 6 || i == 8 || i == 10) ss << '-'; ss << std::setw(2) << static_cast(uid[i]); } return ss.str(); } std::string to_string(const std::array &uid) { std::stringstream ss; ss << std::hex << std::setfill('0'); for (int i = 0; i < 8; ++i) { if (i == 4) ss << '-'; ss << std::setw(2) << static_cast(uid[i]); } return ss.str(); } std::ostream &operator<<(std::ostream &out, const VkConformanceVersion &v) { return out << static_cast(v.major) << "." << static_cast(v.minor) << "." << static_cast(v.subminor) << "." << static_cast(v.patch); } enum class OutputType { text, html, json, vkconfig_output }; struct PrinterCreateDetails { OutputType output_type = OutputType::text; bool print_to_file = false; std::string file_name = APP_SHORT_NAME ".txt"; std::string start_string = ""; }; class Printer { public: Printer(const PrinterCreateDetails &details, std::ostream &out, const APIVersion vulkan_version) : output_type(details.output_type), out(out) { StackNode node{}; node.is_first_item = false; node.indents = 0; switch (output_type) { case (OutputType::text): out << std::string(strlen(APP_UPPER_CASE_NAME), '=') << "\n"; out << APP_UPPER_CASE_NAME "\n"; out << std::string(strlen(APP_UPPER_CASE_NAME), '=') << "\n\n"; out << API_NAME " Instance Version: " << APIVersion(vulkan_version) << "\n\n\n"; break; case (OutputType::html): out << "\n"; out << "\n"; out << "\t\n"; out << "\t\t" APP_SHORT_NAME "\n"; out << "\t\t\n"; out << "\t\n"; out << "\t\n"; out << "\t\t\n"; out << "\t\t
\n"; out << "\t\t\t
" API_NAME " Instance Version: " << APIVersion(vulkan_version) << "
\n\t\t\t
\n"; node.indents = 3; break; case (OutputType::json): case (OutputType::vkconfig_output): out << details.start_string; node.indents = 1; break; default: break; } object_stack.push(node); } ~Printer() { switch (output_type) { case (OutputType::text): break; case (OutputType::html): out << "\t\t
\n"; out << "\t\n"; out << "\n"; break; case (OutputType::json): case (OutputType::vkconfig_output): out << "\n}\n"; break; } assert(!object_stack.empty() && "mismatched number of ObjectStart/ObjectEnd or ArrayStart/ArrayEnd's"); object_stack.pop(); assert(object_stack.empty() && "indents must be zero at program end"); }; Printer(const Printer &) = delete; const Printer &operator=(const Printer &) = delete; OutputType Type() { return output_type; } // When an error occurs, call this to create a valid output file. Needed for json/html void FinishOutput() { while (!object_stack.empty()) { switch (output_type) { case (OutputType::text): break; case (OutputType::html): while (get_top().indents > 3) { out << "\n"; } break; case (OutputType::json): case (OutputType::vkconfig_output): out << "\n" << std::string(static_cast(get_top().indents), '\t'); if (get_top().is_array) { out << "]"; } else { out << "}"; } break; default: break; } object_stack.pop(); } } // Custom Formatting // use by prepending with p.SetXXX().ObjectStart/ArrayStart Printer &SetHeader() { get_top().set_next_header = true; return *this; } Printer &SetSubHeader() { get_top().set_next_subheader = true; return *this; } Printer &SetOpenDetails() { get_top().set_details_open = true; return *this; } Printer &SetAlwaysOpenDetails(bool value = true) { get_top().should_always_open = value; return *this; } Printer &SetTitleAsType() { get_top().set_object_name_as_type = true; return *this; } Printer &SetAsType() { get_top().set_as_type = true; return *this; } Printer &SetIgnoreMinWidthInChild() { get_top().ignore_min_width_parameter_in_child = true; return *this; } Printer &SetMinKeyWidth(size_t min_key_width) { get_top().min_key_width = min_key_width; return *this; } Printer &SetElementIndex(int index) { assert(index >= 0 && "cannot set element index to a negative value"); get_top().element_index = index; return *this; } Printer &SetValueDescription(std::string str) { value_description = str; return *this; } void ObjectStart(std::string object_name, int32_t count_subobjects = -1) { switch (output_type) { case (OutputType::text): { out << std::string(static_cast(get_top().indents), '\t') << object_name; if (!value_description.empty()) { out << " (" << value_description << ")"; value_description = {}; } if (get_top().element_index != -1) { out << "[" << get_top().element_index << "]"; } out << ":"; if (count_subobjects >= 0) { out << " count = " << count_subobjects; } out << "\n"; size_t headersize = object_name.size() + 1; if (count_subobjects >= 0) { headersize += 9 + std::to_string(count_subobjects).size(); } if (get_top().element_index != -1) { headersize += 2 + std::to_string(get_top().element_index).size(); get_top().element_index = -1; } PrintHeaderUnderlines(headersize); break; } case (OutputType::html): out << std::string(static_cast(get_top().indents), '\t'); if (get_top().set_details_open || get_top().should_always_open) { out << "
"; get_top().set_details_open = false; } else { out << "
"; } out << ""; if (get_top().set_object_name_as_type) { out << "" << object_name << ""; get_top().set_object_name_as_type = false; } else { out << object_name; } if (!value_description.empty()) { out << " (" << value_description << ")"; value_description = {}; } if (get_top().element_index != -1) { out << "[" << get_top().element_index << "]"; get_top().element_index = -1; } if (count_subobjects >= 0) { out << ": count = " << std::to_string(count_subobjects) << ""; } out << "\n"; break; case (OutputType::json): if (!get_top().is_first_item) { out << ",\n"; } else { get_top().is_first_item = false; } out << std::string(static_cast(get_top().indents), '\t'); // Objects with no name are elements in an array of objects if (get_top().is_array || object_name == "" || get_top().element_index != -1) { out << "{\n"; get_top().element_index = -1; } else { out << "\"" << object_name << "\": {\n"; } if (!value_description.empty()) { value_description = {}; } break; case (OutputType::vkconfig_output): if (!get_top().is_first_item) { out << ",\n"; } else { get_top().is_first_item = false; } out << std::string(static_cast(get_top().indents), '\t'); if (get_top().is_array && get_top().element_index != -1) { out << "\"" << object_name << "[" << get_top().element_index << "]\": "; get_top().element_index = -1; } else if (!get_top().is_array) { out << "\"" << object_name << "\": "; } out << "{\n"; if (!value_description.empty()) { value_description = {}; } break; default: break; } push_node(false); } void ObjectEnd() { assert(get_top().is_array == false && "cannot call ObjectEnd while inside an Array"); object_stack.pop(); assert(get_top().indents >= 0 && "indents cannot go below zero"); switch (output_type) { case (OutputType::text): break; case (OutputType::html): out << std::string(static_cast(get_top().indents), '\t') << "
\n"; break; case (OutputType::json): case (OutputType::vkconfig_output): out << "\n" << std::string(static_cast(get_top().indents), '\t') << "}"; break; default: break; } } void ArrayStart(std::string array_name, size_t element_count = 0) { switch (output_type) { case (OutputType::text): { out << std::string(static_cast(get_top().indents), '\t') << array_name << ":"; size_t underline_count = array_name.size() + 1; if (element_count > 0) { out << " count = " << element_count; underline_count += 9 + std::to_string(element_count).size(); } out << "\n"; PrintHeaderUnderlines(underline_count); break; } case (OutputType::html): out << std::string(static_cast(get_top().indents), '\t'); if (get_top().set_details_open || get_top().should_always_open) { out << "
"; get_top().set_details_open = false; } else { out << "
"; } out << "" << array_name; if (element_count > 0) { out << ": count = " << element_count << ""; } out << "\n"; break; case (OutputType::json): case (OutputType::vkconfig_output): if (!get_top().is_first_item) { out << ",\n"; } else { get_top().is_first_item = false; } out << std::string(static_cast(get_top().indents), '\t'); if (!get_top().is_array) { out << "\"" << array_name << "\": "; } out << "[\n"; break; default: break; } push_node(true); } void ArrayEnd() { assert(get_top().is_array == true && "cannot call ArrayEnd while inside an Object"); object_stack.pop(); assert(get_top().indents >= 0 && "indents cannot go below zero"); switch (output_type) { case (OutputType::text): break; case (OutputType::html): out << std::string(static_cast(get_top().indents), '\t') << "
\n"; break; case (OutputType::json): case (OutputType::vkconfig_output): out << "\n" << std::string(static_cast(get_top().indents), '\t') << "]"; break; default: break; } } // For printing key-value pairs. // value_description is for reference information and is displayed inside parenthesis after the value template void PrintKeyValue(std::string key, T value) { // If we are inside of an array, Print the value as an element if (get_top().is_array) { PrintElement(value); return; } switch (output_type) { case (OutputType::text): out << std::string(static_cast(get_top().indents), '\t') << key; if (get_top().min_key_width > key.size() && !get_top().ignore_min_width_parameter) { out << std::string(get_top().min_key_width - key.size(), ' '); } out << " = " << value; if (value_description != "") { out << " (" << value_description << ")"; value_description = {}; } out << "\n"; break; case (OutputType::html): out << std::string(static_cast(get_top().indents), '\t') << "
" << key; if (get_top().min_key_width > key.size()) { out << std::string(get_top().min_key_width - key.size(), ' '); } if (get_top().set_as_type) { get_top().set_as_type = false; out << " = " << value << ""; } else { out << " = " << value << ""; } if (!value_description.empty()) { out << " (" << value_description << ")"; value_description = {}; } out << "
\n"; break; case (OutputType::json): case (OutputType::vkconfig_output): if (!get_top().is_first_item) { out << ",\n"; } else { get_top().is_first_item = false; } out << std::string(static_cast(get_top().indents), '\t'); if (!get_top().is_array) { out << "\"" << key << "\": "; } if (!value_description.empty()) { out << "\"" << value << " (" << value_description << ")\""; value_description = {}; } else { out << value; } default: break; } } // Need a specialization to handle C style arrays since they are implicitly converted to pointers template void PrintKeyValue(std::string key, const uint8_t (&values)[N]) { switch (output_type) { case (OutputType::json): { ArrayStart(key, N); for (uint32_t i = 0; i < N; i++) { PrintElement(static_cast(values[i])); } ArrayEnd(); break; } default: { std::array arr{}; std::copy(std::begin(values), std::end(values), std::begin(arr)); PrintKeyString(key, to_string(arr)); break; } } } // For printing key - string pairs (necessary because of json) void PrintKeyString(std::string key, std::string value) { switch (output_type) { case (OutputType::text): case (OutputType::html): PrintKeyValue(key, value); break; case (OutputType::json): case (OutputType::vkconfig_output): if (!value_description.empty()) { // PrintKeyValue adds the necessary quotes when printing with a value description set PrintKeyValue(key, EscapeJSONCString(value)); } else { PrintKeyValue(key, std::string("\"") + EscapeJSONCString(value) + "\""); } break; default: break; } } // For printing key - string pairs (necessary because of json) void PrintKeyBool(std::string key, bool value) { PrintKeyValue(key, value ? "true" : "false"); } // print inside array template void PrintElement(T element) { // If we are inside of an object, just use an empty string as the key if (!get_top().is_array) { PrintKeyValue("placeholder", element); return; } switch (output_type) { case (OutputType::text): out << std::string(static_cast(get_top().indents), '\t') << element << "\n"; break; case (OutputType::html): out << std::string(static_cast(get_top().indents), '\t') << "
"; if (get_top().set_as_type) { get_top().set_as_type = false; out << "" << element << ""; } else { out << "" << element << ""; } out << "
\n"; break; case (OutputType::json): case (OutputType::vkconfig_output): if (!get_top().is_first_item) { out << ",\n"; } else { get_top().is_first_item = false; } out << std::string(static_cast(get_top().indents), '\t') << element; break; default: break; } } void PrintString(std::string string) { switch (output_type) { case (OutputType::text): case (OutputType::html): PrintElement(string); break; case (OutputType::json): case (OutputType::vkconfig_output): PrintElement("\"" + EscapeJSONCString(string) + "\""); default: break; } } void PrintExtension(std::string ext_name, uint32_t revision, size_t min_width = 0) { switch (output_type) { case (OutputType::text): out << std::string(static_cast(get_top().indents), '\t') << ext_name << std::string(min_width - ext_name.size(), ' ') << " : extension revision " << revision << "\n"; break; case (OutputType::html): out << std::string(static_cast(get_top().indents), '\t') << "
" << DecorateAsType(ext_name) << std::string(min_width - ext_name.size(), ' ') << " : extension revision " << DecorateAsValue(std::to_string(revision)) << "
\n"; break; case (OutputType::json): PrintKeyValue(ext_name, revision); break; case (OutputType::vkconfig_output): ObjectStart(ext_name); PrintKeyValue("specVersion", revision); ObjectEnd(); break; default: break; } } void AddNewline() { if (output_type == OutputType::text) { out << "\n"; } } void IndentIncrease() { if (output_type == OutputType::text) { get_top().indents++; } } void IndentDecrease() { if (output_type == OutputType::text) { get_top().indents--; assert(get_top().indents >= 0 && "indents cannot go below zero"); } } std::string DecorateAsType(const std::string &input) { if (output_type == OutputType::html) return "" + input + ""; else return input; } std::string DecorateAsValue(const std::string &input) { if (output_type == OutputType::html) return "" + input + ""; else return input; } protected: OutputType output_type; std::ostream &out; struct StackNode { int indents = 0; // header, subheader bool set_next_header = false; bool set_next_subheader = false; // html coloring bool set_as_type = false; // open
bool set_details_open = false; // always open
bool should_always_open = false; // make object titles the color of types bool set_object_name_as_type = false; // ignores the min_width parameter bool ignore_min_width_parameter = false; // sets the next created object/child to ignore the min_width parameter bool ignore_min_width_parameter_in_child = false; // keep track of the spacing between names and values size_t min_key_width = 0; // objects which are in an array int element_index = -1; // negative one is the sentinel value // json bool is_first_item; // for json: for adding a comma in between objects bool is_array; // for json: match pairs of {}'s and []'s }; // Contains the details about the current 'object' in the tree std::stack object_stack; // Helper to get the current top of the object_stack StackNode &get_top() { return object_stack.top(); } // Optional 'description' for values // Must be set right before the Print() function is called std::string value_description; // can only be called after a node was manually pushed onto the stack in the constructor void push_node(bool array) { StackNode node{}; node.indents = get_top().indents + 1; node.is_array = array; node.is_first_item = true; node.ignore_min_width_parameter = get_top().ignore_min_width_parameter_in_child; object_stack.push(node); } // utility void PrintHeaderUnderlines(size_t length) { assert(get_top().indents >= 0 && "indents must not be negative"); assert(length <= 10000 && "length shouldn't be unreasonably large"); if (get_top().set_next_header) { out << std::string(static_cast(get_top().indents), '\t') << std::string(length, '=') << "\n"; get_top().set_next_header = false; } else if (get_top().set_next_subheader) { out << std::string(static_cast(get_top().indents), '\t') << std::string(length, '-') << "\n"; get_top().set_next_subheader = false; } } // Replace special characters in strings with their escaped versions. // std::string EscapeJSONCString(std::string string) { if (output_type == OutputType::text || output_type == OutputType::html) return string; std::string out{}; for (size_t i = 0; i < string.size(); i++) { char c = string[i]; char out_c = c; switch (c) { case '\"': case '\\': out.push_back('\\'); break; case '\b': out.push_back('\\'); out_c = 'b'; break; case '\f': out.push_back('\\'); out_c = 'f'; break; case '\n': out.push_back('\\'); out_c = 'n'; break; case '\r': out.push_back('\\'); out_c = 'r'; break; case '\t': out.push_back('\\'); out_c = 't'; break; } out.push_back(out_c); } return out; } }; // Purpose: When a Printer starts an object or array it will automatically indent the output. This isn't // always desired, requiring a manual decrease of indention. This wrapper facilitates that while also // automatically re-indenting the output to the previous indent level on scope exit. class IndentWrapper { public: IndentWrapper(Printer &p) : p(p) { if (p.Type() == OutputType::text) p.IndentDecrease(); } ~IndentWrapper() { if (p.Type() == OutputType::text) p.IndentIncrease(); } private: Printer &p; }; class ObjectWrapper { public: ObjectWrapper(Printer &p, std::string object_name) : p(p) { p.ObjectStart(object_name); } ObjectWrapper(Printer &p, std::string object_name, size_t count_subobjects) : p(p) { p.ObjectStart(object_name, static_cast(count_subobjects)); } ~ObjectWrapper() { p.ObjectEnd(); } private: Printer &p; }; class ArrayWrapper { public: ArrayWrapper(Printer &p, std::string array_name, size_t element_count = 0) : p(p) { p.ArrayStart(array_name, element_count); } ~ArrayWrapper() { p.ArrayEnd(); } private: Printer &p; };