ADD: aspect ratio preserving completed
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
#include <core/device/images.h>
|
||||
#include <core/util/initializers.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include "stb_image.h"
|
||||
|
||||
@@ -137,6 +140,89 @@ void vkutil::copy_image_to_image(VkCommandBuffer cmd, VkImage source, VkImage de
|
||||
vkCmdBlitImage2(cmd, &blitInfo);
|
||||
}
|
||||
//< 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
|
||||
static inline int compute_full_mip_count(VkExtent2D imageSize)
|
||||
{
|
||||
|
||||
@@ -5,7 +5,17 @@ namespace vkutil {
|
||||
|
||||
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);
|
||||
// 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);
|
||||
// Variant that generates exactly mipLevels levels (starting at base level 0).
|
||||
void generate_mipmaps_levels(VkCommandBuffer cmd, VkImage image, VkExtent2D imageSize, int mipLevels);
|
||||
|
||||
@@ -23,9 +23,9 @@ void SwapchainManager::init_swapchain()
|
||||
// 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 current window extent
|
||||
// depth/draw/gbuffer sized to fixed logical render extent (letterboxed)
|
||||
auto create_frame_images = [this]() {
|
||||
VkExtent3D drawImageExtent = { _windowExtent.width, _windowExtent.height, 1 };
|
||||
VkExtent3D drawImageExtent = { kRenderWidth, kRenderHeight, 1 };
|
||||
|
||||
// Draw HDR target
|
||||
_drawImage.imageFormat = VK_FORMAT_R16G16B16A16_SFLOAT;
|
||||
@@ -118,7 +118,7 @@ void SwapchainManager::create_swapchain(uint32_t width, uint32_t height)
|
||||
//use vsync present mode
|
||||
.set_desired_present_mode(VK_PRESENT_MODE_FIFO_KHR)
|
||||
.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()
|
||||
.value();
|
||||
|
||||
@@ -127,6 +127,7 @@ void SwapchainManager::create_swapchain(uint32_t width, uint32_t height)
|
||||
_swapchain = vkbSwapchain.swapchain;
|
||||
_swapchainImages = vkbSwapchain.get_images().value();
|
||||
_swapchainImageViews = vkbSwapchain.get_image_views().value();
|
||||
_swapchainImageLayouts.assign(_swapchainImages.size(), VK_IMAGE_LAYOUT_UNDEFINED);
|
||||
}
|
||||
|
||||
void SwapchainManager::destroy_swapchain() const
|
||||
@@ -142,83 +143,36 @@ void SwapchainManager::destroy_swapchain() const
|
||||
|
||||
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());
|
||||
|
||||
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.height = h;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ public:
|
||||
VkExtent2D swapchainExtent() const { return _swapchainExtent; }
|
||||
const std::vector<VkImage> &swapchainImages() const { return _swapchainImages; }
|
||||
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 depthImage() const { return _depthImage; }
|
||||
@@ -47,6 +49,7 @@ private:
|
||||
|
||||
std::vector<VkImage> _swapchainImages;
|
||||
std::vector<VkImageView> _swapchainImageViews;
|
||||
std::vector<VkImageLayout> _swapchainImageLayouts;
|
||||
|
||||
AllocatedImage _drawImage = {};
|
||||
AllocatedImage _depthImage = {};
|
||||
|
||||
@@ -759,19 +759,32 @@ void VulkanEngine::draw()
|
||||
|
||||
uint32_t swapchainImageIndex;
|
||||
|
||||
VkResult e = vkAcquireNextImageKHR(_deviceManager->device(), _swapchainManager->swapchain(), 1000000000,
|
||||
VkResult e = vkAcquireNextImageKHR(_deviceManager->device(),
|
||||
_swapchainManager->swapchain(),
|
||||
1000000000,
|
||||
get_current_frame()._swapchainSemaphore,
|
||||
nullptr, &swapchainImageIndex);
|
||||
nullptr,
|
||||
&swapchainImageIndex);
|
||||
if (e == VK_ERROR_OUT_OF_DATE_KHR)
|
||||
{
|
||||
resize_requested = true;
|
||||
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,
|
||||
_swapchainManager->drawImage().imageExtent.height) * renderScale;
|
||||
_drawExtent.width = std::min(_swapchainManager->swapchainExtent().width,
|
||||
_swapchainManager->drawImage().imageExtent.width) * renderScale;
|
||||
// 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<uint32_t>(static_cast<float>(_logicalRenderExtent.width) * renderScale);
|
||||
_drawExtent.height = static_cast<uint32_t>(static_cast<float>(_logicalRenderExtent.height) * renderScale);
|
||||
|
||||
VK_CHECK(vkResetFences(_deviceManager->device(), 1, &get_current_frame()._renderFence));
|
||||
|
||||
@@ -1041,7 +1054,11 @@ void VulkanEngine::draw()
|
||||
presentInfo.pImageIndices = &swapchainImageIndex;
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -1065,13 +1082,23 @@ void VulkanEngine::run()
|
||||
if (e.type == SDL_QUIT) bQuit = true;
|
||||
if (e.type == SDL_WINDOWEVENT)
|
||||
{
|
||||
if (e.window.event == SDL_WINDOWEVENT_MINIMIZED)
|
||||
switch (e.window.event)
|
||||
{
|
||||
freeze_rendering = true;
|
||||
}
|
||||
if (e.window.event == SDL_WINDOWEVENT_RESTORED)
|
||||
{
|
||||
freeze_rendering = false;
|
||||
case SDL_WINDOWEVENT_MINIMIZED:
|
||||
freeze_rendering = true;
|
||||
break;
|
||||
case SDL_WINDOWEVENT_RESTORED:
|
||||
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)
|
||||
@@ -1190,7 +1217,12 @@ void VulkanEngine::run()
|
||||
}
|
||||
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,
|
||||
|
||||
@@ -251,4 +251,8 @@ private:
|
||||
void init_mesh_pipeline();
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user