mirror of
https://github.com/Nexoniarz/Fry3DX.git
synced 2026-11-03 12:34:27 +00:00
Fry3DX 0.4: an easy 3D modeller
Shapes with live settings, shape editing, knife and hole tools, drawing shapes onto other objects, combining objects (add / subtract / overlap), copies, thickness, painting with stickers and layers, a Layout tab, a Print tab with checks, packed .fry files with autosave, export to GLB, glTF, FBX, OBJ, 3MF, STL and PLY, folders in Layers, its own crash screen, and a website for GitHub Pages in docs/. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NMSrd5oe1GpUD7E6d825KP
This commit is contained in:
commit
f3e8eca87b
135 changed files with 23423 additions and 0 deletions
1641
engine/rhi/vulkan/vk_backend.cpp
Normal file
1641
engine/rhi/vulkan/vk_backend.cpp
Normal file
File diff suppressed because it is too large
Load diff
356
engine/rhi/vulkan/vk_device.cpp
Normal file
356
engine/rhi/vulkan/vk_device.cpp
Normal file
|
|
@ -0,0 +1,356 @@
|
|||
#define VMA_IMPLEMENTATION
|
||||
#define VMA_STATIC_VULKAN_FUNCTIONS 0
|
||||
#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1
|
||||
#include "rhi/vulkan/vk_device.h"
|
||||
#include "core/log.h"
|
||||
#include "platform/window.h"
|
||||
|
||||
#include <SDL3/SDL_vulkan.h>
|
||||
#include <VkBootstrap.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
namespace keel::vk {
|
||||
|
||||
void check(VkResult r, const char* expr, const char* file, int line) {
|
||||
if (r == VK_SUCCESS) return;
|
||||
log::error("{} failed with VkResult {} ({}:{})", expr, int(r), file, line);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
void transitionImage(VkCommandBuffer cmd, VkImage image, VkImageLayout from, VkImageLayout to, VkImageAspectFlags aspect) {
|
||||
VkImageMemoryBarrier2 barrier{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2};
|
||||
// Coarse but correct for now; per-pass barriers come with the render graph.
|
||||
barrier.srcStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
|
||||
barrier.srcAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT;
|
||||
barrier.dstStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
|
||||
barrier.dstAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT | VK_ACCESS_2_MEMORY_READ_BIT;
|
||||
barrier.oldLayout = from;
|
||||
barrier.newLayout = to;
|
||||
barrier.image = image;
|
||||
barrier.subresourceRange = {aspect, 0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS};
|
||||
VkDependencyInfo dep{VK_STRUCTURE_TYPE_DEPENDENCY_INFO};
|
||||
dep.imageMemoryBarrierCount = 1;
|
||||
dep.pImageMemoryBarriers = &barrier;
|
||||
vkCmdPipelineBarrier2(cmd, &dep);
|
||||
}
|
||||
|
||||
static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity,
|
||||
VkDebugUtilsMessageTypeFlagsEXT,
|
||||
const VkDebugUtilsMessengerCallbackDataEXT* data, void*) {
|
||||
if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) log::error("[vulkan] {}", data->pMessage);
|
||||
else if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) log::warn("[vulkan] {}", data->pMessage);
|
||||
return VK_FALSE;
|
||||
}
|
||||
|
||||
bool Device::init(const Window& window, bool validation) {
|
||||
window_ = &window;
|
||||
if (volkInitialize() != VK_SUCCESS) {
|
||||
log::error("No Vulkan loader found - is a Vulkan driver installed?");
|
||||
return false;
|
||||
}
|
||||
|
||||
Uint32 extCount = 0;
|
||||
const char* const* sdlExts = SDL_Vulkan_GetInstanceExtensions(&extCount);
|
||||
vkb::InstanceBuilder ib(vkGetInstanceProcAddr);
|
||||
ib.set_app_name("Fry3DX").require_api_version(1, 3, 0).enable_extensions(extCount, sdlExts);
|
||||
if (validation) ib.request_validation_layers().set_debug_callback(debugCallback);
|
||||
auto inst = ib.build();
|
||||
if (!inst) {
|
||||
log::error("Vulkan instance: {}", inst.error().message());
|
||||
return false;
|
||||
}
|
||||
instance_ = inst->instance;
|
||||
messenger_ = inst->debug_messenger;
|
||||
volkLoadInstance(instance_);
|
||||
|
||||
if (!SDL_Vulkan_CreateSurface(window.handle(), instance_, nullptr, &surface_)) {
|
||||
log::error("SDL_Vulkan_CreateSurface: {}", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
VkPhysicalDeviceVulkan13Features f13{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES};
|
||||
f13.dynamicRendering = VK_TRUE;
|
||||
f13.synchronization2 = VK_TRUE;
|
||||
f13.shaderDemoteToHelperInvocation = VK_TRUE; // `discard` compiles to demote in Vulkan 1.3 SPIR-V
|
||||
VkPhysicalDeviceVulkan12Features f12{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES};
|
||||
f12.bufferDeviceAddress = VK_TRUE;
|
||||
f12.descriptorIndexing = VK_TRUE;
|
||||
f12.runtimeDescriptorArray = VK_TRUE;
|
||||
f12.descriptorBindingPartiallyBound = VK_TRUE;
|
||||
f12.descriptorBindingSampledImageUpdateAfterBind = VK_TRUE;
|
||||
f12.shaderSampledImageArrayNonUniformIndexing = VK_TRUE;
|
||||
VkPhysicalDeviceFeatures f10{};
|
||||
f10.samplerAnisotropy = VK_TRUE;
|
||||
f10.independentBlend = VK_TRUE; // (the G-buffer pass blends its attachments differently)
|
||||
auto phys = vkb::PhysicalDeviceSelector(*inst)
|
||||
.set_surface(surface_)
|
||||
.set_minimum_version(1, 3)
|
||||
.set_required_features_13(f13)
|
||||
.set_required_features_12(f12)
|
||||
.set_required_features(f10)
|
||||
.prefer_gpu_device_type(vkb::PreferredDeviceType::discrete)
|
||||
.select();
|
||||
if (!phys) {
|
||||
log::error("No suitable GPU (needs Vulkan 1.3): {}", phys.error().message());
|
||||
return false;
|
||||
}
|
||||
vkb::PhysicalDevice pd = phys.value();
|
||||
physical_ = pd.physical_device;
|
||||
log::info("GPU: {}", pd.properties.deviceName);
|
||||
// Ray queries where the GPU has them (not needed: what uses them is skipped without).
|
||||
VkPhysicalDeviceAccelerationStructureFeaturesKHR asf{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR};
|
||||
asf.accelerationStructure = VK_TRUE;
|
||||
VkPhysicalDeviceRayQueryFeaturesKHR rqf{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_QUERY_FEATURES_KHR};
|
||||
rqf.rayQuery = VK_TRUE;
|
||||
const char* rt[] = {VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME, VK_KHR_RAY_QUERY_EXTENSION_NAME, VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME};
|
||||
rayQuery_ = pd.enable_extensions_if_present(3, rt) && pd.enable_extension_features_if_present(asf) && pd.enable_extension_features_if_present(rqf);
|
||||
log::info("Ray queries: {}", rayQuery_ ? "yes" : "no");
|
||||
|
||||
memoryBudget_ = pd.enable_extension_if_present(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME); // (real VRAM use, for the debug overlay)
|
||||
gpuName_ = pd.properties.deviceName;
|
||||
VkPhysicalDeviceDriverProperties drv{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES};
|
||||
VkPhysicalDeviceProperties2 props2{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &drv};
|
||||
vkGetPhysicalDeviceProperties2(pd.physical_device, &props2);
|
||||
gpuDriver_ = std::string(drv.driverName) + (drv.driverInfo[0] ? std::string(" ") + drv.driverInfo : std::string());
|
||||
|
||||
auto dev = vkb::DeviceBuilder(pd).build();
|
||||
if (!dev) {
|
||||
log::error("Vulkan device: {}", dev.error().message());
|
||||
return false;
|
||||
}
|
||||
device_ = dev->device;
|
||||
volkLoadDevice(device_);
|
||||
queue_ = dev->get_queue(vkb::QueueType::graphics).value();
|
||||
queueFamily_ = dev->get_queue_index(vkb::QueueType::graphics).value();
|
||||
|
||||
VmaVulkanFunctions fns{};
|
||||
fns.vkGetInstanceProcAddr = vkGetInstanceProcAddr;
|
||||
fns.vkGetDeviceProcAddr = vkGetDeviceProcAddr;
|
||||
VmaAllocatorCreateInfo aci{};
|
||||
aci.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT | (memoryBudget_ ? VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT : 0);
|
||||
aci.vulkanApiVersion = VK_API_VERSION_1_3;
|
||||
aci.instance = instance_;
|
||||
aci.physicalDevice = physical_;
|
||||
aci.device = device_;
|
||||
aci.pVulkanFunctions = &fns;
|
||||
VK_CHECK(vmaCreateAllocator(&aci, &allocator_));
|
||||
|
||||
for (Frame& f : frames_) {
|
||||
VkCommandPoolCreateInfo pci{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO};
|
||||
pci.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
||||
pci.queueFamilyIndex = queueFamily_;
|
||||
VK_CHECK(vkCreateCommandPool(device_, &pci, nullptr, &f.pool));
|
||||
VkCommandBufferAllocateInfo ai{VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO};
|
||||
ai.commandPool = f.pool;
|
||||
ai.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||
ai.commandBufferCount = 1;
|
||||
VK_CHECK(vkAllocateCommandBuffers(device_, &ai, &f.cmd));
|
||||
VkSemaphoreCreateInfo sci{VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
|
||||
VK_CHECK(vkCreateSemaphore(device_, &sci, nullptr, &f.acquired));
|
||||
VkFenceCreateInfo fci{VK_STRUCTURE_TYPE_FENCE_CREATE_INFO};
|
||||
fci.flags = VK_FENCE_CREATE_SIGNALED_BIT;
|
||||
VK_CHECK(vkCreateFence(device_, &fci, nullptr, &f.inFlight));
|
||||
}
|
||||
VkCommandPoolCreateInfo pci{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO};
|
||||
pci.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
||||
pci.queueFamilyIndex = queueFamily_;
|
||||
VK_CHECK(vkCreateCommandPool(device_, &pci, nullptr, &immPool_));
|
||||
VkCommandBufferAllocateInfo ai{VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO};
|
||||
ai.commandPool = immPool_;
|
||||
ai.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||
ai.commandBufferCount = 1;
|
||||
VK_CHECK(vkAllocateCommandBuffers(device_, &ai, &immCmd_));
|
||||
VkFenceCreateInfo fci{VK_STRUCTURE_TYPE_FENCE_CREATE_INFO};
|
||||
VK_CHECK(vkCreateFence(device_, &fci, nullptr, &immFence_));
|
||||
return createSwapchain();
|
||||
}
|
||||
|
||||
void Device::immediateSubmit(const std::function<void(VkCommandBuffer)>& record) {
|
||||
VK_CHECK(vkResetCommandBuffer(immCmd_, 0));
|
||||
VkCommandBufferBeginInfo bi{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
|
||||
bi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
||||
VK_CHECK(vkBeginCommandBuffer(immCmd_, &bi));
|
||||
record(immCmd_);
|
||||
VK_CHECK(vkEndCommandBuffer(immCmd_));
|
||||
VkCommandBufferSubmitInfo cbi{VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO};
|
||||
cbi.commandBuffer = immCmd_;
|
||||
VkSubmitInfo2 si{VK_STRUCTURE_TYPE_SUBMIT_INFO_2};
|
||||
si.commandBufferInfoCount = 1;
|
||||
si.pCommandBufferInfos = &cbi;
|
||||
VK_CHECK(vkQueueSubmit2(queue_, 1, &si, immFence_));
|
||||
VK_CHECK(vkWaitForFences(device_, 1, &immFence_, VK_TRUE, UINT64_MAX));
|
||||
VK_CHECK(vkResetFences(device_, 1, &immFence_));
|
||||
}
|
||||
|
||||
Buffer Device::createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, bool hostVisible, bool readback) {
|
||||
VkBufferCreateInfo bci{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
|
||||
bci.size = size;
|
||||
bci.usage = usage | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
|
||||
VmaAllocationCreateInfo aci{};
|
||||
aci.usage = VMA_MEMORY_USAGE_AUTO;
|
||||
if (hostVisible)
|
||||
aci.flags = (readback ? VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT : VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT) | VMA_ALLOCATION_CREATE_MAPPED_BIT;
|
||||
Buffer b;
|
||||
VmaAllocationInfo info{};
|
||||
VK_CHECK(vmaCreateBuffer(allocator_, &bci, &aci, &b.buffer, &b.alloc, &info));
|
||||
b.mapped = info.pMappedData;
|
||||
VkBufferDeviceAddressInfo dai{VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO};
|
||||
dai.buffer = b.buffer;
|
||||
b.address = vkGetBufferDeviceAddress(device_, &dai);
|
||||
return b;
|
||||
}
|
||||
|
||||
void Device::destroyBuffer(Buffer& b) {
|
||||
if (b.buffer) vmaDestroyBuffer(allocator_, b.buffer, b.alloc);
|
||||
b = {};
|
||||
}
|
||||
|
||||
Buffer Device::uploadBuffer(const void* data, VkDeviceSize size, VkBufferUsageFlags usage) {
|
||||
Buffer staging = createBuffer(size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, true);
|
||||
std::memcpy(staging.mapped, data, size);
|
||||
Buffer b = createBuffer(size, usage | VK_BUFFER_USAGE_TRANSFER_DST_BIT, false);
|
||||
immediateSubmit([&](VkCommandBuffer cmd) {
|
||||
VkBufferCopy copy{0, 0, size};
|
||||
vkCmdCopyBuffer(cmd, staging.buffer, b.buffer, 1, ©);
|
||||
});
|
||||
destroyBuffer(staging);
|
||||
return b;
|
||||
}
|
||||
|
||||
bool Device::createSwapchain() {
|
||||
uint32_t w, h;
|
||||
window_->pixelSize(w, h);
|
||||
if (w == 0 || h == 0) return false;
|
||||
|
||||
vkDeviceWaitIdle(device_);
|
||||
VkSwapchainKHR old = swapchain_;
|
||||
auto sc = vkb::SwapchainBuilder(physical_, device_, surface_)
|
||||
.set_desired_format({VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR})
|
||||
.set_desired_present_mode(vsync_ ? VK_PRESENT_MODE_FIFO_KHR : VK_PRESENT_MODE_MAILBOX_KHR)
|
||||
.add_fallback_present_mode(vsync_ ? VK_PRESENT_MODE_FIFO_KHR : VK_PRESENT_MODE_IMMEDIATE_KHR)
|
||||
.set_desired_extent(w, h)
|
||||
.add_image_usage_flags(VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT)
|
||||
.set_old_swapchain(old)
|
||||
.build();
|
||||
if (!sc) {
|
||||
log::error("Swapchain: {}", sc.error().message());
|
||||
return false;
|
||||
}
|
||||
destroySwapchainViews();
|
||||
if (old) vkDestroySwapchainKHR(device_, old, nullptr);
|
||||
|
||||
swapchain_ = sc->swapchain;
|
||||
swapchainFormat_ = sc->image_format;
|
||||
extent_ = sc->extent;
|
||||
images_ = sc->get_images().value();
|
||||
views_ = sc->get_image_views().value();
|
||||
renderDone_.resize(images_.size());
|
||||
for (VkSemaphore& s : renderDone_) {
|
||||
VkSemaphoreCreateInfo sci{VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
|
||||
VK_CHECK(vkCreateSemaphore(device_, &sci, nullptr, &s));
|
||||
}
|
||||
swapchainDirty_ = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Device::destroySwapchainViews() {
|
||||
for (VkImageView v : views_) vkDestroyImageView(device_, v, nullptr);
|
||||
for (VkSemaphore s : renderDone_) vkDestroySemaphore(device_, s, nullptr);
|
||||
views_.clear();
|
||||
renderDone_.clear();
|
||||
}
|
||||
|
||||
VkCommandBuffer Device::beginFrame() {
|
||||
if (swapchainDirty_ && !createSwapchain()) return nullptr;
|
||||
|
||||
Frame& f = frames_[frameIndex_];
|
||||
VK_CHECK(vkWaitForFences(device_, 1, &f.inFlight, VK_TRUE, UINT64_MAX));
|
||||
VkResult r = vkAcquireNextImageKHR(device_, swapchain_, UINT64_MAX, f.acquired, VK_NULL_HANDLE, &imageIndex_);
|
||||
if (r == VK_ERROR_OUT_OF_DATE_KHR) {
|
||||
swapchainDirty_ = true;
|
||||
return nullptr;
|
||||
}
|
||||
if (r != VK_SUBOPTIMAL_KHR) VK_CHECK(r);
|
||||
VK_CHECK(vkResetFences(device_, 1, &f.inFlight));
|
||||
|
||||
VK_CHECK(vkResetCommandBuffer(f.cmd, 0));
|
||||
VkCommandBufferBeginInfo bi{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
|
||||
bi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
||||
VK_CHECK(vkBeginCommandBuffer(f.cmd, &bi));
|
||||
transitionImage(f.cmd, images_[imageIndex_], VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
||||
return f.cmd;
|
||||
}
|
||||
|
||||
void Device::endFrame() {
|
||||
Frame& f = frames_[frameIndex_];
|
||||
transitionImage(f.cmd, images_[imageIndex_], VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
|
||||
VK_CHECK(vkEndCommandBuffer(f.cmd));
|
||||
|
||||
VkSemaphoreSubmitInfo wait{VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO};
|
||||
wait.semaphore = f.acquired;
|
||||
wait.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
VkSemaphoreSubmitInfo signal{VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO};
|
||||
signal.semaphore = renderDone_[imageIndex_];
|
||||
signal.stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
|
||||
VkCommandBufferSubmitInfo cbi{VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO};
|
||||
cbi.commandBuffer = f.cmd;
|
||||
VkSubmitInfo2 si{VK_STRUCTURE_TYPE_SUBMIT_INFO_2};
|
||||
si.waitSemaphoreInfoCount = 1;
|
||||
si.pWaitSemaphoreInfos = &wait;
|
||||
si.commandBufferInfoCount = 1;
|
||||
si.pCommandBufferInfos = &cbi;
|
||||
si.signalSemaphoreInfoCount = 1;
|
||||
si.pSignalSemaphoreInfos = &signal;
|
||||
VK_CHECK(vkQueueSubmit2(queue_, 1, &si, f.inFlight));
|
||||
|
||||
VkPresentInfoKHR pi{VK_STRUCTURE_TYPE_PRESENT_INFO_KHR};
|
||||
pi.waitSemaphoreCount = 1;
|
||||
pi.pWaitSemaphores = &renderDone_[imageIndex_];
|
||||
pi.swapchainCount = 1;
|
||||
pi.pSwapchains = &swapchain_;
|
||||
pi.pImageIndices = &imageIndex_;
|
||||
VkResult r = vkQueuePresentKHR(queue_, &pi);
|
||||
if (r == VK_ERROR_OUT_OF_DATE_KHR || r == VK_SUBOPTIMAL_KHR) swapchainDirty_ = true;
|
||||
else VK_CHECK(r);
|
||||
|
||||
frameIndex_ = (frameIndex_ + 1) % kFramesInFlight;
|
||||
}
|
||||
|
||||
void Device::shutdown() {
|
||||
if (!device_) return;
|
||||
vkDeviceWaitIdle(device_);
|
||||
for (Frame& f : frames_) {
|
||||
vkDestroyCommandPool(device_, f.pool, nullptr);
|
||||
vkDestroySemaphore(device_, f.acquired, nullptr);
|
||||
vkDestroyFence(device_, f.inFlight, nullptr);
|
||||
}
|
||||
vkDestroyCommandPool(device_, immPool_, nullptr);
|
||||
vkDestroyFence(device_, immFence_, nullptr);
|
||||
destroySwapchainViews();
|
||||
vkDestroySwapchainKHR(device_, swapchain_, nullptr);
|
||||
vmaDestroyAllocator(allocator_);
|
||||
vkDestroyDevice(device_, nullptr);
|
||||
vkDestroySurfaceKHR(instance_, surface_, nullptr);
|
||||
if (messenger_) vkDestroyDebugUtilsMessengerEXT(instance_, messenger_, nullptr);
|
||||
vkDestroyInstance(instance_, nullptr);
|
||||
device_ = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
void Device::vram(uint64_t& total, uint64_t& used, uint64_t& ours) const {
|
||||
total = used = ours = 0;
|
||||
if (!allocator_) return;
|
||||
const VkPhysicalDeviceMemoryProperties* mem = nullptr;
|
||||
vmaGetMemoryProperties(allocator_, &mem);
|
||||
VmaBudget budgets[VK_MAX_MEMORY_HEAPS];
|
||||
vmaGetHeapBudgets(allocator_, budgets);
|
||||
for (uint32_t h = 0; h < mem->memoryHeapCount; ++h) {
|
||||
if (!(mem->memoryHeaps[h].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT)) continue;
|
||||
total += mem->memoryHeaps[h].size;
|
||||
used += budgets[h].usage; // (the whole process's, with VK_EXT_memory_budget; VMA's own blocks without)
|
||||
ours += budgets[h].statistics.blockBytes;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace keel::vk
|
||||
115
engine/rhi/vulkan/vk_device.h
Normal file
115
engine/rhi/vulkan/vk_device.h
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
#pragma once
|
||||
// Vulkan 1.3 device, swapchain and frames in flight.
|
||||
// Uses dynamic rendering and synchronization2; no render passes.
|
||||
#include <volk.h>
|
||||
#include <vk_mem_alloc.h>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace keel {
|
||||
class Window;
|
||||
}
|
||||
|
||||
namespace keel::vk {
|
||||
|
||||
#define VK_CHECK(x) ::keel::vk::check((x), #x, __FILE__, __LINE__)
|
||||
void check(VkResult r, const char* expr, const char* file, int line);
|
||||
|
||||
void transitionImage(VkCommandBuffer cmd, VkImage image, VkImageLayout from, VkImageLayout to,
|
||||
VkImageAspectFlags aspect = VK_IMAGE_ASPECT_COLOR_BIT);
|
||||
|
||||
struct Buffer {
|
||||
VkBuffer buffer = VK_NULL_HANDLE;
|
||||
VmaAllocation alloc = VK_NULL_HANDLE;
|
||||
VkDeviceAddress address = 0;
|
||||
void* mapped = nullptr; // host-visible buffers only
|
||||
};
|
||||
|
||||
class Device {
|
||||
public:
|
||||
static constexpr uint32_t kFramesInFlight = 2;
|
||||
|
||||
bool init(const Window& window, bool validation);
|
||||
void shutdown();
|
||||
|
||||
// Returns a command buffer recording into the current swapchain image (already in
|
||||
// COLOR_ATTACHMENT_OPTIMAL), or nullptr when no frame can be drawn (minimized, resizing).
|
||||
VkCommandBuffer beginFrame();
|
||||
void endFrame();
|
||||
|
||||
// Records and runs commands right away, waiting for completion. For loading, not per frame.
|
||||
void immediateSubmit(const std::function<void(VkCommandBuffer)>& record);
|
||||
// hostVisible: mapped CPU-writable memory (staging); otherwise device-local.
|
||||
// readback: host-cached memory the CPU reads from (screenshots); otherwise write-combined.
|
||||
Buffer createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, bool hostVisible, bool readback = false);
|
||||
void destroyBuffer(Buffer& b);
|
||||
// Device-local buffer filled with `data` through a staging copy.
|
||||
Buffer uploadBuffer(const void* data, VkDeviceSize size, VkBufferUsageFlags usage);
|
||||
|
||||
void setVsync(bool vsync) { vsync_ = vsync; swapchainDirty_ = true; }
|
||||
void markResized() { swapchainDirty_ = true; }
|
||||
void waitIdle() { vkDeviceWaitIdle(device_); }
|
||||
|
||||
VkInstance instance() const { return instance_; }
|
||||
VkPhysicalDevice physicalDevice() const { return physical_; }
|
||||
// Hardware ray queries (VK_KHR_ray_query + acceleration structures): real-time GI uses them.
|
||||
bool rayQuery() const { return rayQuery_; }
|
||||
VkDevice device() const { return device_; }
|
||||
VkQueue queue() const { return queue_; }
|
||||
uint32_t queueFamily() const { return queueFamily_; }
|
||||
VmaAllocator allocator() const { return allocator_; }
|
||||
const std::string& gpuName() const { return gpuName_; }
|
||||
const std::string& gpuDriver() const { return gpuDriver_; }
|
||||
// VRAM (the device-local heaps): total, used by everyone (ours if the driver can't say), ours.
|
||||
void vram(uint64_t& total, uint64_t& used, uint64_t& ours) const;
|
||||
|
||||
VkFormat swapchainFormat() const { return swapchainFormat_; }
|
||||
VkExtent2D extent() const { return extent_; }
|
||||
uint32_t frameIndex() const { return frameIndex_; } // which frame-in-flight slot is recording
|
||||
uint32_t imageCount() const { return uint32_t(images_.size()); }
|
||||
VkImage currentImage() const { return images_[imageIndex_]; }
|
||||
VkImageView currentImageView() const { return views_[imageIndex_]; }
|
||||
|
||||
private:
|
||||
struct Frame {
|
||||
VkCommandPool pool = VK_NULL_HANDLE;
|
||||
VkCommandBuffer cmd = VK_NULL_HANDLE;
|
||||
VkSemaphore acquired = VK_NULL_HANDLE;
|
||||
VkFence inFlight = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
bool createSwapchain();
|
||||
void destroySwapchainViews();
|
||||
|
||||
const Window* window_ = nullptr;
|
||||
VkInstance instance_ = VK_NULL_HANDLE;
|
||||
VkDebugUtilsMessengerEXT messenger_ = VK_NULL_HANDLE;
|
||||
VkSurfaceKHR surface_ = VK_NULL_HANDLE;
|
||||
VkPhysicalDevice physical_ = VK_NULL_HANDLE;
|
||||
bool rayQuery_ = false, memoryBudget_ = false;
|
||||
std::string gpuName_, gpuDriver_;
|
||||
VkDevice device_ = VK_NULL_HANDLE;
|
||||
VkQueue queue_ = VK_NULL_HANDLE;
|
||||
uint32_t queueFamily_ = 0;
|
||||
VmaAllocator allocator_ = VK_NULL_HANDLE;
|
||||
|
||||
VkSwapchainKHR swapchain_ = VK_NULL_HANDLE;
|
||||
VkFormat swapchainFormat_ = VK_FORMAT_B8G8R8A8_UNORM;
|
||||
VkExtent2D extent_{};
|
||||
std::vector<VkImage> images_;
|
||||
std::vector<VkImageView> views_;
|
||||
std::vector<VkSemaphore> renderDone_; // per swapchain image, so present never reuses one in flight
|
||||
bool vsync_ = true;
|
||||
bool swapchainDirty_ = false;
|
||||
|
||||
Frame frames_[kFramesInFlight];
|
||||
VkCommandPool immPool_ = VK_NULL_HANDLE;
|
||||
VkCommandBuffer immCmd_ = VK_NULL_HANDLE;
|
||||
VkFence immFence_ = VK_NULL_HANDLE;
|
||||
uint32_t frameIndex_ = 0;
|
||||
uint32_t imageIndex_ = 0;
|
||||
};
|
||||
|
||||
} // namespace keel::vk
|
||||
Loading…
Reference in a new issue