1577 lines
71 KiB
C++
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, ®ion);
|
|
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
|