Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add command tracking example #10

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<platforms>` 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++.
Expand All @@ -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.
Expand Down
133 changes: 133 additions & 0 deletions examples/command_tracker/linear_allocator.h
Original file line number Diff line number Diff line change
@@ -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 <array>
#include <cassert>
#include <cstddef>
#include <iostream>
#include <memory>
#include <new>
#include <vector>

//
// 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 <size_t kDefaultBlockSize = 1024 * 32, size_t kAlignment = 8>
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<Block>(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<Block>(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<std::unique_ptr<Block>> blocks_;
};

#endif // LINEAR_ALLOCATOR_HEADER
28 changes: 28 additions & 0 deletions examples/command_tracker/templates/command_common.cc.jinja2
Original file line number Diff line number Diff line change
@@ -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
59 changes: 59 additions & 0 deletions examples/command_tracker/templates/command_common.h.jinja2
Original file line number Diff line number Diff line change
@@ -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 <cstring>
#include <iostream>
#include <vector>
#include <vulkan/vulkan.h>

// 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
Loading