EDIT: Docs and minor bug fixed

This commit is contained in:
2025-10-29 22:51:28 +09:00
parent 97177dade3
commit 0226c0b5b3
26 changed files with 348 additions and 17 deletions

View File

@@ -120,6 +120,9 @@ private:
void cleanup();
};
// Small compute manager for one-off pipelines and persistent instances.
// It owns a dedicated descriptor allocator and provides helpers to build
// pipelines, set bindings, and dispatch work (immediate or on a provided cmd).
class ComputeManager
{
public:

View File

@@ -309,6 +309,11 @@ bool AssetManager::removeMesh(const std::string &name)
{
auto it = _meshCache.find(name);
if (it == _meshCache.end()) return false;
if (_engine && _engine->_rayManager)
{
// Clean up BLAS cached for this mesh (if ray tracing is enabled)
_engine->_rayManager->removeBLASForBuffer(it->second->meshBuffers.vertexBuffer.buffer);
}
if (_engine && _engine->_resourceManager)
{
_engine->_resourceManager->destroy_buffer(it->second->meshBuffers.indexBuffer);

View File

@@ -5,6 +5,9 @@
class DeviceManager;
// Per-frame state used by the renderer and passes.
// Owns a command buffer, sync primitives, a transient descriptor pool, and a
// deletion queue for resources that should be destroyed when the frame is done.
struct FrameResources
{
VkSemaphore _swapchainSemaphore = VK_NULL_HANDLE;

View File

@@ -3,6 +3,9 @@
#include "SDL2/SDL.h"
#include "SDL2/SDL_vulkan.h"
// Create Vulkan instance/device, enable debug/validation (in Debug), pick a GPU,
// and set up VMA with buffer device address. If available, enable Ray Query and
// Acceleration Structure extensions + features.
void DeviceManager::init_vulkan(SDL_Window *window)
{
vkb::InstanceBuilder builder;

View File

@@ -1,3 +1,17 @@
// Engine bootstrap, frame loop, and render-graph wiring.
//
// Responsibilities
// - Initialize SDL + Vulkan managers (device, resources, descriptors, samplers, pipelines).
// - Create swapchain + default images and build the Render Graph each frame.
// - Publish an EngineContext so passes and subsystems access perframe state uniformly.
// - Drive ImGui + debug UIs and optional raytracing TLAS rebuilds.
//
// See also:
// - docs/EngineContext.md
// - docs/RenderGraph.md
// - docs/FrameResources.md
// - docs/RayTracing.md
//
//> includes
#include "vk_engine.h"
#include <core/vk_images.h>

View File

@@ -32,6 +32,8 @@
#include "render/rg_graph.h"
#include "core/vk_raytracing.h"
// Number of frames-in-flight. Affects per-frame command buffers, fences,
// semaphores, and transient descriptor pools in FrameResources.
constexpr unsigned int FRAME_OVERLAP = 2;
// Compute push constants and effects are declared in compute/vk_compute.h now.

View File

@@ -25,6 +25,9 @@ struct GraphicsPipelineCreateInfo
std::function<void(PipelineBuilder &)> configure;
};
// Graphics pipeline registry with hot-reload support.
// Stores specs keyed by name, builds on demand, and can rebuild when shader
// timestamps change. Also forwards a minimal Compute API to ComputeManager.
class PipelineManager
{
public:

View File

@@ -307,3 +307,21 @@ VkAccelerationStructureKHR RayTracingManager::buildTLASFromDrawContext(const Dra
return _tlas.handle;
}
void RayTracingManager::removeBLASForBuffer(VkBuffer vertexBuffer)
{
if (!vertexBuffer) return;
VkDevice dv = _device->device();
auto it = _blasByVB.find(vertexBuffer);
if (it == _blasByVB.end()) return;
if (it->second.handle)
{
_vkDestroyAccelerationStructureKHR(dv, it->second.handle, nullptr);
}
if (it->second.storage.buffer)
{
_resources->destroy_buffer(it->second.storage);
}
_blasByVB.erase(it);
}

View File

@@ -15,18 +15,24 @@
VkDeviceAddress deviceAddress{0};
};
class RayTracingManager {
public:
void init(DeviceManager* dev, ResourceManager* res);
void cleanup();
// Ray tracing helper that caches BLAS per mesh and rebuilds TLAS per frame
// for hybrid/full ray query shadows. See docs/RayTracing.md.
class RayTracingManager {
public:
void init(DeviceManager* dev, ResourceManager* res);
void cleanup();
// Build (or get) BLAS for a mesh. Safe to call multiple times.
AccelStructureHandle getOrBuildBLAS(const std::shared_ptr<MeshAsset>& mesh);
// Rebuild TLAS from current draw context; returns TLAS handle (or null if unavailable)
VkAccelerationStructureKHR buildTLASFromDrawContext(const DrawContext& dc);
VkAccelerationStructureKHR tlas() const { return _tlas.handle; }
VkDeviceAddress tlasAddress() const { return _tlas.deviceAddress; }
VkAccelerationStructureKHR buildTLASFromDrawContext(const DrawContext& dc);
VkAccelerationStructureKHR tlas() const { return _tlas.handle; }
VkDeviceAddress tlasAddress() const { return _tlas.deviceAddress; }
// Remove and destroy a cached BLAS associated with a vertex buffer.
// Safe to call even if no BLAS exists for the buffer.
void removeBLASForBuffer(VkBuffer vertexBuffer);
private:
// function pointers (resolved on init)

View File

@@ -48,7 +48,12 @@ AllocatedBuffer ResourceManager::create_buffer(size_t allocSize, VkBufferUsageFl
VmaAllocationCreateInfo vmaallocInfo = {};
vmaallocInfo.usage = memoryUsage;
vmaallocInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
// Map buffers only when CPU-visible memory is requested
if (memoryUsage == VMA_MEMORY_USAGE_CPU_TO_GPU ||
memoryUsage == VMA_MEMORY_USAGE_CPU_ONLY)
{
vmaallocInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
}
AllocatedBuffer newBuffer{};
VK_CHECK(vmaCreateBuffer(_deviceManager->allocator(), &bufferInfo, &vmaallocInfo,
@@ -129,11 +134,33 @@ AllocatedImage ResourceManager::create_image(VkExtent3D size, VkFormat format, V
return newImage;
}
// Returns byte size per texel for a subset of common formats.
static inline size_t bytes_per_texel(VkFormat fmt)
{
switch (fmt)
{
case VK_FORMAT_R8_UNORM:
case VK_FORMAT_R8_SRGB:
return 1;
case VK_FORMAT_R8G8_UNORM:
case VK_FORMAT_R8G8_SRGB:
return 2;
case VK_FORMAT_R8G8B8A8_UNORM:
case VK_FORMAT_R8G8B8A8_SRGB:
case VK_FORMAT_B8G8R8A8_UNORM:
case VK_FORMAT_B8G8R8A8_SRGB:
return 4;
default:
return 4; // STB path uploads 4 channels
}
}
AllocatedImage ResourceManager::create_image(const void *data, VkExtent3D size, VkFormat format,
VkImageUsageFlags usage,
bool mipmapped)
{
size_t data_size = size.depth * size.width * size.height * 4;
size_t bpp = bytes_per_texel(format);
size_t data_size = static_cast<size_t>(size.depth) * size.width * size.height * bpp;
AllocatedBuffer uploadbuffer = create_buffer(data_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
VMA_MEMORY_USAGE_CPU_TO_GPU);
@@ -480,8 +507,10 @@ void ResourceManager::register_upload_pass(RenderGraph &graph, FrameResources &f
if (upload.generateMips)
{
// NOTE: generate_mipmaps() transitions the image to
// VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL at the end.
// Do not transition back to TRANSFER here. See docs/ResourceManager.md.
vkutil::generate_mipmaps(cmd, image, VkExtent2D{upload.extent.width, upload.extent.height});
vkutil::transition_image(cmd, image, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
}
}
});

View File

@@ -7,6 +7,9 @@ class DeviceManager;
class RenderGraph;
struct FrameResources;
// VMA-backed allocator + upload helper.
// Creates buffers/images, offers an immediate-submit path, and supports
// deferring uploads into a single Render Graph transfer pass per frame.
class ResourceManager
{
public:

View File

@@ -1,11 +1,17 @@
#include "vk_swapchain.h"
#include <SDL_video.h>
#include "SDL2/SDL_vulkan.h"
#include "vk_device.h"
#include "vk_initializers.h"
#include "vk_resource.h"
// Swapchain + per-frame targets (HDR draw, depth, GBuffer) management.
//
// Create/resize/destroy logic keeps per-frame images in a local deletion queue
// so they are cleaned up with the swapchain. The engine imports those images
// into the Render Graph each frame.
void SwapchainManager::init_swapchain()
{
create_swapchain(_windowExtent.width, _windowExtent.height);
@@ -115,12 +121,13 @@ void SwapchainManager::create_swapchain(uint32_t width, uint32_t height)
void SwapchainManager::destroy_swapchain() const
{
vkDestroySwapchainKHR(_deviceManager->device(), _swapchain, nullptr);
for (auto _swapchainImageView: _swapchainImageViews)
// Destroy image views before the swapchain for stricter driver orderliness.
// (Most drivers tolerate either order, but views reference swapchain images.)
for (auto view : _swapchainImageViews)
{
vkDestroyImageView(_deviceManager->device(), _swapchainImageView, nullptr);
vkDestroyImageView(_deviceManager->device(), view, nullptr);
}
vkDestroySwapchainKHR(_deviceManager->device(), _swapchain, nullptr);
}
void SwapchainManager::resize_swapchain(struct SDL_Window *window)
@@ -133,7 +140,8 @@ void SwapchainManager::resize_swapchain(struct SDL_Window *window)
_deletionQueue.flush();
int w, h;
SDL_GetWindowSize(window, &w, &h);
// HiDPI-aware drawable size for correct pixel dimensions
SDL_Vulkan_GetDrawableSize(window, &w, &h);
_windowExtent.width = w;
_windowExtent.height = h;

View File

@@ -59,6 +59,15 @@ RGBufferHandle RenderGraph::create_buffer(const RGBufferDesc &desc)
return _resources.add_transient(desc);
}
// Render Graph: builds a per-frame DAG from declared image/buffer accesses,
// inserts precise barriers and layouts, and records passes using dynamic rendering.
//
// Key steps:
// - add_pass(): store declarations and callbacks (build to declare, record to issue commands)
// - compile(): topologically sort by read/write hazards and generate Vk*Barrier2 sequences
// - execute(): emit pre-pass barriers, begin dynamic rendering if attachments exist, invoke record()
//
// See docs/RenderGraph.md for API overview and pass patterns.
void RenderGraph::add_pass(const char *name, RGPassType type, BuildCallback build, RecordCallback record)
{
Pass p{};

View File

@@ -15,6 +15,8 @@
#include "vk_swapchain.h"
#include "render/rg_graph.h"
// Basic conservative frustum test against RenderObject AABB.
// Clip space uses Vulkan Z0 (0..w). Returns true if any part of the box is inside.
bool is_visible(const RenderObject &obj, const glm::mat4 &viewproj)
{
const std::array<glm::vec3, 8> corners{

View File

@@ -185,7 +185,9 @@ void LightingPass::draw_lighting(VkCommandBuffer cmd,
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, _pipelineLayout, 1, 1,
&_gBufferInputDescriptorSet, 0, nullptr);
// Allocate and write shadow descriptor set for this frame (set = 2)
// Allocate and write shadow descriptor set for this frame (set = 2).
// When RT is enabled, TLAS is bound in the global set at (set=0, binding=1)
// via DescriptorManager::gpuSceneDataLayout(). See docs/RayTracing.md.
VkDescriptorSet shadowSet = ctxLocal->currentFrame->_frameDescriptors.allocate(
deviceManager->device(), _shadowDescriptorLayout);
{

View File

@@ -92,7 +92,9 @@ void TransparentPass::draw_transparent(VkCommandBuffer cmd,
writer.write_buffer(0, gpuSceneDataBuffer.buffer, sizeof(GPUSceneData), 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER);
writer.update_set(deviceManager->device(), globalDescriptor);
// Sort transparent back-to-front using camera-space depth
// Sort transparent back-to-front using camera-space depth.
// We approximate object depth by transforming the mesh bounds origin.
// For better results consider using per-object center or per-draw depth range.
std::vector<const RenderObject *> draws;
draws.reserve(dc.TransparentSurfaces.size());
for (const auto &r: dc.TransparentSurfaces) draws.push_back(&r);

View File

@@ -576,6 +576,10 @@ void LoadedGLTF::clearAll()
for (auto &[k, v]: meshes)
{
if (creator->_rayManager)
{
creator->_rayManager->removeBLASForBuffer(v->meshBuffers.vertexBuffer.buffer);
}
creator->_resourceManager->destroy_buffer(v->meshBuffers.indexBuffer);
creator->_resourceManager->destroy_buffer(v->meshBuffers.vertexBuffer);
}