From 80a18e82b77caf984c8c5b5ee523893ae6a50b3a Mon Sep 17 00:00:00 2001 From: nmzik Date: Tue, 8 Sep 2026 11:17:23 +0400 Subject: [PATCH] graphics: simplify image allocation --- CMakeLists.txt | 2 + src/graphics/host_gpu/graphicContext.h | 9 +-- .../host_gpu/renderer/image/image.cpp | 30 +++----- src/graphics/host_gpu/vma.cpp | 29 +++++--- .../presentation/window/swapchain.cpp | 15 +--- tests/ShaderRecompilerComputeTests.cpp | 73 +++++++++++++++++++ 6 files changed, 107 insertions(+), 51 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ecc2e82ab..5980ae429 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -567,6 +567,8 @@ if(BUILD_TESTING) add_test(NAME texture_cache_layered_image COMMAND $ --layered-image-only) + add_test(NAME host_image_allocation + COMMAND $ --host-image-allocation-only) add_test(NAME texture_cache_image_views COMMAND $ --image-view-cache-only) add_test(NAME texture_cache_storage_sampled diff --git a/src/graphics/host_gpu/graphicContext.h b/src/graphics/host_gpu/graphicContext.h index 5fa8d00fa..01e661072 100644 --- a/src/graphics/host_gpu/graphicContext.h +++ b/src/graphics/host_gpu/graphicContext.h @@ -15,7 +15,6 @@ namespace Libs::Graphics { struct VulkanImage; -struct VulkanMemory; inline constexpr uint32_t VULKAN_TARGET_API_VERSION = VK_API_VERSION_1_3; @@ -117,12 +116,6 @@ private: m_image_format_properties; }; -struct VulkanMemory { - vk::MemoryPropertyFlags property = {}; - vk::MemoryPropertyFlags preferred_property = {}; - VmaAllocation allocation = nullptr; -}; - struct VulkanImageState { vk::PipelineStageFlags2 pl_stage = vk::PipelineStageFlagBits2::eAllCommands; vk::AccessFlags2 access_mask = vk::AccessFlagBits2::eNone; @@ -144,7 +137,7 @@ struct VulkanImage { vk::Image image = nullptr; VulkanImageState state; std::vector subresource_states; - Graphics::VulkanMemory memory; + VmaAllocation allocation = nullptr; }; diff --git a/src/graphics/host_gpu/renderer/image/image.cpp b/src/graphics/host_gpu/renderer/image/image.cpp index 5b1a7bc15..9d4eaa4da 100644 --- a/src/graphics/host_gpu/renderer/image/image.cpp +++ b/src/graphics/host_gpu/renderer/image/image.cpp @@ -661,26 +661,17 @@ Image::Image(GraphicContext& graphics, CommandScheduler& scheduler, const ImageI return; } - backing.format = info.pixel_format; - backing.image_type = HostImageType(info.type); - backing.extent = info.extent; - backing.layers = info.IsVolume() ? 1u : info.resources.layers; - backing.mip_levels = info.resources.levels; - backing.samples = info.samples; - backing.flags = ImageCreateFlags(graphics, info); - backing.usage = ImageUsageFlags(graphics, info); - vk::ImageCreateInfo create {}; - create.flags = backing.flags; - create.imageType = backing.image_type; - create.extent = backing.extent; - create.mipLevels = backing.mip_levels; - create.arrayLayers = backing.layers; - create.format = backing.format; + create.flags = ImageCreateFlags(graphics, info); + create.imageType = HostImageType(info.type); + create.extent = info.extent; + create.mipLevels = info.resources.levels; + create.arrayLayers = info.IsVolume() ? 1u : info.resources.layers; + create.format = info.pixel_format; create.tiling = vk::ImageTiling::eOptimal; - create.initialLayout = backing.state.layout; - create.usage = backing.usage; - create.samples = vulkan_sample_count(backing.samples); + create.initialLayout = vk::ImageLayout::eUndefined; + create.usage = ImageUsageFlags(graphics, info); + create.samples = vulkan_sample_count(info.samples); vk::ImageFormatProperties properties {}; if (graphics.GetImageFormatProperties(create.format, create.imageType, create.tiling, @@ -691,10 +682,9 @@ Image::Image(GraphicContext& graphics, CommandScheduler& scheduler, const ImageI "flags=0x%x samples=%u\n", static_cast(create.format), static_cast(create.imageType), static_cast(create.usage), - static_cast(create.flags), backing.samples); + static_cast(create.flags), info.samples); } - backing.memory.property = vk::MemoryPropertyFlagBits::eDeviceLocal; if (!graphics.CreateImage(create, backing)) { EXIT("failed to create image: extent=%ux%ux%u format=%d layers=%u levels=%u\n", create.extent.width, create.extent.height, create.extent.depth, diff --git a/src/graphics/host_gpu/vma.cpp b/src/graphics/host_gpu/vma.cpp index 2ea71ed50..f7b512f32 100644 --- a/src/graphics/host_gpu/vma.cpp +++ b/src/graphics/host_gpu/vma.cpp @@ -130,35 +130,42 @@ uint64_t GraphicContext::GetTotalMemoryBudget() const { bool GraphicContext::CreateImage(const vk::ImageCreateInfo& image_info, VulkanImage& image) { KYTY_PROFILER_FUNCTION(); - EXIT_IF(allocator == nullptr || image.image != nullptr || image.memory.allocation != nullptr); + EXIT_IF(allocator == nullptr || image.image != nullptr || image.allocation != nullptr); - auto& memory = image.memory; VmaAllocationCreateInfo alloc_info {}; - alloc_info.requiredFlags = static_cast(memory.property); - alloc_info.preferredFlags = - static_cast(memory.preferred_property); + alloc_info.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; vk::Image::CType native_image = VK_NULL_HANDLE; const auto result = static_cast( vmaCreateImage(allocator, static_cast(image_info), - &alloc_info, &native_image, &memory.allocation, nullptr)); + &alloc_info, &native_image, &image.allocation, nullptr)); image.image = native_image; if (result != vk::Result::eSuccess) { LogMemoryBudget(); return false; } + image.format = image_info.format; + image.image_type = image_info.imageType; + image.extent = image_info.extent; + image.layers = image_info.arrayLayers; + image.mip_levels = image_info.mipLevels; + image.samples = static_cast(image_info.samples); + image.usage = image_info.usage; + image.flags = image_info.flags; + image.state = {.layout = image_info.initialLayout}; + image.subresource_states.clear(); + return true; } void GraphicContext::DeleteImage(VulkanImage& image) { KYTY_PROFILER_FUNCTION(); - EXIT_IF(allocator == nullptr || image.image == nullptr || image.memory.allocation == nullptr); + EXIT_IF(allocator == nullptr || image.image == nullptr || image.allocation == nullptr); - auto& memory = image.memory; - vmaDestroyImage(allocator, image.image, memory.allocation); - image.image = nullptr; - memory.allocation = nullptr; + vmaDestroyImage(allocator, image.image, image.allocation); + image.image = nullptr; + image.allocation = nullptr; } } // namespace Libs::Graphics diff --git a/src/graphics/presentation/window/swapchain.cpp b/src/graphics/presentation/window/swapchain.cpp index e4d5fc5b4..166d77216 100644 --- a/src/graphics/presentation/window/swapchain.cpp +++ b/src/graphics/presentation/window/swapchain.cpp @@ -191,24 +191,15 @@ void Presenter::Frame::Configure(GraphicContext& graphics, vk::Extent2D extent, } if (dst.image != nullptr) { graphics.DeleteImage(dst); - dst.memory = {}; } - dst.extent = {extent.width, extent.height, 1}; - dst.format = format; - dst.layers = 1; - dst.mip_levels = 1; - dst.state = {}; - dst.subresource_states.clear(); - dst.memory.property = vk::MemoryPropertyFlagBits::eDeviceLocal; - vk::ImageCreateInfo create {}; create.sType = vk::StructureType::eImageCreateInfo; create.imageType = vk::ImageType::e2D; - create.extent = {dst.extent.width, dst.extent.height, 1}; + create.extent = {extent.width, extent.height, 1}; create.mipLevels = 1; create.arrayLayers = 1; - create.format = dst.format; + create.format = format; create.tiling = vk::ImageTiling::eOptimal; create.initialLayout = vk::ImageLayout::eUndefined; create.usage = vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst; @@ -216,7 +207,7 @@ void Presenter::Frame::Configure(GraphicContext& graphics, vk::Extent2D extent, create.samples = vk::SampleCountFlagBits::e1; if (!graphics.CreateImage(create, dst)) { EXIT("failed to allocate prepared presentation image, extent=%ux%u format=%d\n", - dst.extent.width, dst.extent.height, static_cast(dst.format)); + extent.width, extent.height, static_cast(format)); } } diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index bd1e77054..49c750699 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -1762,6 +1762,73 @@ public: return Renderer(); } + void CheckHostImageAllocation() { + constexpr const char *name = "HostImageAllocation"; + auto &graphics = RuntimeContext(); + VulkanImage image; + vk::ImageCreateInfo create{}; + create.flags = vk::ImageCreateFlagBits::eMutableFormat; + create.imageType = vk::ImageType::e2D; + create.format = vk::Format::eR8G8B8A8Unorm; + create.extent = {16, 8, 1}; + create.mipLevels = 3; + create.arrayLayers = 4; + create.samples = vk::SampleCountFlagBits::e1; + create.tiling = vk::ImageTiling::eOptimal; + create.usage = vk::ImageUsageFlagBits::eTransferSrc | + vk::ImageUsageFlagBits::eTransferDst | + vk::ImageUsageFlagBits::eSampled; + create.initialLayout = vk::ImageLayout::eUndefined; + + for (uint32_t pass = 0; pass < 2; ++pass) { + Require(name, "create", graphics.CreateImage(create, image), + "could not allocate native image"); + Require(name, "allocation metadata", + image.image != nullptr && image.allocation != nullptr && + image.format == create.format && + image.image_type == create.imageType && + image.extent == create.extent && + image.layers == create.arrayLayers && + image.mip_levels == create.mipLevels && + image.samples == static_cast(create.samples) && + image.usage == create.usage && image.flags == create.flags, + "native image metadata did not follow its creation descriptor"); + Require(name, "fresh synchronization state", + image.state.layout == create.initialLayout && + image.state.access_mask == vk::AccessFlags2{} && + image.state.pl_stage == vk::PipelineStageFlagBits2::eAllCommands && + image.subresource_states.empty(), + "new allocation retained synchronization state from an old image"); + VkMemoryPropertyFlags properties{}; + vmaGetAllocationMemoryProperties(graphics.allocator, image.allocation, + &properties); + Require(name, "allocation policy", + (properties & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0, + "native image lost its device-local memory requirement"); + + // A reused presentation image may still describe its previous recording. + // No commands use this fixture; seed that bookkeeping before retirement. + image.state = {vk::PipelineStageFlagBits2::eTransfer, + vk::AccessFlagBits2::eTransferWrite, + vk::ImageLayout::eTransferDstOptimal}; + image.subresource_states.assign(image.layers * image.mip_levels, image.state); + graphics.DeleteImage(image); + Require(name, "retirement", + image.image == nullptr && image.allocation == nullptr, + "retired native image retained an allocation handle"); + + create.flags = vk::ImageCreateFlagBits::e2DArrayCompatible; + create.imageType = vk::ImageType::e3D; + create.format = vk::Format::eR32Uint; + create.extent = {8, 4, 2}; + create.mipLevels = 2; + create.arrayLayers = 1; + create.usage = vk::ImageUsageFlagBits::eTransferSrc | + vk::ImageUsageFlagBits::eTransferDst; + } + std::printf("[host] %-32s ok\n", name); + } + void CheckDescriptorHeapLargeSet() { EnsureRuntimeContext(); std::array bindings{}; @@ -28116,6 +28183,11 @@ int main(int argc, char **argv) { vulkan.CheckSchedulerTimeline(); return 0; } + if (argc == 2 && std::strcmp(argv[1], "--host-image-allocation-only") == 0) { + VulkanHarness vulkan; + vulkan.CheckHostImageAllocation(); + return 0; + } if (argc == 2 && std::strcmp(argv[1], "--occlusion-dump-only") == 0) { VulkanHarness vulkan; CheckPm4SyntheticOcclusionCounterDump(vulkan.RuntimeRenderer()); @@ -28428,6 +28500,7 @@ int main(int argc, char **argv) { CheckIndirectImageKeySwitch(); CheckPs5GameExampleImageClearRuntimeShape(); vulkan.CheckSchedulerTimeline(); + vulkan.CheckHostImageAllocation(); vulkan.CheckDescriptorHeapLargeSet(); vulkan.CheckGraphicsPushConstantBank(); vulkan.CheckGpuMappedRangeLifecycle();