diff --git a/README.md b/README.md index 4c847cf..0848ba4 100644 --- a/README.md +++ b/README.md @@ -24,12 +24,18 @@ Please note that VkSpecGen is not an officially supported Google product. ## Examples Multiple examples are provided in `examples` directory. -The code will be generated in the root folder. You need `python3` to run the examples. +The code will be generated in the example folder. You need `python3` to run the examples. + You can pass the path to the Vulkan API registry file (`vk.xml`) using `-s` or `--spec`. If the path is not provided, the generator scripts expect to find the registry file in the root directory of the VkSpecGen. You can pass the target platform for the layer using `-p` or `--platform`. The default platform is the core platform. For the list of valid Vulkan platforms please see `` node in `vk.xml` file. +Please note that the examples are only provided for learning purposes, and they +may need improvements to be used in an application of interest. +Before incorporating the examples into your project, please make sure they correctly +satisfy your requirements. + ### Generate Flatbuffers Schema `examples/flatbuffer/vkflat.py` generates flatbuffers schema for Vulkan types in C++. @@ -48,6 +54,20 @@ for the build to be successful, you need to have the proper version of the Vulkan headers installed. The headers should match the version of the `vk.xml` that you used to generate the layer. +### Tracking Vulkan Commands + +`examples/command_tracker/vk_command_tracker.py` generates the code for +tracking Vulkan commands. This example is borrowed from the +[Graphics Flight Recorder](https://github.com/googlestadia/gfr) project. +GFR is a Vulkan layer to help trackdown and identify the cause of GPU hangs and +crashes. GFR uses VkSpecGen and Jinja2 templates to generate the layer boilerplate +code and track the Vulkan API of interest. The generated code includes command +tracking, command recording and command printing functions for the command buffer +related API, which is the Vulkan commands in which the first parameter is +a command buffer. You can find the respective filter defined as a Jinja2 macro +in `vulkan_macros.jinja2`. + + ## Test The tests currently require a real `vk.xml`, which makes them version-dependent. diff --git a/examples/command_tracker/linear_allocator.h b/examples/command_tracker/linear_allocator.h new file mode 100644 index 0000000..e3f435b --- /dev/null +++ b/examples/command_tracker/linear_allocator.h @@ -0,0 +1,133 @@ +/* + Copyright 2018 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. +*/ + +#ifndef LINEAR_ALLOCATOR_HEADER +#define LINEAR_ALLOCATOR_HEADER + +#include +#include +#include +#include +#include +#include +#include + +// +// LinearAllocator is a block based linear allocator for fast allocation of +// simple data structures. +// +// Unlike using a std::vector as a linear allocator the block based approach +// means that re-allocation will not occur when resizing the vector. This is +// needed so that pointers may be serialized internally from this allocator. +// +// Memory is managed in fixed sized blocks, when a block is full a new block +// will be created. The block will be the size of the larger of the requested +// allocation (+ alignment padding) and the default block size. +// +// Destructors will not be called when Reset. +// +template +class LinearAllocator { + static_assert(kAlignment > 0 && 0 == (kAlignment & (kAlignment - 1)), + "Power of 2 required"); + + public: + LinearAllocator() : active_block_(0) { + blocks_.push_back(std::make_unique(kDefaultBlockSize)); + } + + void* Alloc(const size_t size) { + assert(blocks_.size() > 0); + + // Try and alloc from the active block. + auto& block = blocks_[active_block_]; + auto alloc = block->Alloc(size); + if (alloc) { + return alloc; + } + + // The current block is full, try another. + do { + // No free blocks, allocate a new one. + if (active_block_ == blocks_.size() - 1) { + auto new_block_size = std::max(size + kAlignment, kDefaultBlockSize); + blocks_.push_back(std::make_unique(new_block_size)); + } + + active_block_++; + alloc = blocks_[active_block_]->Alloc(size); + } while (alloc == nullptr); + + assert(alloc); + return alloc; + } + + void Reset() { + for (auto& block : blocks_) { + block->Reset(); + } + + active_block_ = 0; + } + + size_t NumBlocksAllocated() const { return blocks_.size(); } + size_t GetDefaultBlockSize() const { return kDefaultBlockSize; } + + private: + class Block { + public: + Block(size_t blocksize) { + blocksize_ = blocksize; + std::set_new_handler(NewHandler); + data_ = new char[blocksize_]; + Reset(); + } + + ~Block() { delete[] data_; } + + void* Alloc(const size_t size) { + auto room = blocksize_ - (head_ - data_); + + uintptr_t h = (uintptr_t)head_; + + // Round up to alignment and check if we fit. + char* alloc = (char*)((h + kAlignment - 1) & ~(kAlignment - 1)); + if (alloc + size > data_ + blocksize_) { + return nullptr; + } + + head_ = alloc + size; + return alloc; + } + + void Reset() { head_ = data_; } + + private: + static void NewHandler() { + std::cerr << "Memory allocation failed!" << std::endl; + std::set_new_handler(nullptr); + }; + size_t blocksize_; + char* head_; + char* data_; + }; + + private: + int active_block_; + std::vector> blocks_; +}; + +#endif // LINEAR_ALLOCATOR_HEADER diff --git a/examples/command_tracker/templates/command_common.cc.jinja2 b/examples/command_tracker/templates/command_common.cc.jinja2 new file mode 100644 index 0000000..19b601d --- /dev/null +++ b/examples/command_tracker/templates/command_common.cc.jinja2 @@ -0,0 +1,28 @@ +{# +Parameters: +copyright_notice copyright notice +-#} +{{ copyright_notice }} +{% import 'vulkan_macros.jinja2' as vulkan -%} + +// NOLINTBEGIN +#include "command_common.h" + +// Returns the name of given command. This only cares for commands that have one +// parameter of type VkCommandBuffer. +const char *Command::GetCommandName(const Command &cmd) { + switch (cmd.type) { + default: + case Command::Type::kUnknown: return "Unknown"; +{%- for platform in registry.platforms.values() -%} +{% if platform.macro != '' -%}#ifdef {{ platform.macro }} {%- endif %} +{% for cmd_name, cmd in platform.commands.items() -%} +{% if vulkan.contained_in_groups(cmd, ['all_cb_commands']) %} + case Command::Type::k{{cmd_name[2:]}}: return "{{cmd_name}}"; +{%- endif %} +{%- endfor %} +{% if platform.macro != '' %}#endif // {{ platform.macro }}{% endif %} +{%- endfor %} + } +} +// NOLINTEND diff --git a/examples/command_tracker/templates/command_common.h.jinja2 b/examples/command_tracker/templates/command_common.h.jinja2 new file mode 100644 index 0000000..c665c25 --- /dev/null +++ b/examples/command_tracker/templates/command_common.h.jinja2 @@ -0,0 +1,59 @@ +{# +Parameters: +copyright_notice copyright notice +-#} +{{ copyright_notice }} +{% import 'vulkan_macros.jinja2' as vulkan -%} + +// NOLINTBEGIN +// clang-format off +#ifndef COMMAND_COMMON_H_ +#define COMMAND_COMMON_H_ + +#include +#include +#include +#include + +// Enumerate commands that have one parameter of type VkCommandBuffer. +struct Command +{ + enum Type { + kUnknown, +{%- filter indent(4) -%} +{% for platform in registry.platforms.values() -%} +{% if platform.macro != '' %}#ifdef {{ platform.macro }} {% endif %} +{% for cmd_name, cmd in platform.commands.items() -%} +{% if vulkan.contained_in_groups(cmd, ['all_cb_commands']) -%} + k{{cmd_name[2:]}}, +{% endif %} +{%- endfor %} +{% if platform.macro != '' %}#endif // {{ platform.macro }}{% endif %} +{%- endfor %} +{%- endfilter %} + }; + + public: + static const char *GetCommandName(const Command &cmd); + + Type type; + uint32_t id; + void *parameters; +}; + +// Define structs for command parameters +{% for platform in registry.platforms.values() -%} +{% if platform.macro != '' %}#ifdef {{ platform.macro }} {% endif %} +{% for cmd_name, cmd in platform.commands.items() -%} +{% if vulkan.contained_in_groups(cmd, ['all_cb_commands']) %} +struct {{cmd_name[2:]}}Args { +{% for p in cmd.parameters -%} + {{vulkan.full_writable_type_name(p)|indent(2, True)}}; +{% endfor %}}; +{% endif %} +{%- endfor %} +{% if platform.macro != '' %}#endif // {{ platform.macro }}{% endif %} +{%- endfor %} + +#endif // COMMAND_COMMON_H_ +// NOLINTEND diff --git a/examples/command_tracker/templates/command_printer.cc.jinja2 b/examples/command_tracker/templates/command_printer.cc.jinja2 new file mode 100644 index 0000000..a45bf27 --- /dev/null +++ b/examples/command_tracker/templates/command_printer.cc.jinja2 @@ -0,0 +1,260 @@ +{# +Parameters: +copyright_notice copyright notice +-#} +{{ copyright_notice }} +{% import 'vulkan_macros.jinja2' as vulkan -%} + +// NOLINTBEGIN +#include + +#include "command_common.h" +#include "command_printer.h" +#include "util.h" + +std::ostream & PrintNextPtr(std::ostream & os, const void *pNext) { + if (pNext == nullptr) { + os << "nullptr"; + return os; + } + + os << std::endl; + ScopedOstream sonextptr(os); + const VkStruct *pStruct = reinterpret_cast(pNext); + PrintVkStruct(os, pStruct); + return PrintNextPtr(os, pStruct->pNext); +} + +// Define enum to string functions. +{% for enum in enums -%} +const char *{{enum.name}}ToString({{enum.name}} e) { +switch (e) { + {% for v in enum.unique_values() -%} + case {{ v }}: return "{{ v }}"; + {% endfor -%} + default: return "Unknown {{ enum.name }}"; +} +} +{% endfor %} + +// Define ostream operators for enums. +{% for enum in enums -%} +std::ostream & operator << (std::ostream & os, const {{enum.name}} &t) { + os << {{enum.name}}ToString(t); + return os; +} +{% endfor %} + +{# Set #} +{% set custom_structure_printers = ['VkWriteDescriptorSet'] %} + +{%- macro array_length(p, obj) -%} + {% if isinstance(p.type, FixedArray) -%}{{p.type.length}} + {% else %} + {{ p.type.length_expr(obj) }} + {% endif %} +{%- endmacro -%} + +{# Macro to print the members of a Struct #} +{% macro print_member(p, obj=None) -%} + {%- set prefix -%} + {%- if obj -%} + {{obj}}. + {%- endif -%} + {%- endset -%} + { + os << "- # parameter:" << std::endl; + ScopedOstream sop(os); + os << "name: {{ p.name }}" << std::endl; + // {{ p.name }} -> {{ p.__class__.__name__ }} -> {{ p.type.name }} + {% if isinstance(p.type, FixedArray) or isinstance(p.type, DynamicArray)-%} + {%- if p.type.base_type.name == 'char' -%} + os << "value: " << {{prefix}}{{ p.name }} << std::endl; + {%- elif p.type.base_type.name == 'void' -%} + { + if ({{ array_length(p, obj) }} == 0) { + os << "value: nullptr" << std::endl; + } else { + os << std::hex; + os << "members:" << std::endl; + { + ScopedOstream soarray(os); + const uint8_t *p = (const uint8_t *){{prefix}}{{ p.name }}; + for (uint32_t i = 0; i < {{ array_length(p, obj) }}; ++i) { + os << "- 0x" << ((uint16_t)p[i]) << std::endl; + } + } + os << std::dec; + } + } + {%- else -%} + { + if ({{ array_length(p, obj) }} == 0) { + os << "value: nullptr" << std::endl; + } else { + os << "members:" << std::endl; + { + ScopedOstream soarray(os); + os << "- # {{ p.type.base_type.name }}" << std::endl; + {% if isinstance(p.type.base_type, Struct) -%} + for (uint32_t i = 0; i < {{ array_length(p, obj) }}; ++i) { + ScopedOstream somember(os); + os << "members:"<< std::endl << {{prefix}}{{ p.name }}[i] << std::endl; + } + {%- else -%} + for (uint32_t i = 0; i < {{ array_length(p, obj) }}; ++i) { + ScopedOstream somember(os); + os << "value: " << {{prefix}}{{ p.name }}[i] << std::endl; + } + {%- endif %} + } + } + } + {%- endif -%} + {% elif isinstance(p.type, NextPtr) %} + // pNext + os << "value: "; + PrintNextPtr(os, {{prefix}}{{p.name}}); + os << std::endl; + {% elif isinstance(p.type, Pointer) and p.type.base_type.name == 'void' %} + // void + os << "value: NOT_AVAILABLE" << std::endl; + {% elif isinstance(p.type, Pointer) -%} + // pointer + if ({{prefix}}{{p.name}}) { + {% if isinstance(p.type.base_type, Struct) -%} + os << "members:" << std::endl << *{{prefix}}{{p.name}} << std::endl; + {%- else -%} + os << "value: " << *{{prefix}}{{p.name}} << std::endl; + {%- endif %} + } else { + os << "value: nullptr" << std::endl; + } + {% else -%} + {% if isinstance(p.type, Struct) -%} + os << "members:" << std::endl << {{prefix}}{{p.name}} << std::endl; + {%- else -%} + os << "value: " << {{prefix}}{{p.name}} << std::endl; + {%- endif %} + {%- endif %} + } +{% endmacro -%} + +// Define all ostream operators. +{% for platform in registry.platforms.values() %} +{% if platform.macro != '' %}#ifdef {{ platform.macro }} {% endif %} +{% for type in platform_structs[platform.name] -%} +{% if type.name not in custom_structure_printers -%} + +std::ostream & operator << (std::ostream & os, const {{type.name}} &t) { + ScopedOstream sos(os); + {% for p in type.members -%} + {{ print_member(p, 't') }} + {%- endfor %} + return os; +} + +{% endif %} +{% endfor %} +{% if platform.macro != '' %}#endif // {{ platform.macro }}{% endif %} +{% endfor %} + +{# Custom printers #} +std::ostream &operator<<(std::ostream &os, const VkWriteDescriptorSet &t) { + ScopedOstream sos(os); + os << "sType: "; + os << t.sType << std::endl; + + os << "pNext: "; + // void + + os << "dstSet: "; + os << t.dstSet << std::endl; + + os << "dstBinding: "; + os << t.dstBinding << std::endl; + + os << "dstArrayElement: "; + os << t.dstArrayElement << std::endl; + + os << "descriptorCount: "; + os << t.descriptorCount << std::endl; + + os << "descriptorType: "; + os << t.descriptorType << std::endl; + + switch (t.descriptorType){ + case VK_DESCRIPTOR_TYPE_SAMPLER: + case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER: + case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE: + case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE: + os << "pImageInfo: "; + for (uint32_t i = 0; i < t.descriptorCount; ++i) { + os << t.pImageInfo[i] << std::endl; + } + break; + + case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER: + case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER: + os << "pTexelBufferView: "; + for (uint32_t i = 0; i < t.descriptorCount; ++i) { + os << t.pTexelBufferView[i] << std::endl; + } + break; + + case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER: + case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER: + case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC: + case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC: + case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT: + os << "pBufferInfo: "; + for (uint32_t i = 0; i < t.descriptorCount; ++i) { + os << t.pBufferInfo[i] << std::endl; + } + break; + + default: + os << "Unknown Descriptor Type: " << t.descriptorType << std::endl; + } + + return os; +} + +{# Generic struct printer for pNext chain printing #} +// Print out a VkStruct +std::ostream & PrintVkStruct(std::ostream & os, const VkStruct *pStruct) { + switch (pStruct->sType) { +{% for platform in registry.platforms.values() %} +{% if platform.macro != '' %}#ifdef {{ platform.macro }} {% endif %} +{% for type in platform_structs[platform.name] -%} + {%- if type.members|length > 0 and type.members[0].type.name == 'VkStructureType' -%} + {%- for stype in type.members[0].values -%} + case {{stype}}: + {%- endfor -%} + os << *reinterpret_cast(pStruct); break; + {%- endif -%} +{% endfor %} +{% if platform.macro != '' %}#endif // {{ platform.macro }}{% endif %} +{% endfor %} + default: break; + } + + return os; +} + +// Define Print functions. + +{% for platform in registry.platforms.values() %} +{% if platform.macro != '' %}#ifdef {{ platform.macro }} {% endif %} +{% for cmd_name, cmd in platform.commands.items() -%} +{% if vulkan.contained_in_groups(cmd, ['all_cb_commands']) %} +void CommandPrinter::Print{{cmd_name[2:]}}Args(std::ostream & os, const {{cmd_name[2:]}}Args &args){ + {% for p in cmd.parameters -%} + {{print_member(p, 'args')}} + {% endfor -%} +} +{% endif %} +{% endfor %} +{% if platform.macro != '' %}#endif // {{ platform.macro }}{% endif %} +{% endfor %} +// NOLINTEND diff --git a/examples/command_tracker/templates/command_printer.h.jinja2 b/examples/command_tracker/templates/command_printer.h.jinja2 new file mode 100644 index 0000000..93e15f3 --- /dev/null +++ b/examples/command_tracker/templates/command_printer.h.jinja2 @@ -0,0 +1,114 @@ +{# +Parameters: +copyright_notice copyright notice +-#} +{{ copyright_notice }} +{% import 'vulkan_macros.jinja2' as vulkan -%} + +// NOLINTBEGIN +// clang-format off +#ifndef COMMAND_PRINTER_H_ +#define COMMAND_PRINTER_H_ + +#include +#include + +#include "command_common.h" + +struct VkStruct { + VkStructureType sType; + void *pNext; +}; + +class ScopedOstream : public std::streambuf +{ +public: + explicit ScopedOstream(std::ostream &os, int indent = 4): + os_(&os), + sb_(os_->rdbuf()), + line_start_(true), + indent_(indent) + { + os_->rdbuf(this); + } + + virtual ~ScopedOstream() + { + os_->rdbuf(sb_); + } + +protected: + virtual int overflow(int ch) { + if (line_start_ && ch != '\n'){ + for (int i = 0; i < indent_; ++i) { + sb_->sputc(' '); + } + } + + line_start_ = '\n' == ch; + return sb_->sputc(ch); + } + +private: + std::ostream *os_; + std::streambuf *sb_; + bool line_start_; + int indent_; +}; + +// Declare generic struct printer. +std::ostream & PrintVkStruct(std::ostream & os, const VkStruct *pStruct); + +// Declare pNext chain printer. +std::ostream & PrintNextPtr(std::ostream & os, const void *pNext); + +// Declare enum to string functions. +{% for enum in enums -%} +const char *{{enum.name}}ToString({{enum.name}} e); +{% endfor %} + +// Declare ostream operators for enums. +{% for enum in enums -%} +std::ostream & operator << (std::ostream & os, const {{enum.name}} &t); +{% endfor %} + +{# Set #} +{% set custom_structure_printers = ['VkWriteDescriptorSet'] %} + +// Declare all ostream operators. +{% for platform in registry.platforms.values() %} +{% if platform.macro != '' %}#ifdef {{ platform.macro }} {% endif %} +{% for type in platform_structs[platform.name] -%} +{% if type.name not in custom_structure_printers -%} +std::ostream & operator << (std::ostream & os, const {{type.name}} &t); +{%- endif %} +{% endfor %} +{% if platform.macro != '' %}#endif // {{ platform.macro }}{% endif %} +{% endfor %} + +{#- Custom printers #} +{#- VkWriteDescriptorSet uses a custom print function due to how the descriptor + type interacts with the pImageInfo/pBufferInfo/pTexelBufferView arrays. + -#} +std::ostream &operator<<(std::ostream &os, const VkWriteDescriptorSet &t); + + +// Declare print functions. + +class CommandPrinter { + public: + void SetNameResolver(const ObjectInfoDB *name_resolver); + +{%- for platform in registry.platforms.values() %} +{% if platform.macro != '' %}#ifdef {{ platform.macro }} {% endif %} +{% for cmd_name, cmd in platform.commands.items() -%} +{% if vulkan.contained_in_groups(cmd, ['all_cb_commands']) %} + void Print{{cmd_name[2:]}}Args(std::ostream & os, const {{cmd_name[2:]}}Args &args); +{%- endif %} +{%- endfor %} +{% if platform.macro != '' %}#endif // {{ platform.macro }}{% endif %} +{% endfor %} +}; + +#endif // COMMAND_PRINTER_H_ +// NOLINTEND diff --git a/examples/command_tracker/templates/command_recorder.cc.jinja2 b/examples/command_tracker/templates/command_recorder.cc.jinja2 new file mode 100644 index 0000000..da41709 --- /dev/null +++ b/examples/command_tracker/templates/command_recorder.cc.jinja2 @@ -0,0 +1,103 @@ +{# +Parameters: +copyright_notice copyright notice +-#} +{{ copyright_notice }} +{% import 'vulkan_macros.jinja2' as vulkan -%} + +// NOLINTBEGIN +// clang-format off +#include +#include + +#include "command_common.h" +#include "command_recorder.h" + +{%- macro copy_type_member(p) -%} + {%- if p.type.name == 'string' -%} + ptr[i].{{ p.name }} = nullptr; + if (src[start_index + i].{{ p.name }}) { + ptr[i].{{ p.name }} = CopyArray<>(src[start_index + i].{{ p.name }}, 0, strlen(src[start_index + i].{{ p.name }})+1); + } + {%- elif isinstance(p.type, DynamicArray) or isinstance(p.type, Pointer)-%} + {%- if p.type.base_type.name == 'void' -%} + ptr[i].{{p.name}} = src[start_index + i].{{p.name}}; + {%- else -%} + ptr[i].{{p.name}} = nullptr; + if (src[start_index + i].{{p.name}}) { + ptr[i].{{p.name}} = CopyArray<>(src[start_index + i].{{p.name}}, 0, 1); + } + {%- endif -%} + {%- elif isinstance(p.type, FixedArray)-%} + std::memcpy(ptr[i].{{p.name}}, src[start_index + i].{{p.name}}, sizeof(src[start_index + i].{{p.name}})); + {%- else -%} + ptr[i].{{p.name}} = src[start_index + i].{{p.name}}; + {%- endif %} +{%- endmacro %} + +// Declare CopyArray template functions. We need this declaration since the +// templates call each other and we don't have control over the order of the +// definitions. +{% for platform in registry.platforms.values() %} +{% if platform.macro != '' %}#ifdef {{ platform.macro }} {% endif %} +{% for type in platform_structs[platform.name] %} +template<> +{{type.name}} *CommandRecorder::CopyArray<{{type.name}}>(const {{type.name}} *src, uint64_t start_index, uint64_t count); +{% endfor %} +{% if platform.macro != '' %}#endif // {{ platform.macro }}{% endif %} +{% endfor %} + +// Define CopyArray template functions. +{% for platform in registry.platforms.values() %} +{% if platform.macro != '' %}#ifdef {{ platform.macro }} {% endif %} +{% for type in platform_structs[platform.name] %} +template<> +{{type.name}} *CommandRecorder::CopyArray<{{type.name}}>(const {{type.name}} *src, uint64_t start_index, uint64_t count) { + auto ptr = reinterpret_cast<{{type.name}} *>(m_allocator.Alloc(sizeof({{type.name}}) * count)); + for (uint64_t i = 0; i < count; ++i) { + {%- if isinstance(type, Struct) and type.is_union -%} + {{ copy_type_member(type.members[0]) }} + {%- else -%} + {%- for p in type.members %} + {{ copy_type_member(p) }} + {%- endfor %} + {%- endif %} + } + return ptr; +} +{% endfor %} +{% if platform.macro != '' %}#endif // {{ platform.macro }}{% endif %} +{% endfor %} + +// Define arguments recorder functions. +{% for platform in registry.platforms.values() -%} +{% if platform.macro != '' %}#ifdef {{ platform.macro }} {% endif %} +{% for cmd_name, cmd in platform.commands.items() -%} +{% if vulkan.contained_in_groups(cmd, ['all_cb_commands']) %} +{{cmd_name[2:]}}Args *CommandRecorder::Record{{cmd_name[2:]}}({{vulkan.command_parameters_prototype(cmd)}}) { + auto *args = Alloc<{{cmd_name[2:]}}Args>(); + {% for p in cmd.parameters -%} + {% if isinstance(p.type, FixedArray) -%} + for (uint32_t i = 0; i < {{ p.type.length }}; ++i) { + args->{{p.name}}[i] = {{p.name}}[i]; + } + {% else -%} + args->{{p.name}} = {{p.name}}; + {% if isinstance(p.type, Pointer) and p.type.base_type.name != 'void' -%} + if ({{p.name}}) { + args->{{p.name}} = CopyArray({{p.name}}, static_cast(0U), static_cast(1U)); + } + {% elif isinstance(p.type, DynamicArray) and p.type.base_type.name != 'void' -%} + if ({{p.name}}) { + args->{{p.name}} = CopyArray({{p.name}}, static_cast(0U), static_cast({{ p.type.length_expr() }})); + } + {% endif %} + {%- endif %} + {%- endfor %} + return args; +} +{% endif %} +{%- endfor %} +{% if platform.macro != '' %}#endif // {{ platform.macro }}{% endif %} +{%- endfor %} +// NOLINTEND diff --git a/examples/command_tracker/templates/command_recorder.h.jinja2 b/examples/command_tracker/templates/command_recorder.h.jinja2 new file mode 100644 index 0000000..cfeb78f --- /dev/null +++ b/examples/command_tracker/templates/command_recorder.h.jinja2 @@ -0,0 +1,48 @@ +{# +Parameters: +copyright_notice copyright notice +-#} +{{ copyright_notice }} +{% import 'vulkan_macros.jinja2' as vulkan -%} + +// NOLINTBEGIN +// clang-format off +#ifndef COMMAND_RECORDER_H_ +#define COMMAND_RECORDER_H_ + +#include +#include +#include +#include + +#include "linear_allocator.h" +#include "command_common.h" + +class CommandRecorder +{ + public: + void Reset() { m_allocator.Reset(); } + +{% for platform in registry.platforms.values() -%} +{% if platform.macro != '' %}#ifdef {{ platform.macro }} {% endif %} +{% for cmd_name, cmd in platform.commands.items() -%} +{% if vulkan.contained_in_groups(cmd, ['all_cb_commands']) -%} + {{cmd_name[2:]}}Args *Record{{cmd_name[2:]}}({{vulkan.command_parameters_prototype(cmd)}}); +{% endif %} +{%- endfor %} +{% if platform.macro != '' %}#endif // {{ platform.macro }}{% endif %} +{%- endfor %} + + private: + template T *Alloc() { return new(m_allocator.Alloc(sizeof(T))) T; } + template T *CopyArray(const T *src, uint64_t start_index, uint64_t count) { + auto ptr = reinterpret_cast(m_allocator.Alloc(sizeof(T) * count)); + std::memcpy(ptr, src, sizeof(T) * count); + return ptr; + } + + LinearAllocator<> m_allocator; +}; + +#endif // COMMAND_RECORDER_H_ +// NOLINTEND diff --git a/examples/command_tracker/templates/command_tracker.cc.jinja2 b/examples/command_tracker/templates/command_tracker.cc.jinja2 new file mode 100644 index 0000000..1b4361e --- /dev/null +++ b/examples/command_tracker/templates/command_tracker.cc.jinja2 @@ -0,0 +1,74 @@ +{# +Parameters: +copyright_notice copyright notice +-#} +{{ copyright_notice }} +{% import 'vulkan_macros.jinja2' as vulkan -%} + +// NOLINTBEGIN +// clang-format off +#include +#include + +#include "command_common.h" +#include "command_printer.h" +#include "command_tracker.h" + +void CommandTracker::Reset() +{ + commands_.clear(); + recorder_.Reset(); +} + +void CommandTracker::SetNameResolver(const ObjectInfoDB *name_resolver) +{ + printer_.SetNameResolver(name_resolver); +} + +void CommandTracker::PrintCommandParameters(std::ostream &os, const Command &cmd, uint32_t indentation) +{ + ScopedOstream sos(os, indentation); + switch (cmd.type) + { + case Command::Type::kUnknown: + os << ""; + break; +{% for platform in registry.platforms.values() -%} +{% if platform.macro != '' %}#ifdef {{ platform.macro }} {% endif %} +{% for cmd_name, cmd in platform.commands.items() -%} +{% if vulkan.contained_in_groups(cmd, ['all_cb_commands']) %} + case Command::Type::k{{cmd_name[2:]}}: + if (cmd.parameters) { + auto args = reinterpret_cast<{{cmd_name[2:]}}Args *>(cmd.parameters); + printer_.Print{{cmd_name[2:]}}Args(os, *args); + } + break; +{% endif %} +{%- endfor %} +{% if platform.macro != '' %}#endif // {{ platform.macro }}{% endif %} +{%- endfor %} + }; +} + +{% for platform in registry.platforms.values() -%} +{% if platform.macro != '' %}#ifdef {{ platform.macro }} {% endif %} +{% for cmd_name, cmd in platform.commands.items() -%} +{% if vulkan.contained_in_groups(cmd, ['all_cb_commands']) %} +void CommandTracker::TrackPre{{cmd_name[2:]}}({{vulkan.command_parameters_prototype(cmd)}}) +{ + Command cmd; + cmd.type = Command::Type::k{{cmd_name[2:]}}; + cmd.id = static_cast(commands_.size()) + 1; + cmd.parameters = recorder_.Record{{cmd_name[2:]}}({{vulkan.command_parameters(cmd)}}); + commands_.push_back(cmd); +} + +void CommandTracker::TrackPost{{cmd_name[2:]}}({{vulkan.command_parameters_prototype(cmd)}}) +{ + assert(commands_.back().type == Command::Type::k{{cmd_name[2:]}}); +} +{% endif %} +{%- endfor %} +{% if platform.macro != '' %}#endif // {{ platform.macro }}{% endif %} +{%- endfor %} +// NOLINTEND diff --git a/examples/command_tracker/templates/command_tracker.h.jinja2 b/examples/command_tracker/templates/command_tracker.h.jinja2 new file mode 100644 index 0000000..7d829e7 --- /dev/null +++ b/examples/command_tracker/templates/command_tracker.h.jinja2 @@ -0,0 +1,48 @@ +{# +Parameters: +copyright_notice copyright notice +-#} +{{ copyright_notice }} +{% import 'vulkan_macros.jinja2' as vulkan -%} + +// NOLINTBEGIN +// clang-format off +#ifndef COMMAND_TRACKER_H_ +#define COMMAND_TRACKER_H_ + +#include +#include +#include + +#include "command_common.h" +#include "command_printer.h" +#include "command_recorder.h" + +class CommandTracker +{ + public: + void Reset(); + void SetNameResolver(const ObjectInfoDB *name_resolver); + void PrintCommandParameters(std::ostream &os, const Command &cmd, uint32_t indentation); + + const std::vector &GetCommands() const { return commands_; } + std::vector &GetCommands() { return commands_; } + +{%- for platform in registry.platforms.values() -%} +{% if platform.macro != '' %}#ifdef {{ platform.macro }} {% endif %} +{% for cmd_name, cmd in platform.commands.items() -%} +{% if vulkan.contained_in_groups(cmd, ['all_cb_commands'])%} + void TrackPre{{cmd_name[2:]}}({{vulkan.command_parameters_prototype(cmd)}}); + void TrackPost{{cmd_name[2:]}}({{vulkan.command_parameters_prototype(cmd)}}); +{% endif %} +{%- endfor %} +{% if platform.macro != '' %}#endif // {{ platform.macro }}{% endif %} +{%- endfor %} + + private: + std::vector commands_; + CommandPrinter printer_; + CommandRecorder recorder_; +}; +#endif // COMMAND_TRACKER_H_ +// NOLINTEND diff --git a/examples/command_tracker/templates/vulkan_macros.jinja2 b/examples/command_tracker/templates/vulkan_macros.jinja2 new file mode 100644 index 0000000..bf547fc --- /dev/null +++ b/examples/command_tracker/templates/vulkan_macros.jinja2 @@ -0,0 +1,72 @@ +{#- Shared macros for working with Vulkan layers -#} + +{# Handle type modifiers #} +{%- macro cpp_type(p) -%} + {%- if isinstance(p, Pointer) -%}{{ cpp_type(p.base_type) }}* + {%- elif isinstance(p, FixedArray) -%}{{ cpp_type(p.base_type) }} + {%- elif isinstance(p, DynamicArray) -%}{{ cpp_type(p.base_type) }}* + {%- else -%} + {%- if 'string' == p.name -%}char * + {%- else -%} {{ p.name }} + {%- endif -%} + {%- endif -%} +{%- endmacro -%} + +{# Returns the fully qualified C type name of member/parameter #} +{%- macro cpp_field_type(p) -%} + {%- if p.type.is_const -%}const {%endif-%} + {{ cpp_type(p.type) }} +{%- endmacro -%} + +{# Returns post-name attributes for parameters/members #} +{%- macro cpp_field_type_post(p) -%} + {%- if isinstance(p.type, FixedArray) -%}[{{p.type.length}}] + {%- endif -%} +{%- endmacro -%} + +{# Returns the fully qualified C type and name of member/parameter #} +{%- macro full_type_name(p) -%} + {{ cpp_field_type(p) }} {{p.name}}{{cpp_field_type_post(p)}} +{%- endmacro -%} + +{# Returns the fully qualified C type and name of member/parameter, + except for fixed size array where const qualifier is dropped so the + array can be filled later. #} +{%- macro full_writable_type_name(p) -%} + {%- if p.type.is_const and not isinstance(p.type, FixedArray) -%}const {%endif-%} + {{ cpp_type(p.type) }} {{p.name}}{{cpp_field_type_post(p)}} +{%- endmacro -%} + +{# Returns the commands parameters as the function prototype #} +{%- macro command_parameters_prototype(cmd) -%} +{% for p in cmd.parameters -%}{{full_type_name(p)}}{% if not loop.last %}, {% endif %}{% endfor %} +{%- endmacro -%} + +{# Returns the commands parameters #} +{%- macro command_parameters(cmd) -%} +{% for p in cmd.parameters -%}{{p.name}}{% if not loop.last %}, {% endif %}{% endfor %} +{%- endmacro -%} + +{# Returns true if the command matches any of the predefined special intercept + groups. Current special intercept groups are: + - all_vulkan_commands + - all_cb_commands: all commands that work on a command buffer, including + begin, reset, end and all vkCmd commands. + - all_vk_cmds +#} +{%- macro contained_in_groups(cmd, groups) -%} +{% if 'all_vulkan_commands' in groups %} + {{ True }} +{% elif 'all_cb_commands' in groups and cmd.parameters|length > 0 and cmd.parameters[0].type.name=='VkCommandBuffer' %} + {{ True }} +{% elif 'all_vk_cmds' in groups and cmd.name[:5] == 'vkCmd' %} + {{ True }} +{% endif %} +{%- endmacro -%} + +{%- macro get_default_return_value(return_type) -%} +{%- if 'VkResult' == return_type -%} VK_SUCCESS +{%- elif 'PFN_vkVoidFunction' == return_type -%} nullptr +{%- else -%} 0 +{%- endif -%} +{%- endmacro -%} diff --git a/examples/command_tracker/vk_command_tracker.py b/examples/command_tracker/vk_command_tracker.py new file mode 100644 index 0000000..0cff32a --- /dev/null +++ b/examples/command_tracker/vk_command_tracker.py @@ -0,0 +1,133 @@ +# Copyright (C) 2021 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. + +# vklayerprint is a sample generator that creates a Vulkan layer +# that will print out Vulkan command parmeters to console. + +# This is meant to be an example of how to use the vkcodgen registry with +# a variety of different template techniques. + +import jinja2 +import argparse +import os +import sys + +currentdir = os.path.dirname(os.path.realpath(__file__)) +vkspecgendir = os.path.dirname(os.path.dirname(currentdir)) +sys.path.append(vkspecgendir) + +import vkapi # noqa + + +def visit_type(t: vkapi.TypeModifier, visited: dict): + if t.name not in visited: + if isinstance(t, vkapi.Struct): + for p in t.members: + visit_type(p.type, visited) + visited[t.name] = t + elif isinstance(t, vkapi.Pointer) or isinstance( + t, vkapi.DynamicArray) or isinstance(t, vkapi.FixedArray): + visit_type(t.base_type, visited) + else: + visited[t.name] = t + + +# Return the set of types referenced by a command list. +def referenced_types(commands: list): + visited = {} + for cmd in commands: + for p in cmd.parameters: + visit_type(p.type, visited) + + return visited + + +# Main routine - setup Jinja and +parser = argparse.ArgumentParser() +parser.add_argument("-s", "--spec", help="path to the vk.xml spec file") +parser.add_argument("-p", + "--platform", + help="target platforms for the layer[win32, xcb, ggp, ...]", + nargs='+') + +args = parser.parse_args() + +specfile = args.spec +if specfile is None: + specfile = 'vk.xml' + +if not os.path.exists(specfile): + print("Vulkan spec '" + os.path.abspath(specfile) + "' not found.") + exit(-1) + +print("Loading Vulkan spec '" + os.path.abspath(specfile) + "'.") + +# Handle multiple platforms but always handle core. +platforms = [''] +if args.platform is not None: + platforms.extend(args.platform) + +print(f'Platforms: {platforms}') +registry = vkapi.Registry(specfile, platforms=platforms) + +# Figure out which types we need to understand based on our command set. +types = referenced_types(registry.commands.values()) + +# Create a Jinja2 templating environment +env = vkapi.JinjaEnvironment( + registry, + loader=jinja2.FileSystemLoader(searchpath=f'{currentdir}/templates')) + +platform_structs = { + k: [t for t in v.types.values() if isinstance(t, vkapi.Struct) + ] for (k, v) in registry.platforms.items() +} + +parameters = { + 'enums': [t for t in types.values() if isinstance(t, vkapi.Enum)], + 'platform_structs': platform_structs, + 'copyright_notice': """/* + * Copyright (C) 2021 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. + * + * THIS FILE IS GENERATED BY VkSpecGen. DO NOT EDIT. + */ + """ +} + +generated_dir = f'{currentdir}/generated' +os.makedirs(generated_dir, exist_ok=True) + +templates_dir = f'{currentdir}/templates' +helper_templates = ['vulkan_macros.jinja2'] +for file in os.listdir(templates_dir): + filename = os.fsdecode(file) + if filename.endswith('.jinja2') and filename not in helper_templates: + output_name = filename.removesuffix('.jinja2') + tmp = env.from_string(open(f'{templates_dir}/{filename}').read()) + out = tmp.render(parameters) + with open(f'{generated_dir}/{output_name}', 'w') as text_file: + text_file.write(out) + os.system(f'clang-format -i -style=google {generated_dir}/{output_name}') diff --git a/examples/print_layer/vklayerprint.py b/examples/print_layer/vklayerprint.py index d569322..0471f6f 100644 --- a/examples/print_layer/vklayerprint.py +++ b/examples/print_layer/vklayerprint.py @@ -101,8 +101,9 @@ def referenced_types(commands: list): } generated_dir = f'{currentdir}/generated' -templates_dir = f'{currentdir}/templates' os.makedirs(generated_dir, exist_ok=True) + +templates_dir = f'{currentdir}/templates' tmp = env.from_string(open(f'{templates_dir}/layerprint.cc.jinja2').read()) out = tmp.render(parameters) with open(f'{generated_dir}/print.cc', 'w') as text_file: