blob: 8aff2594ffea7018b3f6bd671b8eba7b6195c2c7 [file] [log] [blame] [edit]
/* Copyright (c) 2015-2016 The Khronos Group Inc.
* Copyright (c) 2015-2016 Valve Corporation
* Copyright (c) 2015-2016 LunarG, Inc.
* Copyright (C) 2015-2016 Google 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: Lenny Komow <lenny@lunarg.com>
*/
#pragma once
#define NOMINMAX
#include "vk_loader_platform.h"
#include "vulkan/vk_layer.h"
#include "vk_layer_config.h"
#include "vk_layer_table.h"
#include "vk_layer_extension_utils.h"
#include "vk_layer_utils.h"
#include <algorithm>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <ostream>
#include <sstream>
#include <string.h>
#include <string>
#include <type_traits>
#include <map>
#include <unordered_map>
#include <vector>
#include <unordered_set>
#include <utility>
#define MAX_STRING_LENGTH 1024
enum class ApiDumpFormat {
Text,
Html,
};
class ApiDumpSettings {
public:
ApiDumpSettings() {
// Get the output file settings and create a stream for it
const char *file_option = getLayerOption("lunarg_api_dump.file");
if (file_option != NULL && strcmp(file_option, "TRUE") == 0) {
use_cout = false;
const char *filename_option = getLayerOption("lunarg_api_dump.log_filename");
if (filename_option != NULL && strcmp(filename_option, "") != 0)
output_stream.open(filename_option, std::ofstream::out | std::ostream::trunc);
else
output_stream.open("vk_apidump.txt", std::ofstream::out | std::ostream::trunc);
} else {
use_cout = true;
}
// Get the remaining settings
output_format = readFormatOption("lunarg_api_dump.output_format", ApiDumpFormat::Text);
show_params = readBoolOption("lunarg_api_dump.detailed", true);
show_address = !readBoolOption("lunarg_api_dump.no_addr", false);
should_flush = readBoolOption("lunarg_api_dump.flush", true);
indent_size = std::max(readIntOption("lunarg_api_dump.indent_size", 4), 0);
show_type = readBoolOption("lunarg_api_dump.show_types", true);
name_size = std::max(readIntOption("lunarg_api_dump.name_size", 32), 0);
type_size = std::max(readIntOption("lunarg_api_dump.type_size", 0), 0);
use_spaces = readBoolOption("lunarg_api_dump.use_spaces", true);
show_shader = readBoolOption("lunarg_api_dump.show_shader", false);
// Generate HTML heading if specified
if (output_format == ApiDumpFormat::Html) {
// Find the layer path
std::string layer_path;
#ifdef _WIN32
char temp[MAX_STRING_LENGTH];
int bytes = GetEnvironmentVariableA("VK_LAYER_PATH", temp, MAX_STRING_LENGTH - 1);
if (0 < bytes) {
std::string location = temp;
layer_path = location.substr(0, location.rfind("\\"));
size_t index = 0;
while (true) {
index = layer_path.find("\\", index);
if (index == std::string::npos) {
break;
}
layer_path.replace(index, 1, "/");
index++;
}
} else {
layer_path = "";
}
#elif __GNUC__
layer_path = getenv("VK_LAYER_PATH");
if (layer_path.length() > 0) {
layer_path = layer_path.substr(0, layer_path.rfind("/"));
} else {
layer_path = "";
}
#endif
// clang-format off
// Insert html heading
stream() <<
"<!doctype html>"
"<html>"
"<head>"
"<title>Vulkan API Dump</title>"
"<style type='text/css'>"
"html {"
"background-color: #0b1e48;"
"background-image: url('https://vulkan.lunarg.com/img/bg-starfield.jpg');"
"background-position: center;"
"-webkit-background-size: cover;"
"-moz-background-size: cover;"
"-o-background-size: cover;"
"background-size: cover;"
"background-attachment: fixed;"
"background-repeat: no-repeat;"
"height: 100%;"
"}"
"#header {"
"z-index: -1;"
"}"
"#header>img {"
"position: absolute;"
"width: 160px;"
"margin-left: -280px;"
"top: -10px;"
"left: 50%;"
"}"
"#header>h1 {"
"font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif;"
"font-size: 44px;"
"font-weight: 200;"
"text-shadow: 4px 4px 5px #000;"
"color: #eee;"
"position: absolute;"
"width: 400px;"
"margin-left: -80px;"
"top: 8px;"
"left: 50%;"
"}"
"body {"
"font-family: Consolas, monaco, monospace;"
"font-size: 14px;"
"line-height: 20px;"
"color: #eee;"
"height: 100%;"
"margin: 0;"
"overflow: hidden;"
"}"
"#wrapper {"
"background-color: rgba(0, 0, 0, 0.7);"
"border: 1px solid #446;"
"box-shadow: 0px 0px 10px #000;"
"padding: 8px 12px;"
"display: inline-block;"
"position: absolute;"
"top: 80px;"
"bottom: 25px;"
"left: 50px;"
"right: 50px;"
"overflow: auto;"
"}"
"details>*:not(summary) {"
"margin-left: 22px;"
"}"
"summary:only-child {"
"display: block;"
"padding-left: 15px;"
"}"
"details>summary:only-child::-webkit-details-marker {"
"display: none;"
"padding-left: 15px;"
"}"
".var, .type, .val {"
"display: inline;"
"margin: 0 6px;"
"}"
".type {"
"color: #acf;"
"}"
".val {"
"color: #afa;"
"text-align: right;"
"}"
".thd {"
"color: #888;"
"}"
"</style>"
"</head>"
"<body>"
"<div id='header'>"
"<img src='https://lunarg.com/wp-content/uploads/2016/02/LunarG-wReg-150.png' />"
"<h1>Vulkan API Dump</h1>"
"</div>"
"<div id='wrapper'>";
// clang-format on
}
}
~ApiDumpSettings() {
if (output_format == ApiDumpFormat::Html) {
// Close off html
stream() << "</div></body></html>";
}
if (!use_cout)
output_stream.close();
}
inline ApiDumpFormat format() const { return output_format; }
std::ostream &formatNameType(std::ostream &stream, int indents, const char *name, const char *type) const {
stream << indentation(indents) << name << ": ";
if (use_spaces)
stream << spaces(name_size - (int)strlen(name) - 2);
else
stream << tabs((name_size - (int)strlen(name) - 3 + indent_size) / indent_size);
if (show_type && use_spaces)
stream << type << spaces(type_size - (int)strlen(type));
else if (show_type && !use_spaces)
stream << type << tabs((type_size - (int)strlen(type) - 1 + indent_size) / indent_size);
return stream << " = ";
}
inline const char *indentation(int indents) const {
if (use_spaces)
return spaces(indents * indent_size);
else
return tabs(indents);
}
inline bool shouldFlush() const { return should_flush; }
inline bool showAddress() const { return show_address; }
inline bool showParams() const { return show_params; }
inline bool showShader() const { return show_shader; }
inline bool showType() const { return show_type; }
inline std::ostream &stream() const { return use_cout ? std::cout : *(std::ofstream *)&output_stream; }
private:
inline static bool readBoolOption(const char *option, bool default_value) {
const char *string_option = getLayerOption(option);
if (string_option != NULL && strcmp(string_option, "TRUE") == 0)
return true;
else if (string_option != NULL && strcmp(string_option, "FALSE") == 0)
return false;
else
return default_value;
}
inline static int readIntOption(const char *option, int default_value) {
const char *string_option = getLayerOption(option);
int value;
if (sscanf(string_option, "%d", &value) != 1) {
return default_value;
} else {
return value;
}
}
inline static ApiDumpFormat readFormatOption(const char *option, ApiDumpFormat default_value) {
const char *string_option = getLayerOption(option);
if (strcmp(string_option, "Text") == 0)
return ApiDumpFormat::Text;
else if (strcmp(string_option, "Html") == 0)
return ApiDumpFormat::Html;
else
return default_value;
}
inline static const char *spaces(int count) { return SPACES + (MAX_SPACES - std::max(count, 0)); }
inline static const char *tabs(int count) { return TABS + (MAX_TABS - std::max(count, 0)); }
bool use_cout;
std::ofstream output_stream;
ApiDumpFormat output_format;
bool show_params;
bool show_address;
bool should_flush;
bool show_type;
int indent_size;
int name_size;
int type_size;
bool use_spaces;
bool show_shader;
static const char *const SPACES;
static const int MAX_SPACES = 72;
static const char *const TABS;
static const int MAX_TABS = 18;
};
const char *const ApiDumpSettings::SPACES = " ";
const char *const ApiDumpSettings::TABS = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
class ApiDumpInstance {
public:
inline ApiDumpInstance() : dump_settings(NULL), frame_count(0), thread_count(0) {
loader_platform_thread_create_mutex(&output_mutex);
loader_platform_thread_create_mutex(&frame_mutex);
loader_platform_thread_create_mutex(&thread_mutex);
loader_platform_thread_create_mutex(&cmd_buffer_state_mutex);
}
inline ~ApiDumpInstance() {
if (dump_settings != NULL) delete dump_settings;
loader_platform_thread_delete_mutex(&thread_mutex);
loader_platform_thread_delete_mutex(&frame_mutex);
loader_platform_thread_delete_mutex(&output_mutex);
loader_platform_thread_delete_mutex(&cmd_buffer_state_mutex);
}
inline uint64_t frameCount() {
loader_platform_thread_lock_mutex(&frame_mutex);
uint64_t count = frame_count;
loader_platform_thread_unlock_mutex(&frame_mutex);
return count;
}
inline void nextFrame() {
loader_platform_thread_lock_mutex(&frame_mutex);
++frame_count;
loader_platform_thread_unlock_mutex(&frame_mutex);
}
inline loader_platform_thread_mutex *outputMutex() { return &output_mutex; }
inline const ApiDumpSettings &settings() {
if (dump_settings == NULL) dump_settings = new ApiDumpSettings();
return *dump_settings;
}
uint32_t threadID() {
loader_platform_thread_id id = loader_platform_get_thread_id();
loader_platform_thread_lock_mutex(&thread_mutex);
for (uint32_t i = 0; i < thread_count; ++i) {
if (thread_map[i] == id) {
loader_platform_thread_unlock_mutex(&thread_mutex);
return i;
}
}
uint32_t new_index = thread_count;
thread_map[thread_count++] = id;
assert(thread_count < MAX_THREADS);
loader_platform_thread_unlock_mutex(&thread_mutex);
return new_index;
}
inline VkCommandBufferLevel getCmdBufferLevel(VkCommandBuffer cmd_buffer) {
loader_platform_thread_lock_mutex(&cmd_buffer_state_mutex);
const auto level_iter = cmd_buffer_level.find(cmd_buffer);
assert(level_iter != cmd_buffer_level.end());
const auto level = level_iter->second;
loader_platform_thread_unlock_mutex(&cmd_buffer_state_mutex);
return level;
}
inline void eraseCmdBuffers(VkDevice device, VkCommandPool cmd_pool, std::vector<VkCommandBuffer> cmd_buffers) {
cmd_buffers.erase(std::remove(cmd_buffers.begin(), cmd_buffers.end(), nullptr), cmd_buffers.end());
if (!cmd_buffers.empty()) {
loader_platform_thread_lock_mutex(&cmd_buffer_state_mutex);
const auto pool_cmd_buffers_iter = cmd_buffer_pools.find(std::make_pair(device, cmd_pool));
assert(pool_cmd_buffers_iter != cmd_buffer_pools.end());
for (const auto cmd_buffer : cmd_buffers) {
pool_cmd_buffers_iter->second.erase(cmd_buffer);
assert(cmd_buffer_level.count(cmd_buffer) > 0);
cmd_buffer_level.erase(cmd_buffer);
}
loader_platform_thread_unlock_mutex(&cmd_buffer_state_mutex);
}
}
inline void addCmdBuffers(VkDevice device, VkCommandPool cmd_pool, std::vector<VkCommandBuffer> cmd_buffers,
VkCommandBufferLevel level) {
loader_platform_thread_lock_mutex(&cmd_buffer_state_mutex);
auto &pool_cmd_buffers = cmd_buffer_pools[std::make_pair(device, cmd_pool)];
pool_cmd_buffers.insert(cmd_buffers.begin(), cmd_buffers.end());
for (const auto cmd_buffer : cmd_buffers) {
assert(cmd_buffer_level.count(cmd_buffer) == 0);
cmd_buffer_level[cmd_buffer] = level;
}
loader_platform_thread_unlock_mutex(&cmd_buffer_state_mutex);
}
inline void eraseCmdBufferPool(VkDevice device, VkCommandPool cmd_pool) {
if (cmd_pool != VK_NULL_HANDLE) {
loader_platform_thread_lock_mutex(&cmd_buffer_state_mutex);
const auto cmd_buffers_iter = cmd_buffer_pools.find(std::make_pair(device, cmd_pool));
assert(cmd_buffers_iter != cmd_buffer_pools.end());
cmd_buffers_iter->second.clear();
loader_platform_thread_unlock_mutex(&cmd_buffer_state_mutex);
}
}
static inline ApiDumpInstance &current() { return current_instance; }
private:
static ApiDumpInstance current_instance;
ApiDumpSettings *dump_settings;
loader_platform_thread_mutex output_mutex;
loader_platform_thread_mutex frame_mutex;
uint64_t frame_count;
static const size_t MAX_THREADS = 513;
loader_platform_thread_mutex thread_mutex;
loader_platform_thread_id thread_map[MAX_THREADS];
uint32_t thread_count;
loader_platform_thread_mutex cmd_buffer_state_mutex;
std::map<std::pair<VkDevice, VkCommandPool>, std::unordered_set<VkCommandBuffer> > cmd_buffer_pools;
std::unordered_map<VkCommandBuffer, VkCommandBufferLevel> cmd_buffer_level;
};
ApiDumpInstance ApiDumpInstance::current_instance;
//==================================== Text Backend Helpers ======================================//
template <typename T, typename... Args>
inline void dump_text_array(const T *array, size_t len, const ApiDumpSettings &settings, const char *type_string,
const char *child_type, const char *name, int indents,
std::ostream &(*dump)(const T, const ApiDumpSettings &, int, Args... args), Args... args) {
settings.formatNameType(settings.stream(), indents, name, type_string);
if (array == NULL) {
settings.stream() << "NULL\n";
return;
}
if (settings.showAddress())
settings.stream() << (void *)array << "\n";
else
settings.stream() << "address\n";
for (size_t i = 0; i < len && array != NULL; ++i) {
std::stringstream stream;
stream << name << '[' << i << ']';
std::string indexName = stream.str();
dump_text_value(array[i], settings, child_type, indexName.c_str(), indents + 1, dump, args...);
}
}
template <typename T, typename... Args>
inline void dump_text_array(const T *array, size_t len, const ApiDumpSettings &settings, const char *type_string,
const char *child_type, const char *name, int indents,
std::ostream &(*dump)(const T &, const ApiDumpSettings &, int, Args... args), Args... args) {
settings.formatNameType(settings.stream(), indents, name, type_string);
if (array == NULL) {
settings.stream() << "NULL\n";
return;
}
if (settings.showAddress())
settings.stream() << (void *)array << "\n";
else
settings.stream() << "address\n";
for (size_t i = 0; i < len && array != NULL; ++i) {
std::stringstream stream;
stream << name << '[' << i << ']';
std::string indexName = stream.str();
dump_text_value(array[i], settings, child_type, indexName.c_str(), indents + 1, dump, args...);
}
}
template <typename T, typename... Args>
inline void dump_text_pointer(const T *pointer, const ApiDumpSettings &settings, const char *type_string, const char *name,
int indents, std::ostream &(*dump)(const T, const ApiDumpSettings &, int, Args... args),
Args... args) {
if (pointer == NULL) {
settings.formatNameType(settings.stream(), indents, name, type_string);
settings.stream() << "NULL\n";
} else {
dump_text_value(*pointer, settings, type_string, name, indents, dump, args...);
}
}
template <typename T, typename... Args>
inline void dump_text_pointer(const T *pointer, const ApiDumpSettings &settings, const char *type_string, const char *name,
int indents, std::ostream &(*dump)(const T &, const ApiDumpSettings &, int, Args... args),
Args... args) {
if (pointer == NULL) {
settings.formatNameType(settings.stream(), indents, name, type_string);
settings.stream() << "NULL\n";
} else {
dump_text_value(*pointer, settings, type_string, name, indents, dump, args...);
}
}
template <typename T, typename... Args>
inline void dump_text_value(const T object, const ApiDumpSettings &settings, const char *type_string, const char *name, int indents,
std::ostream &(*dump)(const T, const ApiDumpSettings &, int, Args... args), Args... args) {
settings.formatNameType(settings.stream(), indents, name, type_string);
dump(object, settings, indents, args...) << "\n";
}
template <typename T, typename... Args>
inline void dump_text_value(const T &object, const ApiDumpSettings &settings, const char *type_string, const char *name,
int indents, std::ostream &(*dump)(const T &, const ApiDumpSettings &, int, Args... args),
Args... args) {
settings.formatNameType(settings.stream(), indents, name, type_string);
dump(object, settings, indents, args...);
}
inline void dump_text_special(const char *text, const ApiDumpSettings &settings, const char *type_string, const char *name,
int indents) {
settings.formatNameType(settings.stream(), indents, name, type_string);
settings.stream() << text << "\n";
}
inline bool dump_text_bitmaskOption(const std::string &option, std::ostream &stream, bool isFirst) {
if (isFirst)
stream << " (";
else
stream << " | ";
stream << option;
return false;
}
inline std::ostream &dump_text_cstring(const char *object, const ApiDumpSettings &settings, int indents) {
if (object == NULL)
return settings.stream() << "NULL";
else
return settings.stream() << "\"" << object << "\"";
}
inline std::ostream &dump_text_void(const void *object, const ApiDumpSettings &settings, int indents) {
if (object == NULL)
return settings.stream() << "NULL";
else if (settings.showAddress())
return settings.stream() << object;
else
return settings.stream() << "address";
}
inline std::ostream &dump_text_int(int object, const ApiDumpSettings &settings, int indents) {
return settings.stream() << object;
}
//==================================== Html Backend Helpers ======================================//
inline std::ostream &dump_html_nametype(std::ostream &stream, bool showType, const char *name, const char *type) {
stream << "<div class='var'>" << name << "</div>";
if (showType) {
stream << "<div class='type'>" << type << "</div>";
}
return stream;
}
template <typename T, typename... Args>
inline void dump_html_array(const T *array, size_t len, const ApiDumpSettings &settings, const char *type_string,
const char *child_type, const char *name, int indents,
std::ostream &(*dump)(const T, const ApiDumpSettings &, int, Args... args), Args... args) {
if (array == NULL) {
settings.stream() << "<details class='data'><summary>";
dump_html_nametype(settings.stream(), settings.showType(), name, type_string);
settings.stream() << "<div class='val'>";
settings.stream() << "NULL</div></summary></details>";
return;
}
settings.stream() << "<details class='data'><summary>";
dump_html_nametype(settings.stream(), settings.showType(), name, type_string);
settings.stream() << "<div class='val'>";
if (settings.showAddress())
settings.stream() << (void *)array << "\n";
else
settings.stream() << "address\n";
settings.stream() << "</div></summary>";
for (size_t i = 0; i < len && array != NULL; ++i) {
std::stringstream stream;
stream << name << '[' << i << ']';
std::string indexName = stream.str();
dump_html_value(array[i], settings, child_type, indexName.c_str(), indents + 1, dump, args...);
}
settings.stream() << "</details>";
}
template <typename T, typename... Args>
inline void dump_html_array(const T *array, size_t len, const ApiDumpSettings &settings, const char *type_string,
const char *child_type, const char *name, int indents,
std::ostream &(*dump)(const T &, const ApiDumpSettings &, int, Args... args), Args... args) {
if (array == NULL) {
settings.stream() << "<details class='data'><summary>";
dump_html_nametype(settings.stream(), settings.showType(), name, type_string);
settings.stream() << "<div class='val'>";
settings.stream() << "NULL</div></summary></details>";
return;
}
settings.stream() << "<details class='data'><summary>";
dump_html_nametype(settings.stream(), settings.showType(), name, type_string);
settings.stream() << "<div class='val'>";
if (settings.showAddress())
settings.stream() << (void *)array << "\n";
else
settings.stream() << "address\n";
settings.stream() << "</div></summary>";
for (size_t i = 0; i < len && array != NULL; ++i) {
std::stringstream stream;
stream << name << '[' << i << ']';
std::string indexName = stream.str();
dump_html_value(array[i], settings, child_type, indexName.c_str(), indents + 1, dump, args...);
}
settings.stream() << "</details>";
}
template <typename T, typename... Args>
inline void dump_html_pointer(const T *pointer, const ApiDumpSettings &settings, const char *type_string, const char *name,
int indents, std::ostream &(*dump)(const T, const ApiDumpSettings &, int, Args... args),
Args... args) {
if (pointer == NULL) {
settings.stream() << "<details class='data'><summary>";
dump_html_nametype(settings.stream(), settings.showType(), name, type_string);
settings.stream() << "<div class='val'>NULL</div></summary></details>";
} else {
dump_html_value(*pointer, settings, type_string, name, indents, dump, args...);
}
}
template <typename T, typename... Args>
inline void dump_html_pointer(const T *pointer, const ApiDumpSettings &settings, const char *type_string, const char *name,
int indents, std::ostream &(*dump)(const T &, const ApiDumpSettings &, int, Args... args),
Args... args) {
if (pointer == NULL) {
settings.stream() << "<details class='data'><summary>";
dump_html_nametype(settings.stream(), settings.showType(), name, type_string);
settings.stream() << "<div class='val'>NULL</div></summary></details>";
} else {
dump_html_value(*pointer, settings, type_string, name, indents, dump, args...);
}
}
template <typename T, typename... Args>
inline void dump_html_value(const T object, const ApiDumpSettings &settings, const char *type_string, const char *name, int indents,
std::ostream &(*dump)(const T, const ApiDumpSettings &, int, Args... args), Args... args) {
settings.stream() << "<details class='data'><summary>";
dump_html_nametype(settings.stream(), settings.showType(), name, type_string);
dump(object, settings, indents, args...);
settings.stream() << "</details>";
}
template <typename T, typename... Args>
inline void dump_html_value(const T &object, const ApiDumpSettings &settings, const char *type_string, const char *name,
int indents, std::ostream &(*dump)(const T &, const ApiDumpSettings &, int, Args... args),
Args... args) {
settings.stream() << "<details class='data'><summary>";
dump_html_nametype(settings.stream(), settings.showType(), name, type_string);
dump(object, settings, indents, args...);
settings.stream() << "</details>";
}
inline void dump_html_special(
const char *text, const ApiDumpSettings &settings, const char *type_string,
const char *name, int indents) {
settings.stream() << "<details class='data'><summary>";
dump_html_nametype(settings.stream(), settings.showType(), name, type_string);
settings.stream() << "<div class='val'>" << text << "</div></summary></details>";
}
inline bool dump_html_bitmaskOption(const std::string &option,
std::ostream &stream, bool isFirst) {
if (isFirst)
stream << " (";
else
stream << " | ";
stream << option;
return false;
}
inline std::ostream &dump_html_cstring(const char *object,
const ApiDumpSettings &settings,
int indents) {
settings.stream() << "<div class='val'>";
if (object == NULL)
settings.stream() << "NULL";
else
settings.stream() << "\"" << object << "\"";
return settings.stream() << "</div>";
}
inline std::ostream &dump_html_void(const void *object,
const ApiDumpSettings &settings,
int indents) {
settings.stream() << "<div class='val'>";
if (object == NULL)
settings.stream() << "NULL";
else if (settings.showAddress())
settings.stream() << object;
else
settings.stream() << "address";
return settings.stream() << "</div>";
}
inline std::ostream &dump_html_int(int object, const ApiDumpSettings &settings, int indents) {
settings.stream() << "<div class='val'>";
settings.stream() << object;
return settings.stream() << "</div>";
}