Files
Boundard/src/render.cpp
T

1577 lines
71 KiB
C++

#include <render.hpp>
#include <camera.hpp>
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <png.h>
#include <stdexcept>
#include <unordered_map>
#include <vector>
namespace boundard::render {
namespace {
constexpr std::array<uint32_t, 377> kQuadVertShader = {
0x07230203,0x00010000,0x000d000b,0x0000002e,0x00000000,0x00020011,0x00000001,0x0006000b,0x00000001,0x4c534c47,0x6474732e,0x3035342e,
0x00000000,0x0003000e,0x00000000,0x00000001,0x0009000f,0x00000000,0x00000004,0x6e69616d,0x00000000,0x0000000d,0x00000019,0x00000023,
0x00000025,0x00030003,0x00000002,0x000001c2,0x000a0004,0x475f4c47,0x4c474f4f,0x70635f45,0x74735f70,0x5f656c79,0x656e696c,0x7269645f,
0x69746365,0x00006576,0x00080004,0x475f4c47,0x4c474f4f,0x6e695f45,0x64756c63,0x69645f65,0x74636572,0x00657669,0x00040005,0x00000004,
0x6e69616d,0x00000000,0x00060005,0x0000000b,0x505f6c67,0x65567265,0x78657472,0x00000000,0x00060006,0x0000000b,0x00000000,0x505f6c67,
0x7469736f,0x006e6f69,0x00070006,0x0000000b,0x00000001,0x505f6c67,0x746e696f,0x657a6953,0x00000000,0x00070006,0x0000000b,0x00000002,
0x435f6c67,0x4470696c,0x61747369,0x0065636e,0x00070006,0x0000000b,0x00000003,0x435f6c67,0x446c6c75,0x61747369,0x0065636e,0x00030005,
0x0000000d,0x00000000,0x00030005,0x00000011,0x004f4255,0x00050006,0x00000011,0x00000000,0x65646f6d,0x0000006c,0x00030005,0x00000013,
0x006f6275,0x00040005,0x00000019,0x736f5061,0x00000000,0x00040005,0x00000023,0x6c6f4376,0x0000726f,0x00040005,0x00000025,0x6c6f4361,
0x0000726f,0x00030005,0x00000027,0x00004350,0x00050006,0x00000027,0x00000000,0x6f6c6f63,0x00000072,0x00030005,0x00000029,0x00006370,
0x00030047,0x0000000b,0x00000002,0x00050048,0x0000000b,0x00000000,0x0000000b,0x00000000,0x00050048,0x0000000b,0x00000001,0x0000000b,
0x00000001,0x00050048,0x0000000b,0x00000002,0x0000000b,0x00000003,0x00050048,0x0000000b,0x00000003,0x0000000b,0x00000004,0x00030047,
0x00000011,0x00000002,0x00040048,0x00000011,0x00000000,0x00000005,0x00050048,0x00000011,0x00000000,0x00000007,0x00000010,0x00050048,
0x00000011,0x00000000,0x00000023,0x00000000,0x00040047,0x00000013,0x00000021,0x00000000,0x00040047,0x00000013,0x00000022,0x00000000,
0x00040047,0x00000019,0x0000001e,0x00000000,0x00040047,0x00000023,0x0000001e,0x00000000,0x00040047,0x00000025,0x0000001e,0x00000001,
0x00030047,0x00000027,0x00000002,0x00050048,0x00000027,0x00000000,0x00000023,0x00000000,0x00020013,0x00000002,0x00030021,0x00000003,
0x00000002,0x00030016,0x00000006,0x00000020,0x00040017,0x00000007,0x00000006,0x00000004,0x00040015,0x00000008,0x00000020,0x00000000,
0x0004002b,0x00000008,0x00000009,0x00000001,0x0004001c,0x0000000a,0x00000006,0x00000009,0x0006001e,0x0000000b,0x00000007,0x00000006,
0x0000000a,0x0000000a,0x00040020,0x0000000c,0x00000003,0x0000000b,0x0004003b,0x0000000c,0x0000000d,0x00000003,0x00040015,0x0000000e,
0x00000020,0x00000001,0x0004002b,0x0000000e,0x0000000f,0x00000000,0x00040018,0x00000010,0x00000007,0x00000004,0x0003001e,0x00000011,
0x00000010,0x00040020,0x00000012,0x00000002,0x00000011,0x0004003b,0x00000012,0x00000013,0x00000002,0x00040020,0x00000014,0x00000002,
0x00000010,0x00040017,0x00000017,0x00000006,0x00000002,0x00040020,0x00000018,0x00000001,0x00000017,0x0004003b,0x00000018,0x00000019,
0x00000001,0x0004002b,0x00000006,0x0000001b,0x00000000,0x0004002b,0x00000006,0x0000001c,0x3f800000,0x00040020,0x00000021,0x00000003,
0x00000007,0x0004003b,0x00000021,0x00000023,0x00000003,0x00040020,0x00000024,0x00000001,0x00000007,0x0004003b,0x00000024,0x00000025,
0x00000001,0x0003001e,0x00000027,0x00000007,0x00040020,0x00000028,0x00000009,0x00000027,0x0004003b,0x00000028,0x00000029,0x00000009,
0x00040020,0x0000002a,0x00000009,0x00000007,0x00050036,0x00000002,0x00000004,0x00000000,0x00000003,0x000200f8,0x00000005,0x00050041,
0x00000014,0x00000015,0x00000013,0x0000000f,0x0004003d,0x00000010,0x00000016,0x00000015,0x0004003d,0x00000017,0x0000001a,0x00000019,
0x00050051,0x00000006,0x0000001d,0x0000001a,0x00000000,0x00050051,0x00000006,0x0000001e,0x0000001a,0x00000001,0x00070050,0x00000007,
0x0000001f,0x0000001d,0x0000001e,0x0000001b,0x0000001c,0x00050091,0x00000007,0x00000020,0x00000016,0x0000001f,0x00050041,0x00000021,
0x00000022,0x0000000d,0x0000000f,0x0003003e,0x00000022,0x00000020,0x0004003d,0x00000007,0x00000026,0x00000025,0x00050041,0x0000002a,
0x0000002b,0x00000029,0x0000000f,0x0004003d,0x00000007,0x0000002c,0x0000002b,0x00050085,0x00000007,0x0000002d,0x00000026,0x0000002c,
0x0003003e,0x00000023,0x0000002d,0x000100fd,0x00010038,
};
constexpr std::array<uint32_t, 112> kQuadFragShader = {
0x07230203,0x00010000,0x000d000b,0x0000000d,0x00000000,0x00020011,0x00000001,0x0006000b,0x00000001,0x4c534c47,0x6474732e,0x3035342e,
0x00000000,0x0003000e,0x00000000,0x00000001,0x0007000f,0x00000004,0x00000004,0x6e69616d,0x00000000,0x00000009,0x0000000b,0x00030010,
0x00000004,0x00000007,0x00030003,0x00000002,0x000001c2,0x000a0004,0x475f4c47,0x4c474f4f,0x70635f45,0x74735f70,0x5f656c79,0x656e696c,
0x7269645f,0x69746365,0x00006576,0x00080004,0x475f4c47,0x4c474f4f,0x6e695f45,0x64756c63,0x69645f65,0x74636572,0x00657669,0x00040005,
0x00000004,0x6e69616d,0x00000000,0x00050005,0x00000009,0x4374756f,0x726f6c6f,0x00000000,0x00040005,0x0000000b,0x6c6f4376,0x0000726f,
0x00040047,0x00000009,0x0000001e,0x00000000,0x00040047,0x0000000b,0x0000001e,0x00000000,0x00020013,0x00000002,0x00030021,0x00000003,
0x00000002,0x00030016,0x00000006,0x00000020,0x00040017,0x00000007,0x00000006,0x00000004,0x00040020,0x00000008,0x00000003,0x00000007,
0x0004003b,0x00000008,0x00000009,0x00000003,0x00040020,0x0000000a,0x00000001,0x00000007,0x0004003b,0x0000000a,0x0000000b,0x00000001,
0x00050036,0x00000002,0x00000004,0x00000000,0x00000003,0x000200f8,0x00000005,0x0004003d,0x00000007,0x0000000c,0x0000000b,0x0003003e,
0x00000009,0x0000000c,0x000100fd,0x00010038,
};
struct FrameData {
VkFence in_flight = VK_NULL_HANDLE;
VkSemaphore image_available = VK_NULL_HANDLE;
VkSemaphore render_finished = VK_NULL_HANDLE;
VkCommandBuffer command_buffer = VK_NULL_HANDLE;
};
void check(VkResult result, const char* what) {
if (result != VK_SUCCESS) {
throw std::runtime_error(what);
}
}
#ifdef VK_EXT_debug_utils
VKAPI_ATTR VkBool32 VKAPI_CALL debug_callback(
VkDebugUtilsMessageSeverityFlagBitsEXT severity, VkDebugUtilsMessageTypeFlagsEXT type,
const VkDebugUtilsMessengerCallbackDataEXT* callback_data, void*) {
if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
std::fprintf(stderr, "VK ERROR: %s\n", callback_data->pMessage);
}
return VK_FALSE;
}
#endif
uint32_t find_memory_type(VkPhysicalDevice physical_device, uint32_t type_filter,
VkMemoryPropertyFlags properties) {
VkPhysicalDeviceMemoryProperties memory_properties{};
vkGetPhysicalDeviceMemoryProperties(physical_device, &memory_properties);
for (uint32_t i = 0; i < memory_properties.memoryTypeCount; ++i) {
if ((type_filter & (1u << i)) &&
(memory_properties.memoryTypes[i].propertyFlags & properties) == properties) {
return i;
}
}
throw std::runtime_error("failed to find suitable Vulkan memory type");
}
void create_buffer(VkPhysicalDevice physical_device, VkDevice device, VkDeviceSize size,
VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer,
VkDeviceMemory& memory) {
VkBufferCreateInfo buffer_info{};
buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
buffer_info.size = size;
buffer_info.usage = usage;
buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
check(vkCreateBuffer(device, &buffer_info, nullptr, &buffer), "vkCreateBuffer");
VkMemoryRequirements requirements{};
vkGetBufferMemoryRequirements(device, buffer, &requirements);
VkMemoryAllocateInfo alloc_info{};
alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
alloc_info.allocationSize = requirements.size;
alloc_info.memoryTypeIndex =
find_memory_type(physical_device, requirements.memoryTypeBits, properties);
check(vkAllocateMemory(device, &alloc_info, nullptr, &memory), "vkAllocateMemory");
check(vkBindBufferMemory(device, buffer, memory, 0), "vkBindBufferMemory");
}
VkShaderModule create_shader_module(VkDevice device, const uint32_t* code, size_t size) {
VkShaderModuleCreateInfo create_info{};
create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
create_info.codeSize = size;
create_info.pCode = code;
VkShaderModule module = VK_NULL_HANDLE;
check(vkCreateShaderModule(device, &create_info, nullptr, &module), "vkCreateShaderModule");
return module;
}
std::vector<uint32_t> load_shader(const char* name) {
const std::string path = std::string(BOUNDARD_SHADER_DIR) + "/" + name;
std::ifstream file(path, std::ios::binary | std::ios::ate);
if (!file.is_open()) {
throw std::runtime_error("failed to open shader: " + path);
}
const std::streamsize size = file.tellg();
if (size <= 0 || size % static_cast<std::streamsize>(sizeof(uint32_t)) != 0) {
throw std::runtime_error("invalid SPIR-V shader: " + path);
}
std::vector<uint32_t> code(static_cast<size_t>(size) / sizeof(uint32_t));
file.seekg(0);
file.read(reinterpret_cast<char*>(code.data()), size);
if (!file) {
throw std::runtime_error("failed to read shader: " + path);
}
return code;
}
bool load_png_rgba(const std::string& path, uint32_t& width, uint32_t& height,
std::vector<unsigned char>& pixels) {
png_image image{};
image.version = PNG_IMAGE_VERSION;
if (!png_image_begin_read_from_file(&image, path.c_str())) {
return false;
}
image.format = PNG_FORMAT_RGBA;
width = image.width;
height = image.height;
pixels.resize(PNG_IMAGE_SIZE(image));
const bool loaded = png_image_finish_read(&image, nullptr, pixels.data(), 0, nullptr) != 0;
png_image_free(&image);
return loaded;
}
} // namespace
struct Renderer::Impl {
GLFWwindow* window = nullptr;
VkInstance instance = VK_NULL_HANDLE;
VkDebugUtilsMessengerEXT debug_messenger = VK_NULL_HANDLE;
VkSurfaceKHR surface = VK_NULL_HANDLE;
VkPhysicalDevice physical_device = VK_NULL_HANDLE;
VkDevice device = VK_NULL_HANDLE;
VkQueue graphics_queue = VK_NULL_HANDLE;
VkQueue present_queue = VK_NULL_HANDLE;
uint32_t graphics_family = 0;
uint32_t present_family = 0;
VkSwapchainKHR swapchain = VK_NULL_HANDLE;
VkFormat swapchain_format = VK_FORMAT_UNDEFINED;
VkExtent2D swapchain_extent{};
std::vector<VkImage> swapchain_images;
std::vector<VkImageView> swapchain_image_views;
std::vector<VkFramebuffer> framebuffers;
VkRenderPass render_pass = VK_NULL_HANDLE;
VkDescriptorSetLayout descriptor_set_layout = VK_NULL_HANDLE;
VkDescriptorPool descriptor_pool = VK_NULL_HANDLE;
VkPipelineLayout pipeline_layout = VK_NULL_HANDLE;
VkPipeline pipeline = VK_NULL_HANDLE;
std::atomic<bool> framebuffer_resized{false};
VkCommandPool command_pool = VK_NULL_HANDLE;
std::vector<FrameData> frames;
std::vector<VkFence> images_in_flight;
struct MeshResource {
VkBuffer vertex_buffer = VK_NULL_HANDLE;
VkDeviceMemory vertex_buffer_memory = VK_NULL_HANDLE;
VkBuffer index_buffer = VK_NULL_HANDLE;
VkDeviceMemory index_buffer_memory = VK_NULL_HANDLE;
uint32_t index_count = 0;
VkImage image = VK_NULL_HANDLE;
VkDeviceMemory image_memory = VK_NULL_HANDLE;
VkImageView image_view = VK_NULL_HANDLE;
VkSampler sampler = VK_NULL_HANDLE;
VkDescriptorSet descriptor_set = VK_NULL_HANDLE;
};
std::unordered_map<const Mesh*, MeshResource> mesh_resources;
size_t current_frame = 0;
bool initialized = false;
Renderer::InputCallback input_callback;
void init(int width, int height, const char* title);
void draw(const Actor* const* actors, size_t actor_count, const Camera* camera);
void upload_mesh(const Mesh& mesh);
void create_texture(MeshResource& resource, const std::string& path);
VkCommandBuffer begin_transfer();
void end_transfer(VkCommandBuffer command_buffer);
void recreate_swapchain();
void destroy();
};
Renderer::Renderer() : impl_(new Impl()) {}
Renderer::~Renderer() {
destroy();
delete impl_;
}
void Renderer::init(int width, int height, const char* title) {
impl_->init(width, height, title);
}
void Renderer::draw(const Actor* const* actors, size_t actor_count) {
impl_->draw(actors, actor_count, nullptr);
}
void Renderer::draw(const Actor* const* actors, size_t actor_count, const Camera* camera) {
impl_->draw(actors, actor_count, camera);
}
bool Renderer::should_close() const {
return impl_->window != nullptr && glfwWindowShouldClose(impl_->window);
}
void Renderer::poll_events() const {
glfwPollEvents();
}
void Renderer::set_input_callback(InputCallback callback) {
impl_->input_callback = std::move(callback);
}
bool Renderer::is_key_down(int key) const {
return impl_->window != nullptr && glfwGetKey(impl_->window, key) == GLFW_PRESS;
}
bool Renderer::is_mouse_button_down(int button) const {
return impl_->window != nullptr && glfwGetMouseButton(impl_->window, button) == GLFW_PRESS;
}
std::array<double, 2> Renderer::cursor_position() const {
if (impl_->window == nullptr) {
return {0.0, 0.0};
}
double x = 0.0;
double y = 0.0;
glfwGetCursorPos(impl_->window, &x, &y);
return {x, y};
}
void Renderer::destroy() {
impl_->destroy();
}
#if 0 // InputMap implementation moved to input.cpp.
void InputMap::add_action(const std::string& action) {
std::lock_guard<std::mutex> lock(mutex_);
actions_.try_emplace(action);
}
void InputMap::bind_key(const std::string& action, int key) {
std::lock_guard<std::mutex> lock(mutex_);
ActionState& state = actions_[action];
state.keys.insert(key);
state.held_keys.try_emplace(key, false);
}
void InputMap::unbind_key(const std::string& action, int key) {
std::lock_guard<std::mutex> lock(mutex_);
auto action_it = actions_.find(action);
if (action_it == actions_.end()) {
return;
}
ActionState& state = action_it->second;
state.keys.erase(key);
auto held_it = state.held_keys.find(key);
if (held_it != state.held_keys.end() && held_it->second) {
held_it->second = false;
if (state.held_key_count > 0) {
--state.held_key_count;
}
if (state.held_key_count + state.held_button_count == 0) {
state.pressed = false;
state.just_released = true;
}
}
state.held_keys.erase(key);
}
void InputMap::bind_mouse_button(const std::string& action, int button) {
std::lock_guard<std::mutex> lock(mutex_);
ActionState& state = actions_[action];
state.mouse_buttons.insert(button);
state.held_buttons.try_emplace(button, false);
}
void InputMap::unbind_mouse_button(const std::string& action, int button) {
std::lock_guard<std::mutex> lock(mutex_);
auto action_it = actions_.find(action);
if (action_it == actions_.end()) {
return;
}
ActionState& state = action_it->second;
state.mouse_buttons.erase(button);
auto held_it = state.held_buttons.find(button);
if (held_it != state.held_buttons.end() && held_it->second) {
held_it->second = false;
if (state.held_button_count > 0) {
--state.held_button_count;
}
if (state.held_key_count + state.held_button_count == 0) {
state.pressed = false;
state.just_released = true;
}
}
state.held_buttons.erase(button);
}
void InputMap::handle_event(const InputEvent& event) {
std::lock_guard<std::mutex> lock(mutex_);
for (auto& [action, state] : actions_) {
std::unordered_set<int>* bindings = nullptr;
std::unordered_map<int, bool>* held_map = nullptr;
size_t* held_count = nullptr;
if (event.type == InputEvent::Type::Key) {
bindings = &state.keys;
held_map = &state.held_keys;
held_count = &state.held_key_count;
} else if (event.type == InputEvent::Type::MouseButton) {
bindings = &state.mouse_buttons;
held_map = &state.held_buttons;
held_count = &state.held_button_count;
} else {
continue;
}
const int input_id = event.type == InputEvent::Type::Key ? event.key : event.button;
if (bindings->find(input_id) == bindings->end()) {
continue;
}
if (event.action == InputEvent::Action::Press) {
bool& held = (*held_map)[input_id];
if (!held) {
held = true;
++(*held_count);
if (state.held_key_count + state.held_button_count == 1) {
state.pressed = true;
state.just_pressed = true;
}
}
} else if (event.action == InputEvent::Action::Release) {
auto held_it = held_map->find(input_id);
if (held_it == held_map->end() || !held_it->second) {
continue;
}
held_it->second = false;
if (*held_count > 0) {
--(*held_count);
}
if (state.held_key_count + state.held_button_count == 0) {
state.pressed = false;
state.just_released = true;
}
}
}
}
void InputMap::end_frame() {
std::lock_guard<std::mutex> lock(mutex_);
for (auto& [action, state] : actions_) {
state.just_pressed = false;
state.just_released = false;
}
}
bool InputMap::is_action_pressed(const std::string& action) const {
std::lock_guard<std::mutex> lock(mutex_);
auto it = actions_.find(action);
return it != actions_.end() && it->second.pressed;
}
bool InputMap::is_action_just_pressed(const std::string& action) const {
std::lock_guard<std::mutex> lock(mutex_);
auto it = actions_.find(action);
return it != actions_.end() && it->second.just_pressed;
}
bool InputMap::is_action_just_released(const std::string& action) const {
std::lock_guard<std::mutex> lock(mutex_);
auto it = actions_.find(action);
return it != actions_.end() && it->second.just_released;
}
float InputMap::get_action_strength(const std::string& action) const {
std::lock_guard<std::mutex> lock(mutex_);
auto it = actions_.find(action);
return it != actions_.end() && it->second.pressed ? 1.0F : 0.0F;
}
float InputMap::get_axis(const std::string& negative_action,
const std::string& positive_action) const {
return get_action_strength(positive_action) - get_action_strength(negative_action);
}
#endif
void Renderer::Impl::init(int width, int height, const char* title) {
if (initialized) {
return;
}
if (!glfwInit()) {
throw std::runtime_error("failed to initialize GLFW");
}
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
window = glfwCreateWindow(width, height, title, nullptr, nullptr);
if (window == nullptr) {
throw std::runtime_error("failed to create GLFW window");
}
glfwSetWindowUserPointer(window, this);
glfwSetFramebufferSizeCallback(window, [](GLFWwindow* target, int, int) {
auto* impl = static_cast<Renderer::Impl*>(glfwGetWindowUserPointer(target));
if (impl != nullptr) impl->framebuffer_resized = true;
});
glfwSetKeyCallback(window, [](GLFWwindow* target, int key, int scancode, int action,
int mods) {
auto* impl = static_cast<Renderer::Impl*>(glfwGetWindowUserPointer(target));
if (impl == nullptr || !impl->input_callback) {
return;
}
InputEvent event;
event.type = InputEvent::Type::Key;
event.action = static_cast<InputEvent::Action>(action);
event.key = key;
event.scancode = scancode;
event.mods = mods;
impl->input_callback(event);
});
glfwSetMouseButtonCallback(window, [](GLFWwindow* target, int button, int action,
int mods) {
auto* impl = static_cast<Renderer::Impl*>(glfwGetWindowUserPointer(target));
if (impl == nullptr || !impl->input_callback) {
return;
}
InputEvent event;
event.type = InputEvent::Type::MouseButton;
event.action = static_cast<InputEvent::Action>(action);
event.button = button;
event.mods = mods;
impl->input_callback(event);
});
glfwSetCursorPosCallback(window, [](GLFWwindow* target, double x, double y) {
auto* impl = static_cast<Renderer::Impl*>(glfwGetWindowUserPointer(target));
if (impl == nullptr || !impl->input_callback) {
return;
}
InputEvent event;
event.type = InputEvent::Type::CursorMove;
event.x = x;
event.y = y;
impl->input_callback(event);
});
glfwSetScrollCallback(window, [](GLFWwindow* target, double x, double y) {
auto* impl = static_cast<Renderer::Impl*>(glfwGetWindowUserPointer(target));
if (impl == nullptr || !impl->input_callback) {
return;
}
InputEvent event;
event.type = InputEvent::Type::Scroll;
event.x = x;
event.y = y;
impl->input_callback(event);
});
// 让窗口管理器完成首次表面配置,避免交换链尺寸在首帧后才变化。
glfwPollEvents();
uint32_t glfw_extension_count = 0;
const char** glfw_extensions = glfwGetRequiredInstanceExtensions(&glfw_extension_count);
std::vector<const char*> instance_extensions(glfw_extensions,
glfw_extensions + glfw_extension_count);
instance_extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
const char* validation_layer = "VK_LAYER_KHRONOS_validation";
bool use_validation = false;
uint32_t layer_count = 0;
vkEnumerateInstanceLayerProperties(&layer_count, nullptr);
std::vector<VkLayerProperties> available_layers(layer_count);
vkEnumerateInstanceLayerProperties(&layer_count, available_layers.data());
for (const auto& layer : available_layers) {
if (std::strcmp(layer.layerName, validation_layer) == 0) {
use_validation = true;
break;
}
}
VkApplicationInfo app_info{};
app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
app_info.pApplicationName = title;
app_info.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
app_info.pEngineName = "OpenCGE";
app_info.engineVersion = VK_MAKE_VERSION(1, 0, 0);
app_info.apiVersion = VK_API_VERSION_1_0;
VkInstanceCreateInfo instance_info{};
instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
instance_info.pApplicationInfo = &app_info;
instance_info.enabledExtensionCount = static_cast<uint32_t>(instance_extensions.size());
instance_info.ppEnabledExtensionNames = instance_extensions.data();
if (use_validation) {
instance_info.enabledLayerCount = 1;
instance_info.ppEnabledLayerNames = &validation_layer;
}
check(vkCreateInstance(&instance_info, nullptr, &instance), "vkCreateInstance");
if (use_validation) {
auto create_messenger = reinterpret_cast<PFN_vkCreateDebugUtilsMessengerEXT>(
vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"));
if (create_messenger != nullptr) {
VkDebugUtilsMessengerCreateInfoEXT messenger_info{};
messenger_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
messenger_info.messageSeverity =
VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
messenger_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
messenger_info.pfnUserCallback = debug_callback;
create_messenger(instance, &messenger_info, nullptr, &debug_messenger);
}
}
check(glfwCreateWindowSurface(instance, window, nullptr, &surface), "glfwCreateWindowSurface");
uint32_t physical_device_count = 0;
check(vkEnumeratePhysicalDevices(instance, &physical_device_count, nullptr),
"vkEnumeratePhysicalDevices");
if (physical_device_count == 0) {
throw std::runtime_error("no Vulkan physical device");
}
std::vector<VkPhysicalDevice> physical_devices(physical_device_count);
check(vkEnumeratePhysicalDevices(instance, &physical_device_count, physical_devices.data()),
"vkEnumeratePhysicalDevices");
int best_score = -1;
for (VkPhysicalDevice candidate : physical_devices) {
uint32_t family_count = 0;
vkGetPhysicalDeviceQueueFamilyProperties(candidate, &family_count, nullptr);
std::vector<VkQueueFamilyProperties> families(family_count);
vkGetPhysicalDeviceQueueFamilyProperties(candidate, &family_count, families.data());
int graphics_index = -1;
int present_index = -1;
bool swapchain_supported = false;
uint32_t extension_count = 0;
vkEnumerateDeviceExtensionProperties(candidate, nullptr, &extension_count, nullptr);
std::vector<VkExtensionProperties> extensions(extension_count);
vkEnumerateDeviceExtensionProperties(candidate, nullptr, &extension_count,
extensions.data());
for (const auto& extension : extensions) {
if (std::strcmp(extension.extensionName, VK_KHR_SWAPCHAIN_EXTENSION_NAME) == 0) {
swapchain_supported = true;
break;
}
}
for (uint32_t i = 0; i < family_count; ++i) {
if (families[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
graphics_index = static_cast<int>(i);
}
VkBool32 supports_present = VK_FALSE;
vkGetPhysicalDeviceSurfaceSupportKHR(candidate, i, surface, &supports_present);
if (supports_present) {
present_index = static_cast<int>(i);
}
if (graphics_index >= 0 && present_index >= 0) {
break;
}
}
VkPhysicalDeviceProperties properties{};
vkGetPhysicalDeviceProperties(candidate, &properties);
int score = 0;
if (graphics_index >= 0 && present_index >= 0 && swapchain_supported) {
score += 1000;
}
if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
score += 100;
}
if (score > best_score) {
best_score = score;
physical_device = candidate;
graphics_family = static_cast<uint32_t>(graphics_index);
present_family = static_cast<uint32_t>(present_index);
}
}
if (best_score < 1000) {
throw std::runtime_error("no suitable Vulkan device (graphics + present + swapchain)");
}
std::vector<VkDeviceQueueCreateInfo> queue_infos;
std::array<uint32_t, 2> unique_families = {graphics_family, present_family};
float queue_priority = 1.0F;
for (uint32_t family : unique_families) {
VkDeviceQueueCreateInfo queue_info{};
queue_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queue_info.queueFamilyIndex = family;
queue_info.queueCount = 1;
queue_info.pQueuePriorities = &queue_priority;
queue_infos.push_back(queue_info);
}
if (queue_infos.size() == 2 && graphics_family == present_family) {
queue_infos.resize(1);
}
VkDeviceCreateInfo device_info{};
device_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
device_info.queueCreateInfoCount = static_cast<uint32_t>(queue_infos.size());
device_info.pQueueCreateInfos = queue_infos.data();
const char* device_extension = VK_KHR_SWAPCHAIN_EXTENSION_NAME;
device_info.enabledExtensionCount = 1;
device_info.ppEnabledExtensionNames = &device_extension;
check(vkCreateDevice(physical_device, &device_info, nullptr, &device), "vkCreateDevice");
vkGetDeviceQueue(device, graphics_family, 0, &graphics_queue);
vkGetDeviceQueue(device, present_family, 0, &present_queue);
VkSurfaceCapabilitiesKHR capabilities{};
check(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, surface, &capabilities),
"vkGetPhysicalDeviceSurfaceCapabilitiesKHR");
uint32_t format_count = 0;
check(vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &format_count, nullptr),
"vkGetPhysicalDeviceSurfaceFormatsKHR");
std::vector<VkSurfaceFormatKHR> formats(format_count);
check(vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &format_count,
formats.data()),
"vkGetPhysicalDeviceSurfaceFormatsKHR");
VkSurfaceFormatKHR surface_format = formats[0];
for (const auto& format : formats) {
if (format.format == VK_FORMAT_B8G8R8A8_SRGB &&
format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
surface_format = format;
break;
}
}
uint32_t present_mode_count = 0;
check(vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &present_mode_count,
nullptr),
"vkGetPhysicalDeviceSurfacePresentModesKHR");
std::vector<VkPresentModeKHR> present_modes(present_mode_count);
check(vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &present_mode_count,
present_modes.data()),
"vkGetPhysicalDeviceSurfacePresentModesKHR");
VkPresentModeKHR present_mode = VK_PRESENT_MODE_FIFO_KHR;
for (VkPresentModeKHR mode : present_modes) {
if (mode == VK_PRESENT_MODE_MAILBOX_KHR) {
present_mode = mode;
break;
}
}
if (capabilities.currentExtent.width != UINT32_MAX) {
swapchain_extent = capabilities.currentExtent;
} else {
int fb_width = 0;
int fb_height = 0;
glfwGetFramebufferSize(window, &fb_width, &fb_height);
swapchain_extent.width =
std::clamp(static_cast<uint32_t>(fb_width), capabilities.minImageExtent.width,
capabilities.maxImageExtent.width);
swapchain_extent.height =
std::clamp(static_cast<uint32_t>(fb_height), capabilities.minImageExtent.height,
capabilities.maxImageExtent.height);
}
uint32_t image_count = capabilities.minImageCount + 1;
if (capabilities.maxImageCount > 0 && image_count > capabilities.maxImageCount) {
image_count = capabilities.maxImageCount;
}
VkSwapchainCreateInfoKHR swapchain_info{};
swapchain_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
swapchain_info.surface = surface;
swapchain_info.minImageCount = image_count;
swapchain_info.imageFormat = surface_format.format;
swapchain_info.imageColorSpace = surface_format.colorSpace;
swapchain_info.imageExtent = swapchain_extent;
swapchain_info.imageArrayLayers = 1;
swapchain_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
swapchain_info.imageSharingMode =
graphics_family == present_family ? VK_SHARING_MODE_EXCLUSIVE : VK_SHARING_MODE_CONCURRENT;
swapchain_info.queueFamilyIndexCount = graphics_family == present_family ? 0 : 2;
std::array<uint32_t, 2> families = {graphics_family, present_family};
swapchain_info.pQueueFamilyIndices = families.data();
swapchain_info.preTransform = capabilities.currentTransform;
swapchain_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
swapchain_info.presentMode = present_mode;
swapchain_info.clipped = VK_TRUE;
swapchain_info.oldSwapchain = VK_NULL_HANDLE;
swapchain_format = surface_format.format;
check(vkCreateSwapchainKHR(device, &swapchain_info, nullptr, &swapchain),
"vkCreateSwapchainKHR");
uint32_t actual_image_count = 0;
check(vkGetSwapchainImagesKHR(device, swapchain, &actual_image_count, nullptr),
"vkGetSwapchainImagesKHR");
swapchain_images.resize(actual_image_count);
check(vkGetSwapchainImagesKHR(device, swapchain, &actual_image_count, swapchain_images.data()),
"vkGetSwapchainImagesKHR");
swapchain_image_views.resize(actual_image_count);
for (uint32_t i = 0; i < actual_image_count; ++i) {
VkImageViewCreateInfo view_info{};
view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
view_info.image = swapchain_images[i];
view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
view_info.format = swapchain_format;
view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
view_info.subresourceRange.baseMipLevel = 0;
view_info.subresourceRange.levelCount = 1;
view_info.subresourceRange.baseArrayLayer = 0;
view_info.subresourceRange.layerCount = 1;
check(vkCreateImageView(device, &view_info, nullptr, &swapchain_image_views[i]),
"vkCreateImageView");
}
VkAttachmentDescription color_attachment{};
color_attachment.format = swapchain_format;
color_attachment.samples = VK_SAMPLE_COUNT_1_BIT;
color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
color_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
color_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
color_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
color_attachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
VkAttachmentReference color_reference{};
color_reference.attachment = 0;
color_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkSubpassDescription subpass{};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &color_reference;
VkRenderPassCreateInfo render_pass_info{};
render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
render_pass_info.attachmentCount = 1;
render_pass_info.pAttachments = &color_attachment;
render_pass_info.subpassCount = 1;
render_pass_info.pSubpasses = &subpass;
check(vkCreateRenderPass(device, &render_pass_info, nullptr, &render_pass),
"vkCreateRenderPass");
framebuffers.resize(actual_image_count);
for (uint32_t i = 0; i < actual_image_count; ++i) {
VkFramebufferCreateInfo framebuffer_info{};
framebuffer_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
framebuffer_info.renderPass = render_pass;
framebuffer_info.attachmentCount = 1;
framebuffer_info.pAttachments = &swapchain_image_views[i];
framebuffer_info.width = swapchain_extent.width;
framebuffer_info.height = swapchain_extent.height;
framebuffer_info.layers = 1;
check(vkCreateFramebuffer(device, &framebuffer_info, nullptr, &framebuffers[i]),
"vkCreateFramebuffer");
}
VkCommandPoolCreateInfo command_pool_info{};
command_pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
command_pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
command_pool_info.queueFamilyIndex = graphics_family;
check(vkCreateCommandPool(device, &command_pool_info, nullptr, &command_pool),
"vkCreateCommandPool");
const uint32_t frame_count = actual_image_count;
VkSemaphoreCreateInfo semaphore_info{};
semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
frames.resize(frame_count);
images_in_flight.resize(actual_image_count, VK_NULL_HANDLE);
for (FrameData& frame : frames) {
check(vkCreateSemaphore(device, &semaphore_info, nullptr, &frame.image_available),
"vkCreateSemaphore");
check(vkCreateSemaphore(device, &semaphore_info, nullptr, &frame.render_finished),
"vkCreateSemaphore");
VkFenceCreateInfo fence_info{};
fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fence_info.flags = VK_FENCE_CREATE_SIGNALED_BIT;
check(vkCreateFence(device, &fence_info, nullptr, &frame.in_flight), "vkCreateFence");
VkCommandBufferAllocateInfo allocate_info{};
allocate_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocate_info.commandPool = command_pool;
allocate_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocate_info.commandBufferCount = 1;
check(vkAllocateCommandBuffers(device, &allocate_info, &frame.command_buffer),
"vkAllocateCommandBuffers");
}
VkDescriptorSetLayoutBinding layout_binding{};
layout_binding.binding = 0;
layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
layout_binding.descriptorCount = 1;
layout_binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
VkDescriptorSetLayoutCreateInfo descriptor_layout_info{};
descriptor_layout_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
descriptor_layout_info.bindingCount = 1;
descriptor_layout_info.pBindings = &layout_binding;
check(vkCreateDescriptorSetLayout(device, &descriptor_layout_info, nullptr,
&descriptor_set_layout),
"vkCreateDescriptorSetLayout");
VkPushConstantRange push_constant_range{};
push_constant_range.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
push_constant_range.offset = 0;
push_constant_range.size = sizeof(float) * 20;
VkPipelineLayoutCreateInfo pipeline_layout_info{};
pipeline_layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipeline_layout_info.setLayoutCount = 1;
pipeline_layout_info.pSetLayouts = &descriptor_set_layout;
pipeline_layout_info.pushConstantRangeCount = 1;
pipeline_layout_info.pPushConstantRanges = &push_constant_range;
check(vkCreatePipelineLayout(device, &pipeline_layout_info, nullptr, &pipeline_layout),
"vkCreatePipelineLayout");
VkDescriptorPoolSize pool_size{};
pool_size.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
pool_size.descriptorCount = 1024;
VkDescriptorPoolCreateInfo pool_info{};
pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
pool_info.maxSets = 1024;
pool_info.poolSizeCount = 1;
pool_info.pPoolSizes = &pool_size;
check(vkCreateDescriptorPool(device, &pool_info, nullptr, &descriptor_pool),
"vkCreateDescriptorPool");
const std::vector<uint32_t> vertex_shader = load_shader("textured_quad.vert.spv");
const std::vector<uint32_t> fragment_shader = load_shader("textured_quad.frag.spv");
VkShaderModule vertex_module = create_shader_module(
device, vertex_shader.data(), vertex_shader.size() * sizeof(uint32_t));
VkShaderModule fragment_module = create_shader_module(
device, fragment_shader.data(), fragment_shader.size() * sizeof(uint32_t));
VkPipelineShaderStageCreateInfo vertex_stage{};
vertex_stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
vertex_stage.stage = VK_SHADER_STAGE_VERTEX_BIT;
vertex_stage.module = vertex_module;
vertex_stage.pName = "main";
VkPipelineShaderStageCreateInfo fragment_stage{};
fragment_stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
fragment_stage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
fragment_stage.module = fragment_module;
fragment_stage.pName = "main";
std::array<VkPipelineShaderStageCreateInfo, 2> stages = {vertex_stage, fragment_stage};
VkVertexInputBindingDescription vertex_binding{};
vertex_binding.binding = 0;
vertex_binding.stride = sizeof(Mesh::Vertex);
vertex_binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
std::array<VkVertexInputAttributeDescription, 3> vertex_attributes{};
vertex_attributes[0].binding = 0;
vertex_attributes[0].location = 0;
vertex_attributes[0].format = VK_FORMAT_R32G32_SFLOAT;
vertex_attributes[0].offset = offsetof(Mesh::Vertex, position);
vertex_attributes[1].binding = 0;
vertex_attributes[1].location = 1;
vertex_attributes[1].format = VK_FORMAT_R32G32B32A32_SFLOAT;
vertex_attributes[1].offset = offsetof(Mesh::Vertex, color);
vertex_attributes[2].binding = 0;
vertex_attributes[2].location = 2;
vertex_attributes[2].format = VK_FORMAT_R32G32_SFLOAT;
vertex_attributes[2].offset = offsetof(Mesh::Vertex, uv);
VkPipelineVertexInputStateCreateInfo vertex_input{};
vertex_input.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertex_input.vertexBindingDescriptionCount = 1;
vertex_input.pVertexBindingDescriptions = &vertex_binding;
vertex_input.vertexAttributeDescriptionCount =
static_cast<uint32_t>(vertex_attributes.size());
vertex_input.pVertexAttributeDescriptions = vertex_attributes.data();
VkPipelineInputAssemblyStateCreateInfo input_assembly{};
input_assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
input_assembly.primitiveRestartEnable = VK_FALSE;
VkViewport viewport{};
viewport.x = 0.0F;
viewport.y = 0.0F;
viewport.width = static_cast<float>(swapchain_extent.width);
viewport.height = static_cast<float>(swapchain_extent.height);
viewport.minDepth = 0.0F;
viewport.maxDepth = 1.0F;
VkRect2D scissor{};
scissor.offset = {0, 0};
scissor.extent = swapchain_extent;
VkPipelineViewportStateCreateInfo viewport_state{};
viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewport_state.viewportCount = 1;
viewport_state.pViewports = &viewport;
viewport_state.scissorCount = 1;
viewport_state.pScissors = &scissor;
VkPipelineRasterizationStateCreateInfo rasterizer{};
rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterizer.depthClampEnable = VK_FALSE;
rasterizer.rasterizerDiscardEnable = VK_FALSE;
rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
rasterizer.cullMode = VK_CULL_MODE_NONE;
rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
rasterizer.depthBiasEnable = VK_FALSE;
rasterizer.lineWidth = 1.0F;
VkPipelineMultisampleStateCreateInfo multisampling{};
multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
multisampling.sampleShadingEnable = VK_FALSE;
VkPipelineColorBlendAttachmentState color_blend_attachment{};
color_blend_attachment.blendEnable = VK_TRUE;
color_blend_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
color_blend_attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
color_blend_attachment.colorBlendOp = VK_BLEND_OP_ADD;
color_blend_attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
color_blend_attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
color_blend_attachment.alphaBlendOp = VK_BLEND_OP_ADD;
color_blend_attachment.colorWriteMask =
VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT |
VK_COLOR_COMPONENT_A_BIT;
VkPipelineColorBlendStateCreateInfo color_blending{};
color_blending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
color_blending.logicOpEnable = VK_FALSE;
color_blending.attachmentCount = 1;
color_blending.pAttachments = &color_blend_attachment;
VkGraphicsPipelineCreateInfo pipeline_info{};
pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipeline_info.stageCount = static_cast<uint32_t>(stages.size());
pipeline_info.pStages = stages.data();
pipeline_info.pVertexInputState = &vertex_input;
pipeline_info.pInputAssemblyState = &input_assembly;
pipeline_info.pViewportState = &viewport_state;
pipeline_info.pRasterizationState = &rasterizer;
pipeline_info.pMultisampleState = &multisampling;
pipeline_info.pDepthStencilState = nullptr;
pipeline_info.pColorBlendState = &color_blending;
const std::array<VkDynamicState, 2> dynamic_states = {
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR,
};
VkPipelineDynamicStateCreateInfo dynamic_state{};
dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
dynamic_state.dynamicStateCount = static_cast<uint32_t>(dynamic_states.size());
dynamic_state.pDynamicStates = dynamic_states.data();
pipeline_info.pDynamicState = &dynamic_state;
pipeline_info.layout = pipeline_layout;
pipeline_info.renderPass = render_pass;
pipeline_info.subpass = 0;
check(vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipeline_info, nullptr, &pipeline),
"vkCreateGraphicsPipelines");
vkDestroyShaderModule(device, vertex_module, nullptr);
vkDestroyShaderModule(device, fragment_module, nullptr);
initialized = true;
}
VkCommandBuffer Renderer::Impl::begin_transfer() {
VkCommandBufferAllocateInfo allocate_info{};
allocate_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocate_info.commandPool = command_pool;
allocate_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocate_info.commandBufferCount = 1;
VkCommandBuffer command_buffer = VK_NULL_HANDLE;
check(vkAllocateCommandBuffers(device, &allocate_info, &command_buffer), "vkAllocateCommandBuffers");
VkCommandBufferBeginInfo begin_info{};
begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
check(vkBeginCommandBuffer(command_buffer, &begin_info), "vkBeginCommandBuffer");
return command_buffer;
}
void Renderer::Impl::end_transfer(VkCommandBuffer command_buffer) {
check(vkEndCommandBuffer(command_buffer), "vkEndCommandBuffer");
VkSubmitInfo submit_info{};
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &command_buffer;
check(vkQueueSubmit(graphics_queue, 1, &submit_info, VK_NULL_HANDLE), "vkQueueSubmit");
check(vkQueueWaitIdle(graphics_queue), "vkQueueWaitIdle");
vkFreeCommandBuffers(device, command_pool, 1, &command_buffer);
}
void Renderer::Impl::create_texture(MeshResource& resource, const std::string& path) {
uint32_t width = 1;
uint32_t height = 1;
std::vector<unsigned char> pixels = {255, 255, 255, 255};
if (!path.empty() && !load_png_rgba(path, width, height, pixels)) {
std::fprintf(stderr, "无法加载 PNG 纹理 %s,已使用白色纹理\n", path.c_str());
width = 1;
height = 1;
pixels = {255, 255, 255, 255};
}
VkBuffer staging_buffer = VK_NULL_HANDLE;
VkDeviceMemory staging_memory = VK_NULL_HANDLE;
const VkDeviceSize size = static_cast<VkDeviceSize>(pixels.size());
create_buffer(physical_device, device, size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
staging_buffer, staging_memory);
void* mapped = nullptr;
check(vkMapMemory(device, staging_memory, 0, size, 0, &mapped), "vkMapMemory");
std::memcpy(mapped, pixels.data(), pixels.size());
vkUnmapMemory(device, staging_memory);
VkImageCreateInfo image_info{};
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
image_info.imageType = VK_IMAGE_TYPE_2D;
image_info.extent = {width, height, 1};
image_info.mipLevels = 1;
image_info.arrayLayers = 1;
image_info.format = VK_FORMAT_R8G8B8A8_UNORM;
image_info.tiling = VK_IMAGE_TILING_OPTIMAL;
image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
image_info.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
image_info.samples = VK_SAMPLE_COUNT_1_BIT;
image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
check(vkCreateImage(device, &image_info, nullptr, &resource.image), "vkCreateImage");
VkMemoryRequirements requirements{};
vkGetImageMemoryRequirements(device, resource.image, &requirements);
VkMemoryAllocateInfo alloc_info{};
alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
alloc_info.allocationSize = requirements.size;
alloc_info.memoryTypeIndex = find_memory_type(physical_device, requirements.memoryTypeBits,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
check(vkAllocateMemory(device, &alloc_info, nullptr, &resource.image_memory), "vkAllocateMemory");
check(vkBindImageMemory(device, resource.image, resource.image_memory, 0), "vkBindImageMemory");
VkCommandBuffer command_buffer = begin_transfer();
VkImageMemoryBarrier barrier{};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = resource.image;
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier.subresourceRange.levelCount = 1;
barrier.subresourceRange.layerCount = 1;
barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
VkBufferImageCopy region{};
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.layerCount = 1;
region.imageExtent = {width, height, 1};
vkCmdCopyBufferToImage(command_buffer, staging_buffer, resource.image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
end_transfer(command_buffer);
vkDestroyBuffer(device, staging_buffer, nullptr);
vkFreeMemory(device, staging_memory, nullptr);
VkImageViewCreateInfo view_info{};
view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
view_info.image = resource.image;
view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
view_info.format = VK_FORMAT_R8G8B8A8_UNORM;
view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
view_info.subresourceRange.levelCount = 1;
view_info.subresourceRange.layerCount = 1;
check(vkCreateImageView(device, &view_info, nullptr, &resource.image_view), "vkCreateImageView");
VkSamplerCreateInfo sampler_info{};
sampler_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
sampler_info.magFilter = VK_FILTER_LINEAR;
sampler_info.minFilter = VK_FILTER_LINEAR;
sampler_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
sampler_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
sampler_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
sampler_info.maxLod = 0.0F;
check(vkCreateSampler(device, &sampler_info, nullptr, &resource.sampler), "vkCreateSampler");
VkDescriptorSetAllocateInfo set_info{};
set_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
set_info.descriptorPool = descriptor_pool;
set_info.descriptorSetCount = 1;
set_info.pSetLayouts = &descriptor_set_layout;
check(vkAllocateDescriptorSets(device, &set_info, &resource.descriptor_set), "vkAllocateDescriptorSets");
VkDescriptorImageInfo image_descriptor{};
image_descriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
image_descriptor.imageView = resource.image_view;
image_descriptor.sampler = resource.sampler;
VkWriteDescriptorSet write{};
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write.dstSet = resource.descriptor_set;
write.dstBinding = 0;
write.descriptorCount = 1;
write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
write.pImageInfo = &image_descriptor;
vkUpdateDescriptorSets(device, 1, &write, 0, nullptr);
}
void Renderer::Impl::upload_mesh(const Mesh& mesh) {
if (mesh_resources.find(&mesh) != mesh_resources.end()) return;
MeshResource& resource = mesh_resources[&mesh];
resource.index_count = static_cast<uint32_t>(mesh.indices.size());
if (mesh.vertices.empty() || resource.index_count == 0) return;
const VkDeviceSize vertex_size = sizeof(Mesh::Vertex) * mesh.vertices.size();
create_buffer(physical_device, device, vertex_size, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
resource.vertex_buffer, resource.vertex_buffer_memory);
void* vertex_mapped = nullptr;
check(vkMapMemory(device, resource.vertex_buffer_memory, 0, vertex_size, 0, &vertex_mapped),
"vkMapMemory");
std::memcpy(vertex_mapped, mesh.vertices.data(), vertex_size);
vkUnmapMemory(device, resource.vertex_buffer_memory);
const VkDeviceSize index_size = sizeof(uint32_t) * resource.index_count;
create_buffer(physical_device, device, index_size, VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
resource.index_buffer, resource.index_buffer_memory);
void* index_mapped = nullptr;
check(vkMapMemory(device, resource.index_buffer_memory, 0, index_size, 0, &index_mapped),
"vkMapMemory");
std::memcpy(index_mapped, mesh.indices.data(), index_size);
vkUnmapMemory(device, resource.index_buffer_memory);
create_texture(resource, mesh.texture);
}
void Renderer::Impl::recreate_swapchain() {
int width = 0;
int height = 0;
glfwGetFramebufferSize(window, &width, &height);
if (width == 0 || height == 0) return;
check(vkDeviceWaitIdle(device), "vkDeviceWaitIdle");
VkSurfaceCapabilitiesKHR capabilities{};
check(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, surface, &capabilities),
"vkGetPhysicalDeviceSurfaceCapabilitiesKHR");
uint32_t format_count = 0;
check(vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &format_count, nullptr),
"vkGetPhysicalDeviceSurfaceFormatsKHR");
std::vector<VkSurfaceFormatKHR> formats(format_count);
check(vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &format_count, formats.data()),
"vkGetPhysicalDeviceSurfaceFormatsKHR");
VkSurfaceFormatKHR surface_format = formats.front();
for (const VkSurfaceFormatKHR& format : formats) {
if (format.format == VK_FORMAT_B8G8R8A8_SRGB &&
format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
surface_format = format;
break;
}
}
if (surface_format.format != swapchain_format) {
throw std::runtime_error("swapchain format changed; renderer recreation is required");
}
uint32_t present_mode_count = 0;
check(vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &present_mode_count,
nullptr),
"vkGetPhysicalDeviceSurfacePresentModesKHR");
std::vector<VkPresentModeKHR> present_modes(present_mode_count);
check(vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &present_mode_count,
present_modes.data()),
"vkGetPhysicalDeviceSurfacePresentModesKHR");
VkPresentModeKHR present_mode = VK_PRESENT_MODE_FIFO_KHR;
for (VkPresentModeKHR mode : present_modes) {
if (mode == VK_PRESENT_MODE_MAILBOX_KHR) {
present_mode = mode;
break;
}
}
VkExtent2D extent{};
if (capabilities.currentExtent.width != UINT32_MAX) {
extent = capabilities.currentExtent;
} else {
extent.width = std::clamp(static_cast<uint32_t>(width), capabilities.minImageExtent.width,
capabilities.maxImageExtent.width);
extent.height = std::clamp(static_cast<uint32_t>(height), capabilities.minImageExtent.height,
capabilities.maxImageExtent.height);
}
uint32_t image_count = capabilities.minImageCount + 1;
if (capabilities.maxImageCount > 0 && image_count > capabilities.maxImageCount) {
image_count = capabilities.maxImageCount;
}
for (VkFramebuffer framebuffer : framebuffers) {
vkDestroyFramebuffer(device, framebuffer, nullptr);
}
framebuffers.clear();
for (VkImageView view : swapchain_image_views) {
vkDestroyImageView(device, view, nullptr);
}
swapchain_image_views.clear();
const VkSwapchainKHR old_swapchain = swapchain;
if (old_swapchain != VK_NULL_HANDLE) {
vkDestroySwapchainKHR(device, old_swapchain, nullptr);
swapchain = VK_NULL_HANDLE;
}
VkSwapchainCreateInfoKHR create_info{};
create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
create_info.surface = surface;
create_info.minImageCount = image_count;
create_info.imageFormat = surface_format.format;
create_info.imageColorSpace = surface_format.colorSpace;
create_info.imageExtent = extent;
create_info.imageArrayLayers = 1;
create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
std::array<uint32_t, 2> families = {graphics_family, present_family};
create_info.imageSharingMode = graphics_family == present_family
? VK_SHARING_MODE_EXCLUSIVE
: VK_SHARING_MODE_CONCURRENT;
create_info.queueFamilyIndexCount = graphics_family == present_family ? 0 : 2;
create_info.pQueueFamilyIndices = families.data();
create_info.preTransform = capabilities.currentTransform;
create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
create_info.presentMode = present_mode;
create_info.clipped = VK_TRUE;
create_info.oldSwapchain = VK_NULL_HANDLE;
check(vkCreateSwapchainKHR(device, &create_info, nullptr, &swapchain), "vkCreateSwapchainKHR");
swapchain_extent = extent;
uint32_t actual_image_count = 0;
check(vkGetSwapchainImagesKHR(device, swapchain, &actual_image_count, nullptr),
"vkGetSwapchainImagesKHR");
if (actual_image_count > frames.size()) {
throw std::runtime_error("resized swapchain needs more frame resources");
}
swapchain_images.resize(actual_image_count);
check(vkGetSwapchainImagesKHR(device, swapchain, &actual_image_count, swapchain_images.data()),
"vkGetSwapchainImagesKHR");
swapchain_image_views.resize(actual_image_count);
for (uint32_t i = 0; i < actual_image_count; ++i) {
VkImageViewCreateInfo view_info{};
view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
view_info.image = swapchain_images[i];
view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
view_info.format = swapchain_format;
view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
view_info.subresourceRange.levelCount = 1;
view_info.subresourceRange.layerCount = 1;
check(vkCreateImageView(device, &view_info, nullptr, &swapchain_image_views[i]),
"vkCreateImageView");
}
framebuffers.resize(actual_image_count);
for (uint32_t i = 0; i < actual_image_count; ++i) {
VkFramebufferCreateInfo framebuffer_info{};
framebuffer_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
framebuffer_info.renderPass = render_pass;
framebuffer_info.attachmentCount = 1;
framebuffer_info.pAttachments = &swapchain_image_views[i];
framebuffer_info.width = swapchain_extent.width;
framebuffer_info.height = swapchain_extent.height;
framebuffer_info.layers = 1;
check(vkCreateFramebuffer(device, &framebuffer_info, nullptr, &framebuffers[i]),
"vkCreateFramebuffer");
}
images_in_flight.assign(actual_image_count, VK_NULL_HANDLE);
framebuffer_resized = false;
}
void Renderer::Impl::draw(const Actor* const* actors, size_t actor_count, const Camera* camera) {
if (!initialized || actor_count == 0) {
return;
}
FrameData& frame = frames[current_frame % frames.size()];
check(vkWaitForFences(device, 1, &frame.in_flight, VK_TRUE, UINT64_MAX), "vkWaitForFences");
uint32_t image_index = 0;
VkResult acquire_result = vkAcquireNextImageKHR(device, swapchain, UINT64_MAX,
frame.image_available,
VK_NULL_HANDLE, &image_index);
if (acquire_result == VK_ERROR_OUT_OF_DATE_KHR) {
recreate_swapchain();
return;
}
if (acquire_result != VK_SUCCESS && acquire_result != VK_SUBOPTIMAL_KHR) {
check(acquire_result, "vkAcquireNextImageKHR");
}
if (images_in_flight[image_index] != VK_NULL_HANDLE) {
check(vkWaitForFences(device, 1, &images_in_flight[image_index], VK_TRUE, UINT64_MAX),
"vkWaitForFences(image)");
}
images_in_flight[image_index] = frame.in_flight;
check(vkResetFences(device, 1, &frame.in_flight), "vkResetFences");
for (size_t i = 0; i < actor_count; ++i) {
const Actor& actor = *actors[i];
const auto& mesh = actor.mesh();
if (mesh != nullptr) {
upload_mesh(*mesh);
}
}
check(vkResetCommandBuffer(frame.command_buffer, 0), "vkResetCommandBuffer");
VkCommandBufferBeginInfo begin_info{};
begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
check(vkBeginCommandBuffer(frame.command_buffer, &begin_info), "vkBeginCommandBuffer");
VkClearValue clear_value{};
clear_value.color = {{0.03F, 0.03F, 0.06F, 1.0F}};
VkRenderPassBeginInfo render_pass_begin{};
render_pass_begin.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
render_pass_begin.renderPass = render_pass;
render_pass_begin.framebuffer = framebuffers[image_index];
render_pass_begin.renderArea.offset = {0, 0};
render_pass_begin.renderArea.extent = swapchain_extent;
render_pass_begin.clearValueCount = 1;
render_pass_begin.pClearValues = &clear_value;
vkCmdBeginRenderPass(frame.command_buffer, &render_pass_begin, VK_SUBPASS_CONTENTS_INLINE);
vkCmdBindPipeline(frame.command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
VkViewport viewport{};
viewport.width = static_cast<float>(swapchain_extent.width);
viewport.height = static_cast<float>(swapchain_extent.height);
viewport.minDepth = 0.0F;
viewport.maxDepth = 1.0F;
VkRect2D scissor{};
scissor.extent = swapchain_extent;
vkCmdSetViewport(frame.command_buffer, 0, 1, &viewport);
vkCmdSetScissor(frame.command_buffer, 0, 1, &scissor);
for (size_t i = 0; i < actor_count; ++i) {
const Actor& actor = *actors[i];
if (actor.mesh() == nullptr) continue;
const auto resource_it = mesh_resources.find(actor.mesh().get());
if (resource_it == mesh_resources.end()) continue;
const MeshResource& resource = resource_it->second;
if (resource.vertex_buffer == VK_NULL_HANDLE || resource.index_buffer == VK_NULL_HANDLE ||
resource.descriptor_set == VK_NULL_HANDLE || resource.index_count == 0) continue;
VkDeviceSize offset = 0;
vkCmdBindVertexBuffers(frame.command_buffer, 0, 1, &resource.vertex_buffer, &offset);
vkCmdBindIndexBuffer(frame.command_buffer, resource.index_buffer, 0, VK_INDEX_TYPE_UINT32);
vkCmdBindDescriptorSets(frame.command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout,
0, 1, &resource.descriptor_set, 0, nullptr);
auto position = actor.global_position();
auto facing = actor.global_facing();
float camera_zoom = 1.0F;
if (camera != nullptr) {
const auto camera_position = camera->global_position();
const auto camera_facing = camera->global_facing();
const float camera_length = std::sqrt(camera_facing[0] * camera_facing[0] +
camera_facing[1] * camera_facing[1]);
const float camera_x = camera_length > 0.0001F ? camera_facing[0] / camera_length : 1.0F;
const float camera_y = camera_length > 0.0001F ? camera_facing[1] / camera_length : 0.0F;
const float dx = position[0] - camera_position[0];
const float dy = position[1] - camera_position[1];
position = {camera_x * dx + camera_y * dy, -camera_y * dx + camera_x * dy};
facing = {camera_x * facing[0] + camera_y * facing[1],
-camera_y * facing[0] + camera_x * facing[1]};
camera_zoom = camera->zoom();
}
const float scale = 0.35F * camera_zoom;
const float horizontal_projection =
static_cast<float>(swapchain_extent.height) / static_cast<float>(swapchain_extent.width);
float facing_x = facing[0];
float facing_y = facing[1];
const float facing_length = std::sqrt(facing_x * facing_x + facing_y * facing_y);
if (facing_length > 0.0001F) {
facing_x /= facing_length;
facing_y /= facing_length;
} else {
facing_x = 1.0F;
facing_y = 0.0F;
}
std::array<float, 20> constants = {{
scale * facing_x * horizontal_projection, scale * facing_y, 0.0F, 0.0F,
-scale * facing_y * horizontal_projection, scale * facing_x, 0.0F, 0.0F,
0.0F, 0.0F, 1.0F, 0.0F,
position[0] * horizontal_projection, position[1], 0.0F, 1.0F,
0.0F, 0.0F, 0.0F, 0.0F,
}};
const std::array<float, 4> color = actor.mesh()->color();
std::copy(color.begin(), color.end(), constants.begin() + 16);
vkCmdPushConstants(frame.command_buffer, pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT, 0,
sizeof(constants), constants.data());
vkCmdDrawIndexed(frame.command_buffer, resource.index_count, 1, 0, 0, 0);
}
vkCmdEndRenderPass(frame.command_buffer);
check(vkEndCommandBuffer(frame.command_buffer), "vkEndCommandBuffer");
VkPipelineStageFlags wait_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
VkSubmitInfo submit_info{};
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submit_info.waitSemaphoreCount = 1;
submit_info.pWaitSemaphores = &frame.image_available;
submit_info.pWaitDstStageMask = &wait_stage;
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &frame.command_buffer;
// Present 不提供 fence;同一交换链图像再次被获取前,不能重用其等待的信号量。
FrameData& image_frame = frames[image_index];
submit_info.signalSemaphoreCount = 1;
submit_info.pSignalSemaphores = &image_frame.render_finished;
check(vkQueueSubmit(graphics_queue, 1, &submit_info, frame.in_flight), "vkQueueSubmit");
VkPresentInfoKHR present_info{};
present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
present_info.waitSemaphoreCount = 1;
present_info.pWaitSemaphores = &image_frame.render_finished;
present_info.swapchainCount = 1;
present_info.pSwapchains = &swapchain;
present_info.pImageIndices = &image_index;
VkResult present_result = vkQueuePresentKHR(present_queue, &present_info);
if (present_result == VK_ERROR_OUT_OF_DATE_KHR || present_result == VK_SUBOPTIMAL_KHR ||
framebuffer_resized) {
recreate_swapchain();
} else if (present_result != VK_SUCCESS) {
check(present_result, "vkQueuePresentKHR");
}
(void)present_result;
current_frame++;
}
void Renderer::Impl::destroy() {
if (!initialized) {
return;
}
if (device != VK_NULL_HANDLE) {
vkDeviceWaitIdle(device);
}
for (FrameData& frame : frames) {
if (frame.in_flight != VK_NULL_HANDLE) {
vkDestroyFence(device, frame.in_flight, nullptr);
}
if (frame.image_available != VK_NULL_HANDLE) {
vkDestroySemaphore(device, frame.image_available, nullptr);
}
if (frame.render_finished != VK_NULL_HANDLE) {
vkDestroySemaphore(device, frame.render_finished, nullptr);
}
if (frame.command_buffer != VK_NULL_HANDLE) {
vkFreeCommandBuffers(device, command_pool, 1, &frame.command_buffer);
}
}
for (auto& [mesh, resource] : mesh_resources) {
if (resource.sampler != VK_NULL_HANDLE) vkDestroySampler(device, resource.sampler, nullptr);
if (resource.image_view != VK_NULL_HANDLE) vkDestroyImageView(device, resource.image_view, nullptr);
if (resource.image != VK_NULL_HANDLE) vkDestroyImage(device, resource.image, nullptr);
if (resource.image_memory != VK_NULL_HANDLE) vkFreeMemory(device, resource.image_memory, nullptr);
if (resource.vertex_buffer != VK_NULL_HANDLE) vkDestroyBuffer(device, resource.vertex_buffer, nullptr);
if (resource.vertex_buffer_memory != VK_NULL_HANDLE) vkFreeMemory(device, resource.vertex_buffer_memory, nullptr);
if (resource.index_buffer != VK_NULL_HANDLE) vkDestroyBuffer(device, resource.index_buffer, nullptr);
if (resource.index_buffer_memory != VK_NULL_HANDLE) vkFreeMemory(device, resource.index_buffer_memory, nullptr);
}
mesh_resources.clear();
if (descriptor_pool != VK_NULL_HANDLE) {
vkDestroyDescriptorPool(device, descriptor_pool, nullptr);
}
if (descriptor_set_layout != VK_NULL_HANDLE) {
vkDestroyDescriptorSetLayout(device, descriptor_set_layout, nullptr);
}
if (pipeline != VK_NULL_HANDLE) {
vkDestroyPipeline(device, pipeline, nullptr);
}
if (pipeline_layout != VK_NULL_HANDLE) {
vkDestroyPipelineLayout(device, pipeline_layout, nullptr);
}
if (command_pool != VK_NULL_HANDLE) {
vkDestroyCommandPool(device, command_pool, nullptr);
}
for (VkFramebuffer framebuffer : framebuffers) {
if (framebuffer != VK_NULL_HANDLE) {
vkDestroyFramebuffer(device, framebuffer, nullptr);
}
}
if (render_pass != VK_NULL_HANDLE) {
vkDestroyRenderPass(device, render_pass, nullptr);
}
for (VkImageView view : swapchain_image_views) {
if (view != VK_NULL_HANDLE) {
vkDestroyImageView(device, view, nullptr);
}
}
if (swapchain != VK_NULL_HANDLE) {
vkDestroySwapchainKHR(device, swapchain, nullptr);
}
if (device != VK_NULL_HANDLE) {
vkDestroyDevice(device, nullptr);
}
if (surface != VK_NULL_HANDLE && instance != VK_NULL_HANDLE) {
vkDestroySurfaceKHR(instance, surface, nullptr);
}
if (debug_messenger != VK_NULL_HANDLE && instance != VK_NULL_HANDLE) {
auto destroy_messenger = reinterpret_cast<PFN_vkDestroyDebugUtilsMessengerEXT>(
vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"));
if (destroy_messenger != nullptr) {
destroy_messenger(instance, debug_messenger, nullptr);
}
debug_messenger = VK_NULL_HANDLE;
}
if (instance != VK_NULL_HANDLE) {
vkDestroyInstance(instance, nullptr);
}
if (window != nullptr) {
glfwDestroyWindow(window);
glfwTerminate();
}
swapchain_images.clear();
swapchain_image_views.clear();
framebuffers.clear();
frames.clear();
initialized = false;
}
} // namespace boundard::render