From eb5a04e95a480c8a63f16fead2dc5d1fa5deaf27 Mon Sep 17 00:00:00 2001 From: hydrogendeuteride Date: Sat, 13 Dec 2025 23:26:35 +0900 Subject: [PATCH] ADD: changeable resolution --- CMakeLists.txt | 4 + src/core/device/swapchain.cpp | 184 +++++++++++++++++++-------------- src/core/device/swapchain.h | 4 + src/core/engine.cpp | 83 +++++++++++++-- src/core/engine.h | 6 ++ src/core/engine_ui.cpp | 52 +++++++++- src/render/passes/lighting.cpp | 51 +++++---- src/render/passes/lighting.h | 5 +- 8 files changed, 285 insertions(+), 104 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f24e51c..add535f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,10 @@ endif() find_package(Vulkan REQUIRED) +# Third-party deps are vendored; keep builds offline-friendly by default. +# BVH2's CMake enables tests by default, which would FetchContent googletest. +set(BVH2_ENABLE_TESTS OFF CACHE BOOL "Disable BVH2 tests (offline builds)" FORCE) + add_subdirectory(third_party) set (CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/bin") diff --git a/src/core/device/swapchain.cpp b/src/core/device/swapchain.cpp index 1a7bd01..072eda2 100644 --- a/src/core/device/swapchain.cpp +++ b/src/core/device/swapchain.cpp @@ -16,83 +16,15 @@ void SwapchainManager::init_swapchain() { create_swapchain(_windowExtent.width, _windowExtent.height); - // Create images used across the frame (draw, depth, GBuffer) - // Split to helper so we can reuse on resize - // (Definition added below) - // - // On creation we also push a cleanup lambda to _deletionQueue for final shutdown. - // On resize we will flush that queue first to destroy previous resources. - - // depth/draw/gbuffer sized to fixed logical render extent (letterboxed) - auto create_frame_images = [this]() { - VkExtent3D drawImageExtent = { kRenderWidth, kRenderHeight, 1 }; - - // Draw HDR target - _drawImage.imageFormat = VK_FORMAT_R16G16B16A16_SFLOAT; - _drawImage.imageExtent = drawImageExtent; - - VkImageUsageFlags drawImageUsages{}; - drawImageUsages |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; - drawImageUsages |= VK_IMAGE_USAGE_STORAGE_BIT; - drawImageUsages |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; - // Post-processing (tonemap) samples HDR; allow sampling. - drawImageUsages |= VK_IMAGE_USAGE_SAMPLED_BIT; - - VkImageCreateInfo rimg_info = vkinit::image_create_info(_drawImage.imageFormat, drawImageUsages, drawImageExtent); - - VmaAllocationCreateInfo rimg_allocinfo = {}; - rimg_allocinfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; - rimg_allocinfo.requiredFlags = static_cast(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - - vmaCreateImage(_deviceManager->allocator(), &rimg_info, &rimg_allocinfo, - &_drawImage.image, &_drawImage.allocation, nullptr); - - VkImageViewCreateInfo rview_info = vkinit::imageview_create_info(_drawImage.imageFormat, _drawImage.image, - VK_IMAGE_ASPECT_COLOR_BIT); - VK_CHECK(vkCreateImageView(_deviceManager->device(), &rview_info, nullptr, &_drawImage.imageView)); - - // Depth - _depthImage.imageFormat = VK_FORMAT_D32_SFLOAT; - _depthImage.imageExtent = drawImageExtent; - VkImageUsageFlags depthImageUsages{}; - depthImageUsages |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; - VkImageCreateInfo dimg_info = vkinit::image_create_info(_depthImage.imageFormat, depthImageUsages, drawImageExtent); - vmaCreateImage(_deviceManager->allocator(), &dimg_info, &rimg_allocinfo, &_depthImage.image, - &_depthImage.allocation, nullptr); - VkImageViewCreateInfo dview_info = vkinit::imageview_create_info(_depthImage.imageFormat, _depthImage.image, - VK_IMAGE_ASPECT_DEPTH_BIT); - VK_CHECK(vkCreateImageView(_deviceManager->device(), &dview_info, nullptr, &_depthImage.imageView)); - - // GBuffer (SRGB not used to keep linear lighting) - _gBufferPosition = _resourceManager->create_image(drawImageExtent, VK_FORMAT_R32G32B32A32_SFLOAT, - VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT); - _gBufferNormal = _resourceManager->create_image(drawImageExtent, VK_FORMAT_R16G16B16A16_SFLOAT, - VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT); - _gBufferAlbedo = _resourceManager->create_image(drawImageExtent, VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT); - _gBufferExtra = _resourceManager->create_image(drawImageExtent, VK_FORMAT_R16G16B16A16_SFLOAT, - VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT); - _idBuffer = _resourceManager->create_image(drawImageExtent, VK_FORMAT_R32_UINT, - VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | - VK_IMAGE_USAGE_TRANSFER_SRC_BIT | - VK_IMAGE_USAGE_SAMPLED_BIT); - - _deletionQueue.push_function([=]() { - vkDestroyImageView(_deviceManager->device(), _drawImage.imageView, nullptr); - vmaDestroyImage(_deviceManager->allocator(), _drawImage.image, _drawImage.allocation); - - vkDestroyImageView(_deviceManager->device(), _depthImage.imageView, nullptr); - vmaDestroyImage(_deviceManager->allocator(), _depthImage.image, _depthImage.allocation); - - _resourceManager->destroy_image(_gBufferPosition); - _resourceManager->destroy_image(_gBufferNormal); - _resourceManager->destroy_image(_gBufferAlbedo); - _resourceManager->destroy_image(_gBufferExtra); - _resourceManager->destroy_image(_idBuffer); - }); - }; - - create_frame_images(); + // Create images used across the frame (draw, depth, GBuffer). + // These are sized to _renderExtent (independent of the swapchain extent) + // so the engine can render at a different internal resolution and then + // upscale/letterbox into the swapchain. + if (_renderExtent.width == 0 || _renderExtent.height == 0) + { + _renderExtent = _windowExtent; + } + resize_render_targets(_renderExtent); } void SwapchainManager::cleanup() @@ -102,6 +34,104 @@ void SwapchainManager::cleanup() fmt::print("SwapchainManager::cleanup()\n"); } +void SwapchainManager::resize_render_targets(VkExtent2D renderExtent) +{ + if (!_deviceManager || !_resourceManager) return; + if (renderExtent.width == 0 || renderExtent.height == 0) return; + + // Avoid doing work when nothing changes (common when called every frame). + if (_renderExtent.width == renderExtent.width && + _renderExtent.height == renderExtent.height && + _drawImage.image != VK_NULL_HANDLE && + _depthImage.image != VK_NULL_HANDLE) + { + return; + } + + // Ensure no in-flight work references these images before we destroy them. + vkDeviceWaitIdle(_deviceManager->device()); + + // Destroy previous targets (if any), then recreate at the new extent. + _deletionQueue.flush(); + _renderExtent = renderExtent; + + VkExtent3D drawImageExtent = { _renderExtent.width, _renderExtent.height, 1 }; + + // Draw HDR target + _drawImage = {}; + _drawImage.imageFormat = VK_FORMAT_R16G16B16A16_SFLOAT; + _drawImage.imageExtent = drawImageExtent; + + VkImageUsageFlags drawImageUsages{}; + drawImageUsages |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + drawImageUsages |= VK_IMAGE_USAGE_STORAGE_BIT; + drawImageUsages |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + // Post-processing (tonemap) samples HDR; allow sampling. + drawImageUsages |= VK_IMAGE_USAGE_SAMPLED_BIT; + + VkImageCreateInfo rimg_info = vkinit::image_create_info(_drawImage.imageFormat, drawImageUsages, drawImageExtent); + + VmaAllocationCreateInfo rimg_allocinfo = {}; + rimg_allocinfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + rimg_allocinfo.requiredFlags = static_cast(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + + vmaCreateImage(_deviceManager->allocator(), &rimg_info, &rimg_allocinfo, + &_drawImage.image, &_drawImage.allocation, nullptr); + + VkImageViewCreateInfo rview_info = vkinit::imageview_create_info(_drawImage.imageFormat, _drawImage.image, + VK_IMAGE_ASPECT_COLOR_BIT); + VK_CHECK(vkCreateImageView(_deviceManager->device(), &rview_info, nullptr, &_drawImage.imageView)); + + // Depth + _depthImage = {}; + _depthImage.imageFormat = VK_FORMAT_D32_SFLOAT; + _depthImage.imageExtent = drawImageExtent; + VkImageUsageFlags depthImageUsages{}; + depthImageUsages |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + VkImageCreateInfo dimg_info = vkinit::image_create_info(_depthImage.imageFormat, depthImageUsages, drawImageExtent); + vmaCreateImage(_deviceManager->allocator(), &dimg_info, &rimg_allocinfo, &_depthImage.image, + &_depthImage.allocation, nullptr); + VkImageViewCreateInfo dview_info = vkinit::imageview_create_info(_depthImage.imageFormat, _depthImage.image, + VK_IMAGE_ASPECT_DEPTH_BIT); + VK_CHECK(vkCreateImageView(_deviceManager->device(), &dview_info, nullptr, &_depthImage.imageView)); + + // GBuffer (SRGB not used to keep linear lighting) + _gBufferPosition = _resourceManager->create_image(drawImageExtent, VK_FORMAT_R32G32B32A32_SFLOAT, + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT); + _gBufferNormal = _resourceManager->create_image(drawImageExtent, VK_FORMAT_R16G16B16A16_SFLOAT, + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT); + _gBufferAlbedo = _resourceManager->create_image(drawImageExtent, VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT); + _gBufferExtra = _resourceManager->create_image(drawImageExtent, VK_FORMAT_R16G16B16A16_SFLOAT, + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT); + _idBuffer = _resourceManager->create_image(drawImageExtent, VK_FORMAT_R32_UINT, + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | + VK_IMAGE_USAGE_TRANSFER_SRC_BIT | + VK_IMAGE_USAGE_SAMPLED_BIT); + + _deletionQueue.push_function([this]() { + if (_drawImage.imageView) vkDestroyImageView(_deviceManager->device(), _drawImage.imageView, nullptr); + if (_drawImage.image) vmaDestroyImage(_deviceManager->allocator(), _drawImage.image, _drawImage.allocation); + + if (_depthImage.imageView) vkDestroyImageView(_deviceManager->device(), _depthImage.imageView, nullptr); + if (_depthImage.image) vmaDestroyImage(_deviceManager->allocator(), _depthImage.image, _depthImage.allocation); + + if (_gBufferPosition.image) _resourceManager->destroy_image(_gBufferPosition); + if (_gBufferNormal.image) _resourceManager->destroy_image(_gBufferNormal); + if (_gBufferAlbedo.image) _resourceManager->destroy_image(_gBufferAlbedo); + if (_gBufferExtra.image) _resourceManager->destroy_image(_gBufferExtra); + if (_idBuffer.image) _resourceManager->destroy_image(_idBuffer); + + _drawImage = {}; + _depthImage = {}; + _gBufferPosition = {}; + _gBufferNormal = {}; + _gBufferAlbedo = {}; + _gBufferExtra = {}; + _idBuffer = {}; + }); +} + void SwapchainManager::create_swapchain(uint32_t width, uint32_t height) { vkb::SwapchainBuilder swapchainBuilder{ diff --git a/src/core/device/swapchain.h b/src/core/device/swapchain.h index e0410d6..dcbc1aa 100644 --- a/src/core/device/swapchain.h +++ b/src/core/device/swapchain.h @@ -18,6 +18,7 @@ public: void create_swapchain(uint32_t width, uint32_t height); void destroy_swapchain() const; void resize_swapchain(struct SDL_Window *window); + void resize_render_targets(VkExtent2D renderExtent); VkSwapchainKHR swapchain() const { return _swapchain; } VkFormat swapchainImageFormat() const { return _swapchainImageFormat; } @@ -35,6 +36,8 @@ public: AllocatedImage gBufferExtra() const { return _gBufferExtra; } AllocatedImage idBuffer() const { return _idBuffer; } VkExtent2D windowExtent() const { return _windowExtent; } + VkExtent2D renderExtent() const { return _renderExtent; } + void set_render_extent(VkExtent2D extent) { _renderExtent = extent; } bool resize_requested{false}; @@ -46,6 +49,7 @@ private: VkFormat _swapchainImageFormat = {}; VkExtent2D _swapchainExtent = {}; VkExtent2D _windowExtent{kRenderWidth, kRenderHeight}; + VkExtent2D _renderExtent{kRenderWidth, kRenderHeight}; std::vector _swapchainImages; std::vector _swapchainImageViews; diff --git a/src/core/engine.cpp b/src/core/engine.cpp index 0a4f16d..997abab 100644 --- a/src/core/engine.cpp +++ b/src/core/engine.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include "config.h" @@ -58,6 +59,28 @@ void vk_engine_draw_debug_ui(VulkanEngine *eng); VulkanEngine *loadedEngine = nullptr; +static VkExtent2D clamp_nonzero_extent(VkExtent2D extent) +{ + if (extent.width == 0) extent.width = 1; + if (extent.height == 0) extent.height = 1; + return extent; +} + +static VkExtent2D scaled_extent(VkExtent2D logicalExtent, float scale) +{ + logicalExtent = clamp_nonzero_extent(logicalExtent); + if (!std::isfinite(scale)) scale = 1.0f; + scale = std::clamp(scale, 0.1f, 4.0f); + + const float fw = static_cast(logicalExtent.width) * scale; + const float fh = static_cast(logicalExtent.height) * scale; + + VkExtent2D out{}; + out.width = static_cast(std::max(1.0f, std::floor(fw))); + out.height = static_cast(std::max(1.0f, std::floor(fh))); + return out; +} + static bool file_exists_nothrow(const std::string &path) { if (path.empty()) return false; @@ -146,9 +169,11 @@ void VulkanEngine::init() // We initialize SDL and create a window with it. SDL_Init(SDL_INIT_VIDEO); - // Initialize fixed logical render resolution for the engine. + // Initialize default logical render resolution for the engine. _logicalRenderExtent.width = kRenderWidth; _logicalRenderExtent.height = kRenderHeight; + _logicalRenderExtent = clamp_nonzero_extent(_logicalRenderExtent); + _drawExtent = scaled_extent(_logicalRenderExtent, renderScale); constexpr auto window_flags = static_cast(SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE); @@ -190,6 +215,7 @@ void VulkanEngine::init() _context->logicalRenderExtent = _logicalRenderExtent; _swapchainManager->init(_deviceManager.get(), _resourceManager.get()); + _swapchainManager->set_render_extent(_drawExtent); _swapchainManager->init_swapchain(); // Fill remaining context pointers now that managers exist @@ -306,6 +332,45 @@ void VulkanEngine::init() _isInitialized = true; } +void VulkanEngine::set_logical_render_extent(VkExtent2D extent) +{ + extent = clamp_nonzero_extent(extent); + if (_logicalRenderExtent.width == extent.width && _logicalRenderExtent.height == extent.height) + { + return; + } + + _logicalRenderExtent = extent; + if (_context) + { + _context->logicalRenderExtent = _logicalRenderExtent; + } + + VkExtent2D newDraw = scaled_extent(_logicalRenderExtent, renderScale); + if (_swapchainManager) + { + _swapchainManager->resize_render_targets(newDraw); + } +} + +void VulkanEngine::set_render_scale(float scale) +{ + if (!std::isfinite(scale)) scale = 1.0f; + scale = std::clamp(scale, 0.1f, 4.0f); + if (std::abs(renderScale - scale) < 1e-4f) + { + return; + } + + renderScale = scale; + + VkExtent2D newDraw = scaled_extent(_logicalRenderExtent, renderScale); + if (_swapchainManager) + { + _swapchainManager->resize_render_targets(newDraw); + } +} + void VulkanEngine::init_default_data() { //> default_img @@ -758,6 +823,17 @@ void VulkanEngine::draw() } } + // Compute desired internal render-target extent from logical extent + render scale. + _drawExtent = scaled_extent(_logicalRenderExtent, renderScale); + if (_swapchainManager) + { + VkExtent2D current = _swapchainManager->renderExtent(); + if (current.width != _drawExtent.width || current.height != _drawExtent.height) + { + _swapchainManager->resize_render_targets(_drawExtent); + } + } + uint32_t swapchainImageIndex; VkResult e = vkAcquireNextImageKHR(_deviceManager->device(), @@ -782,11 +858,6 @@ void VulkanEngine::draw() VK_CHECK(e); } - // Fixed logical render resolution (letterboxed): draw extent is derived - // from the engine's logical render size instead of the swapchain/window. - _drawExtent.width = static_cast(static_cast(_logicalRenderExtent.width) * renderScale); - _drawExtent.height = static_cast(static_cast(_logicalRenderExtent.height) * renderScale); - VK_CHECK(vkResetFences(_deviceManager->device(), 1, &get_current_frame()._renderFence)); //now that we are sure that the commands finished executing, we can safely reset the command buffer to begin recording again. diff --git a/src/core/engine.h b/src/core/engine.h index 7080bac..ed3adcc 100644 --- a/src/core/engine.h +++ b/src/core/engine.h @@ -203,6 +203,12 @@ public: //run main loop void run(); + // Rendering resolution controls: + // - logicalRenderExtent controls camera aspect and picking (letterboxed view). + // - renderScale controls the internal render target pixel count (logical * scale). + void set_logical_render_extent(VkExtent2D extent); + void set_render_scale(float scale); + // Query a conservative streaming texture budget for the texture cache. size_t query_texture_budget_bytes() const; diff --git a/src/core/engine_ui.cpp b/src/core/engine_ui.cpp index 0ed5a25..2b76d7d 100644 --- a/src/core/engine_ui.cpp +++ b/src/core/engine_ui.cpp @@ -51,7 +51,57 @@ namespace ImGui::InputFloat4("data4", reinterpret_cast(&selected.data.data4)); ImGui::Separator(); - ImGui::SliderFloat("Render Scale", &eng->renderScale, 0.3f, 1.f); + ImGui::TextUnformatted("Render Resolution"); + + static int pendingLogicalW = 0; + static int pendingLogicalH = 0; + if (pendingLogicalW <= 0 || pendingLogicalH <= 0) + { + pendingLogicalW = static_cast(eng->_logicalRenderExtent.width); + pendingLogicalH = static_cast(eng->_logicalRenderExtent.height); + } + + ImGui::InputInt("Logical Width", &pendingLogicalW); + ImGui::InputInt("Logical Height", &pendingLogicalH); + + if (ImGui::Button("Apply Logical Resolution")) + { + uint32_t w = static_cast(pendingLogicalW > 0 ? pendingLogicalW : 1); + uint32_t h = static_cast(pendingLogicalH > 0 ? pendingLogicalH : 1); + eng->set_logical_render_extent(VkExtent2D{w, h}); + } + ImGui::SameLine(); + if (ImGui::Button("720p")) + { + pendingLogicalW = 1280; + pendingLogicalH = 720; + } + ImGui::SameLine(); + if (ImGui::Button("1080p")) + { + pendingLogicalW = 1920; + pendingLogicalH = 1080; + } + ImGui::SameLine(); + if (ImGui::Button("1440p")) + { + pendingLogicalW = 2560; + pendingLogicalH = 1440; + } + + static float pendingScale = 1.0f; + if (!ImGui::IsAnyItemActive()) + { + pendingScale = eng->renderScale; + } + bool scaleChanged = ImGui::SliderFloat("Render Scale", &pendingScale, 0.25f, 2.0f); + bool applyScale = scaleChanged && ImGui::IsItemDeactivatedAfterEdit(); + ImGui::SameLine(); + applyScale = ImGui::Button("Apply Scale") || applyScale; + if (applyScale) + { + eng->set_render_scale(pendingScale); + } } // IBL test grid spawner (spheres varying metallic/roughness) diff --git a/src/render/passes/lighting.cpp b/src/render/passes/lighting.cpp index 28ca473..c2ee711 100644 --- a/src/render/passes/lighting.cpp +++ b/src/render/passes/lighting.cpp @@ -45,22 +45,6 @@ void LightingPass::init(EngineContext *context) nullptr, VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT); } - // Allocate and write GBuffer descriptor set - _gBufferInputDescriptorSet = _context->getDescriptors()->allocate( - _context->getDevice()->device(), _gBufferInputDescriptorLayout); - { - DescriptorWriter writer; - writer.write_image(0, _context->getSwapchain()->gBufferPosition().imageView, _context->getSamplers()->defaultLinear(), - VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); - writer.write_image(1, _context->getSwapchain()->gBufferNormal().imageView, _context->getSamplers()->defaultLinear(), - VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); - writer.write_image(2, _context->getSwapchain()->gBufferAlbedo().imageView, _context->getSamplers()->defaultLinear(), - VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); - writer.write_image(3, _context->getSwapchain()->gBufferExtra().imageView, _context->getSamplers()->defaultLinear(), - VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); - writer.update_set(_context->getDevice()->device(), _gBufferInputDescriptorSet); - } - // Shadow map descriptor layout (set = 2, updated per-frame). Use array of cascades { DescriptorLayoutBuilder builder; @@ -164,9 +148,9 @@ void LightingPass::register_graph(RenderGraph *graph, builder.write_color(drawHandle); }, - [this, drawHandle, shadowCascades](VkCommandBuffer cmd, const RGPassResources &res, EngineContext *ctx) + [this, drawHandle, gbufferPosition, gbufferNormal, gbufferAlbedo, gbufferExtra, shadowCascades](VkCommandBuffer cmd, const RGPassResources &res, EngineContext *ctx) { - draw_lighting(cmd, ctx, res, drawHandle, shadowCascades); + draw_lighting(cmd, ctx, res, drawHandle, gbufferPosition, gbufferNormal, gbufferAlbedo, gbufferExtra, shadowCascades); }); } @@ -174,6 +158,10 @@ void LightingPass::draw_lighting(VkCommandBuffer cmd, EngineContext *context, const RGPassResources &resources, RGImageHandle drawHandle, + RGImageHandle gbufferPosition, + RGImageHandle gbufferNormal, + RGImageHandle gbufferAlbedo, + RGImageHandle gbufferExtra, std::span shadowCascades) { EngineContext *ctxLocal = context ? context : _context; @@ -188,6 +176,15 @@ void LightingPass::draw_lighting(VkCommandBuffer cmd, VkImageView drawView = resources.image_view(drawHandle); if (drawView == VK_NULL_HANDLE) return; + VkImageView posView = resources.image_view(gbufferPosition); + VkImageView nrmView = resources.image_view(gbufferNormal); + VkImageView albView = resources.image_view(gbufferAlbedo); + VkImageView extView = resources.image_view(gbufferExtra); + if (posView == VK_NULL_HANDLE || nrmView == VK_NULL_HANDLE || albView == VK_NULL_HANDLE || extView == VK_NULL_HANDLE) + { + return; + } + // Choose RT only if TLAS is valid; otherwise fall back to non-RT. const bool haveRTFeatures = ctxLocal->getDevice()->supportsAccelerationStructure(); const VkAccelerationStructureKHR tlas = (ctxLocal->ray ? ctxLocal->ray->tlas() : VK_NULL_HANDLE); @@ -235,11 +232,27 @@ void LightingPass::draw_lighting(VkCommandBuffer cmd, } writer.update_set(deviceManager->device(), globalDescriptor); + // Allocate and write GBuffer descriptor set for this frame (set = 1). + VkDescriptorSet gbufferSet = ctxLocal->currentFrame->_frameDescriptors.allocate( + deviceManager->device(), _gBufferInputDescriptorLayout); + { + DescriptorWriter gbw; + gbw.write_image(0, posView, ctxLocal->getSamplers()->defaultLinear(), + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); + gbw.write_image(1, nrmView, ctxLocal->getSamplers()->defaultLinear(), + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); + gbw.write_image(2, albView, ctxLocal->getSamplers()->defaultLinear(), + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); + gbw.write_image(3, extView, ctxLocal->getSamplers()->defaultLinear(), + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); + gbw.update_set(deviceManager->device(), gbufferSet); + } + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, _pipeline); vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, _pipelineLayout, 0, 1, &globalDescriptor, 0, nullptr); vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, _pipelineLayout, 1, 1, - &_gBufferInputDescriptorSet, 0, nullptr); + &gbufferSet, 0, nullptr); // 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) diff --git a/src/render/passes/lighting.h b/src/render/passes/lighting.h index 5c8ae66..dfcd403 100644 --- a/src/render/passes/lighting.h +++ b/src/render/passes/lighting.h @@ -27,7 +27,6 @@ private: EngineContext *_context = nullptr; VkDescriptorSetLayout _gBufferInputDescriptorLayout = VK_NULL_HANDLE; - VkDescriptorSet _gBufferInputDescriptorSet = VK_NULL_HANDLE; VkDescriptorSetLayout _shadowDescriptorLayout = VK_NULL_HANDLE; // set=2 (array) // Fallbacks if IBL is not loaded AllocatedImage _fallbackIbl2D{}; // 1x1 black @@ -41,6 +40,10 @@ private: EngineContext *context, const class RGPassResources &resources, RGImageHandle drawHandle, + RGImageHandle gbufferPosition, + RGImageHandle gbufferNormal, + RGImageHandle gbufferAlbedo, + RGImageHandle gbufferExtra, std::span shadowCascades); DeletionQueue _deletionQueue;