Skip to content

Refactorings - #16

Merged
kimkulling merged 1 commit into
mainfrom
feature/add_vulkan_device
Jul 14, 2026
Merged

Refactorings#16
kimkulling merged 1 commit into
mainfrom
feature/add_vulkan_device

Conversation

@kimkulling

@kimkulling kimkulling commented Jul 14, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • Bug Fixes

    • Improved Vulkan buffer-copy success reporting.
    • Strengthened graphics resource initialization, cleanup, synchronization, and swapchain handling for more reliable rendering.
    • Improved handling of Vulkan queue and device capabilities.
  • Refactor

    • Standardized Vulkan rendering resource management to improve consistency and maintainability without changing the public API.
  • Documentation

    • Expanded developer-facing documentation for behavior tree nodes and Vulkan buffer management.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The PR reorganizes Vulkan renderer state around m* members, introduces queue-family and device-requirement types, updates resource lifecycle operations, restricts buffer helper linkage, fixes a buffer return path, and adjusts a behavior-tree stub and documentation.

Changes

Vulkan renderer

Layer / File(s) Summary
Vulkan type and device contracts
src/runtime/renderer/vulkantypes.h, src/runtime/renderer/vulkandevice.*, src/runtime/renderer/vulkanbuffer.*
Queue-family and device-requirement declarations are reorganized, buffer helpers become translation-unit local, the buffer device member is removed, and copyBuffer explicitly returns success.
Renderer state and Vulkan setup
src/runtime/renderer/RHIVulkan.cpp
Vulkan implementation state, device setup, swapchain creation, pipeline construction, and initialization consistently use m* members.
Rendering and resource operations
src/runtime/renderer/RHIVulkan.cpp
Framebuffer, command, synchronization, texture, geometry, descriptor, and submission paths use the updated renderer state.
Renderer cleanup and resize wiring
src/runtime/renderer/RHIVulkan.cpp
Shutdown destroys resources through prefixed handles, and resize updates the prefixed framebuffer-resized flag.

Behavior tree declarations

Layer / File(s) Summary
Node stub and documentation
src/runtime/ai/behavior_tree.h
The addChild stub body and documentation ordering for getNodeType and getNodeStatus are adjusted.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested reviewers: kullingk

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is too generic and does not convey the main changes in the pull request. Use a concise title that names the primary refactor, such as the Vulkan renderer/device cleanup or behavior tree documentation update.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/add_vulkan_device

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@kimkulling
kimkulling merged commit b180b33 into main Jul 14, 2026
2 of 3 checks passed
@kimkulling
kimkulling deleted the feature/add_vulkan_device branch July 14, 2026 20:06
@sonarqubecloud

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/runtime/renderer/RHIVulkan.cpp (4)

1116-1148: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Duplicate (and stale) texture layout transition call.

Lines 1142 and 1144 issue the identical transitionImageLayout(mTextureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) call back-to-back. The second call is not just redundant — it also submits a stale oldLayout (the image is already SHADER_READ_ONLY_OPTIMAL after the first call), and each call performs a synchronous vkQueueWaitIdle via endSingleTimeCommands.

🐛 Proposed fix
         transitionImageLayout(mTextureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
-
-        transitionImageLayout(mTextureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/renderer/RHIVulkan.cpp` around lines 1116 - 1148, Remove the
duplicate second transitionImageLayout call in RHIImpl::createTextureImage,
keeping the first transition from VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL to
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL before staging-buffer cleanup.

382-439: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

createLogicalDevice inconsistently reads from mQueueFamilyIndices instead of its own qfIndices parameter.

Line 383 refreshes the parameter qfIndices, but lines 386 and 400 read mQueueFamilyIndices (the struct member) instead, while lines 435-436 correctly use qfIndices. This only produces correct results today because the sole call site (mImpl->createLogicalDevice(mImpl->mEnableValidationLayers, mImpl->mPhysicalDevice, mImpl->mQueueFamilyIndices)) happens to pass mQueueFamilyIndices by reference. If ever called with a different QueueFamilyIndices instance, lines 386/400 would silently use stale/unrelated data from mQueueFamilyIndices, risking a std::bad_optional_access if it's still empty.

🐛 Proposed fix
-        std::set<uint32_t> uniqueQueueFamilies = { mQueueFamilyIndices.graphicsFamily.value(), mQueueFamilyIndices.presentFamily.value() };
+        std::set<uint32_t> uniqueQueueFamilies = { qfIndices.graphicsFamily.value(), qfIndices.presentFamily.value() };
...
-        queueCreateInfo.queueFamilyIndex = mQueueFamilyIndices.graphicsFamily.value();
+        queueCreateInfo.queueFamilyIndex = qfIndices.graphicsFamily.value();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/renderer/RHIVulkan.cpp` around lines 382 - 439, Use the refreshed
qfIndices parameter consistently throughout createLogicalDevice: replace the
mQueueFamilyIndices reads when constructing uniqueQueueFamilies and configuring
the queue create info with qfIndices. Preserve the existing queue retrieval and
device creation behavior.

1491-1530: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Inverted validation-layer support check.

if (mImpl->mEnableValidationLayers && !mImpl->checkValidationLayerSupport()) enables the validation-layer createInfo fields exactly when support checking fails, and takes the else (disabling layers) when support checking succeeds — the opposite of the intended behavior. If validation is requested but VK_LAYER_KHRONOS_validation isn't installed, this forces vkCreateInstance to request it anyway (risking VK_ERROR_LAYER_NOT_PRESENT), while a system that does have it installed never gets it enabled.

🐛 Proposed fix
-        if (mImpl->mEnableValidationLayers && !mImpl->checkValidationLayerSupport()) {
+        if (mImpl->mEnableValidationLayers && mImpl->checkValidationLayerSupport()) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/renderer/RHIVulkan.cpp` around lines 1491 - 1530, Correct the
condition in the validation-layer setup around
mImpl->checkValidationLayerSupport() so validation layers are enabled only when
mImpl->mEnableValidationLayers is true and support checking succeeds. Preserve
the existing fallback that clears enabled layers and pNext when validation is
disabled or unsupported.

648-773: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

mVertShaderModule/mFragShaderModule are dead fields — the shutdown destroy calls on them are permanent no-ops.

createGraphicsPipeline creates local vertShaderModule/fragShaderModule (lines 652-653) and destroys those locals immediately after pipeline creation (lines 771-772). The struct's mVertShaderModule/mFragShaderModule members are never assigned, so RHI::shutdown()'s vkDestroyShaderModule(mImpl->mDevice, mImpl->mFragShaderModule, nullptr) / ...mVertShaderModule... calls always operate on null handles. No leak occurs (locals are already cleaned up), but the members are misleading dead state.

♻️ Proposed fix (remove the dead members and no-op shutdown calls)
-        VkShaderModule mVertShaderModule{};
-        VkShaderModule mFragShaderModule{};

And in RHI::shutdown():

-        vkDestroyShaderModule(mImpl->mDevice, mImpl->mFragShaderModule, nullptr);
-        vkDestroyShaderModule(mImpl->mDevice, mImpl->mVertShaderModule, nullptr);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/renderer/RHIVulkan.cpp` around lines 648 - 773, Remove the unused
mVertShaderModule and mFragShaderModule members from the RHI implementation, and
remove their corresponding vkDestroyShaderModule calls from RHI::shutdown().
Keep the local shader module creation and destruction in
RHIImpl::createGraphicsPipeline unchanged.
🧹 Nitpick comments (3)
src/runtime/renderer/vulkantypes.h (1)

14-23: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add Doxygen comments to the new QueueFamilyIndices public API.

The struct and isComplete() have no @brief/documentation, unlike VulkanDevice elsewhere in this PR which is properly documented.

As per coding guidelines, "Use Doxygen-style comments in header files for public APIs (@brief, @param, @return, etc.)."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/renderer/vulkantypes.h` around lines 14 - 23, Add Doxygen
documentation to the public QueueFamilyIndices struct and its isComplete()
method in vulkantypes.h, including `@brief` descriptions and an `@return`
description for isComplete().

Source: Coding guidelines

src/runtime/renderer/vulkandevice.h (1)

36-52: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the new DeviceRequirements/DeviceProperties public structs.

Neither struct has Doxygen comments, unlike VulkanDevice just below them.

As per coding guidelines, "Use Doxygen-style comments in header files for public APIs (@brief, @param, @return, etc.)."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/renderer/vulkandevice.h` around lines 36 - 52, Document the
public DeviceRequirements and DeviceProperties structs in Doxygen style, adding
concise `@brief` descriptions and documenting their public fields, including
extension lists, feature requirements, queue requirements, device properties,
available extensions, and queue families.

Source: Coding guidelines

src/runtime/renderer/vulkanbuffer.h (1)

39-52: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Public methods still lack Doxygen @brief/@param/@return tags.

Only the class-level summary was added; init, cleanup, map, unmap, bind, copyTo, and the getters remain undocumented.

As per coding guidelines, "Use Doxygen-style comments in header files for public APIs (@brief, @param, @return, etc.)."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/renderer/vulkanbuffer.h` around lines 39 - 52, Add Doxygen
comments for each public method in VulkanBuffer: init, cleanup, map, unmap,
bind, copyTo, getBuffer, getMemory, and getSize. Include an appropriate `@brief`,
document every parameter with `@param`, and describe return values with `@return`
for non-void methods, while preserving the existing declarations and behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/runtime/renderer/RHIVulkan.cpp`:
- Around line 1569-1602: Remove the redundant vkDestroySwapchainKHR call from
RHI::shutdown because mImpl->cleanupSwapChain() already destroys the swapchain.
Keep cleanupSwapChain() as the single owner of swapchain destruction and leave
the remaining shutdown resource cleanup unchanged.
- Around line 1532-1553: Update the Vulkan initialization flow after
vkEnumeratePhysicalDevices to handle enumeration failures or zero devices before
accessing physicalDevices[0], then select only a device accepted by
isDeviceSuitable(). Check the return value of SDL_Vulkan_CreateSurface and the
result of createLogicalDevice, returning false immediately when either fails;
preserve the existing setup sequence only after a valid surface and suitable
device are established.

In `@src/runtime/renderer/vulkanbuffer.h`:
- Line 38: Add SEGFAULT_EXPORT to the VulkanBuffer class declaration so it is
exported from the shared runtime library and remains usable through
VulkanDevice::createBuffer() for external consumers.

In `@src/runtime/renderer/vulkandevice.h`:
- Around line 67-69: Update the Doxygen comment for the VulkanDevice constructor
to document the actual physicalDevice parameter, replacing the stale instance
description while preserving the existing `@brief` and public API documentation
style.

---

Outside diff comments:
In `@src/runtime/renderer/RHIVulkan.cpp`:
- Around line 1116-1148: Remove the duplicate second transitionImageLayout call
in RHIImpl::createTextureImage, keeping the first transition from
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
before staging-buffer cleanup.
- Around line 382-439: Use the refreshed qfIndices parameter consistently
throughout createLogicalDevice: replace the mQueueFamilyIndices reads when
constructing uniqueQueueFamilies and configuring the queue create info with
qfIndices. Preserve the existing queue retrieval and device creation behavior.
- Around line 1491-1530: Correct the condition in the validation-layer setup
around mImpl->checkValidationLayerSupport() so validation layers are enabled
only when mImpl->mEnableValidationLayers is true and support checking succeeds.
Preserve the existing fallback that clears enabled layers and pNext when
validation is disabled or unsupported.
- Around line 648-773: Remove the unused mVertShaderModule and mFragShaderModule
members from the RHI implementation, and remove their corresponding
vkDestroyShaderModule calls from RHI::shutdown(). Keep the local shader module
creation and destruction in RHIImpl::createGraphicsPipeline unchanged.

---

Nitpick comments:
In `@src/runtime/renderer/vulkanbuffer.h`:
- Around line 39-52: Add Doxygen comments for each public method in
VulkanBuffer: init, cleanup, map, unmap, bind, copyTo, getBuffer, getMemory, and
getSize. Include an appropriate `@brief`, document every parameter with `@param`,
and describe return values with `@return` for non-void methods, while preserving
the existing declarations and behavior.

In `@src/runtime/renderer/vulkandevice.h`:
- Around line 36-52: Document the public DeviceRequirements and DeviceProperties
structs in Doxygen style, adding concise `@brief` descriptions and documenting
their public fields, including extension lists, feature requirements, queue
requirements, device properties, available extensions, and queue families.

In `@src/runtime/renderer/vulkantypes.h`:
- Around line 14-23: Add Doxygen documentation to the public QueueFamilyIndices
struct and its isComplete() method in vulkantypes.h, including `@brief`
descriptions and an `@return` description for isComplete().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e1a39763-4f15-4de0-838c-c5c1bab44a3a

📥 Commits

Reviewing files that changed from the base of the PR and between d7874ef and 1ca662d.

📒 Files selected for processing (7)
  • src/runtime/ai/behavior_tree.h
  • src/runtime/renderer/RHIVulkan.cpp
  • src/runtime/renderer/vulkanbuffer.cpp
  • src/runtime/renderer/vulkanbuffer.h
  • src/runtime/renderer/vulkandevice.cpp
  • src/runtime/renderer/vulkandevice.h
  • src/runtime/renderer/vulkantypes.h

Comment on lines +1532 to +1553
result = vkCreateInstance(&createInfo, nullptr, &mImpl->mInstance);
if (result != VK_SUCCESS) {
return false;
}
volkLoadInstance(mImpl->instance);
volkLoadInstance(mImpl->mInstance);

uint32_t physicalDeviceCount = 0;
vkEnumeratePhysicalDevices(mImpl->instance, &physicalDeviceCount, nullptr);
uint32_t physicalDeviceCount{ 0 };
vkEnumeratePhysicalDevices(mImpl->mInstance, &physicalDeviceCount, nullptr);
std::vector<VkPhysicalDevice> physicalDevices(physicalDeviceCount);
vkEnumeratePhysicalDevices(mImpl->instance, &physicalDeviceCount, physicalDevices.data());
mImpl->physicalDevice = physicalDevices[0];
vkEnumeratePhysicalDevices(mImpl->mInstance, &physicalDeviceCount, physicalDevices.data());
mImpl->mPhysicalDevice = physicalDevices[0];

SDL_Vulkan_CreateSurface(mImpl->window, mImpl->instance, &mImpl->surface);
SDL_Vulkan_CreateSurface(mImpl->mWindow, mImpl->mInstance, &mImpl->mSurface);

mImpl->createLogicalDevice(mImpl->enableValidationLayers, mImpl->physicalDevice, mImpl->queueFamilyIndices);
mImpl->createLogicalDevice(mImpl->mEnableValidationLayers, mImpl->mPhysicalDevice, mImpl->mQueueFamilyIndices);

mImpl->createSwapChain();
mImpl->createImageViews();
mImpl->createRenderPass();
mImpl->createDescriptorSetLayout();
mImpl->createGraphicsPipeline();
mImpl->createCommandPool(mImpl->queueFamilyIndices);
mImpl->createCommandPool(mImpl->mQueueFamilyIndices);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

set -euo pipefail

printf '\n== file outline ==\n'
ast-grep outline src/runtime/renderer/RHIVulkan.cpp --view expanded || true

printf '\n== relevant symbols ==\n'
rg -n "isDeviceSuitable|checkDeviceExtensionSupport|createLogicalDevice|SDL_Vulkan_CreateSurface|vkEnumeratePhysicalDevices|createTextureSampler|samplerInfo.anisotropyEnable" src/runtime/renderer/RHIVulkan.cpp

printf '\n== surrounding init lines ==\n'
sed -n '1500,1585p' src/runtime/renderer/RHIVulkan.cpp

printf '\n== helper implementations ==\n'
sed -n '1,700p' src/runtime/renderer/RHIVulkan.cpp | rg -n "isDeviceSuitable|checkDeviceExtensionSupport|createLogicalDevice|createTextureSampler|findQueueFamilies|pickPhysicalDevice|createSurface|SDL_Vulkan_CreateSurface" -n -A40 -B20

printf '\n== all call sites of createLogicalDevice ==\n'
rg -n "createLogicalDevice\\(" src/runtime/renderer

Repository: kimkulling/Segfault

Length of output: 32462


🏁 Script executed:

set -euo pipefail
ast-grep outline src/runtime/renderer/RHIVulkan.cpp --view expanded || true
rg -n "isDeviceSuitable|checkDeviceExtensionSupport|createLogicalDevice|SDL_Vulkan_CreateSurface|vkEnumeratePhysicalDevices|createTextureSampler|samplerInfo.anisotropyEnable" src/runtime/renderer/RHIVulkan.cpp
sed -n '1500,1585p' src/runtime/renderer/RHIVulkan.cpp
sed -n '1,700p' src/runtime/renderer/RHIVulkan.cpp | rg -n "isDeviceSuitable|checkDeviceExtensionSupport|createLogicalDevice|createTextureSampler|findQueueFamilies|pickPhysicalDevice|createSurface|SDL_Vulkan_CreateSurface" -n -A40 -B20
rg -n "createLogicalDevice\\(" src/runtime/renderer

Repository: kimkulling/Segfault

Length of output: 32313


🌐 Web query:

Vulkan vkEnumeratePhysicalDevices may return zero devices SDL_Vulkan_CreateSurface return value documentation bool

💡 Result:

When using vkEnumeratePhysicalDevices, receiving a count of zero physical devices typically indicates that no Vulkan-compatible hardware or drivers were detected by the Vulkan loader [1][2]. It does not necessarily mean the function failed; it can return VK_SUCCESS with a count of zero if the system has no Vulkan-capable devices, or it may return an error (such as VK_ERROR_INITIALIZATION_FAILED) if the loader itself encountered a problem during discovery [2][3]. Regarding SDL_Vulkan_CreateSurface, the return type has changed across different versions of the Simple Directmedia Layer (SDL) library: In SDL 2.x, SDL_Vulkan_CreateSurface returns an SDL_bool type, where SDL_TRUE indicates success and SDL_FALSE indicates failure [4][5]. In SDL 3.x, the function signature was updated to return an integer (int). Specifically, it returns 0 on success and -1 on failure [6][7][8]. If an error occurs, you can use SDL_GetError to retrieve detailed information [6][7][8]. Always check the specific documentation for the version of SDL you are using to ensure correct implementation [6][4].

Citations:


Guard Vulkan init before selecting the adapter

  • physicalDevices[0] can go out of bounds when vkEnumeratePhysicalDevices returns 0.
  • Check SDL_Vulkan_CreateSurface(...) and createLogicalDevice(...) here, and select only a device that passes isDeviceSuitable(); otherwise the init path can continue with an invalid surface/device and fail later in createTextureSampler().
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/renderer/RHIVulkan.cpp` around lines 1532 - 1553, Update the
Vulkan initialization flow after vkEnumeratePhysicalDevices to handle
enumeration failures or zero devices before accessing physicalDevices[0], then
select only a device accepted by isDeviceSuitable(). Check the return value of
SDL_Vulkan_CreateSurface and the result of createLogicalDevice, returning false
immediately when either fails; preserve the existing setup sequence only after a
valid surface and suitable device are established.

Comment on lines 1569 to +1602
bool RHI::shutdown() {
mImpl->cleanupSwapChain();
vkDestroyImage(mImpl->device, mImpl->textureImage, nullptr);
vkDestroySampler(mImpl->device, mImpl->textureSampler, nullptr);
vkDestroyImageView(mImpl->device, mImpl->textureImageView, nullptr);
vkDestroyImage(mImpl->mDevice, mImpl->mTextureImage, nullptr);
vkDestroySampler(mImpl->mDevice, mImpl->mTextureSampler, nullptr);
vkDestroyImageView(mImpl->mDevice, mImpl->mTextureImageView, nullptr);

vkFreeMemory(mImpl->device, mImpl->textureImageMemory, nullptr);
vkFreeMemory(mImpl->mDevice, mImpl->mTextureImageMemory, nullptr);
for (size_t i = 0; i < RHIImpl::MAX_FRAMES_IN_FLIGHT; i++) {
vkDestroyBuffer(mImpl->device, mImpl->uniformBuffers[i], nullptr);
vkFreeMemory(mImpl->device, mImpl->uniformBuffersMemory[i], nullptr);
vkDestroyBuffer(mImpl->mDevice, mImpl->mUniformBuffers[i], nullptr);
vkFreeMemory(mImpl->mDevice, mImpl->mUniformBuffersMemory[i], nullptr);
}
vkDestroyDescriptorPool(mImpl->device, mImpl->descriptorPool, nullptr);
vkDestroyDescriptorSetLayout(mImpl->device, mImpl->descriptorSetLayout, nullptr);
vkDestroyBuffer(mImpl->device, mImpl->vertexBuffer, nullptr);
vkFreeMemory(mImpl->device, mImpl->vertexBufferMemory, nullptr);
vkDestroyDescriptorPool(mImpl->mDevice, mImpl->mDescriptorPool, nullptr);
vkDestroyDescriptorSetLayout(mImpl->mDevice, mImpl->mDescriptorSetLayout, nullptr);
vkDestroyBuffer(mImpl->mDevice, mImpl->mVertexBuffer, nullptr);
vkFreeMemory(mImpl->mDevice, mImpl->mVertexBufferMemory, nullptr);

vkDestroyBuffer(mImpl->device, mImpl->indexBuffer, nullptr);
vkFreeMemory(mImpl->device, mImpl->indexBufferMemory, nullptr);
vkDestroyBuffer(mImpl->mDevice, mImpl->mIndexBuffer, nullptr);
vkFreeMemory(mImpl->mDevice, mImpl->mIndexBufferMemory, nullptr);

for (size_t i = 0; i < RHIImpl::MAX_FRAMES_IN_FLIGHT; i++) {
vkDestroySemaphore(mImpl->device, mImpl->renderFinishedSemaphores[i], nullptr);
vkDestroySemaphore(mImpl->device, mImpl->imageAvailableSemaphores[i], nullptr);
vkDestroyFence(mImpl->device, mImpl->inFlightFences[i], nullptr);
vkDestroySemaphore(mImpl->mDevice, mImpl->mRenderFinishedSemaphores[i], nullptr);
vkDestroySemaphore(mImpl->mDevice, mImpl->mImageAvailableSemaphores[i], nullptr);
vkDestroyFence(mImpl->mDevice, mImpl->mInFlightFences[i], nullptr);
}

vkDestroyCommandPool(mImpl->device, mImpl->commandPool, nullptr);
vkDestroyCommandPool(mImpl->mDevice, mImpl->mCommandPool, nullptr);

vkDestroyShaderModule(mImpl->device, mImpl->fragShaderModule, nullptr);
vkDestroyShaderModule(mImpl->device, mImpl->vertShaderModule, nullptr);
vkDestroyPipelineLayout(mImpl->device, mImpl->pipelineLayout, nullptr);
vkDestroyRenderPass(mImpl->device, mImpl->renderPass, nullptr);
vkDestroyShaderModule(mImpl->mDevice, mImpl->mFragShaderModule, nullptr);
vkDestroyShaderModule(mImpl->mDevice, mImpl->mVertShaderModule, nullptr);
vkDestroyPipelineLayout(mImpl->mDevice, mImpl->mPipelineLayout, nullptr);
vkDestroyRenderPass(mImpl->mDevice, mImpl->mRenderPass, nullptr);

vkDestroySwapchainKHR(mImpl->device, mImpl->swapChain, nullptr);
vkDestroyDevice(mImpl->device, nullptr);
vkDestroySwapchainKHR(mImpl->mDevice, mImpl->mSwapChain, nullptr);
vkDestroyDevice(mImpl->mDevice, nullptr);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Swapchain is destroyed twice in RHI::shutdown.

Line 1570 calls mImpl->cleanupSwapChain(), whose body already calls vkDestroySwapchainKHR(mDevice, mSwapChain, nullptr) without resetting mSwapChain to VK_NULL_HANDLE. Line 1601 then calls vkDestroySwapchainKHR(mImpl->mDevice, mImpl->mSwapChain, nullptr) again on the same (already-destroyed) handle — Vulkan's valid-usage rules require the handle to be either valid or VK_NULL_HANDLE, so this double-destroy is undefined behavior and a known source of loader/driver crashes.

🐛 Proposed fix
         vkDestroyCommandPool(mImpl->mDevice, mImpl->mCommandPool, nullptr);

         vkDestroyShaderModule(mImpl->mDevice, mImpl->mFragShaderModule, nullptr);
         vkDestroyShaderModule(mImpl->mDevice, mImpl->mVertShaderModule, nullptr);
         vkDestroyPipelineLayout(mImpl->mDevice, mImpl->mPipelineLayout, nullptr);
         vkDestroyRenderPass(mImpl->mDevice, mImpl->mRenderPass, nullptr);

-        vkDestroySwapchainKHR(mImpl->mDevice, mImpl->mSwapChain, nullptr);
         vkDestroyDevice(mImpl->mDevice, nullptr);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
bool RHI::shutdown() {
mImpl->cleanupSwapChain();
vkDestroyImage(mImpl->device, mImpl->textureImage, nullptr);
vkDestroySampler(mImpl->device, mImpl->textureSampler, nullptr);
vkDestroyImageView(mImpl->device, mImpl->textureImageView, nullptr);
vkDestroyImage(mImpl->mDevice, mImpl->mTextureImage, nullptr);
vkDestroySampler(mImpl->mDevice, mImpl->mTextureSampler, nullptr);
vkDestroyImageView(mImpl->mDevice, mImpl->mTextureImageView, nullptr);
vkFreeMemory(mImpl->device, mImpl->textureImageMemory, nullptr);
vkFreeMemory(mImpl->mDevice, mImpl->mTextureImageMemory, nullptr);
for (size_t i = 0; i < RHIImpl::MAX_FRAMES_IN_FLIGHT; i++) {
vkDestroyBuffer(mImpl->device, mImpl->uniformBuffers[i], nullptr);
vkFreeMemory(mImpl->device, mImpl->uniformBuffersMemory[i], nullptr);
vkDestroyBuffer(mImpl->mDevice, mImpl->mUniformBuffers[i], nullptr);
vkFreeMemory(mImpl->mDevice, mImpl->mUniformBuffersMemory[i], nullptr);
}
vkDestroyDescriptorPool(mImpl->device, mImpl->descriptorPool, nullptr);
vkDestroyDescriptorSetLayout(mImpl->device, mImpl->descriptorSetLayout, nullptr);
vkDestroyBuffer(mImpl->device, mImpl->vertexBuffer, nullptr);
vkFreeMemory(mImpl->device, mImpl->vertexBufferMemory, nullptr);
vkDestroyDescriptorPool(mImpl->mDevice, mImpl->mDescriptorPool, nullptr);
vkDestroyDescriptorSetLayout(mImpl->mDevice, mImpl->mDescriptorSetLayout, nullptr);
vkDestroyBuffer(mImpl->mDevice, mImpl->mVertexBuffer, nullptr);
vkFreeMemory(mImpl->mDevice, mImpl->mVertexBufferMemory, nullptr);
vkDestroyBuffer(mImpl->device, mImpl->indexBuffer, nullptr);
vkFreeMemory(mImpl->device, mImpl->indexBufferMemory, nullptr);
vkDestroyBuffer(mImpl->mDevice, mImpl->mIndexBuffer, nullptr);
vkFreeMemory(mImpl->mDevice, mImpl->mIndexBufferMemory, nullptr);
for (size_t i = 0; i < RHIImpl::MAX_FRAMES_IN_FLIGHT; i++) {
vkDestroySemaphore(mImpl->device, mImpl->renderFinishedSemaphores[i], nullptr);
vkDestroySemaphore(mImpl->device, mImpl->imageAvailableSemaphores[i], nullptr);
vkDestroyFence(mImpl->device, mImpl->inFlightFences[i], nullptr);
vkDestroySemaphore(mImpl->mDevice, mImpl->mRenderFinishedSemaphores[i], nullptr);
vkDestroySemaphore(mImpl->mDevice, mImpl->mImageAvailableSemaphores[i], nullptr);
vkDestroyFence(mImpl->mDevice, mImpl->mInFlightFences[i], nullptr);
}
vkDestroyCommandPool(mImpl->device, mImpl->commandPool, nullptr);
vkDestroyCommandPool(mImpl->mDevice, mImpl->mCommandPool, nullptr);
vkDestroyShaderModule(mImpl->device, mImpl->fragShaderModule, nullptr);
vkDestroyShaderModule(mImpl->device, mImpl->vertShaderModule, nullptr);
vkDestroyPipelineLayout(mImpl->device, mImpl->pipelineLayout, nullptr);
vkDestroyRenderPass(mImpl->device, mImpl->renderPass, nullptr);
vkDestroyShaderModule(mImpl->mDevice, mImpl->mFragShaderModule, nullptr);
vkDestroyShaderModule(mImpl->mDevice, mImpl->mVertShaderModule, nullptr);
vkDestroyPipelineLayout(mImpl->mDevice, mImpl->mPipelineLayout, nullptr);
vkDestroyRenderPass(mImpl->mDevice, mImpl->mRenderPass, nullptr);
vkDestroySwapchainKHR(mImpl->device, mImpl->swapChain, nullptr);
vkDestroyDevice(mImpl->device, nullptr);
vkDestroySwapchainKHR(mImpl->mDevice, mImpl->mSwapChain, nullptr);
vkDestroyDevice(mImpl->mDevice, nullptr);
bool RHI::shutdown() {
mImpl->cleanupSwapChain();
vkDestroyImage(mImpl->mDevice, mImpl->mTextureImage, nullptr);
vkDestroySampler(mImpl->mDevice, mImpl->mTextureSampler, nullptr);
vkDestroyImageView(mImpl->mDevice, mImpl->mTextureImageView, nullptr);
vkFreeMemory(mImpl->mDevice, mImpl->mTextureImageMemory, nullptr);
for (size_t i = 0; i < RHIImpl::MAX_FRAMES_IN_FLIGHT; i++) {
vkDestroyBuffer(mImpl->mDevice, mImpl->mUniformBuffers[i], nullptr);
vkFreeMemory(mImpl->mDevice, mImpl->mUniformBuffersMemory[i], nullptr);
}
vkDestroyDescriptorPool(mImpl->mDevice, mImpl->mDescriptorPool, nullptr);
vkDestroyDescriptorSetLayout(mImpl->mDevice, mImpl->mDescriptorSetLayout, nullptr);
vkDestroyBuffer(mImpl->mDevice, mImpl->mVertexBuffer, nullptr);
vkFreeMemory(mImpl->mDevice, mImpl->mVertexBufferMemory, nullptr);
vkDestroyBuffer(mImpl->mDevice, mImpl->mIndexBuffer, nullptr);
vkFreeMemory(mImpl->mDevice, mImpl->mIndexBufferMemory, nullptr);
for (size_t i = 0; i < RHIImpl::MAX_FRAMES_IN_FLIGHT; i++) {
vkDestroySemaphore(mImpl->mDevice, mImpl->mRenderFinishedSemaphores[i], nullptr);
vkDestroySemaphore(mImpl->mDevice, mImpl->mImageAvailableSemaphores[i], nullptr);
vkDestroyFence(mImpl->mDevice, mImpl->mInFlightFences[i], nullptr);
}
vkDestroyCommandPool(mImpl->mDevice, mImpl->mCommandPool, nullptr);
vkDestroyShaderModule(mImpl->mDevice, mImpl->mFragShaderModule, nullptr);
vkDestroyShaderModule(mImpl->mDevice, mImpl->mVertShaderModule, nullptr);
vkDestroyPipelineLayout(mImpl->mDevice, mImpl->mPipelineLayout, nullptr);
vkDestroyRenderPass(mImpl->mDevice, mImpl->mRenderPass, nullptr);
vkDestroyDevice(mImpl->mDevice, nullptr);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/renderer/RHIVulkan.cpp` around lines 1569 - 1602, Remove the
redundant vkDestroySwapchainKHR call from RHI::shutdown because
mImpl->cleanupSwapChain() already destroys the swapchain. Keep
cleanupSwapChain() as the single owner of swapchain destruction and leave the
remaining shutdown resource cleanup unchanged.

/// mapping, binding, and copying of data to the buffer, as well as cleanup of
/// resources when they are no longer needed.
//---------------------------------------------------------------------------------------------
class VulkanBuffer {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== File list ==\n'
git ls-files | rg 'src/runtime/renderer/vulkan(buffer|device)\.(h|cpp)$|src/runtime/renderer/.*vulkan.*\.(h|cpp)$'

printf '\n== Outline: vulkanbuffer.h ==\n'
ast-grep outline src/runtime/renderer/vulkanbuffer.h --view expanded || true

printf '\n== Outline: vulkandevice.h ==\n'
ast-grep outline src/runtime/renderer/vulkandevice.h --view expanded || true

printf '\n== vulkanbuffer.h (selected lines) ==\n'
cat -n src/runtime/renderer/vulkanbuffer.h | sed -n '1,220p'

printf '\n== vulkandevice.h (selected lines) ==\n'
cat -n src/runtime/renderer/vulkandevice.h | sed -n '1,220p'

printf '\n== vulkandevice.cpp references ==\n'
rg -n "createBuffer|VulkanBuffer" src/runtime/renderer/vulkandevice.cpp src/runtime/renderer/vulkanbuffer.cpp src/runtime/renderer/vulkanbuffer.h src/runtime/renderer/vulkandevice.h

printf '\n== export macro usage ==\n'
rg -n "SEGFAULT_EXPORT" src/runtime/renderer | sed -n '1,200p'

Repository: kimkulling/Segfault

Length of output: 11862


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,220p' src/runtime/renderer/vulkanbuffer.h
printf '\n---\n'
sed -n '1,220p' src/runtime/renderer/vulkandevice.h
printf '\n---\n'
sed -n '1,220p' src/runtime/renderer/vulkandevice.cpp
printf '\n---\n'
sed -n '1,220p' src/runtime/renderer/vulkanbuffer.cpp

Repository: kimkulling/Segfault

Length of output: 16719


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== core/segfault.h ==\n'
sed -n '1,220p' src/core/segfault.h

printf '\n== build files mentioning renderer/core library targets ==\n'
rg -n "add_library|SHARED|STATIC|SEGFAULT_EXPORT|renderer|core" CMakeLists.txt cmake src -g 'CMakeLists.txt' -g '*.cmake' -g 'meson.build' -g 'Makefile' | sed -n '1,240p'

printf '\n== VulkanBuffer usages outside its own files ==\n'
rg -n "\bVulkanBuffer\b" src | sed -n '1,240p'

Repository: kimkulling/Segfault

Length of output: 243


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== locate segfault.h ==\n'
fd -a 'segfault.h' .

printf '\n== locate build files ==\n'
fd -a 'CMakeLists.txt|*.cmake|meson.build|Makefile' .

printf '\n== search for SEGFAULT_EXPORT definition ==\n'
rg -n "define\s+SEGFAULT_EXPORT|SEGFAULT_EXPORT" . -g '!**/build/**' -g '!**/dist/**' | sed -n '1,240p'

Repository: kimkulling/Segfault

Length of output: 593


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== src/runtime/core/segfault.h ==\n'
sed -n '1,220p' src/runtime/core/segfault.h

printf '\n== build files ==\n'
fd -a --glob 'CMakeLists.txt' --glob '*.cmake' --glob 'meson.build' --glob 'Makefile' .

printf '\n== SEGFAULT_EXPORT definition/search ==\n'
rg -n "SEGFAULT_EXPORT|define[[:space:]]+SEGFAULT_EXPORT" src CMakeLists.txt cmake -g '!**/build/**' -g '!**/dist/**' | sed -n '1,240p'

printf '\n== library target declarations ==\n'
rg -n "add_library|set_target_properties|target_compile_definitions|target_include_directories|SHARED|STATIC" CMakeLists.txt src cmake -g 'CMakeLists.txt' -g '*.cmake' | sed -n '1,260p'

Repository: kimkulling/Segfault

Length of output: 5689


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== top-level CMakeLists.txt ==\n'
sed -n '1,260p' CMakeLists.txt

printf '\n== src/runtime/CMakeLists.txt ==\n'
sed -n '1,260p' src/runtime/CMakeLists.txt

printf '\n== visibility / shared-library settings ==\n'
rg -n "CMAKE_CXX_VISIBILITY_PRESET|VISIBILITY_INLINES_HIDDEN|add_library|SHARED|STATIC|POSITION_INDEPENDENT_CODE|SEGFAULT_BUILD_EXPORT|target_compile_definitions|target_compile_options" CMakeLists.txt src/runtime/CMakeLists.txt src -g 'CMakeLists.txt' | sed -n '1,260p'

Repository: kimkulling/Segfault

Length of output: 3317


Export VulkanBuffer from the shared runtime library. segfault_runtime is built as SHARED, and VulkanDevice::createBuffer() exposes VulkanBuffer* in its public API. Without SEGFAULT_EXPORT on VulkanBuffer, external consumers can hit unresolved symbol/visibility failures on the DLL boundary.

🛠️ Proposed fix
-    class VulkanBuffer {
+    class SEGFAULT_EXPORT VulkanBuffer {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class VulkanBuffer {
class SEGFAULT_EXPORT VulkanBuffer {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/renderer/vulkanbuffer.h` at line 38, Add SEGFAULT_EXPORT to the
VulkanBuffer class declaration so it is exported from the shared runtime library
and remains usable through VulkanDevice::createBuffer() for external consumers.

Source: Coding guidelines

Comment on lines 67 to +69
/// @brief Constructs a VulkanDevice.
/// @param instance The Vulkan instance.
VulkanDevice(VkPhysicalDevice physicalDevice);
explicit VulkanDevice(VkPhysicalDevice physicalDevice);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Stale @param doc on the constructor.

@param instance The Vulkan instance. doesn't match the actual parameter physicalDevice (VkPhysicalDevice). Likely leftover from an earlier signature.

As per coding guidelines, "Use Doxygen-style comments in header files for public APIs (@brief, @param, @return, etc.)."

📝 Proposed fix
         /// `@brief` Constructs a VulkanDevice.
-        /// `@param` instance The Vulkan instance.
+        /// `@param` physicalDevice The Vulkan physical device to wrap.
         explicit VulkanDevice(VkPhysicalDevice physicalDevice);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// @brief Constructs a VulkanDevice.
/// @param instance The Vulkan instance.
VulkanDevice(VkPhysicalDevice physicalDevice);
explicit VulkanDevice(VkPhysicalDevice physicalDevice);
/// `@brief` Constructs a VulkanDevice.
/// `@param` physicalDevice The Vulkan physical device to wrap.
explicit VulkanDevice(VkPhysicalDevice physicalDevice);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/renderer/vulkandevice.h` around lines 67 - 69, Update the Doxygen
comment for the VulkanDevice constructor to document the actual physicalDevice
parameter, replacing the stale instance description while preserving the
existing `@brief` and public API documentation style.

Source: Coding guidelines

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant