vulkaninfo: new vkconfig_output backdend
[platform/upstream/Vulkan-Tools.git] / vulkaninfo / outputprinter.h
1 /*
2  * Copyright (c) 2019 The Khronos Group Inc.
3  * Copyright (c) 2019 Valve Corporation
4  * Copyright (c) 2019 LunarG, Inc.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  *
18  * Author: Charles Giessen <charles@lunarg.com>
19  *
20  */
21
22 #pragma once
23
24 #include <iomanip>
25 #include <iostream>
26 #include <ostream>
27 #include <stack>
28 #include <sstream>
29 #include <string>
30
31 #include <assert.h>
32
33 std::string insert_quotes(std::string s) { return "\"" + s + "\""; }
34
35 std::string to_string_16(uint8_t uid[16]) {
36     std::stringstream ss;
37     ss << std::hex << std::setfill('0');
38     for (int i = 0; i < 16; ++i) {
39         if (i == 4 || i == 6 || i == 8 || i == 10) ss << '-';
40         ss << std::setw(2) << static_cast<unsigned>(uid[i]);
41     }
42     return ss.str();
43 }
44
45 std::string to_string_8(uint8_t uid[8]) {
46     std::stringstream ss;
47     ss << std::hex << std::setfill('0');
48     for (int i = 0; i < 8; ++i) {
49         if (i == 4) ss << '-';
50         ss << std::setw(2) << static_cast<unsigned>(uid[i]);
51     }
52     return ss.str();
53 }
54
55 std::string VkVersionString(uint32_t version) {
56     uint32_t major = version >> 22;
57     uint32_t minor = (version >> 12) & 0x3ff;
58     uint32_t patch = version & 0xfff;
59     return std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(patch);
60 }
61
62 std::string VkVersionString(VulkanVersion v) {
63     return std::to_string(v.major) + "." + std::to_string(v.minor) + "." + std::to_string(v.patch);
64 }
65
66 enum class OutputType { text, html, json, vkconfig_output };
67
68 class Printer {
69    public:
70     Printer(OutputType output_type, std::ostream &out, const uint32_t selected_gpu, const VulkanVersion vulkan_version)
71         : output_type(output_type), out(out) {
72         switch (output_type) {
73             case (OutputType::text):
74                 out << "==========\n";
75                 out << "VULKANINFO\n";
76                 out << "==========\n\n";
77                 out << "Vulkan Instance Version: " << VkVersionString(vulkan_version) << "\n\n\n";
78
79                 break;
80             case (OutputType::html):
81                 out << "<!doctype html>\n";
82                 out << "<html lang='en'>\n";
83                 out << "\t<head>\n";
84                 out << "\t\t<title>vulkaninfo</title>\n";
85                 out << "\t\t<style>\n";
86                 out << "\t\thtml {\n";
87                 out << "\t\t\tbackground-color: #0b1e48;\n";
88                 out << "\t\t\tbackground-image: url(\"https://vulkan.lunarg.com/img/bg-starfield.jpg\");\n";
89                 out << "\t\t\tbackground-position: center;\n";
90                 out << "\t\t\t-webkit-background-size: cover;\n";
91                 out << "\t\t\t-moz-background-size: cover;\n";
92                 out << "\t\t\t-o-background-size: cover;\n";
93                 out << "\t\t\tbackground-size: cover;\n";
94                 out << "\t\t\tbackground-attachment: fixed;\n";
95                 out << "\t\t\tbackground-repeat: no-repeat;\n";
96                 out << "\t\t\theight: 100%;\n";
97                 out << "\t\t}\n";
98                 out << "\t\t#header {\n";
99                 out << "\t\t\tz-index: -1;\n";
100                 out << "\t\t}\n";
101                 out << "\t\t#header>img {\n";
102                 out << "\t\t\tposition: absolute;\n";
103                 out << "\t\t\twidth: 160px;\n";
104                 out << "\t\t\tmargin-left: -280px;\n";
105                 out << "\t\t\ttop: -10px;\n";
106                 out << "\t\t\tleft: 50%;\n";
107                 out << "\t\t}\n";
108                 out << "\t\t#header>h1 {\n";
109                 out << "\t\t\tfont-family: Arial, \"Helvetica Neue\", Helvetica, sans-serif;\n";
110                 out << "\t\t\tfont-size: 44px;\n";
111                 out << "\t\t\tfont-weight: 200;\n";
112                 out << "\t\t\ttext-shadow: 4px 4px 5px #000;\n";
113                 out << "\t\t\tcolor: #eee;\n";
114                 out << "\t\t\tposition: absolute;\n";
115                 out << "\t\t\twidth: 400px;\n";
116                 out << "\t\t\tmargin-left: -80px;\n";
117                 out << "\t\t\ttop: 8px;\n";
118                 out << "\t\t\tleft: 50%;\n";
119                 out << "\t\t}\n";
120                 out << "\t\tbody {\n";
121                 out << "\t\t\tfont-family: Consolas, monaco, monospace;\n";
122                 out << "\t\t\tfont-size: 14px;\n";
123                 out << "\t\t\tline-height: 20px;\n";
124                 out << "\t\t\tcolor: #eee;\n";
125                 out << "\t\t\theight: 100%;\n";
126                 out << "\t\t\tmargin: 0;\n";
127                 out << "\t\t\toverflow: hidden;\n";
128                 out << "\t\t}\n";
129                 out << "\t\t#wrapper {\n";
130                 out << "\t\t\tbackground-color: rgba(0, 0, 0, 0.7);\n";
131                 out << "\t\t\tborder: 1px solid #446;\n";
132                 out << "\t\t\tbox-shadow: 0px 0px 10px #000;\n";
133                 out << "\t\t\tpadding: 8px 12px;\n\n";
134                 out << "\t\t\tdisplay: inline-block;\n";
135                 out << "\t\t\tposition: absolute;\n";
136                 out << "\t\t\ttop: 80px;\n";
137                 out << "\t\t\tbottom: 25px;\n";
138                 out << "\t\t\tleft: 50px;\n";
139                 out << "\t\t\tright: 50px;\n";
140                 out << "\t\t\toverflow: auto;\n";
141                 out << "\t\t}\n";
142                 out << "\t\tdetails>details {\n";
143                 out << "\t\t\tmargin-left: 22px;\n";
144                 out << "\t\t}\n";
145                 out << "\t\tdetails>summary:only-child::-webkit-details-marker {\n";
146                 out << "\t\t\tdisplay: none;\n";
147                 out << "\t\t}\n";
148                 out << "\t\t.var, .type, .val {\n";
149                 out << "\t\t\tdisplay: inline;\n";
150                 out << "\t\t}\n";
151                 out << "\t\t.var {\n";
152                 out << "\t\t}\n";
153                 out << "\t\t.type {\n";
154                 out << "\t\t\tcolor: #acf;\n";
155                 out << "\t\t\tmargin: 0 12px;\n";
156                 out << "\t\t}\n";
157                 out << "\t\t.val {\n";
158                 out << "\t\t\tcolor: #afa;\n";
159                 out << "\t\t\tbackground: #222;\n";
160                 out << "\t\t\ttext-align: right;\n";
161                 out << "\t\t}\n";
162                 out << "\t\t</style>\n";
163                 out << "\t</head>\n";
164                 out << "\t<body>\n";
165                 out << "\t\t<div id='header'>\n";
166                 out << "\t\t\t<h1>vulkaninfo</h1>\n";
167                 out << "\t\t</div>\n";
168                 out << "\t\t<div id='wrapper'>\n";
169
170                 out << "\t\t\t<details><summary>Vulkan Instance Version: <span class='val'>" << VkVersionString(vulkan_version)
171                     << "</span></summary></details>\n\t\t\t<br />\n";
172                 indents += 3;
173                 break;
174             case (OutputType::json):
175                 out << "{\n";
176                 out << "\t\"$schema\": \"https://schema.khronos.org/vulkan/devsim_1_0_0.json#\",\n";
177                 out << "\t\"comments\": {\n";
178                 out << "\t\t\"desc\": \"JSON configuration file describing GPU " << selected_gpu
179                     << ". Generated using the vulkaninfo program.\",\n";
180                 out << "\t\t\"vulkanApiVersion\": \"" << VkVersionString(vulkan_version) << "\"\n";
181                 out << "\t}";
182                 indents++;
183                 is_first_item.push(false);
184                 break;
185             case (OutputType::vkconfig_output):
186                 out << "{\n";
187                 out << "\t\"Vulkan Instance Version\": \"" << VkVersionString(vulkan_version) << "\"";
188                 indents++;
189                 is_first_item.push(false);
190                 break;
191             default:
192                 break;
193         }
194     }
195     ~Printer() {
196         switch (output_type) {
197             case (OutputType::text):
198
199                 break;
200             case (OutputType::html):
201                 out << "\t\t</div>\n";
202                 out << "\t</body>\n";
203                 out << "</html>\n";
204                 indents -= 3;
205                 break;
206             case (OutputType::json):
207             case (OutputType::vkconfig_output):
208                 out << "\n}\n";
209                 indents--;
210                 is_first_item.pop();
211                 assert(is_first_item.empty() && "mismatched number of ObjectStart/ObjectEnd or ArrayStart/ArrayEnd's");
212                 break;
213         }
214         assert(indents == 0 && "indents must be zero at program end");
215     };
216
217     Printer(const Printer &) = delete;
218     const Printer &operator=(const Printer &) = delete;
219
220     OutputType Type() { return output_type; }
221
222     // Custom Formatting
223     // use by prepending with p.SetXXX().ObjectStart/ArrayStart
224
225     Printer &SetHeader() {
226         set_next_header = true;
227         return *this;
228     }
229
230     Printer &SetSubHeader() {
231         set_next_subheader = true;
232         return *this;
233     }
234
235     Printer &SetOpenDetails() {
236         set_details_open = true;
237         return *this;
238     }
239
240     Printer &SetTitleAsType() {
241         set_object_name_as_type = true;
242         return *this;
243     }
244
245     Printer &SetAsType() {
246         set_as_type = true;
247         return *this;
248     }
249
250     Printer &SetElementIndex(int index) {
251         assert(index >= 0 && "cannot set element index to a negative value");
252         element_index = index;
253         return *this;
254     }
255
256     void ObjectStart(std::string object_name, int32_t count_subobjects = -1) {
257         switch (output_type) {
258             case (OutputType::text): {
259                 out << std::string(static_cast<size_t>(indents), '\t') << object_name;
260                 if (element_index != -1) {
261                     out << "[" << element_index << "]";
262                 }
263                 out << ":";
264                 if (count_subobjects >= 0) {
265                     out << " count = " << count_subobjects;
266                 }
267                 out << "\n";
268                 size_t headersize = object_name.size() + 1;
269                 if (count_subobjects >= 0) {
270                     headersize += 9 + std::to_string(count_subobjects).size();
271                 }
272                 if (element_index != -1) {
273                     headersize += 2 + std::to_string(element_index).size();
274                     element_index = -1;
275                 }
276                 PrintHeaderUnderlines(headersize);
277                 break;
278             }
279             case (OutputType::html):
280                 out << std::string(static_cast<size_t>(indents), '\t');
281                 if (set_details_open) {
282                     out << "<details open>";
283                     set_details_open = false;
284                 } else {
285                     out << "<details>";
286                 }
287                 out << "<summary>";
288                 if (set_object_name_as_type) {
289                     out << "<span class='type'>" << object_name << "</span>";
290                     set_object_name_as_type = false;
291                 } else {
292                     out << object_name;
293                 }
294                 if (element_index != -1) {
295                     out << "[<span class='val'>" << element_index << "</span>]";
296                     element_index = -1;
297                 }
298                 if (count_subobjects >= 0) {
299                     out << ": count = <span class='val'>" << std::to_string(count_subobjects) << "</span>";
300                 }
301                 out << "</summary>\n";
302                 break;
303             case (OutputType::json):
304                 if (!is_first_item.top()) {
305                     out << ",\n";
306                 } else {
307                     is_first_item.top() = false;
308                 }
309                 out << std::string(static_cast<size_t>(indents), '\t');
310                 // Objects with no name are elements in an array of objects
311                 if (object_name == "" || element_index != -1) {
312                     out << "{\n";
313                     element_index = -1;
314                 } else {
315                     out << "\"" << object_name << "\": {\n";
316                 }
317
318                 is_first_item.push(true);
319                 break;
320             case (OutputType::vkconfig_output):
321                 if (!is_first_item.top()) {
322                     out << ",\n";
323                 } else {
324                     is_first_item.top() = false;
325                 }
326                 out << std::string(static_cast<size_t>(indents), '\t');
327
328                 if (element_index != -1) {
329                     out << "\"" << object_name << "[" << element_index << "]\": {\n";
330                     element_index = -1;
331                 } else {
332                     out << "\"" << object_name << "\": {\n";
333                 }
334                 is_first_item.push(true);
335                 break;
336             default:
337                 break;
338         }
339         indents++;
340     }
341     void ObjectEnd() {
342         indents--;
343         assert(indents >= 0 && "indents cannot go below zero");
344         switch (output_type) {
345             case (OutputType::text):
346
347                 break;
348             case (OutputType::html):
349                 out << std::string(static_cast<size_t>(indents), '\t') << "</details>\n";
350                 break;
351             case (OutputType::json):
352             case (OutputType::vkconfig_output):
353                 out << "\n" << std::string(static_cast<size_t>(indents), '\t') << "}";
354                 is_first_item.pop();
355                 break;
356             default:
357                 break;
358         }
359     }
360     void ArrayStart(std::string array_name, int32_t element_count = 0) {
361         switch (output_type) {
362             case (OutputType::text): {
363                 out << std::string(static_cast<size_t>(indents), '\t') << array_name << ":";
364                 size_t underline_count = array_name.size() + 1;
365                 if (element_count >= 0) {
366                     out << " count = " << element_count;
367                     underline_count += 9 + std::to_string(element_count).size();
368                 }
369                 out << "\n";
370                 PrintHeaderUnderlines(underline_count);
371                 break;
372             }
373             case (OutputType::html):
374                 out << std::string(static_cast<size_t>(indents), '\t');
375                 if (set_details_open) {
376                     out << "<details open>";
377                     set_details_open = false;
378                 } else {
379                     out << "<details>";
380                 }
381                 out << "<summary>" << array_name;
382                 if (element_count >= 0) {
383                     out << ": count = <span class='val'>" << element_count << "</span>";
384                 }
385                 out << "</summary>\n";
386                 break;
387             case (OutputType::json):
388             case (OutputType::vkconfig_output):
389                 if (!is_first_item.top()) {
390                     out << ",\n";
391                 } else {
392                     is_first_item.top() = false;
393                 }
394                 out << std::string(static_cast<size_t>(indents), '\t') << "\"" << array_name << "\": "
395                     << "[\n";
396                 is_first_item.push(true);
397                 break;
398             default:
399                 break;
400         }
401         indents++;
402     }
403     void ArrayEnd() {
404         indents--;
405         assert(indents >= 0 && "indents cannot go below zero");
406         switch (output_type) {
407             case (OutputType::text):
408
409                 break;
410             case (OutputType::html):
411                 out << std::string(static_cast<size_t>(indents), '\t') << "</details>\n";
412                 break;
413             case (OutputType::json):
414             case (OutputType::vkconfig_output):
415                 out << "\n" << std::string(static_cast<size_t>(indents), '\t') << "]";
416                 is_first_item.pop();
417                 break;
418             default:
419                 break;
420         }
421     }
422
423     // For printing key-value pairs.
424     // min_key_width lines up the values listed
425     // value_description is for reference information and is displayed inside parenthesis after the value
426     template <typename T>
427     void PrintKeyValue(std::string key, T value, size_t min_key_width = 0, std::string value_description = "") {
428         switch (output_type) {
429             case (OutputType::text):
430                 if (min_key_width > key.size()) {
431                     out << std::string(static_cast<size_t>(indents), '\t') << key << std::string(min_key_width - key.size(), ' ');
432                 } else {
433                     out << std::string(static_cast<size_t>(indents), '\t') << key;
434                 }
435                 out << " = " << value;
436                 if (value_description != "") {
437                     out << " (" << value_description << ")";
438                 }
439                 out << "\n";
440                 break;
441             case (OutputType::html):
442                 out << std::string(static_cast<size_t>(indents), '\t') << "<details><summary>" << key;
443                 if (min_key_width > key.size()) {
444                     out << std::string(min_key_width - key.size(), ' ');
445                 }
446                 if (set_as_type) {
447                     set_as_type = false;
448                     out << " = <span class='type'>" << value << "</span>";
449                 } else {
450                     out << " = <span class='val'>" << value << "</span>";
451                 }
452                 if (value_description != "") {
453                     out << " (<span class='val'>" << value_description << "</span>)";
454                 }
455                 out << "</summary></details>\n";
456                 break;
457             case (OutputType::json):
458                 if (!is_first_item.top()) {
459                     out << ",\n";
460                 } else {
461                     is_first_item.top() = false;
462                 }
463                 out << std::string(static_cast<size_t>(indents), '\t') << "\"" << key << "\": " << value;
464                 break;
465             case (OutputType::vkconfig_output):
466                 if (!is_first_item.top()) {
467                     out << ",\n";
468                 } else {
469                     is_first_item.top() = false;
470                 }
471                 out << std::string(static_cast<size_t>(indents), '\t') << "\"" << key << "\": ";
472                 if (value_description != "") {
473                     out << "\"" << value << " (" << value_description << ")\"";
474                 } else {
475                     out << value;
476                 }
477             default:
478                 break;
479         }
480     }
481
482     // For printing key - string pairs (necessary because of json)
483     void PrintKeyString(std::string key, std::string value, size_t min_key_width = 0, std::string value_description = "") {
484         switch (output_type) {
485             case (OutputType::text):
486             case (OutputType::html):
487                 PrintKeyValue(key, value, min_key_width, value_description);
488                 break;
489             case (OutputType::json):
490             case (OutputType::vkconfig_output):
491                 PrintKeyValue(key, std::string("\"") + value + "\"", min_key_width, value_description);
492                 break;
493             default:
494                 break;
495         }
496     }
497
498     // For printing key - string pairs (necessary because of json)
499     void PrintKeyBool(std::string key, bool value, size_t min_key_width = 0, std::string value_description = "") {
500         switch (output_type) {
501             case (OutputType::text):
502             case (OutputType::html):
503             case (OutputType::vkconfig_output):
504                 PrintKeyValue(key, value ? "true" : "false", min_key_width, value_description);
505                 break;
506             case (OutputType::json):
507                 PrintKeyValue(key, value, min_key_width, value_description);
508                 break;
509             default:
510                 break;
511         }
512     }
513
514     // print inside array
515     template <typename T>
516     void PrintElement(T element, std::string value_description = "") {
517         switch (output_type) {
518             case (OutputType::text):
519                 out << std::string(static_cast<size_t>(indents), '\t') << element;
520                 if (value_description != "") {
521                     out << " (" << value_description << ")";
522                 }
523                 out << "\n";
524                 break;
525             case (OutputType::html):
526                 out << std::string(static_cast<size_t>(indents), '\t') << "<details><summary>";
527                 if (set_as_type) {
528                     set_as_type = false;
529                     out << "<span class='type'>" << element << "</span>";
530                 } else {
531                     out << "<span class='val'>" << element << "</span>";
532                 }
533                 if (value_description != "") {
534                     out << " (<span class='val'>" << value_description << "</span>)";
535                 }
536                 out << "</summary></details>\n";
537                 break;
538             case (OutputType::json):
539             case (OutputType::vkconfig_output):
540                 if (!is_first_item.top()) {
541                     out << ",\n";
542                 } else {
543                     is_first_item.top() = false;
544                 }
545                 out << std::string(static_cast<size_t>(indents), '\t') << element;
546                 break;
547             default:
548                 break;
549         }
550     }
551     void PrintString(std::string string, std::string value_description = "") {
552         switch (output_type) {
553             case (OutputType::text):
554             case (OutputType::html):
555                 PrintElement(string, value_description);
556                 break;
557             case (OutputType::json):
558             case (OutputType::vkconfig_output):
559                 PrintElement("\"" + string + "\"", value_description);
560             default:
561                 break;
562         }
563     }
564     void PrintExtension(std::string ext_name, uint32_t revision, int min_width = 0) {
565         switch (output_type) {
566             case (OutputType::text):
567                 out << std::string(static_cast<size_t>(indents), '\t') << ext_name << std::string(min_width - ext_name.size(), ' ')
568                     << " : extension revision " << revision << "\n";
569                 break;
570             case (OutputType::html):
571                 out << std::string(static_cast<size_t>(indents), '\t') << "<details><summary>" << DecorateAsType(ext_name)
572                     << std::string(min_width - ext_name.size(), ' ') << " : extension revision "
573                     << DecorateAsValue(std::to_string(revision)) << "</summary></details>\n";
574                 break;
575             case (OutputType::json):
576                 break;
577             case (OutputType::vkconfig_output):
578                 ObjectStart(ext_name);
579                 PrintKeyValue("specVersion", revision);
580                 ObjectEnd();
581                 break;
582             default:
583                 break;
584         }
585     }
586     void AddNewline() {
587         if (output_type == OutputType::text) {
588             out << "\n";
589         }
590     }
591     void IndentIncrease() {
592         if (output_type == OutputType::text) {
593             indents++;
594         }
595     }
596     void IndentDecrease() {
597         if (output_type == OutputType::text) {
598             indents--;
599             assert(indents >= 0 && "indents cannot go below zero");
600         }
601     }
602
603     std::string DecorateAsType(const std::string &input) {
604         if (output_type == OutputType::html)
605             return "<span class='type'>" + input + "</span>";
606         else
607             return input;
608     }
609
610     std::string DecorateAsValue(const std::string &input) {
611         if (output_type == OutputType::html)
612             return "<span class='val'>" + input + "</span>";
613         else
614             return input;
615     }
616
617    protected:
618     OutputType output_type;
619     std::ostream &out;
620     int indents = 0;
621
622     // header, subheader
623     bool set_next_header = false;
624     bool set_next_subheader = false;
625
626     // html coloring
627     bool set_as_type = false;
628
629     // open <details>
630     bool set_details_open = false;
631
632     // make object titles the color of types
633     bool set_object_name_as_type = false;
634
635     // objects which are in an array
636     int element_index = -1;  // negative one is the sentinel value
637
638     // json
639     std::stack<bool> is_first_item;
640
641     // utility
642     void PrintHeaderUnderlines(size_t length) {
643         assert(indents >= 0 && "indents must not be negative");
644         assert(length <= 10000 && "length shouldn't be unreasonably large");
645         if (set_next_header) {
646             out << std::string(static_cast<size_t>(indents), '\t') << std::string(length, '=') << "\n";
647             set_next_header = false;
648         } else if (set_next_subheader) {
649             out << std::string(static_cast<size_t>(indents), '\t') << std::string(length, '-') << "\n";
650             set_next_subheader = false;
651         }
652     }
653 };
654
655 class ObjectWrapper {
656    public:
657     ObjectWrapper(Printer &p, std::string object_name, int32_t count_subobjects = -1) : p(p) {
658         p.ObjectStart(object_name, count_subobjects);
659     }
660     ~ObjectWrapper() { p.ObjectEnd(); }
661
662    private:
663     Printer &p;
664 };
665
666 class ArrayWrapper {
667    public:
668     ArrayWrapper(Printer &p, std::string array_name, int32_t element_count = 0) : p(p) { p.ArrayStart(array_name, element_count); }
669     ~ArrayWrapper() { p.ArrayEnd(); }
670
671    private:
672     Printer &p;
673 };