#include #include #define GLFW_INCLUDE_VULKAN #include #include #include #include #include #include #include #include #include #include #include #include namespace boundard::render { namespace { constexpr std::array 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 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 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(sizeof(uint32_t)) != 0) { throw std::runtime_error("invalid SPIR-V shader: " + path); } std::vector code(static_cast(size) / sizeof(uint32_t)); file.seekg(0); file.read(reinterpret_cast(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& 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 swapchain_images; std::vector swapchain_image_views; std::vector 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 framebuffer_resized{false}; VkCommandPool command_pool = VK_NULL_HANDLE; std::vector frames; std::vector 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 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 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 lock(mutex_); actions_.try_emplace(action); } void InputMap::bind_key(const std::string& action, int key) { std::lock_guard 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 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 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 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 lock(mutex_); for (auto& [action, state] : actions_) { std::unordered_set* bindings = nullptr; std::unordered_map* 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 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 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 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 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 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(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(glfwGetWindowUserPointer(target)); if (impl == nullptr || !impl->input_callback) { return; } InputEvent event; event.type = InputEvent::Type::Key; event.action = static_cast(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(glfwGetWindowUserPointer(target)); if (impl == nullptr || !impl->input_callback) { return; } InputEvent event; event.type = InputEvent::Type::MouseButton; event.action = static_cast(action); event.button = button; event.mods = mods; impl->input_callback(event); }); glfwSetCursorPosCallback(window, [](GLFWwindow* target, double x, double y) { auto* impl = static_cast(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(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 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 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(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( 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 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 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 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(i); } VkBool32 supports_present = VK_FALSE; vkGetPhysicalDeviceSurfaceSupportKHR(candidate, i, surface, &supports_present); if (supports_present) { present_index = static_cast(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(graphics_index); present_family = static_cast(present_index); } } if (best_score < 1000) { throw std::runtime_error("no suitable Vulkan device (graphics + present + swapchain)"); } std::vector queue_infos; std::array 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(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 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 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(fb_width), capabilities.minImageExtent.width, capabilities.maxImageExtent.width); swapchain_extent.height = std::clamp(static_cast(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 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 vertex_shader = load_shader("textured_quad.vert.spv"); const std::vector 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 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 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(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(swapchain_extent.width); viewport.height = static_cast(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(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 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(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 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(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(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 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 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(width), capabilities.minImageExtent.width, capabilities.maxImageExtent.width); extent.height = std::clamp(static_cast(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 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(swapchain_extent.width); viewport.height = static_cast(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(swapchain_extent.height) / static_cast(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 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 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( 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