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

Implement basic SPIR-V reflection #418

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
39 changes: 15 additions & 24 deletions include/inexor/vulkan-renderer/wrapper/shader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <vulkan/vulkan_core.h>

#include <cstdint>
#include <string>
#include <vector>

Expand All @@ -13,33 +14,27 @@ class Device;
class Shader {
const Device &m_device;
std::string m_name;
std::string m_entry_point;
VkShaderStageFlagBits m_type;
VkShaderModule m_shader_module{VK_NULL_HANDLE};
VkShaderModule m_module{VK_NULL_HANDLE};

public:
/// @brief Construct a shader module from a block of SPIR-V memory.
/// @param device The const reference to a device RAII wrapper instance.
/// @param type The shader type.
/// @param name The internal debug marker name of the VkShaderModule.
/// @param code The memory block of the SPIR-V shader.
/// @param entry_point The name of the entry point, "main" by default.
Shader(const Device &m_device, VkShaderStageFlagBits type, const std::string &name, const std::vector<char> &code,
const std::string &entry_point = "main");
VkShaderStageFlagBits m_stage;

public:
/// @brief Construct a shader module from a SPIR-V file.
/// This constructor loads the file content and just calls the other constructor.
/// @param device The const reference to a device RAII wrapper instance.
/// @param type The shader type.
/// @param name The internal debug marker name of the VkShaderModule.
/// @param file_name The name of the SPIR-V shader file to load.
/// @param entry_point The name of the entry point, "main" by default.
Shader(const Device &m_device, VkShaderStageFlagBits type, const std::string &name, const std::string &file_name,
const std::string &entry_point = "main");
Shader(const Device &m_device, const std::string &name, const std::string &file_name);

/// @brief Construct a shader module from a block of SPIR-V memory.
/// @param device The const reference to a device RAII wrapper instance.
/// @param name The internal debug marker name of the VkShaderModule.
/// @param binary The memory block of the SPIR-V shader.
/// @param entry_point The name of the entry point, "main" by default.
Shader(const Device &m_device, const std::string &name, std::vector<char> &&binary);
Shader(const Shader &) = delete;
Shader(Shader &&) noexcept;

~Shader();

Shader &operator=(const Shader &) = delete;
Expand All @@ -49,16 +44,12 @@ class Shader {
return m_name;
}

[[nodiscard]] const std::string &entry_point() const {
return m_entry_point;
}

[[nodiscard]] VkShaderStageFlagBits type() const {
return m_type;
[[nodiscard]] VkShaderModule module() const {
return m_module;
}

[[nodiscard]] VkShaderModule module() const {
return m_shader_module;
[[nodiscard]] VkShaderStageFlagBits stage() const {
return m_stage;
}
};

Expand Down
5 changes: 2 additions & 3 deletions src/vulkan-renderer/application.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ void Application::load_shaders() {
spdlog::debug("Loading vertex shader file {}.", vertex_shader_file);

// Insert the new shader into the list of shaders.
m_shaders.emplace_back(*m_device, VK_SHADER_STAGE_VERTEX_BIT, "unnamed vertex shader", vertex_shader_file);
m_shaders.emplace_back(*m_device, "unnamed vertex shader", vertex_shader_file);
}

spdlog::debug("Loading fragment shaders.");
Expand All @@ -177,8 +177,7 @@ void Application::load_shaders() {
spdlog::debug("Loading fragment shader file {}.", fragment_shader_file);

// Insert the new shader into the list of shaders.
m_shaders.emplace_back(*m_device, VK_SHADER_STAGE_FRAGMENT_BIT, "unnamed fragment shader",
fragment_shader_file);
m_shaders.emplace_back(*m_device, "unnamed fragment shader", fragment_shader_file);
}

spdlog::debug("Loading shaders finished.");
Expand Down
6 changes: 2 additions & 4 deletions src/vulkan-renderer/imgui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,8 @@ ImGUIOverlay::ImGUIOverlay(const wrapper::Device &device, const wrapper::Swapcha
io.FontGlobalScale = m_scale;

spdlog::debug("Loading ImGUI shaders");
m_vertex_shader = std::make_unique<wrapper::Shader>(m_device, VK_SHADER_STAGE_VERTEX_BIT, "ImGUI vertex shader",
"shaders/ui.vert.spv");
m_fragment_shader = std::make_unique<wrapper::Shader>(m_device, VK_SHADER_STAGE_FRAGMENT_BIT,
"ImGUI fragment shader", "shaders/ui.frag.spv");
m_vertex_shader = std::make_unique<wrapper::Shader>(m_device, "ImGUI vertex shader", "shaders/ui.vert.spv");
m_fragment_shader = std::make_unique<wrapper::Shader>(m_device, "ImGUI fragment shader", "shaders/ui.frag.spv");

// Load font texture

Expand Down
4 changes: 2 additions & 2 deletions src/vulkan-renderer/render_graph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ void GraphicsStage::bind_buffer(const BufferResource *buffer, const std::uint32_
void GraphicsStage::uses_shader(const wrapper::Shader &shader) {
auto create_info = wrapper::make_info<VkPipelineShaderStageCreateInfo>();
create_info.module = shader.module();
create_info.stage = shader.type();
create_info.pName = shader.entry_point().c_str();
create_info.stage = shader.stage();
create_info.pName = "main";
m_shaders.push_back(create_info);
}

Expand Down
65 changes: 44 additions & 21 deletions src/vulkan-renderer/wrapper/shader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "inexor/vulkan-renderer/wrapper/make_info.hpp"

#include <spdlog/spdlog.h>
#include <spirv/unified1/spirv.h>

#include <cassert>
#include <fstream>
Expand All @@ -30,49 +31,71 @@ std::vector<char> read_binary(const std::string &file_name) {
return buffer;
}

VkShaderStageFlagBits shader_stage(SpvExecutionModel execution_model) {
switch (execution_model) {
case SpvExecutionModelVertex:
return VK_SHADER_STAGE_VERTEX_BIT;
case SpvExecutionModelFragment:
return VK_SHADER_STAGE_FRAGMENT_BIT;
case SpvExecutionModelGLCompute:
return VK_SHADER_STAGE_COMPUTE_BIT;
default:
assert(false);
}
}

} // namespace

namespace inexor::vulkan_renderer::wrapper {

Shader::Shader(const Device &device, const VkShaderStageFlagBits type, const std::string &name,
const std::string &file_name, const std::string &entry_point)
: Shader(device, type, name, read_binary(file_name), entry_point) {}
Shader::Shader(const Device &device, const std::string &name, const std::string &file_name)
: Shader(device, name, read_binary(file_name)) {}

Shader::Shader(const Device &device, const VkShaderStageFlagBits type, const std::string &name,
const std::vector<char> &code, const std::string &entry_point)
: m_device(device), m_type(type), m_name(name), m_entry_point(entry_point) {
assert(device.device());
Shader::Shader(const Device &device, const std::string &name, std::vector<char> &&binary)
: m_device(device), m_name(name) {
assert(!name.empty());
assert(!code.empty());
assert(!entry_point.empty());

auto shader_module_ci = make_info<VkShaderModuleCreateInfo>();
shader_module_ci.codeSize = code.size();
assert(!binary.empty());

// When you perform a cast like this, you also need to ensure that the data satisfies the alignment
// requirements of std::uint32_t. Lucky for us, the data is stored in an std::vector where the default
// allocator already ensures that the data satisfies the worst case alignment requirements.
shader_module_ci.pCode = reinterpret_cast<const std::uint32_t *>(code.data()); // NOLINT
const auto *code = reinterpret_cast<const std::uint32_t *>(binary.data()); // NOLINT
auto shader_module_ci = make_info<VkShaderModuleCreateInfo>();
shader_module_ci.codeSize = binary.size();
shader_module_ci.pCode = code;

spdlog::debug("Creating shader module {}.", name);
if (const auto result = vkCreateShaderModule(device.device(), &shader_module_ci, nullptr, &m_shader_module);
spdlog::debug("Creating shader module {}", name);
if (const auto result = vkCreateShaderModule(device.device(), &shader_module_ci, nullptr, &m_module);
result != VK_SUCCESS) {
throw VulkanException("Error: vkCreateShaderModule failed for shader " + name + "!", result);
}

// Assign an internal name using Vulkan debug markers.
m_device.set_debug_marker_name(m_shader_module, VK_DEBUG_REPORT_OBJECT_TYPE_SHADER_MODULE_EXT, name);
m_device.set_debug_marker_name(m_module, VK_DEBUG_REPORT_OBJECT_TYPE_SHADER_MODULE_EXT, name);

// Parse SPIR-V to extract the shader stage.
assert(code[0] == SpvMagicNumber);
const auto *inst = code + 5;
while (inst != code + (binary.size() / 4)) {
// Each instruction starts with a dword with the upper 16 bits holding the total number of words in the
// instruction and the lower 16 bits holding the opcode.
std::uint16_t opcode = (inst[0] >> 0u) & 0xffffu;
std::uint16_t word_count = (inst[0] >> 16u) & 0xffffu;
if (opcode == SpvOpEntryPoint) {
assert(word_count >= 2);
m_stage = shader_stage(static_cast<SpvExecutionModel>(inst[1]));
}
inst += word_count;
}
}

Shader::Shader(Shader &&other) noexcept : m_device(other.m_device) {
m_type = other.m_type;
Shader::Shader(Shader &&other) noexcept : m_device(other.m_device), m_stage(other.m_stage) {
m_name = std::move(other.m_name);
m_entry_point = std::move(other.m_entry_point);
m_shader_module = std::exchange(other.m_shader_module, nullptr);
m_module = std::exchange(other.m_module, nullptr);
}

Shader::~Shader() {
vkDestroyShaderModule(m_device.device(), m_shader_module, nullptr);
vkDestroyShaderModule(m_device.device(), m_module, nullptr);
}

} // namespace inexor::vulkan_renderer::wrapper