ADD: aspect ratio preserving completed

This commit is contained in:
2025-12-12 16:40:53 +09:00
parent 5da5b961f1
commit 5e7de35c52
11 changed files with 356 additions and 110 deletions

View File

@@ -0,0 +1,36 @@
#version 450
layout(location=0) in vec2 inUV;
layout(location=0) out vec4 outColor;
layout(set=0, binding=0) uniform sampler2D uSrc;
layout(push_constant) uniform Push
{
vec2 rect_min; // normalized (0..1) min corner in swapchain UV space
vec2 rect_size; // normalized size in swapchain UV space
} pc;
void main()
{
if (pc.rect_size.x <= 0.0 || pc.rect_size.y <= 0.0)
{
outColor = vec4(0.0, 0.0, 0.0, 1.0);
return;
}
vec2 rect_max = pc.rect_min + pc.rect_size;
vec2 uv = inUV;
if (uv.x < pc.rect_min.x || uv.y < pc.rect_min.y || uv.x > rect_max.x || uv.y > rect_max.y)
{
outColor = vec4(0.0, 0.0, 0.0, 1.0);
return;
}
vec2 local = (uv - pc.rect_min) / pc.rect_size;
local = clamp(local, vec2(0.0), vec2(1.0));
outColor = texture(uSrc, local);
}

View File

@@ -1,6 +1,9 @@
#include <core/device/images.h> #include <core/device/images.h>
#include <core/util/initializers.h> #include <core/util/initializers.h>
#include <algorithm>
#include <cmath>
#define STB_IMAGE_IMPLEMENTATION #define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h" #include "stb_image.h"
@@ -137,6 +140,89 @@ void vkutil::copy_image_to_image(VkCommandBuffer cmd, VkImage source, VkImage de
vkCmdBlitImage2(cmd, &blitInfo); vkCmdBlitImage2(cmd, &blitInfo);
} }
//< copyimg //< copyimg
VkRect2D vkutil::compute_letterbox_rect(VkExtent2D srcSize, VkExtent2D dstSize)
{
VkRect2D rect{};
rect.offset = {0, 0};
rect.extent = dstSize;
if (srcSize.width == 0 || srcSize.height == 0 || dstSize.width == 0 || dstSize.height == 0)
{
return rect;
}
const double srcAspect = double(srcSize.width) / double(srcSize.height);
const double dstAspect = double(dstSize.width) / double(dstSize.height);
if (dstAspect > srcAspect)
{
// Fit by height, bars on left/right.
const double scale = double(dstSize.height) / double(srcSize.height);
uint32_t scaledWidth = static_cast<uint32_t>(std::lround(double(srcSize.width) * scale));
scaledWidth = std::min(scaledWidth, dstSize.width);
const uint32_t offsetX = (dstSize.width - scaledWidth) / 2u;
rect.offset = {static_cast<int32_t>(offsetX), 0};
rect.extent = {scaledWidth, dstSize.height};
}
else
{
// Fit by width, bars on top/bottom.
const double scale = double(dstSize.width) / double(srcSize.width);
uint32_t scaledHeight = static_cast<uint32_t>(std::lround(double(srcSize.height) * scale));
scaledHeight = std::min(scaledHeight, dstSize.height);
const uint32_t offsetY = (dstSize.height - scaledHeight) / 2u;
rect.offset = {0, static_cast<int32_t>(offsetY)};
rect.extent = {dstSize.width, scaledHeight};
}
return rect;
}
void vkutil::copy_image_to_image_letterboxed(VkCommandBuffer cmd,
VkImage source,
VkImage destination,
VkExtent2D srcSize,
VkExtent2D dstSize,
VkFilter filter)
{
VkRect2D dstRect = compute_letterbox_rect(srcSize, dstSize);
VkImageBlit2 blitRegion{ .sType = VK_STRUCTURE_TYPE_IMAGE_BLIT_2, .pNext = nullptr };
blitRegion.srcOffsets[1].x = static_cast<int32_t>(srcSize.width);
blitRegion.srcOffsets[1].y = static_cast<int32_t>(srcSize.height);
blitRegion.srcOffsets[1].z = 1;
blitRegion.dstOffsets[0].x = dstRect.offset.x;
blitRegion.dstOffsets[0].y = dstRect.offset.y;
blitRegion.dstOffsets[0].z = 0;
blitRegion.dstOffsets[1].x = dstRect.offset.x + static_cast<int32_t>(dstRect.extent.width);
blitRegion.dstOffsets[1].y = dstRect.offset.y + static_cast<int32_t>(dstRect.extent.height);
blitRegion.dstOffsets[1].z = 1;
blitRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
blitRegion.srcSubresource.baseArrayLayer = 0;
blitRegion.srcSubresource.layerCount = 1;
blitRegion.srcSubresource.mipLevel = 0;
blitRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
blitRegion.dstSubresource.baseArrayLayer = 0;
blitRegion.dstSubresource.layerCount = 1;
blitRegion.dstSubresource.mipLevel = 0;
VkBlitImageInfo2 blitInfo{ .sType = VK_STRUCTURE_TYPE_BLIT_IMAGE_INFO_2, .pNext = nullptr };
blitInfo.dstImage = destination;
blitInfo.dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
blitInfo.srcImage = source;
blitInfo.srcImageLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
blitInfo.filter = filter;
blitInfo.regionCount = 1;
blitInfo.pRegions = &blitRegion;
vkCmdBlitImage2(cmd, &blitInfo);
}
//> mipgen //> mipgen
static inline int compute_full_mip_count(VkExtent2D imageSize) static inline int compute_full_mip_count(VkExtent2D imageSize)
{ {

View File

@@ -5,7 +5,17 @@ namespace vkutil {
void transition_image(VkCommandBuffer cmd, VkImage image, VkImageLayout currentLayout, VkImageLayout newLayout); void transition_image(VkCommandBuffer cmd, VkImage image, VkImageLayout currentLayout, VkImageLayout newLayout);
// Compute a letterboxed destination rect inside dstSize that preserves srcSize aspect ratio.
VkRect2D compute_letterbox_rect(VkExtent2D srcSize, VkExtent2D dstSize);
void copy_image_to_image(VkCommandBuffer cmd, VkImage source, VkImage destination, VkExtent2D srcSize, VkExtent2D dstSize); void copy_image_to_image(VkCommandBuffer cmd, VkImage source, VkImage destination, VkExtent2D srcSize, VkExtent2D dstSize);
// Blit source into a letterboxed rect in destination (preserves aspect ratio).
void copy_image_to_image_letterboxed(VkCommandBuffer cmd,
VkImage source,
VkImage destination,
VkExtent2D srcSize,
VkExtent2D dstSize,
VkFilter filter = VK_FILTER_LINEAR);
void generate_mipmaps(VkCommandBuffer cmd, VkImage image, VkExtent2D imageSize); void generate_mipmaps(VkCommandBuffer cmd, VkImage image, VkExtent2D imageSize);
// Variant that generates exactly mipLevels levels (starting at base level 0). // Variant that generates exactly mipLevels levels (starting at base level 0).
void generate_mipmaps_levels(VkCommandBuffer cmd, VkImage image, VkExtent2D imageSize, int mipLevels); void generate_mipmaps_levels(VkCommandBuffer cmd, VkImage image, VkExtent2D imageSize, int mipLevels);

View File

@@ -23,9 +23,9 @@ void SwapchainManager::init_swapchain()
// On creation we also push a cleanup lambda to _deletionQueue for final shutdown. // 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. // On resize we will flush that queue first to destroy previous resources.
// depth/draw/gbuffer sized to current window extent // depth/draw/gbuffer sized to fixed logical render extent (letterboxed)
auto create_frame_images = [this]() { auto create_frame_images = [this]() {
VkExtent3D drawImageExtent = { _windowExtent.width, _windowExtent.height, 1 }; VkExtent3D drawImageExtent = { kRenderWidth, kRenderHeight, 1 };
// Draw HDR target // Draw HDR target
_drawImage.imageFormat = VK_FORMAT_R16G16B16A16_SFLOAT; _drawImage.imageFormat = VK_FORMAT_R16G16B16A16_SFLOAT;
@@ -118,7 +118,7 @@ void SwapchainManager::create_swapchain(uint32_t width, uint32_t height)
//use vsync present mode //use vsync present mode
.set_desired_present_mode(VK_PRESENT_MODE_FIFO_KHR) .set_desired_present_mode(VK_PRESENT_MODE_FIFO_KHR)
.set_desired_extent(width, height) .set_desired_extent(width, height)
.add_image_usage_flags(VK_IMAGE_USAGE_TRANSFER_DST_BIT) .add_image_usage_flags(VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)
.build() .build()
.value(); .value();
@@ -127,6 +127,7 @@ void SwapchainManager::create_swapchain(uint32_t width, uint32_t height)
_swapchain = vkbSwapchain.swapchain; _swapchain = vkbSwapchain.swapchain;
_swapchainImages = vkbSwapchain.get_images().value(); _swapchainImages = vkbSwapchain.get_images().value();
_swapchainImageViews = vkbSwapchain.get_image_views().value(); _swapchainImageViews = vkbSwapchain.get_image_views().value();
_swapchainImageLayouts.assign(_swapchainImages.size(), VK_IMAGE_LAYOUT_UNDEFINED);
} }
void SwapchainManager::destroy_swapchain() const void SwapchainManager::destroy_swapchain() const
@@ -142,83 +143,36 @@ void SwapchainManager::destroy_swapchain() const
void SwapchainManager::resize_swapchain(struct SDL_Window *window) void SwapchainManager::resize_swapchain(struct SDL_Window *window)
{ {
int w, h;
// HiDPI-aware drawable size for correct pixel dimensions
SDL_Vulkan_GetDrawableSize(window, &w, &h);
if (w <= 0 || h <= 0)
{
// Window may be minimized or in a transient resize state; keep current swapchain.
resize_requested = true;
return;
}
vkDeviceWaitIdle(_deviceManager->device()); vkDeviceWaitIdle(_deviceManager->device());
destroy_swapchain(); destroy_swapchain();
// Destroy per-frame images before recreating them
_deletionQueue.flush();
int w, h;
// HiDPI-aware drawable size for correct pixel dimensions
SDL_Vulkan_GetDrawableSize(window, &w, &h);
_windowExtent.width = w; _windowExtent.width = w;
_windowExtent.height = h; _windowExtent.height = h;
create_swapchain(_windowExtent.width, _windowExtent.height); create_swapchain(_windowExtent.width, _windowExtent.height);
// Recreate frame images at the new size
// (duplicate the same logic used at init time)
VkExtent3D drawImageExtent = { _windowExtent.width, _windowExtent.height, 1 };
_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;
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<VkMemoryPropertyFlags>(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));
_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));
_gBufferPosition = _resourceManager->create_image(drawImageExtent, VK_FORMAT_R16G16B16A16_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);
});
resize_requested = false; resize_requested = false;
} }
VkImageLayout SwapchainManager::swapchain_image_layout(uint32_t index) const
{
if (index >= _swapchainImageLayouts.size()) return VK_IMAGE_LAYOUT_UNDEFINED;
return _swapchainImageLayouts[index];
}
void SwapchainManager::set_swapchain_image_layout(uint32_t index, VkImageLayout layout)
{
if (index >= _swapchainImageLayouts.size()) return;
_swapchainImageLayouts[index] = layout;
}

View File

@@ -24,6 +24,8 @@ public:
VkExtent2D swapchainExtent() const { return _swapchainExtent; } VkExtent2D swapchainExtent() const { return _swapchainExtent; }
const std::vector<VkImage> &swapchainImages() const { return _swapchainImages; } const std::vector<VkImage> &swapchainImages() const { return _swapchainImages; }
const std::vector<VkImageView> &swapchainImageViews() const { return _swapchainImageViews; } const std::vector<VkImageView> &swapchainImageViews() const { return _swapchainImageViews; }
VkImageLayout swapchain_image_layout(uint32_t index) const;
void set_swapchain_image_layout(uint32_t index, VkImageLayout layout);
AllocatedImage drawImage() const { return _drawImage; } AllocatedImage drawImage() const { return _drawImage; }
AllocatedImage depthImage() const { return _depthImage; } AllocatedImage depthImage() const { return _depthImage; }
@@ -47,6 +49,7 @@ private:
std::vector<VkImage> _swapchainImages; std::vector<VkImage> _swapchainImages;
std::vector<VkImageView> _swapchainImageViews; std::vector<VkImageView> _swapchainImageViews;
std::vector<VkImageLayout> _swapchainImageLayouts;
AllocatedImage _drawImage = {}; AllocatedImage _drawImage = {};
AllocatedImage _depthImage = {}; AllocatedImage _depthImage = {};

View File

@@ -759,19 +759,32 @@ void VulkanEngine::draw()
uint32_t swapchainImageIndex; uint32_t swapchainImageIndex;
VkResult e = vkAcquireNextImageKHR(_deviceManager->device(), _swapchainManager->swapchain(), 1000000000, VkResult e = vkAcquireNextImageKHR(_deviceManager->device(),
_swapchainManager->swapchain(),
1000000000,
get_current_frame()._swapchainSemaphore, get_current_frame()._swapchainSemaphore,
nullptr, &swapchainImageIndex); nullptr,
&swapchainImageIndex);
if (e == VK_ERROR_OUT_OF_DATE_KHR) if (e == VK_ERROR_OUT_OF_DATE_KHR)
{ {
resize_requested = true; resize_requested = true;
return; return;
} }
if (e == VK_SUBOPTIMAL_KHR)
{
// Acquire succeeded and signaled the semaphore. Keep rendering this frame
// so the semaphore gets waited on, but schedule a resize soon.
resize_requested = true;
}
else
{
VK_CHECK(e);
}
_drawExtent.height = std::min(_swapchainManager->swapchainExtent().height, // Fixed logical render resolution (letterboxed): draw extent is derived
_swapchainManager->drawImage().imageExtent.height) * renderScale; // from the engine's logical render size instead of the swapchain/window.
_drawExtent.width = std::min(_swapchainManager->swapchainExtent().width, _drawExtent.width = static_cast<uint32_t>(static_cast<float>(_logicalRenderExtent.width) * renderScale);
_swapchainManager->drawImage().imageExtent.width) * renderScale; _drawExtent.height = static_cast<uint32_t>(static_cast<float>(_logicalRenderExtent.height) * renderScale);
VK_CHECK(vkResetFences(_deviceManager->device(), 1, &get_current_frame()._renderFence)); VK_CHECK(vkResetFences(_deviceManager->device(), 1, &get_current_frame()._renderFence));
@@ -1041,7 +1054,11 @@ void VulkanEngine::draw()
presentInfo.pImageIndices = &swapchainImageIndex; presentInfo.pImageIndices = &swapchainImageIndex;
VkResult presentResult = vkQueuePresentKHR(_deviceManager->graphicsQueue(), &presentInfo); VkResult presentResult = vkQueuePresentKHR(_deviceManager->graphicsQueue(), &presentInfo);
if (presentResult == VK_ERROR_OUT_OF_DATE_KHR) if (_swapchainManager)
{
_swapchainManager->set_swapchain_image_layout(swapchainImageIndex, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
}
if (presentResult == VK_ERROR_OUT_OF_DATE_KHR || presentResult == VK_SUBOPTIMAL_KHR)
{ {
resize_requested = true; resize_requested = true;
} }
@@ -1065,13 +1082,23 @@ void VulkanEngine::run()
if (e.type == SDL_QUIT) bQuit = true; if (e.type == SDL_QUIT) bQuit = true;
if (e.type == SDL_WINDOWEVENT) if (e.type == SDL_WINDOWEVENT)
{ {
if (e.window.event == SDL_WINDOWEVENT_MINIMIZED) switch (e.window.event)
{ {
freeze_rendering = true; case SDL_WINDOWEVENT_MINIMIZED:
} freeze_rendering = true;
if (e.window.event == SDL_WINDOWEVENT_RESTORED) break;
{ case SDL_WINDOWEVENT_RESTORED:
freeze_rendering = false; freeze_rendering = false;
resize_requested = true;
_last_resize_event_ms = SDL_GetTicks();
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
case SDL_WINDOWEVENT_RESIZED:
resize_requested = true;
_last_resize_event_ms = SDL_GetTicks();
break;
default:
break;
} }
} }
if (e.type == SDL_MOUSEMOTION) if (e.type == SDL_MOUSEMOTION)
@@ -1190,7 +1217,12 @@ void VulkanEngine::run()
} }
if (resize_requested) if (resize_requested)
{ {
_swapchainManager->resize_swapchain(_window); const uint32_t now_ms = SDL_GetTicks();
if (now_ms - _last_resize_event_ms >= RESIZE_DEBOUNCE_MS)
{
_swapchainManager->resize_swapchain(_window);
resize_requested = false;
}
} }
// Begin frame: wait for the GPU, resolve pending ID-buffer picks, // Begin frame: wait for the GPU, resolve pending ID-buffer picks,

View File

@@ -251,4 +251,8 @@ private:
void init_mesh_pipeline(); void init_mesh_pipeline();
void init_default_data(); void init_default_data();
// Debounce swapchain recreation during live window resizing.
uint32_t _last_resize_event_ms{0};
static constexpr uint32_t RESIZE_DEBOUNCE_MS = 150;
}; };

View File

@@ -2,12 +2,18 @@
#include <core/context.h> #include <core/context.h>
#include <core/device/images.h> #include <core/device/images.h>
#include <core/util/initializers.h> #include <core/util/initializers.h>
#include <core/pipeline/manager.h>
#include <core/descriptor/descriptors.h>
#include <core/descriptor/manager.h>
#include <core/frame/resources.h>
#include <core/pipeline/sampler.h>
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
#include <queue> #include <queue>
#include <algorithm> #include <algorithm>
#include <cstdio> #include <cstdio>
#include <glm/glm.hpp>
#include <core/device/swapchain.h> #include <core/device/swapchain.h>
#include <core/util/initializers.h> #include <core/util/initializers.h>
@@ -17,6 +23,8 @@
#include "core/device/device.h" #include "core/device/device.h"
#include <chrono> #include <chrono>
#include "assets/manager.h"
void RenderGraph::init(EngineContext *ctx) void RenderGraph::init(EngineContext *ctx)
{ {
_context = ctx; _context = ctx;
@@ -694,11 +702,17 @@ void RenderGraph::execute(VkCommandBuffer cmd)
VkRenderingAttachmentInfo depthInfo{}; VkRenderingAttachmentInfo depthInfo{};
bool hasDepth = false; bool hasDepth = false;
// Choose renderArea as the min of all attachment extents and the desired draw extent // Choose renderArea as the min of all attachment extents.
VkExtent2D chosenExtent{_context->getDrawExtent()}; // Do not pre-clamp to drawExtent here: swapchain passes (ImGui, present)
// should be able to use the full window extent.
VkExtent2D chosenExtent{0, 0};
auto clamp_min = [](VkExtent2D a, VkExtent2D b) { auto clamp_min = [](VkExtent2D a, VkExtent2D b) {
return VkExtent2D{std::min(a.width, b.width), std::min(a.height, b.height)}; return VkExtent2D{std::min(a.width, b.width), std::min(a.height, b.height)};
}; };
auto set_or_clamp = [&](VkExtent2D e) {
if (chosenExtent.width == 0 || chosenExtent.height == 0) chosenExtent = e;
else chosenExtent = clamp_min(chosenExtent, e);
};
// Resolve color attachments // Resolve color attachments
VkExtent2D firstColorExtent{0,0}; VkExtent2D firstColorExtent{0,0};
@@ -714,7 +728,7 @@ void RenderGraph::execute(VkCommandBuffer cmd)
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
if (!a.store) info.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; if (!a.store) info.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
colorInfos.push_back(info); colorInfos.push_back(info);
if (rec->extent.width && rec->extent.height) chosenExtent = clamp_min(chosenExtent, rec->extent); if (rec->extent.width && rec->extent.height) set_or_clamp(rec->extent);
if (firstColorExtent.width == 0 && firstColorExtent.height == 0) if (firstColorExtent.width == 0 && firstColorExtent.height == 0)
{ {
firstColorExtent = rec->extent; firstColorExtent = rec->extent;
@@ -748,10 +762,15 @@ void RenderGraph::execute(VkCommandBuffer cmd)
else depthInfo.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; else depthInfo.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
if (!p.depthAttachment.store) depthInfo.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; if (!p.depthAttachment.store) depthInfo.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
hasDepth = true; hasDepth = true;
if (rec->extent.width && rec->extent.height) chosenExtent = clamp_min(chosenExtent, rec->extent); if (rec->extent.width && rec->extent.height) set_or_clamp(rec->extent);
} }
} }
if (chosenExtent.width == 0 || chosenExtent.height == 0)
{
chosenExtent = _context->getDrawExtent();
}
VkRenderingInfo ri{}; VkRenderingInfo ri{};
ri.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; ri.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
ri.renderArea = VkRect2D{VkOffset2D{0, 0}, chosenExtent}; ri.renderArea = VkRect2D{VkOffset2D{0, 0}, chosenExtent};
@@ -800,17 +819,86 @@ void RenderGraph::add_present_chain(RGImageHandle sourceDraw,
if (!sourceDraw.valid() || !targetSwapchain.valid()) return; if (!sourceDraw.valid() || !targetSwapchain.valid()) return;
add_pass( add_pass(
"CopyToSwapchain", "PresentLetterbox",
RGPassType::Transfer, RGPassType::Graphics,
[sourceDraw, targetSwapchain](RGPassBuilder &builder, EngineContext *) { [sourceDraw, targetSwapchain](RGPassBuilder &builder, EngineContext *) {
builder.read(sourceDraw, RGImageUsage::TransferSrc); builder.read(sourceDraw, RGImageUsage::SampledFragment);
builder.write(targetSwapchain, RGImageUsage::TransferDst); VkClearValue clear{};
clear.color = {{0.f, 0.f, 0.f, 1.f}};
builder.write_color(targetSwapchain, true, clear);
}, },
[sourceDraw, targetSwapchain](VkCommandBuffer cmd, const RGPassResources &res, EngineContext *ctx) { [sourceDraw, targetSwapchain](VkCommandBuffer cmd, const RGPassResources &res, EngineContext *ctx) {
VkImage src = res.image(sourceDraw); if (!ctx || !ctx->currentFrame || !ctx->pipelines) return;
VkImage dst = res.image(targetSwapchain);
if (src == VK_NULL_HANDLE || dst == VK_NULL_HANDLE) return; VkImageView srcView = res.image_view(sourceDraw);
vkutil::copy_image_to_image(cmd, src, dst, ctx->getDrawExtent(), ctx->getSwapchain()->swapchainExtent()); VkImageView dstView = res.image_view(targetSwapchain);
if (srcView == VK_NULL_HANDLE || dstView == VK_NULL_HANDLE) return;
VkPipeline pipeline = VK_NULL_HANDLE;
VkPipelineLayout layout = VK_NULL_HANDLE;
if (!ctx->pipelines->getGraphics("present_letterbox", pipeline, layout))
{
GraphicsPipelineCreateInfo info{};
info.vertexShaderPath = ctx->getAssets()->shaderPath("fullscreen.vert.spv");
info.fragmentShaderPath = ctx->getAssets()->shaderPath("present_letterbox.frag.spv");
info.setLayouts = { ctx->getDescriptorLayouts()->singleImageLayout() };
VkPushConstantRange pcr{};
pcr.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
pcr.offset = 0;
pcr.size = sizeof(glm::vec4);
info.pushConstants = { pcr };
VkFormat swapFmt = ctx->getSwapchain()->swapchainImageFormat();
info.configure = [swapFmt](PipelineBuilder &b) {
b.set_input_topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
b.set_polygon_mode(VK_POLYGON_MODE_FILL);
b.set_cull_mode(VK_CULL_MODE_NONE, VK_FRONT_FACE_CLOCKWISE);
b.set_multisampling_none();
b.disable_depthtest();
b.disable_blending();
b.set_color_attachment_format(swapFmt);
};
if (!ctx->pipelines->createGraphicsPipeline("present_letterbox", info))
{
return;
}
if (!ctx->pipelines->getGraphics("present_letterbox", pipeline, layout))
{
return;
}
}
VkDevice device = ctx->getDevice()->device();
VkDescriptorSetLayout setLayout = ctx->getDescriptorLayouts()->singleImageLayout();
VkDescriptorSet set = ctx->currentFrame->_frameDescriptors.allocate(device, setLayout);
DescriptorWriter writer;
writer.write_image(0, srcView, ctx->getSamplers()->defaultLinear(),
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
writer.update_set(device, set);
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, 1, &set, 0, nullptr);
VkExtent2D srcSize = ctx->getDrawExtent();
VkExtent2D dstSize = ctx->getSwapchain()->swapchainExtent();
VkRect2D dstRect = vkutil::compute_letterbox_rect(srcSize, dstSize);
float minX = dstSize.width > 0 ? float(dstRect.offset.x) / float(dstSize.width) : 0.f;
float minY = dstSize.height > 0 ? float(dstRect.offset.y) / float(dstSize.height) : 0.f;
float sizeX = dstSize.width > 0 ? float(dstRect.extent.width) / float(dstSize.width) : 1.f;
float sizeY = dstSize.height > 0 ? float(dstRect.extent.height) / float(dstSize.height) : 1.f;
glm::vec4 pc{minX, minY, sizeX, sizeY};
vkCmdPushConstants(cmd, layout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(glm::vec4), &pc);
VkViewport vp{0.f, 0.f, float(dstSize.width), float(dstSize.height), 0.f, 1.f};
VkRect2D sc{{0, 0}, dstSize};
vkCmdSetViewport(cmd, 0, 1, &vp);
vkCmdSetScissor(cmd, 0, 1, &sc);
vkCmdDraw(cmd, 3, 1, 0, 0);
}); });
if (appendExtra) if (appendExtra)
@@ -990,9 +1078,8 @@ RGImageHandle RenderGraph::import_swapchain_image(uint32_t index)
d.imageView = views[index]; d.imageView = views[index];
d.format = _context->getSwapchain()->swapchainImageFormat(); d.format = _context->getSwapchain()->swapchainImageFormat();
d.extent = _context->getSwapchain()->swapchainExtent(); d.extent = _context->getSwapchain()->swapchainExtent();
// On first use after swapchain creation, images are in UNDEFINED layout. // Track actual layout across frames. After present, images are in PRESENT_SRC_KHR.
// Start from UNDEFINED so the graph inserts the necessary transition. d.currentLayout = _context->getSwapchain()->swapchain_image_layout(index);
d.currentLayout = VK_IMAGE_LAYOUT_UNDEFINED;
return import_image(d); return import_image(d);
} }

View File

@@ -6,6 +6,7 @@
#include <core/pipeline/manager.h> #include <core/pipeline/manager.h>
#include <core/assets/manager.h> #include <core/assets/manager.h>
#include <core/device/device.h> #include <core/device/device.h>
#include <core/device/swapchain.h>
#include <core/device/resource.h> #include <core/device/resource.h>
#include <core/pipeline/sampler.h> #include <core/pipeline/sampler.h>
#include <render/graph/graph.h> #include <render/graph/graph.h>
@@ -23,6 +24,11 @@ void FxaaPass::init(EngineContext *context)
_inputSetLayout = _context->getDescriptorLayouts()->singleImageLayout(); _inputSetLayout = _context->getDescriptorLayouts()->singleImageLayout();
const VkFormat ldrFormat =
(_context && _context->getSwapchain())
? _context->getSwapchain()->swapchainImageFormat()
: VK_FORMAT_B8G8R8A8_UNORM;
GraphicsPipelineCreateInfo info{}; GraphicsPipelineCreateInfo info{};
info.vertexShaderPath = _context->getAssets()->shaderPath("fullscreen.vert.spv"); info.vertexShaderPath = _context->getAssets()->shaderPath("fullscreen.vert.spv");
info.fragmentShaderPath = _context->getAssets()->shaderPath("fxaa.frag.spv"); info.fragmentShaderPath = _context->getAssets()->shaderPath("fxaa.frag.spv");
@@ -34,14 +40,14 @@ void FxaaPass::init(EngineContext *context)
pcr.size = sizeof(FxaaPush); pcr.size = sizeof(FxaaPush);
info.pushConstants = { pcr }; info.pushConstants = { pcr };
info.configure = [this](PipelineBuilder &b) { info.configure = [ldrFormat](PipelineBuilder &b) {
b.set_input_topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); b.set_input_topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
b.set_polygon_mode(VK_POLYGON_MODE_FILL); b.set_polygon_mode(VK_POLYGON_MODE_FILL);
b.set_cull_mode(VK_CULL_MODE_NONE, VK_FRONT_FACE_CLOCKWISE); b.set_cull_mode(VK_CULL_MODE_NONE, VK_FRONT_FACE_CLOCKWISE);
b.set_multisampling_none(); b.set_multisampling_none();
b.disable_depthtest(); b.disable_depthtest();
b.disable_blending(); b.disable_blending();
b.set_color_attachment_format(VK_FORMAT_R8G8B8A8_UNORM); b.set_color_attachment_format(ldrFormat);
}; };
_context->pipelines->createGraphicsPipeline("fxaa", info); _context->pipelines->createGraphicsPipeline("fxaa", info);
@@ -70,9 +76,14 @@ RGImageHandle FxaaPass::register_graph(RenderGraph *graph, RGImageHandle ldrInpu
return ldrInput; return ldrInput;
} }
const VkFormat ldrFormat =
(_context && _context->getSwapchain())
? _context->getSwapchain()->swapchainImageFormat()
: VK_FORMAT_B8G8R8A8_UNORM;
RGImageDesc desc{}; RGImageDesc desc{};
desc.name = "ldr.fxaa"; desc.name = "ldr.fxaa";
desc.format = VK_FORMAT_R8G8B8A8_UNORM; desc.format = ldrFormat;
desc.extent = _context->getDrawExtent(); desc.extent = _context->getDrawExtent();
desc.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT desc.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
| VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_SAMPLED_BIT

View File

@@ -6,6 +6,7 @@
#include <core/pipeline/manager.h> #include <core/pipeline/manager.h>
#include <core/assets/manager.h> #include <core/assets/manager.h>
#include <core/device/device.h> #include <core/device/device.h>
#include <core/device/swapchain.h>
#include <core/device/resource.h> #include <core/device/resource.h>
#include <core/pipeline/sampler.h> #include <core/pipeline/sampler.h>
#include <render/graph/graph.h> #include <render/graph/graph.h>
@@ -28,6 +29,11 @@ void TonemapPass::init(EngineContext *context)
_inputSetLayout = _context->getDescriptorLayouts()->singleImageLayout(); _inputSetLayout = _context->getDescriptorLayouts()->singleImageLayout();
const VkFormat ldrFormat =
(_context && _context->getSwapchain())
? _context->getSwapchain()->swapchainImageFormat()
: VK_FORMAT_B8G8R8A8_UNORM;
GraphicsPipelineCreateInfo info{}; GraphicsPipelineCreateInfo info{};
info.vertexShaderPath = _context->getAssets()->shaderPath("fullscreen.vert.spv"); info.vertexShaderPath = _context->getAssets()->shaderPath("fullscreen.vert.spv");
info.fragmentShaderPath = _context->getAssets()->shaderPath("tonemap.frag.spv"); info.fragmentShaderPath = _context->getAssets()->shaderPath("tonemap.frag.spv");
@@ -39,14 +45,14 @@ void TonemapPass::init(EngineContext *context)
pcr.size = sizeof(TonemapPush); pcr.size = sizeof(TonemapPush);
info.pushConstants = { pcr }; info.pushConstants = { pcr };
info.configure = [this](PipelineBuilder &b) { info.configure = [ldrFormat](PipelineBuilder &b) {
b.set_input_topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); b.set_input_topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
b.set_polygon_mode(VK_POLYGON_MODE_FILL); b.set_polygon_mode(VK_POLYGON_MODE_FILL);
b.set_cull_mode(VK_CULL_MODE_NONE, VK_FRONT_FACE_CLOCKWISE); b.set_cull_mode(VK_CULL_MODE_NONE, VK_FRONT_FACE_CLOCKWISE);
b.set_multisampling_none(); b.set_multisampling_none();
b.disable_depthtest(); b.disable_depthtest();
b.disable_blending(); b.disable_blending();
b.set_color_attachment_format(VK_FORMAT_R8G8B8A8_UNORM); b.set_color_attachment_format(ldrFormat);
}; };
_context->pipelines->createGraphicsPipeline("tonemap", info); _context->pipelines->createGraphicsPipeline("tonemap", info);
@@ -71,9 +77,14 @@ RGImageHandle TonemapPass::register_graph(RenderGraph *graph, RGImageHandle hdrI
{ {
if (!graph || !hdrInput.valid()) return {}; if (!graph || !hdrInput.valid()) return {};
const VkFormat ldrFormat =
(_context && _context->getSwapchain())
? _context->getSwapchain()->swapchainImageFormat()
: VK_FORMAT_B8G8R8A8_UNORM;
RGImageDesc desc{}; RGImageDesc desc{};
desc.name = "ldr.tonemap"; desc.name = "ldr.tonemap";
desc.format = VK_FORMAT_R8G8B8A8_UNORM; desc.format = ldrFormat;
desc.extent = _context->getDrawExtent(); desc.extent = _context->getDrawExtent();
desc.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT desc.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
| VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_SAMPLED_BIT

View File

@@ -265,10 +265,22 @@ void SceneManager::update_scene()
}; };
// Keep projection FOV in sync with the camera so that CPU ray picking // Keep projection FOV in sync with the camera so that CPU ray picking
// matches what is rendered on-screen. // matches what is rendered inside the fixed logical render area (letterboxed).
const float fov = glm::radians(mainCamera.fovDegrees); const float fov = glm::radians(mainCamera.fovDegrees);
const float aspect = (float) _context->getSwapchain()->windowExtent().width /
(float) _context->getSwapchain()->windowExtent().height; // Derive aspect ratio from the fixed logical render size instead of the window/swapchain.
VkExtent2D logicalExtent{ kRenderWidth, kRenderHeight };
if (_context)
{
VkExtent2D ctxExtent = _context->getLogicalRenderExtent();
if (ctxExtent.width > 0 && ctxExtent.height > 0)
{
logicalExtent = ctxExtent;
}
}
const float aspect = static_cast<float>(logicalExtent.width) /
static_cast<float>(logicalExtent.height);
const float nearPlane = 0.1f; const float nearPlane = 0.1f;
glm::mat4 projection = makeReversedInfinitePerspective(fov, aspect, nearPlane); glm::mat4 projection = makeReversedInfinitePerspective(fov, aspect, nearPlane);
// Vulkan NDC has inverted Y. // Vulkan NDC has inverted Y.