diff --git a/.gitignore b/.gitignore index 692201a..4c04705 100644 --- a/.gitignore +++ b/.gitignore @@ -40,4 +40,5 @@ apps/apple/VulkanSplatting/ThirdParty # xcuserdata under any directory is ignored xcuserdata/ -project.xcconfig \ No newline at end of file +project.xcconfig +/build diff --git a/CMakeLists.txt b/CMakeLists.txt index 1eb2b73..b5e0f97 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,7 @@ if (WIN32 OR APPLE) endif() set(GLM_ENABLE_CXX_20 ON CACHE INTERNAL "Enable experimental features") + set(GLM_DISABLE_AUTO_DETECTION ON CACHE INTERNAL "Disable auto detection to avoid poison-system-directories warning") FetchContent_Declare( glm @@ -65,6 +66,12 @@ FetchContent_Declare(imgui ) FetchContent_MakeAvailable(imgui) +FetchContent_Declare(stb + GIT_REPOSITORY https://github.com/nothings/stb.git + GIT_TAG 5736b15f7ea0ffb08dd38af21067c314d6a3aae9 +) +FetchContent_MakeAvailable(stb) + if (APPLE) add_compile_definitions(__APPLE__) diff --git a/apps/viewer/main.cpp b/apps/viewer/main.cpp index 8393072..7c99dea 100644 --- a/apps/viewer/main.cpp +++ b/apps/viewer/main.cpp @@ -25,6 +25,8 @@ int main(int argc, char** argv) { args::ValueFlag widthFlag{parser, "width", "Set window width", {'w', "width"}}; args::ValueFlag heightFlag{parser, "height", "Set window height", {'h', "height"}}; args::Flag noGuiFlag{parser, "no-gui", "Disable GUI", { "no-gui"}}; + args::ValueFlag outputFlag{parser, "output", "Output image path for headless rendering", {'o', "output"}}; + args::ValueFlagList cameraFlag{parser, "camera", "Camera configuration file path(s)", {'c', "camera"}}; args::Positional scenePath{parser, "scene", "Path to scene file", "scene.ply"}; try { @@ -65,7 +67,7 @@ int main(int argc, char** argv) { // check that the scene file exists if (!std::filesystem::exists(config.scene)) { spdlog::critical("File does not exist: {}", config.scene); - return 0; + return 1; } if (validationLayersFlag) { @@ -86,21 +88,65 @@ int main(int argc, char** argv) { config.enableGui = true; } + if (outputFlag) { + config.outputPath = args::get(outputFlag); + config.enableGui = false; + } + + // Handle multiple camera configurations + std::vector cameraPaths; + if (cameraFlag) { + cameraPaths = args::get(cameraFlag); + + // Multiple cameras only make sense with output path + if (cameraPaths.size() > 1 && !outputFlag) { + spdlog::critical("Multiple camera configurations can only be used with output path (-o)"); + return 1; + } + + // For single camera, use the existing logic + if (cameraPaths.size() == 1) { + config.cameraPath = cameraPaths[0]; + } + } + auto width = widthFlag ? args::get(widthFlag) : 1280; auto height = heightFlag ? args::get(heightFlag) : 720; - config.window = VulkanSplatting::createGlfwWindow("Vulkan Splatting", width, height); + if (outputFlag) { + // For headless mode, we still need a window but make it invisible + config.window = VulkanSplatting::createGlfwWindow("Vulkan Splatting (Headless)", width, height); + } else { + config.window = VulkanSplatting::createGlfwWindow("Vulkan Splatting", width, height); + } #ifndef DEBUG try { #endif - auto renderer = VulkanSplatting(config); - renderer.start(); + // Handle multiple camera configurations + if (cameraPaths.size() > 1) { + spdlog::info("Rendering with {} camera configurations", cameraPaths.size()); + + for (size_t i = 0; i < cameraPaths.size(); ++i) { + spdlog::info("Rendering with camera configuration {}: {}", i + 1, cameraPaths[i]); + + // Create a new config for this camera + auto cameraConfig = config; + cameraConfig.cameraPath = cameraPaths[i]; + + auto renderer = VulkanSplatting(cameraConfig); + renderer.start(); + } + } else { + // Single camera or no camera - use existing logic + auto renderer = VulkanSplatting(config); + renderer.start(); + } #ifndef DEBUG } catch (const std::exception& e) { spdlog::critical(e.what()); std::cout << e.what() << std::endl; - return 0; + return 1; } #endif return 0; diff --git a/camera.txt b/camera.txt new file mode 100644 index 0000000..73f889e --- /dev/null +++ b/camera.txt @@ -0,0 +1,15 @@ +# Camera Configuration File +# Format: key value1 value2 value3 ... + +# Camera position (x, y, z) +position 0.0 2.0 5.0 + +# Camera rotation using Euler angles in degrees (pitch, yaw, roll) +euler -15.0 0.0 0.0 + +# Field of view in degrees +fov 60.0 + +# Near and far clipping planes +near 0.1 +far 100.0 \ No newline at end of file diff --git a/camera_lookat.txt b/camera_lookat.txt new file mode 100644 index 0000000..3d9ae72 --- /dev/null +++ b/camera_lookat.txt @@ -0,0 +1,18 @@ +# Camera Configuration File with Look-At +# Alternative format using target and up vector + +# Camera position +position 3.0 3.0 3.0 + +# Look at target point +target 0.0 0.0 0.0 + +# Up vector (optional, defaults to 0,1,0) +up 0.0 1.0 0.0 + +# Field of view +fov 45.0 + +# Clipping planes +near 0.2 +far 1000.0 \ No newline at end of file diff --git a/include/3dgs/3dgs.h b/include/3dgs/3dgs.h index a4d711e..6c6a0b7 100644 --- a/include/3dgs/3dgs.h +++ b/include/3dgs/3dgs.h @@ -20,6 +20,8 @@ class VulkanSplatting { float near = 0.2f; float far = 1000.0f; bool enableGui = false; + std::optional outputPath = std::nullopt; + std::optional cameraPath = std::nullopt; std::shared_ptr window; }; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 36e817f..afa65bd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -38,6 +38,7 @@ target_include_directories(3dgs_cpp ${imgui_SOURCE_DIR}/backends ${spdlog_SOURCE_DIR}/include ${CMAKE_BINARY_DIR}/shaders + ${stb_SOURCE_DIR} ) add_dependencies(3dgs_cpp shaders) diff --git a/src/CameraConfig.cpp b/src/CameraConfig.cpp new file mode 100644 index 0000000..318bbe4 --- /dev/null +++ b/src/CameraConfig.cpp @@ -0,0 +1,134 @@ +#include "CameraConfig.h" +#include +#include +#include +#include +#include +#define GLM_ENABLE_EXPERIMENTAL +#include +#include + +CameraConfig CameraConfig::loadFromFile(const std::string& filePath) { + std::ifstream file(filePath); + if (!file.is_open()) { + throw std::runtime_error("Could not open camera file: " + filePath); + } + + CameraConfig config; + std::string line; + + while (std::getline(file, line)) { + // Skip empty lines and comments + if (line.empty() || line[0] == '#' || line[0] == '/') { + continue; + } + + std::istringstream iss(line); + std::string key; + if (!(iss >> key)) { + continue; + } + + if (key == "position") { + iss >> config.position.x >> config.position.y >> config.position.z; + spdlog::debug("Camera position: ({}, {}, {})", + config.position.x, config.position.y, config.position.z); + } + else if (key == "rotation") { + // Quaternion format: w x y z + iss >> config.rotation.w >> config.rotation.x >> config.rotation.y >> config.rotation.z; + spdlog::debug("Camera rotation (quat): ({}, {}, {}, {})", + config.rotation.w, config.rotation.x, config.rotation.y, config.rotation.z); + } + else if (key == "euler") { + // Euler angles in degrees: pitch yaw roll + glm::vec3 euler; + iss >> euler.x >> euler.y >> euler.z; + config.eulerAngles = euler; + spdlog::debug("Camera euler angles: ({}, {}, {})", euler.x, euler.y, euler.z); + } + else if (key == "target") { + glm::vec3 target; + iss >> target.x >> target.y >> target.z; + config.target = target; + spdlog::debug("Camera target: ({}, {}, {})", target.x, target.y, target.z); + } + else if (key == "up") { + glm::vec3 up; + iss >> up.x >> up.y >> up.z; + config.up = up; + spdlog::debug("Camera up: ({}, {}, {})", up.x, up.y, up.z); + } + else if (key == "fov") { + iss >> config.fov; + spdlog::debug("Camera FOV: {}", config.fov); + } + else if (key == "near") { + iss >> config.nearPlane; + spdlog::debug("Camera near plane: {}", config.nearPlane); + } + else if (key == "far") { + iss >> config.farPlane; + spdlog::debug("Camera far plane: {}", config.farPlane); + } + else { + spdlog::warn("Unknown camera config key: {}", key); + } + } + + // Compute rotation if specified via alternative methods + if (config.eulerAngles.has_value()) { + config.computeRotationFromEuler(); + } else if (config.target.has_value()) { + config.computeRotationFromLookAt(); + } + + return config; +} + +bool CameraConfig::saveToFile(const CameraConfig& config, const std::string& filePath) { + std::ofstream file(filePath); + if (!file.is_open()) { + return false; + } + + file << "# Camera Configuration File\n"; + file << "# Format: key value1 value2 value3 ...\n\n"; + + file << "position " << config.position.x << " " << config.position.y << " " << config.position.z << "\n"; + file << "rotation " << config.rotation.w << " " << config.rotation.x << " " + << config.rotation.y << " " << config.rotation.z << "\n"; + file << "fov " << config.fov << "\n"; + file << "near " << config.nearPlane << "\n"; + file << "far " << config.farPlane << "\n"; + + return true; +} + +void CameraConfig::computeRotationFromEuler() { + if (!eulerAngles.has_value()) return; + + // Convert degrees to radians + glm::vec3 radians = glm::radians(eulerAngles.value()); + + // Create rotation from Euler angles (pitch, yaw, roll) + rotation = glm::quat(radians); + + spdlog::debug("Computed rotation from Euler: ({}, {}, {}, {})", + rotation.w, rotation.x, rotation.y, rotation.z); +} + +void CameraConfig::computeRotationFromLookAt() { + if (!target.has_value()) return; + + glm::vec3 upVec = up.value_or(glm::vec3(0.0f, 1.0f, 0.0f)); + + // Create look-at matrix + glm::mat4 lookAt = glm::lookAt(position, target.value(), upVec); + + // Extract rotation quaternion from look-at matrix + rotation = glm::quat_cast(lookAt); + + spdlog::debug("Computed rotation from look-at: ({}, {}, {}, {})", + rotation.w, rotation.x, rotation.y, rotation.z); +} \ No newline at end of file diff --git a/src/CameraConfig.h b/src/CameraConfig.h new file mode 100644 index 0000000..225a651 --- /dev/null +++ b/src/CameraConfig.h @@ -0,0 +1,31 @@ +#ifndef CAMERACONFIG_H +#define CAMERACONFIG_H + +#include +#include +#include +#include + +struct CameraConfig { + glm::vec3 position{0.0f, 0.0f, 0.0f}; + glm::quat rotation{1.0f, 0.0f, 0.0f, 0.0f}; // Identity quaternion + float fov = 45.0f; + float nearPlane = 0.1f; + float farPlane = 1000.0f; + + // Alternative: specify rotation as Euler angles (degrees) + std::optional eulerAngles = std::nullopt; + + // Alternative: specify look-at target + std::optional target = std::nullopt; + std::optional up = std::nullopt; + + static CameraConfig loadFromFile(const std::string& filePath); + static bool saveToFile(const CameraConfig& config, const std::string& filePath); + +private: + void computeRotationFromEuler(); + void computeRotationFromLookAt(); +}; + +#endif // CAMERACONFIG_H \ No newline at end of file diff --git a/src/ImageSaver.cpp b/src/ImageSaver.cpp new file mode 100644 index 0000000..d9b9c2d --- /dev/null +++ b/src/ImageSaver.cpp @@ -0,0 +1,132 @@ +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" + +#include "ImageSaver.h" +#include "vulkan/Buffer.h" +#include +#include + +std::string generateIncrementalPath(const std::string& dirPath) { + std::filesystem::path dir(dirPath); + int counter = 1; + + while (true) { + std::string filename = std::to_string(counter) + ".png"; + std::filesystem::path fullPath = dir / filename; + + if (!std::filesystem::exists(fullPath)) { + return fullPath.string(); + } + counter++; + } +} + +void ImageSaver::saveImage(const std::shared_ptr& context, + const std::shared_ptr& image, + const std::string& outputPath) { + + std::string finalPath = outputPath; + + // Check if outputPath is a directory + if (std::filesystem::is_directory(outputPath)) { + finalPath = generateIncrementalPath(outputPath); + spdlog::info("Directory detected, using incremental filename: {}", finalPath); + } + + spdlog::info("Starting image save to {} ({}x{})", finalPath, image->extent.width, image->extent.height); + + auto imageSize = image->extent.width * image->extent.height * 4; // RGBA + + auto buffer = std::make_shared(context, imageSize, + vk::BufferUsageFlagBits::eTransferDst, + VMA_MEMORY_USAGE_CPU_ONLY, + VMA_ALLOCATION_CREATE_MAPPED_BIT); + + spdlog::debug("Created transfer buffer of size {}", imageSize); + + copyImageToBuffer(context, image, buffer); + + spdlog::debug("Image copied to buffer"); + + auto* data = static_cast(buffer->allocation_info.pMappedData); + + if (!data) { + throw std::runtime_error("Buffer data is null - memory mapping failed"); + } + + spdlog::debug("Starting format conversion for {}x{} image", image->extent.width, image->extent.height); + + // Convert BGRA to RGBA if needed + for (uint32_t i = 0; i < image->extent.width * image->extent.height; ++i) { + uint8_t* pixel = &data[i * 4]; + std::swap(pixel[0], pixel[2]); // Swap B and R channels + } + + spdlog::debug("Format conversion complete, writing PNG file"); + + int result = stbi_write_png(finalPath.c_str(), + static_cast(image->extent.width), + static_cast(image->extent.height), + 4, data, + static_cast(image->extent.width * 4)); + + if (result == 0) { + throw std::runtime_error("Failed to save image to " + finalPath); + } + + spdlog::info("Saved image to {}", finalPath); +} + +void ImageSaver::copyImageToBuffer(const std::shared_ptr& context, + const std::shared_ptr& image, + const std::shared_ptr& buffer) { + + auto commandPool = context->device->createCommandPoolUnique( + vk::CommandPoolCreateInfo{vk::CommandPoolCreateFlagBits::eTransient, + context->queues[VulkanContext::Queue::GRAPHICS].queueFamily}); + + auto commandBuffer = std::move(context->device->allocateCommandBuffersUnique( + vk::CommandBufferAllocateInfo(commandPool.get(), vk::CommandBufferLevel::ePrimary, 1))[0]); + + commandBuffer->begin(vk::CommandBufferBeginInfo{vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + + // Transition image layout for transfer + vk::ImageMemoryBarrier barrier{}; + barrier.oldLayout = vk::ImageLayout::ePresentSrcKHR; // Swapchain images are in present layout + barrier.newLayout = vk::ImageLayout::eTransferSrcOptimal; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image->image; + barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + barrier.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead; + + commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput, + vk::PipelineStageFlagBits::eTransfer, + {}, {}, {}, barrier); + + // Copy image to buffer + vk::BufferImageCopy region{}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + region.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eColor; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + region.imageOffset = vk::Offset3D{0, 0, 0}; + region.imageExtent = vk::Extent3D{image->extent.width, image->extent.height, 1}; + + commandBuffer->copyImageToBuffer(image->image, vk::ImageLayout::eTransferSrcOptimal, + buffer->buffer, region); + + commandBuffer->end(); + + auto submitInfo = vk::SubmitInfo{}.setCommandBuffers(commandBuffer.get()); + context->queues[VulkanContext::Queue::GRAPHICS].queue.submit(submitInfo); + context->queues[VulkanContext::Queue::GRAPHICS].queue.waitIdle(); +} \ No newline at end of file diff --git a/src/ImageSaver.h b/src/ImageSaver.h new file mode 100644 index 0000000..941de4f --- /dev/null +++ b/src/ImageSaver.h @@ -0,0 +1,22 @@ +#ifndef IMAGESAVER_H +#define IMAGESAVER_H + +#include +#include +#include "vulkan/VulkanContext.h" + +class Buffer; + +class ImageSaver { +public: + static void saveImage(const std::shared_ptr& context, + const std::shared_ptr& image, + const std::string& outputPath); + +private: + static void copyImageToBuffer(const std::shared_ptr& context, + const std::shared_ptr& image, + const std::shared_ptr& buffer); +}; + +#endif // IMAGESAVER_H \ No newline at end of file diff --git a/src/Renderer.cpp b/src/Renderer.cpp index fe7e4e4..416699f 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -13,12 +13,15 @@ #include #include "vulkan/Utils.h" +#include "ImageSaver.h" +#include "CameraConfig.h" #include void Renderer::initialize() { initializeVulkan(); createGui(); + loadCameraFromFile(); loadSceneToGPU(); createPreprocessPipeline(); createPrefixSumPipeline(); @@ -426,6 +429,11 @@ void Renderer::draw() { } void Renderer::run() { + if (configuration.outputPath.has_value()) { + renderOnceAndSave(); + return; + } + while (running) { if (!window->tick()) { break; @@ -544,10 +552,23 @@ bool Renderer::recordRenderCommandBuffer(uint32_t currentFrame) { sortBufferSizeMultiplier++; } spdlog::info("Reallocating sort buffers. {} -> {}", old, sortBufferSizeMultiplier); - sortKBufferEven->realloc(scene->getNumVertices() * sizeof(uint64_t) * sortBufferSizeMultiplier); - sortKBufferOdd->realloc(scene->getNumVertices() * sizeof(uint64_t) * sortBufferSizeMultiplier); - sortVBufferEven->realloc(scene->getNumVertices() * sizeof(uint32_t) * sortBufferSizeMultiplier); - sortVBufferOdd->realloc(scene->getNumVertices() * sizeof(uint32_t) * sortBufferSizeMultiplier); + + try { + sortKBufferEven->realloc(scene->getNumVertices() * sizeof(uint64_t) * sortBufferSizeMultiplier); + sortKBufferOdd->realloc(scene->getNumVertices() * sizeof(uint64_t) * sortBufferSizeMultiplier); + sortVBufferEven->realloc(scene->getNumVertices() * sizeof(uint32_t) * sortBufferSizeMultiplier); + sortVBufferOdd->realloc(scene->getNumVertices() * sizeof(uint32_t) * sortBufferSizeMultiplier); + } catch (const std::exception& e) { + spdlog::error("Failed to reallocate sort buffers: {}", e.what()); + spdlog::error("GPU may be out of memory. Required size: {} MB per buffer", + (scene->getNumVertices() * sizeof(uint64_t) * sortBufferSizeMultiplier) / (1024.0 * 1024.0)); + + // Reset multiplier to previous value + sortBufferSizeMultiplier = old; + + // Exit gracefully + std::exit(1); + } uint32_t globalInvocationSize = scene->getNumVertices() * sortBufferSizeMultiplier / numRadixSortBlocksPerWorkgroup; @@ -556,7 +577,12 @@ bool Renderer::recordRenderCommandBuffer(uint32_t currentFrame) { auto numWorkgroups = (globalInvocationSize + 256 - 1) / 256; - sortHistBuffer->realloc(numWorkgroups * 256 * sizeof(uint32_t)); + try { + sortHistBuffer->realloc(numWorkgroups * 256 * sizeof(uint32_t)); + } catch (const std::exception& e) { + spdlog::error("Failed to reallocate histogram buffer: {}", e.what()); + std::exit(1); + } recordPreprocessCommandBuffer(); return false; @@ -753,5 +779,77 @@ void Renderer::updateUniforms() { uniformBuffer->upload(&data, sizeof(UniformBuffer), 0); } +void Renderer::loadCameraFromFile() { + if (configuration.cameraPath.has_value()) { + spdlog::info("Loading camera configuration from: {}", configuration.cameraPath.value()); + + auto cameraConfig = CameraConfig::loadFromFile(configuration.cameraPath.value()); + + // Apply camera configuration + camera.position = cameraConfig.position; + camera.rotation = cameraConfig.rotation; + camera.fov = cameraConfig.fov; + camera.nearPlane = cameraConfig.nearPlane; + camera.farPlane = cameraConfig.farPlane; + + spdlog::info("Camera loaded successfully - Position: ({:.2f}, {:.2f}, {:.2f}), FOV: {:.1f}°", + camera.position.x, camera.position.y, camera.position.z, camera.fov); + } +} + +void Renderer::renderOnceAndSave() { + // Headless rendering - render a single frame without presentation + + // Simulate normal frame acquisition + auto ret = context->device->waitForFences(inflightFences[0].get(), VK_TRUE, UINT64_MAX); + if (ret != vk::Result::eSuccess) { + throw std::runtime_error("Failed to wait for fence"); + } + context->device->resetFences(inflightFences[0].get()); + + // Use first swapchain image + currentImageIndex = 0; + + // Skip handleInput() in headless mode - no user interaction + updateUniforms(); + + // Submit preprocessing (same as normal draw()) + auto submitInfo = vk::SubmitInfo{}.setCommandBuffers(preprocessCommandBuffer.get()); + context->queues[VulkanContext::Queue::COMPUTE].queue.submit(submitInfo, inflightFences[0].get()); + + ret = context->device->waitForFences(inflightFences[0].get(), VK_TRUE, UINT64_MAX); + if (ret != vk::Result::eSuccess) { + throw std::runtime_error("Failed to wait for fence"); + } + context->device->resetFences(inflightFences[0].get()); + + // Record render command buffer (same as normal draw()) + if (!recordRenderCommandBuffer(0)) { + // If buffer reallocation happened, we need to try again next frame + // In headless mode, just skip this and try once more + spdlog::warn("Buffer reallocation occurred, retrying render..."); + + submitInfo = vk::SubmitInfo{}.setCommandBuffers(preprocessCommandBuffer.get()); + context->queues[VulkanContext::Queue::COMPUTE].queue.submit(submitInfo, inflightFences[0].get()); + + ret = context->device->waitForFences(inflightFences[0].get(), VK_TRUE, UINT64_MAX); + if (ret != vk::Result::eSuccess) { + throw std::runtime_error("Failed to wait for fence"); + } + context->device->resetFences(inflightFences[0].get()); + + if (!recordRenderCommandBuffer(0)) { + throw std::runtime_error("Failed to record render command buffer after retry"); + } + } + + // Submit render commands (without presentation) + submitInfo = vk::SubmitInfo{}.setCommandBuffers(renderCommandBuffer.get()); + context->queues[VulkanContext::Queue::COMPUTE].queue.submit(submitInfo, inflightFences[0].get()); + + context->device->waitIdle(); + ImageSaver::saveImage(context, swapchain->swapchainImages[currentImageIndex], configuration.outputPath.value()); +} + Renderer::~Renderer() { } diff --git a/src/Renderer.h b/src/Renderer.h index 04347a2..18e8f94 100644 --- a/src/Renderer.h +++ b/src/Renderer.h @@ -165,6 +165,10 @@ class Renderer { void createCommandPool(); void updateUniforms(); + + void loadCameraFromFile(); + + void renderOnceAndSave(); }; diff --git a/src/third_party/vk_enum_string_helper.h b/src/third_party/vk_enum_string_helper.h index 1c84663..fd29844 100644 --- a/src/third_party/vk_enum_string_helper.h +++ b/src/third_party/vk_enum_string_helper.h @@ -7,6 +7,15 @@ // SPDX-License-Identifier: Apache-2.0 #pragma once + +#ifndef VK_DRIVER_ID_MESA_AGXV +#define VK_DRIVER_ID_MESA_AGXV 99999 +#endif + +#ifndef VK_BUFFER_USAGE_2_EXECUTION_GRAPH_SCRATCH_BIT_AMDX +#define VK_BUFFER_USAGE_2_EXECUTION_GRAPH_SCRATCH_BIT_AMDX 99999 +#endif + #ifdef __cplusplus #include #endif diff --git a/src/vulkan/Buffer.cpp b/src/vulkan/Buffer.cpp index a55ada8..47b52ae 100644 --- a/src/vulkan/Buffer.cpp +++ b/src/vulkan/Buffer.cpp @@ -121,10 +121,30 @@ Buffer::~Buffer() { } void Buffer::realloc(uint64_t newSize) { - vmaDestroyBuffer(context->allocator, static_cast(buffer), allocation); - + // Store old buffer info in case new allocation fails + auto oldBuffer = buffer; + auto oldAllocation = allocation; + auto oldSize = size; + auto oldAllocationInfo = allocation_info; + + // Try to allocate new buffer first size = newSize; - alloc(); + buffer = vk::Buffer{}; // Reset to avoid double-free in alloc() error path + allocation = nullptr; + + try { + alloc(); + } catch (const std::exception& e) { + // Restore old buffer info on failure + buffer = oldBuffer; + allocation = oldAllocation; + size = oldSize; + allocation_info = oldAllocationInfo; + throw std::runtime_error("Failed to reallocate buffer: " + std::string(e.what())); + } + + // Only destroy old buffer after successful allocation + vmaDestroyBuffer(context->allocator, static_cast(oldBuffer), oldAllocation); vk::DescriptorBufferInfo bufferInfo(buffer, allocation_info.offset, size); diff --git a/src/vulkan/VulkanContext.h b/src/vulkan/VulkanContext.h index e71bbf2..9806b10 100644 --- a/src/vulkan/VulkanContext.h +++ b/src/vulkan/VulkanContext.h @@ -1,7 +1,7 @@ #ifndef VULKANCONTEXT_H #define VULKANCONTEXT_H #define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1 -#define VULKAN_HPP_TYPESAFE_CONVERSION +#define VULKAN_HPP_TYPESAFE_CONVERSION 1 #define FRAMES_IN_FLIGHT 1 diff --git a/src/vulkan/windowing/GLFWWindow.cpp b/src/vulkan/windowing/GLFWWindow.cpp index 576982d..8522996 100644 --- a/src/vulkan/windowing/GLFWWindow.cpp +++ b/src/vulkan/windowing/GLFWWindow.cpp @@ -8,6 +8,11 @@ GLFWWindow::GLFWWindow(std::string name, int width, int height) { glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + + // Make window invisible if it's headless mode + if (name.find("Headless") != std::string::npos) { + glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); + } window = glfwCreateWindow(width, height, name.c_str(), nullptr, nullptr); }