blob: e17678058d22afdab19ce1279befefdd48ac6cb1 [file] [log] [blame]
/*
* Copyright (c) 2019 The Khronos Group Inc.
* Copyright (c) 2019 Valve Corporation
* Copyright (c) 2019 LunarG, Inc.
*
* 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 <charles@lunarg.com>
*
*/
#pragma once
#include <iomanip>
#include <iostream>
#include <ostream>
#include <stack>
#include <sstream>
#include <string>
#include <assert.h>
std::string insert_quotes(std::string s) { return "\"" + s + "\""; }
std::string to_string_16(const uint8_t uid[16]) {
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<unsigned>(uid[i]);
}
return ss.str();
}
std::string to_string_8(const uint8_t uid[8]) {
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<unsigned>(uid[i]);
}
return ss.str();
}
std::string VkVersionString(uint32_t version) {
uint32_t major = version >> 22;
uint32_t minor = (version >> 12) & 0x3ff;
uint32_t patch = version & 0xfff;
return std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(patch);
}
std::string VkVersionString(VulkanVersion v) {
return std::to_string(v.major) + "." + std::to_string(v.minor) + "." + std::to_string(v.patch);
}
std::string VkConformanceVersionString(const VkConformanceVersion &c) {
return std::to_string(c.major) + "." + std::to_string(c.minor) + "." + std::to_string(c.subminor) + "." +
std::to_string(c.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 = "vulkaninfo.txt";
std::string start_string = "";
};
class Printer {
public:
Printer(const PrinterCreateDetails &details, std::ostream &out, const VulkanVersion 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 << "==========\n";
out << "VULKANINFO\n";
out << "==========\n\n";
out << "Vulkan Instance Version: " << VkVersionString(vulkan_version) << "\n\n\n";
break;
case (OutputType::html):
out << "<!doctype html>\n";
out << "<html lang='en'>\n";
out << "\t<head>\n";
out << "\t\t<title>vulkaninfo</title>\n";
out << "\t\t<style>\n";
out << "\t\thtml {\n";
out << "\t\t\tbackground-color: #0b1e48;\n";
out << "\t\t\tbackground-image: url(\"https://vulkan.lunarg.com/img/bg-starfield.jpg\");\n";
out << "\t\t\tbackground-position: center;\n";
out << "\t\t\t-webkit-background-size: cover;\n";
out << "\t\t\t-moz-background-size: cover;\n";
out << "\t\t\t-o-background-size: cover;\n";
out << "\t\t\tbackground-size: cover;\n";
out << "\t\t\tbackground-attachment: fixed;\n";
out << "\t\t\tbackground-repeat: no-repeat;\n";
out << "\t\t\theight: 100%;\n";
out << "\t\t}\n";
out << "\t\t#header {\n";
out << "\t\t\tz-index: -1;\n";
out << "\t\t}\n";
out << "\t\t#header>img {\n";
out << "\t\t\tposition: absolute;\n";
out << "\t\t\twidth: 160px;\n";
out << "\t\t\tmargin-left: -280px;\n";
out << "\t\t\ttop: -10px;\n";
out << "\t\t\tleft: 50%;\n";
out << "\t\t}\n";
out << "\t\t#header>h1 {\n";
out << "\t\t\tfont-family: Arial, \"Helvetica Neue\", Helvetica, sans-serif;\n";
out << "\t\t\tfont-size: 44px;\n";
out << "\t\t\tfont-weight: 200;\n";
out << "\t\t\ttext-shadow: 4px 4px 5px #000;\n";
out << "\t\t\tcolor: #eee;\n";
out << "\t\t\tposition: absolute;\n";
out << "\t\t\twidth: 400px;\n";
out << "\t\t\tmargin-left: -80px;\n";
out << "\t\t\ttop: 8px;\n";
out << "\t\t\tleft: 50%;\n";
out << "\t\t}\n";
out << "\t\tbody {\n";
out << "\t\t\tfont-family: Consolas, monaco, monospace;\n";
out << "\t\t\tfont-size: 14px;\n";
out << "\t\t\tline-height: 20px;\n";
out << "\t\t\tcolor: #eee;\n";
out << "\t\t\theight: 100%;\n";
out << "\t\t\tmargin: 0;\n";
out << "\t\t\toverflow: hidden;\n";
out << "\t\t}\n";
out << "\t\t#wrapper {\n";
out << "\t\t\tbackground-color: rgba(0, 0, 0, 0.7);\n";
out << "\t\t\tborder: 1px solid #446;\n";
out << "\t\t\tbox-shadow: 0px 0px 10px #000;\n";
out << "\t\t\tpadding: 8px 12px;\n\n";
out << "\t\t\tdisplay: inline-block;\n";
out << "\t\t\tposition: absolute;\n";
out << "\t\t\ttop: 80px;\n";
out << "\t\t\tbottom: 25px;\n";
out << "\t\t\tleft: 50px;\n";
out << "\t\t\tright: 50px;\n";
out << "\t\t\toverflow: auto;\n";
out << "\t\t}\n";
out << "\t\tdetails>details {\n";
out << "\t\t\tmargin-left: 22px;\n";
out << "\t\t}\n";
out << "\t\tdetails>summary:only-child::-webkit-details-marker {\n";
out << "\t\t\tdisplay: none;\n";
out << "\t\t}\n";
out << "\t\t.var, .type, .val {\n";
out << "\t\t\tdisplay: inline;\n";
out << "\t\t}\n";
out << "\t\t.var {\n";
out << "\t\t}\n";
out << "\t\t.type {\n";
out << "\t\t\tcolor: #acf;\n";
out << "\t\t\tmargin: 0 12px;\n";
out << "\t\t}\n";
out << "\t\t.val {\n";
out << "\t\t\tcolor: #afa;\n";
out << "\t\t\tbackground: #222;\n";
out << "\t\t\ttext-align: right;\n";
out << "\t\t}\n";
out << "\t\t</style>\n";
out << "\t</head>\n";
out << "\t<body>\n";
out << "\t\t<div id='header'>\n";
out << "\t\t\t<h1>vulkaninfo</h1>\n";
out << "\t\t</div>\n";
out << "\t\t<div id='wrapper'>\n";
out << "\t\t\t<details><summary>Vulkan Instance Version: <span class='val'>" << VkVersionString(vulkan_version)
<< "</span></summary></details>\n\t\t\t<br />\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</div>\n";
out << "\t</body>\n";
out << "</html>\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 << "</details>\n";
}
break;
case (OutputType::json):
case (OutputType::vkconfig_output):
out << "\n" << std::string(static_cast<size_t>(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;
}
void ObjectStart(std::string object_name, int32_t count_subobjects = -1) {
switch (output_type) {
case (OutputType::text): {
out << std::string(static_cast<size_t>(get_top().indents), '\t') << object_name;
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<size_t>(get_top().indents), '\t');
if (get_top().set_details_open || get_top().should_always_open) {
out << "<details open>";
get_top().set_details_open = false;
} else {
out << "<details>";
}
out << "<summary>";
if (get_top().set_object_name_as_type) {
out << "<span class='type'>" << object_name << "</span>";
get_top().set_object_name_as_type = false;
} else {
out << object_name;
}
if (get_top().element_index != -1) {
out << "[<span class='val'>" << get_top().element_index << "</span>]";
get_top().element_index = -1;
}
if (count_subobjects >= 0) {
out << ": count = <span class='val'>" << std::to_string(count_subobjects) << "</span>";
}
out << "</summary>\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<size_t>(get_top().indents), '\t');
// Objects with no name are elements in an array of objects
if (object_name == "" || get_top().element_index != -1) {
out << "{\n";
get_top().element_index = -1;
} else {
out << "\"" << object_name << "\": {\n";
}
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<size_t>(get_top().indents), '\t');
if (get_top().element_index != -1) {
out << "\"" << object_name << "[" << get_top().element_index << "]\": {\n";
get_top().element_index = -1;
} else {
out << "\"" << object_name << "\": {\n";
}
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<size_t>(get_top().indents), '\t') << "</details>\n";
break;
case (OutputType::json):
case (OutputType::vkconfig_output):
out << "\n" << std::string(static_cast<size_t>(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<size_t>(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<size_t>(get_top().indents), '\t');
if (get_top().set_details_open || get_top().should_always_open) {
out << "<details open>";
get_top().set_details_open = false;
} else {
out << "<details>";
}
out << "<summary>" << array_name;
if (element_count > 0) {
out << ": count = <span class='val'>" << element_count << "</span>";
}
out << "</summary>\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<size_t>(get_top().indents), '\t') << "\"" << array_name << "\": "
<< "[\n";
assert(get_top().is_array == false &&
"Cant start an array object inside another array, must be enclosed in an object");
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<size_t>(get_top().indents), '\t') << "</details>\n";
break;
case (OutputType::json):
case (OutputType::vkconfig_output):
out << "\n" << std::string(static_cast<size_t>(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 <typename T>
void PrintKeyValue(std::string key, T value, std::string value_description = "") {
switch (output_type) {
case (OutputType::text):
out << std::string(static_cast<size_t>(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 << ")";
}
out << "\n";
break;
case (OutputType::html):
out << std::string(static_cast<size_t>(get_top().indents), '\t') << "<details><summary>" << 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 << " = <span class='type'>" << value << "</span>";
} else {
out << " = <span class='val'>" << value << "</span>";
}
if (value_description != "") {
out << " (<span class='val'>" << value_description << "</span>)";
}
out << "</summary></details>\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<size_t>(get_top().indents), '\t') << "\"" << key << "\": " << value;
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<size_t>(get_top().indents), '\t') << "\"" << key << "\": ";
if (value_description != "") {
out << "\"" << value << " (" << value_description << ")\"";
} else {
out << value;
}
default:
break;
}
}
// For printing key - string pairs (necessary because of json)
void PrintKeyString(std::string key, std::string value, std::string value_description = "") {
switch (output_type) {
case (OutputType::text):
case (OutputType::html):
PrintKeyValue(key, value, value_description);
break;
case (OutputType::json):
case (OutputType::vkconfig_output):
PrintKeyValue(key, std::string("\"") + value + "\"", value_description);
break;
default:
break;
}
}
// For printing key - string pairs (necessary because of json)
void PrintKeyBool(std::string key, bool value, std::string value_description = "") {
PrintKeyValue(key, value ? "true" : "false", value_description);
}
// print inside array
template <typename T>
void PrintElement(T element, std::string value_description = "") {
switch (output_type) {
case (OutputType::text):
out << std::string(static_cast<size_t>(get_top().indents), '\t') << element;
if (value_description != "") {
out << " (" << value_description << ")";
}
out << "\n";
break;
case (OutputType::html):
out << std::string(static_cast<size_t>(get_top().indents), '\t') << "<details><summary>";
if (get_top().set_as_type) {
get_top().set_as_type = false;
out << "<span class='type'>" << element << "</span>";
} else {
out << "<span class='val'>" << element << "</span>";
}
if (value_description != "") {
out << " (<span class='val'>" << value_description << "</span>)";
}
out << "</summary></details>\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<size_t>(get_top().indents), '\t') << element;
break;
default:
break;
}
}
void PrintString(std::string string, std::string value_description = "") {
switch (output_type) {
case (OutputType::text):
case (OutputType::html):
PrintElement(string, value_description);
break;
case (OutputType::json):
case (OutputType::vkconfig_output):
PrintElement("\"" + string + "\"", value_description);
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<size_t>(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<size_t>(get_top().indents), '\t') << "<details><summary>" << DecorateAsType(ext_name)
<< std::string(min_width - ext_name.size(), ' ') << " : extension revision "
<< DecorateAsValue(std::to_string(revision)) << "</summary></details>\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 "<span class='type'>" + input + "</span>";
else
return input;
}
std::string DecorateAsValue(const std::string &input) {
if (output_type == OutputType::html)
return "<span class='val'>" + input + "</span>";
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 <details>
bool set_details_open = false;
// always open <details>
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<StackNode> object_stack;
// Helper to get the current top of the object_stack
StackNode &get_top() { return object_stack.top(); }
// 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<size_t>(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<size_t>(get_top().indents), '\t') << std::string(length, '-') << "\n";
get_top().set_next_subheader = false;
}
}
};
// 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<int32_t>(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;
};