Files
metin2-client/src/host/vk_device.cpp
T
shenleiandClaude Opus 5.5 22d534c5eb Split vulkan_window.cpp by responsibility and render() into phases
VulkanWindow's 3000-line TU becomes sdl_window.cpp (window, events,
cursor, screenshot BMP), vk_device.cpp (device, swapchain, MSAA,
framebuffers), vk_pipelines.cpp (render passes, pipelines),
vk_resources.cpp (buffers, render targets, geometry), vk_textures.cpp
(textures, samplers, descriptors), vk_frame.cpp (render) and
ui_batches.cpp; vulkan_window.cpp keeps construction and timing stats.
Function bodies move verbatim.

render() now reads fence/acquire -> prepare_draws -> prepare_ui ->
write_draw_states -> record_passes -> record_readbacks -> submit ->
finish_readbacks -> present, with FrameDraws / Readback carrying the
locals that used to live across 500 lines.

Verified: login-screen screenshot byte-identical to HEAD; game-world
draw/vertex/index/UI counts identical; ctest, port_gate macos+android,
Android build pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 21:54:36 +09:00

252 lines
14 KiB
C++

// VulkanWindow: physical device selection, swapchain, MSAA target and framebuffers.
#include "vulkan_window.h"
#include "host_util.h"
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <stdexcept>
namespace mt_host {
std::uint32_t VulkanWindow::find_memory_type(std::uint32_t type_bits, VkMemoryPropertyFlags flags) const {
VkPhysicalDeviceMemoryProperties properties{};
vkGetPhysicalDeviceMemoryProperties(physical_, &properties);
for (std::uint32_t i = 0; i < properties.memoryTypeCount; ++i)
if ((type_bits & (1u << i)) && (properties.memoryTypes[i].propertyFlags & flags) == flags)
return i;
throw std::runtime_error("no matching Vulkan memory type");
}
void VulkanWindow::select_device() {
std::uint32_t count = 0;
check(vkEnumeratePhysicalDevices(instance_, &count, nullptr), "vkEnumeratePhysicalDevices count");
if (!count) throw std::runtime_error("no Vulkan physical device; set VK_ICD_FILENAMES for MoltenVK");
std::vector<VkPhysicalDevice> candidates(count);
check(vkEnumeratePhysicalDevices(instance_, &count, candidates.data()), "vkEnumeratePhysicalDevices");
for (auto candidate : candidates) {
std::uint32_t family_count = 0;
vkGetPhysicalDeviceQueueFamilyProperties(candidate, &family_count, nullptr);
std::vector<VkQueueFamilyProperties> families(family_count);
vkGetPhysicalDeviceQueueFamilyProperties(candidate, &family_count, families.data());
for (std::uint32_t i = 0; i < family_count; ++i) {
VkBool32 present = VK_FALSE;
check(vkGetPhysicalDeviceSurfaceSupportKHR(candidate, i, surface_, &present), "surface support");
if ((families[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) && present) {
physical_ = candidate; queue_family_ = i; break;
}
}
if (physical_) break;
}
if (!physical_) throw std::runtime_error("no graphics/present queue family");
VkPhysicalDeviceProperties properties{};
vkGetPhysicalDeviceProperties(physical_, &properties);
device_name_ = properties.deviceName;
timestamp_period_ns_ = properties.limits.timestampPeriod;
VkPhysicalDeviceFeatures supported_features{};
vkGetPhysicalDeviceFeatures(physical_, &supported_features);
VkPhysicalDeviceFeatures enabled_features{};
if (supported_features.samplerAnisotropy) {
enabled_features.samplerAnisotropy = VK_TRUE;
max_anisotropy_ = std::min(16.0f, properties.limits.maxSamplerAnisotropy);
}
// Multisampled 3D edges; MT_MSAA=1 keeps the single-sample path, 2/4/8 request a count.
{
int wanted = 4;
if (const char* env = std::getenv("MT_MSAA")) wanted = std::max(1, std::atoi(env));
const VkSampleCountFlags supported =
properties.limits.framebufferColorSampleCounts & properties.limits.framebufferDepthSampleCounts;
samples_ = VK_SAMPLE_COUNT_1_BIT;
for (VkSampleCountFlagBits c : {VK_SAMPLE_COUNT_8_BIT, VK_SAMPLE_COUNT_4_BIT, VK_SAMPLE_COUNT_2_BIT})
if (int(c) <= wanted && (supported & c)) { samples_ = c; break; }
}
std::uint32_t extension_count = 0;
check(vkEnumerateDeviceExtensionProperties(physical_, nullptr, &extension_count, nullptr), "device extensions count");
std::vector<VkExtensionProperties> available(extension_count);
check(vkEnumerateDeviceExtensionProperties(physical_, nullptr, &extension_count, available.data()), "device extensions");
std::vector<const char*> extensions{VK_KHR_SWAPCHAIN_EXTENSION_NAME};
constexpr const char* portability_subset = "VK_KHR_portability_subset";
for (const auto& extension : available)
if (std::strcmp(extension.extensionName, portability_subset) == 0)
extensions.push_back(portability_subset);
const float priority = 1.0f;
VkDeviceQueueCreateInfo queue_info{VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO};
queue_info.queueFamilyIndex = queue_family_; queue_info.queueCount = 1; queue_info.pQueuePriorities = &priority;
VkDeviceCreateInfo device_info{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO};
device_info.queueCreateInfoCount = 1; device_info.pQueueCreateInfos = &queue_info;
device_info.enabledExtensionCount = static_cast<std::uint32_t>(extensions.size());
device_info.ppEnabledExtensionNames = extensions.data();
device_info.pEnabledFeatures = &enabled_features;
check(vkCreateDevice(physical_, &device_info, nullptr, &device_), "vkCreateDevice");
vkGetDeviceQueue(device_, queue_family_, 0, &queue_);
}
void VulkanWindow::create_swapchain() {
VkSurfaceCapabilitiesKHR capabilities{};
check(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_, surface_, &capabilities), "surface capabilities");
std::uint32_t count = 0;
check(vkGetPhysicalDeviceSurfaceFormatsKHR(physical_, surface_, &count, nullptr), "surface formats count");
if (!count) throw std::runtime_error("surface has no formats");
std::vector<VkSurfaceFormatKHR> formats(count);
check(vkGetPhysicalDeviceSurfaceFormatsKHR(physical_, surface_, &count, formats.data()), "surface formats");
VkSurfaceFormatKHR format = formats.front();
for (auto candidate : formats) if (candidate.format == VK_FORMAT_B8G8R8A8_UNORM) format = candidate;
swapchain_format_ = format.format;
int width = 0, height = 0;
SDL_GetWindowSizeInPixels(window_, &width, &height);
if (capabilities.currentExtent.width != UINT32_MAX) extent_ = capabilities.currentExtent;
else {
extent_.width = std::clamp(std::uint32_t(width), capabilities.minImageExtent.width, capabilities.maxImageExtent.width);
extent_.height = std::clamp(std::uint32_t(height), capabilities.minImageExtent.height, capabilities.maxImageExtent.height);
}
present_mode_ = VK_PRESENT_MODE_FIFO_KHR;
if (!vsync_) {
std::uint32_t mode_count = 0;
check(vkGetPhysicalDeviceSurfacePresentModesKHR(physical_, surface_, &mode_count, nullptr), "present modes count");
std::vector<VkPresentModeKHR> modes(mode_count);
if (mode_count)
check(vkGetPhysicalDeviceSurfacePresentModesKHR(physical_, surface_, &mode_count, modes.data()), "present modes");
if (std::find(modes.begin(), modes.end(), VK_PRESENT_MODE_IMMEDIATE_KHR) != modes.end())
present_mode_ = VK_PRESENT_MODE_IMMEDIATE_KHR;
else if (std::find(modes.begin(), modes.end(), VK_PRESENT_MODE_MAILBOX_KHR) != modes.end())
present_mode_ = VK_PRESENT_MODE_MAILBOX_KHR;
}
VkSwapchainCreateInfoKHR info{VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR};
info.surface = surface_;
info.minImageCount = std::min(capabilities.minImageCount + 1, capabilities.maxImageCount ? capabilities.maxImageCount : UINT32_MAX);
info.imageFormat = format.format; info.imageColorSpace = format.colorSpace; info.imageExtent = extent_;
info.imageArrayLayers = 1; info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
(capabilities.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
// Android surfaces can report a quarter-turn transform even when SDL's window is
// already landscape. Rendering in SDL window coordinates and requesting that transform
// again rotates the entire game image on presentation.
info.preTransform = (capabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)
? VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR : capabilities.currentTransform;
#ifdef __ANDROID__
SDL_Log("swapchain: window=%dx%d extent=%ux%u currentTransform=%u preTransform=%u",
width, height, extent_.width, extent_.height,
unsigned(capabilities.currentTransform), unsigned(info.preTransform));
#endif
info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; info.presentMode = present_mode_;
info.clipped = VK_TRUE;
check(vkCreateSwapchainKHR(device_, &info, nullptr, &swapchain_), "vkCreateSwapchainKHR");
check(vkGetSwapchainImagesKHR(device_, swapchain_, &count, nullptr), "swapchain images count");
std::vector<VkImage> images(count);
check(vkGetSwapchainImagesKHR(device_, swapchain_, &count, images.data()), "swapchain images");
swapchain_images_ = images;
for (auto image : images) {
VkImageViewCreateInfo view{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
view.image = image; view.viewType = VK_IMAGE_VIEW_TYPE_2D; view.format = swapchain_format_;
view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
view.subresourceRange.levelCount = 1; view.subresourceRange.layerCount = 1;
VkImageView handle = VK_NULL_HANDLE;
check(vkCreateImageView(device_, &view, nullptr, &handle), "vkCreateImageView");
image_views_.push_back(handle);
}
VkImageCreateInfo depth_info{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
depth_info.imageType = VK_IMAGE_TYPE_2D;
depth_info.format = VK_FORMAT_D32_SFLOAT;
depth_info.extent = {extent_.width, extent_.height, 1};
depth_info.mipLevels = 1; depth_info.arrayLayers = 1;
depth_info.samples = samples_;
depth_info.tiling = VK_IMAGE_TILING_OPTIMAL;
depth_info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
depth_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
check(vkCreateImage(device_, &depth_info, nullptr, &depth_image_), "vkCreateImage depth");
VkMemoryRequirements depth_reqs{};
vkGetImageMemoryRequirements(device_, depth_image_, &depth_reqs);
VkMemoryAllocateInfo depth_alloc{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
depth_alloc.allocationSize = depth_reqs.size;
depth_alloc.memoryTypeIndex = find_memory_type(depth_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
check(vkAllocateMemory(device_, &depth_alloc, nullptr, &depth_memory_), "vkAllocateMemory depth");
check(vkBindImageMemory(device_, depth_image_, depth_memory_, 0), "vkBindImageMemory depth");
VkImageViewCreateInfo depth_view{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
depth_view.image = depth_image_; depth_view.viewType = VK_IMAGE_VIEW_TYPE_2D;
depth_view.format = VK_FORMAT_D32_SFLOAT;
depth_view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
depth_view.subresourceRange.levelCount = 1; depth_view.subresourceRange.layerCount = 1;
check(vkCreateImageView(device_, &depth_view, nullptr, &depth_view_), "vkCreateImageView depth");
if (samples_ != VK_SAMPLE_COUNT_1_BIT) create_msaa_color();
}
void VulkanWindow::recreate_swapchain() {
int w = 0, h = 0;
SDL_GetWindowSizeInPixels(window_, &w, &h);
if (w <= 0 || h <= 0) return;
vkDeviceWaitIdle(device_);
for (auto& entry : pipelines_) vkDestroyPipeline(device_, entry.second, nullptr);
pipelines_.clear();
for (auto fb : framebuffers_) vkDestroyFramebuffer(device_, fb, nullptr);
framebuffers_.clear();
if (depth_view_) { vkDestroyImageView(device_, depth_view_, nullptr); depth_view_ = VK_NULL_HANDLE; }
if (depth_image_) { vkDestroyImage(device_, depth_image_, nullptr); depth_image_ = VK_NULL_HANDLE; }
if (depth_memory_) { vkFreeMemory(device_, depth_memory_, nullptr); depth_memory_ = VK_NULL_HANDLE; }
destroy_msaa_color();
for (auto view : image_views_) vkDestroyImageView(device_, view, nullptr);
image_views_.clear();
if (swapchain_) { vkDestroySwapchainKHR(device_, swapchain_, nullptr); swapchain_ = VK_NULL_HANDLE; }
create_swapchain();
create_framebuffers();
get_pipeline(0, 2, 0);
}
void VulkanWindow::create_msaa_color() {
VkImageCreateInfo info{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
info.imageType = VK_IMAGE_TYPE_2D;
info.format = swapchain_format_;
info.extent = {extent_.width, extent_.height, 1};
info.mipLevels = 1; info.arrayLayers = 1;
info.samples = samples_;
info.tiling = VK_IMAGE_TILING_OPTIMAL;
info.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT;
info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
check(vkCreateImage(device_, &info, nullptr, &msaa_image_), "vkCreateImage msaa color");
VkMemoryRequirements reqs{};
vkGetImageMemoryRequirements(device_, msaa_image_, &reqs);
VkMemoryAllocateInfo alloc{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
alloc.allocationSize = reqs.size;
try {
alloc.memoryTypeIndex = find_memory_type(reqs.memoryTypeBits, VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT);
} catch (const std::runtime_error&) {
alloc.memoryTypeIndex = find_memory_type(reqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
}
check(vkAllocateMemory(device_, &alloc, nullptr, &msaa_memory_), "vkAllocateMemory msaa color");
check(vkBindImageMemory(device_, msaa_image_, msaa_memory_, 0), "vkBindImageMemory msaa color");
VkImageViewCreateInfo view{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
view.image = msaa_image_; view.viewType = VK_IMAGE_VIEW_TYPE_2D; view.format = swapchain_format_;
view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
view.subresourceRange.levelCount = 1; view.subresourceRange.layerCount = 1;
check(vkCreateImageView(device_, &view, nullptr, &msaa_view_), "vkCreateImageView msaa color");
}
void VulkanWindow::destroy_msaa_color() {
if (msaa_view_) { vkDestroyImageView(device_, msaa_view_, nullptr); msaa_view_ = VK_NULL_HANDLE; }
if (msaa_image_) { vkDestroyImage(device_, msaa_image_, nullptr); msaa_image_ = VK_NULL_HANDLE; }
if (msaa_memory_) { vkFreeMemory(device_, msaa_memory_, nullptr); msaa_memory_ = VK_NULL_HANDLE; }
}
void VulkanWindow::create_framebuffers() {
for (auto view : image_views_) {
// Single-sample: {swapchain, depth}. MSAA: {msaa colour, depth, swapchain resolve}.
VkImageView fb_attachments[3] = {view, depth_view_, VK_NULL_HANDLE};
std::uint32_t count = 2;
if (samples_ != VK_SAMPLE_COUNT_1_BIT) {
fb_attachments[0] = msaa_view_;
fb_attachments[2] = view;
count = 3;
}
VkFramebufferCreateInfo framebuffer{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO};
framebuffer.renderPass = pass_; framebuffer.attachmentCount = count; framebuffer.pAttachments = fb_attachments;
framebuffer.width = extent_.width; framebuffer.height = extent_.height; framebuffer.layers = 1;
VkFramebuffer handle = VK_NULL_HANDLE;
check(vkCreateFramebuffer(device_, &framebuffer, nullptr, &handle), "vkCreateFramebuffer");
framebuffers_.push_back(handle);
}
}
} // namespace mt_host