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>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
eab5938072
commit
22d534c5eb
@@ -0,0 +1,251 @@
|
||||
// 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
|
||||
Reference in New Issue
Block a user