shadPS4/src/video_core/texture_cache/image.cpp
squidbus 110a735a04
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / windows-qt (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / macos-qt (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-qt (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / linux-qt-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
externals: Update MoltenVK (#3694)
2025-10-01 18:52:12 -07:00

779 lines
33 KiB
C++

// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <ranges>
#include "common/assert.h"
#include "video_core/renderer_vulkan/liverpool_to_vk.h"
#include "video_core/renderer_vulkan/vk_instance.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/texture_cache/blit_helper.h"
#include "video_core/texture_cache/image.h"
#include <vk_mem_alloc.h>
namespace VideoCore {
using namespace Vulkan;
static vk::ImageUsageFlags ImageUsageFlags(const Vulkan::Instance* instance,
const ImageInfo& info) {
vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eTransferSrc |
vk::ImageUsageFlagBits::eTransferDst |
vk::ImageUsageFlagBits::eSampled;
if (!info.props.is_block) {
if (info.props.is_depth) {
usage |= vk::ImageUsageFlagBits::eDepthStencilAttachment;
} else {
usage |= vk::ImageUsageFlagBits::eColorAttachment;
if (instance->IsAttachmentFeedbackLoopLayoutSupported()) {
usage |= vk::ImageUsageFlagBits::eAttachmentFeedbackLoopEXT;
}
// Always create images with storage flag to avoid needing re-creation in case of e.g
// compute clears This sacrifices a bit of performance but is less work. ExtendedUsage
// flag is also used.
usage |= vk::ImageUsageFlagBits::eStorage;
}
}
return usage;
}
static vk::ImageType ConvertImageType(AmdGpu::ImageType type) noexcept {
switch (type) {
case AmdGpu::ImageType::Color1D:
case AmdGpu::ImageType::Color1DArray:
return vk::ImageType::e1D;
case AmdGpu::ImageType::Color2D:
case AmdGpu::ImageType::Color2DMsaa:
case AmdGpu::ImageType::Color2DArray:
return vk::ImageType::e2D;
case AmdGpu::ImageType::Color3D:
return vk::ImageType::e3D;
default:
UNREACHABLE();
}
}
static vk::FormatFeatureFlags2 FormatFeatureFlags(const vk::ImageUsageFlags usage_flags) {
vk::FormatFeatureFlags2 feature_flags{};
if (usage_flags & vk::ImageUsageFlagBits::eTransferSrc) {
feature_flags |= vk::FormatFeatureFlagBits2::eTransferSrc;
}
if (usage_flags & vk::ImageUsageFlagBits::eTransferDst) {
feature_flags |= vk::FormatFeatureFlagBits2::eTransferDst;
}
if (usage_flags & vk::ImageUsageFlagBits::eSampled) {
feature_flags |= vk::FormatFeatureFlagBits2::eSampledImage;
}
if (usage_flags & vk::ImageUsageFlagBits::eColorAttachment) {
feature_flags |= vk::FormatFeatureFlagBits2::eColorAttachment;
}
if (usage_flags & vk::ImageUsageFlagBits::eDepthStencilAttachment) {
feature_flags |= vk::FormatFeatureFlagBits2::eDepthStencilAttachment;
}
// Note: StorageImage is intentionally ignored for now since it is always set, and can mess up
// compatibility checks.
return feature_flags;
}
UniqueImage::~UniqueImage() {
if (image) {
vmaDestroyImage(allocator, image, allocation);
}
}
void UniqueImage::Create(const vk::ImageCreateInfo& image_ci) {
this->image_ci = image_ci;
ASSERT(!image);
const VmaAllocationCreateInfo alloc_info = {
.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT,
.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,
.requiredFlags = 0,
.preferredFlags = 0,
.pool = VK_NULL_HANDLE,
.pUserData = nullptr,
};
const VkImageCreateInfo image_ci_unsafe = static_cast<VkImageCreateInfo>(image_ci);
VkImage unsafe_image{};
VkResult result = vmaCreateImage(allocator, &image_ci_unsafe, &alloc_info, &unsafe_image,
&allocation, nullptr);
ASSERT_MSG(result == VK_SUCCESS, "Failed allocating image with error {}",
vk::to_string(vk::Result{result}));
image = vk::Image{unsafe_image};
}
Image::Image(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_,
BlitHelper& blit_helper_, Common::SlotVector<ImageView>& slot_image_views_,
const ImageInfo& info_)
: instance{&instance_}, scheduler{&scheduler_}, blit_helper{&blit_helper_},
slot_image_views{&slot_image_views_}, info{info_} {
if (info.pixel_format == vk::Format::eUndefined) {
return;
}
mip_hashes.resize(info.resources.levels);
// Here we force `eExtendedUsage` as don't know all image usage cases beforehand. In normal case
// the texture cache should re-create the resource with the usage requested
vk::ImageCreateFlags flags{vk::ImageCreateFlagBits::eMutableFormat |
vk::ImageCreateFlagBits::eExtendedUsage};
if (info.props.is_volume) {
flags |= vk::ImageCreateFlagBits::e2DArrayCompatible;
}
if (info.props.is_block) {
flags |= vk::ImageCreateFlagBits::eBlockTexelViewCompatible;
}
usage_flags = ImageUsageFlags(instance, info);
format_features = FormatFeatureFlags(usage_flags);
if (info.props.is_depth) {
aspect_mask = vk::ImageAspectFlagBits::eDepth;
if (info.props.has_stencil) {
aspect_mask |= vk::ImageAspectFlagBits::eStencil;
}
}
constexpr auto tiling = vk::ImageTiling::eOptimal;
const auto supported_format = instance->GetSupportedFormat(info.pixel_format, format_features);
const vk::PhysicalDeviceImageFormatInfo2 format_info{
.format = supported_format,
.type = ConvertImageType(info.type),
.tiling = tiling,
.usage = usage_flags,
.flags = flags,
};
const auto image_format_properties =
instance->GetPhysicalDevice().getImageFormatProperties2(format_info);
if (image_format_properties.result == vk::Result::eErrorFormatNotSupported) {
LOG_ERROR(Render_Vulkan, "image format {} type {} is not supported (flags {}, usage {})",
vk::to_string(supported_format), vk::to_string(format_info.type),
vk::to_string(format_info.flags), vk::to_string(format_info.usage));
}
supported_samples = image_format_properties.result == vk::Result::eSuccess
? image_format_properties.value.imageFormatProperties.sampleCounts
: vk::SampleCountFlagBits::e1;
const vk::ImageCreateInfo image_ci = {
.flags = flags,
.imageType = ConvertImageType(info.type),
.format = supported_format,
.extent{
.width = info.size.width,
.height = info.size.height,
.depth = info.size.depth,
},
.mipLevels = static_cast<u32>(info.resources.levels),
.arrayLayers = static_cast<u32>(info.resources.layers),
.samples = LiverpoolToVK::NumSamples(info.num_samples, supported_samples),
.tiling = tiling,
.usage = usage_flags,
.initialLayout = vk::ImageLayout::eUndefined,
};
backing = &backing_images.emplace_back();
backing->num_samples = info.num_samples;
backing->image = UniqueImage{instance->GetDevice(), instance->GetAllocator()};
backing->image.Create(image_ci);
Vulkan::SetObjectName(instance->GetDevice(), GetImage(),
"Image {}x{}x{} {} {} {:#x}:{:#x} L:{} M:{} S:{}", info.size.width,
info.size.height, info.size.depth, AmdGpu::NameOf(info.tile_mode),
vk::to_string(info.pixel_format), info.guest_address, info.guest_size,
info.resources.layers, info.resources.levels, info.num_samples);
}
Image::~Image() = default;
ImageView& Image::FindView(const ImageViewInfo& view_info, bool ensure_guest_samples) {
if (ensure_guest_samples && backing->num_samples > 1 != info.num_samples > 1) {
SetBackingSamples(info.num_samples);
}
const auto& view_infos = backing->image_view_infos;
const auto it = std::ranges::find(view_infos, view_info);
if (it != view_infos.end()) {
const auto view_id = backing->image_view_ids[std::distance(view_infos.begin(), it)];
return (*slot_image_views)[view_id];
}
const auto view_id = slot_image_views->insert(*instance, view_info, *this);
backing->image_view_infos.emplace_back(view_info);
backing->image_view_ids.emplace_back(view_id);
return (*slot_image_views)[view_id];
}
Image::Barriers Image::GetBarriers(vk::ImageLayout dst_layout, vk::AccessFlags2 dst_mask,
vk::PipelineStageFlags2 dst_stage,
std::optional<SubresourceRange> subres_range) {
auto& last_state = backing->state;
auto& subresource_states = backing->subresource_states;
const bool needs_partial_transition =
subres_range &&
(subres_range->base != SubresourceBase{} || subres_range->extent != info.resources);
const bool partially_transited = !subresource_states.empty();
Barriers barriers;
if (needs_partial_transition || partially_transited) {
if (!partially_transited) {
subresource_states.resize(info.resources.levels * info.resources.layers);
std::fill(subresource_states.begin(), subresource_states.end(), last_state);
}
// In case of partial transition, we need to change the specified subresources only.
// Otherwise all subresources need to be set to the same state so we can use a full
// resource transition for the next time.
const auto mips =
needs_partial_transition
? std::ranges::views::iota(subres_range->base.level,
subres_range->base.level + subres_range->extent.levels)
: std::views::iota(0u, info.resources.levels);
const auto layers =
needs_partial_transition
? std::ranges::views::iota(subres_range->base.layer,
subres_range->base.layer + subres_range->extent.layers)
: std::views::iota(0u, info.resources.layers);
for (u32 mip : mips) {
for (u32 layer : layers) {
// NOTE: these loops may produce a lot of small barriers.
// If this becomes a problem, we can optimize it by merging adjacent barriers.
const auto subres_idx = mip * info.resources.layers + layer;
ASSERT(subres_idx < subresource_states.size());
auto& state = subresource_states[subres_idx];
if (state.layout != dst_layout || state.access_mask != dst_mask) {
barriers.emplace_back(vk::ImageMemoryBarrier2{
.srcStageMask = state.pl_stage,
.srcAccessMask = state.access_mask,
.dstStageMask = dst_stage,
.dstAccessMask = dst_mask,
.oldLayout = state.layout,
.newLayout = dst_layout,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = GetImage(),
.subresourceRange{
.aspectMask = aspect_mask,
.baseMipLevel = mip,
.levelCount = 1,
.baseArrayLayer = layer,
.layerCount = 1,
},
});
state.layout = dst_layout;
state.access_mask = dst_mask;
state.pl_stage = dst_stage;
}
}
}
if (!needs_partial_transition) {
subresource_states.clear();
}
} else { // Full resource transition
if (last_state.layout == dst_layout && last_state.access_mask == dst_mask) {
return {};
}
barriers.emplace_back(vk::ImageMemoryBarrier2{
.srcStageMask = last_state.pl_stage,
.srcAccessMask = last_state.access_mask,
.dstStageMask = dst_stage,
.dstAccessMask = dst_mask,
.oldLayout = last_state.layout,
.newLayout = dst_layout,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = GetImage(),
.subresourceRange{
.aspectMask = aspect_mask,
.baseMipLevel = 0,
.levelCount = VK_REMAINING_MIP_LEVELS,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
});
}
last_state.layout = dst_layout;
last_state.access_mask = dst_mask;
last_state.pl_stage = dst_stage;
return barriers;
}
void Image::Transit(vk::ImageLayout dst_layout, vk::AccessFlags2 dst_mask,
std::optional<SubresourceRange> range, vk::CommandBuffer cmdbuf /*= {}*/) {
// Adjust pipieline stage
const vk::PipelineStageFlags2 dst_pl_stage =
(dst_mask == vk::AccessFlagBits2::eTransferRead ||
dst_mask == vk::AccessFlagBits2::eTransferWrite)
? vk::PipelineStageFlagBits2::eTransfer
: vk::PipelineStageFlagBits2::eAllGraphics | vk::PipelineStageFlagBits2::eComputeShader;
const auto barriers = GetBarriers(dst_layout, dst_mask, dst_pl_stage, range);
if (barriers.empty()) {
return;
}
if (!cmdbuf) {
// When using external cmdbuf you are responsible for ending rp.
scheduler->EndRendering();
cmdbuf = scheduler->CommandBuffer();
}
cmdbuf.pipelineBarrier2(vk::DependencyInfo{
.imageMemoryBarrierCount = static_cast<u32>(barriers.size()),
.pImageMemoryBarriers = barriers.data(),
});
}
void Image::Upload(std::span<const vk::BufferImageCopy> upload_copies, vk::Buffer buffer,
u64 offset) {
SetBackingSamples(info.num_samples, false);
scheduler->EndRendering();
const vk::BufferMemoryBarrier2 pre_barrier{
.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands,
.srcAccessMask = vk::AccessFlagBits2::eMemoryWrite,
.dstStageMask = vk::PipelineStageFlagBits2::eTransfer,
.dstAccessMask = vk::AccessFlagBits2::eTransferRead,
.buffer = buffer,
.offset = offset,
.size = info.guest_size,
};
const vk::BufferMemoryBarrier2 post_barrier{
.srcStageMask = vk::PipelineStageFlagBits2::eTransfer,
.srcAccessMask = vk::AccessFlagBits2::eTransferWrite,
.dstStageMask = vk::PipelineStageFlagBits2::eAllCommands,
.dstAccessMask = vk::AccessFlagBits2::eMemoryRead | vk::AccessFlagBits2::eMemoryWrite,
.buffer = buffer,
.offset = offset,
.size = info.guest_size,
};
const auto image_barriers =
GetBarriers(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite,
vk::PipelineStageFlagBits2::eCopy, {});
const auto cmdbuf = scheduler->CommandBuffer();
cmdbuf.pipelineBarrier2(vk::DependencyInfo{
.dependencyFlags = vk::DependencyFlagBits::eByRegion,
.bufferMemoryBarrierCount = 1,
.pBufferMemoryBarriers = &pre_barrier,
.imageMemoryBarrierCount = static_cast<u32>(image_barriers.size()),
.pImageMemoryBarriers = image_barriers.data(),
});
cmdbuf.copyBufferToImage(buffer, GetImage(), vk::ImageLayout::eTransferDstOptimal,
upload_copies);
cmdbuf.pipelineBarrier2(vk::DependencyInfo{
.dependencyFlags = vk::DependencyFlagBits::eByRegion,
.bufferMemoryBarrierCount = 1,
.pBufferMemoryBarriers = &post_barrier,
});
flags &= ~ImageFlagBits::Dirty;
}
void Image::Download(std::span<const vk::BufferImageCopy> download_copies, vk::Buffer buffer,
u64 offset, u64 download_size) {
SetBackingSamples(info.num_samples);
scheduler->EndRendering();
const vk::BufferMemoryBarrier2 pre_barrier = {
.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands,
.srcAccessMask = vk::AccessFlagBits2::eMemoryRead,
.dstStageMask = vk::PipelineStageFlagBits2::eCopy,
.dstAccessMask = vk::AccessFlagBits2::eTransferWrite,
.buffer = buffer,
.offset = offset,
.size = download_size,
};
const vk::BufferMemoryBarrier2 post_barrier = {
.srcStageMask = vk::PipelineStageFlagBits2::eCopy,
.srcAccessMask = vk::AccessFlagBits2::eTransferWrite,
.dstStageMask = vk::PipelineStageFlagBits2::eAllCommands,
.dstAccessMask = vk::AccessFlagBits2::eMemoryRead,
.buffer = buffer,
.offset = offset,
.size = download_size,
};
const auto image_barriers =
GetBarriers(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead,
vk::PipelineStageFlagBits2::eCopy, {});
auto cmdbuf = scheduler->CommandBuffer();
cmdbuf.pipelineBarrier2(vk::DependencyInfo{
.dependencyFlags = vk::DependencyFlagBits::eByRegion,
.bufferMemoryBarrierCount = 1,
.pBufferMemoryBarriers = &pre_barrier,
.imageMemoryBarrierCount = static_cast<u32>(image_barriers.size()),
.pImageMemoryBarriers = image_barriers.data(),
});
cmdbuf.copyImageToBuffer(GetImage(), vk::ImageLayout::eTransferSrcOptimal, buffer,
download_copies);
cmdbuf.pipelineBarrier2(vk::DependencyInfo{
.dependencyFlags = vk::DependencyFlagBits::eByRegion,
.bufferMemoryBarrierCount = 1,
.pBufferMemoryBarriers = &post_barrier,
});
}
static std::pair<u32, u32> SanitizeCopyLayers(const ImageInfo& src_info, const ImageInfo& dst_info,
const u32 depth) {
const auto vk_src_type = ConvertImageType(src_info.type);
const auto vk_dst_type = ConvertImageType(dst_info.type);
u32 src_layers = src_info.resources.layers;
u32 dst_layers = dst_info.resources.layers;
// 3D images can only use 1 layer.
if (vk_src_type == vk::ImageType::e3D && src_layers != 1) {
LOG_WARNING(Render_Vulkan, "Coercing copy 3D source layers {} to 1.", src_layers);
src_layers = 1;
}
if (vk_dst_type == vk::ImageType::e3D && dst_layers != 1) {
LOG_WARNING(Render_Vulkan, "Coercing copy 3D destination layers {} to 1.", dst_layers);
dst_layers = 1;
}
// If the image type is equal, layer count must match. Take the minimum of both.
if (vk_src_type == vk_dst_type) {
if (src_layers != dst_layers) {
LOG_WARNING(Render_Vulkan,
"Coercing copy source layers {} and destination layers {} to minimum.",
src_layers, dst_layers);
src_layers = dst_layers = std::min(src_layers, dst_layers);
}
} else {
// For 2D <-> 3D copies, 2D layer count must equal 3D depth.
if (vk_src_type == vk::ImageType::e2D && vk_dst_type == vk::ImageType::e3D &&
src_layers != depth) {
LOG_WARNING(Render_Vulkan,
"Coercing copy 2D source layers {} to 3D destination depth {}", src_layers,
depth);
src_layers = depth;
}
if (vk_src_type == vk::ImageType::e3D && vk_dst_type == vk::ImageType::e2D &&
dst_layers != depth) {
LOG_WARNING(Render_Vulkan,
"Coercing copy 2D destination layers {} to 3D source depth {}", dst_layers,
depth);
dst_layers = depth;
}
}
return std::make_pair(src_layers, dst_layers);
}
void Image::CopyImage(Image& src_image) {
const auto& src_info = src_image.info;
const u32 num_mips = std::min(src_info.resources.levels, info.resources.levels);
ASSERT(src_info.resources.layers == info.resources.layers || num_mips == 1);
const u32 width = src_info.size.width;
const u32 height = src_info.size.height;
const u32 depth =
info.type == AmdGpu::ImageType::Color3D ? info.size.depth : src_info.size.depth;
SetBackingSamples(info.num_samples, false);
src_image.SetBackingSamples(src_info.num_samples);
boost::container::small_vector<vk::ImageCopy, 8> image_copies;
for (u32 mip = 0; mip < num_mips; ++mip) {
const auto mip_w = std::max(width >> mip, 1u);
const auto mip_h = std::max(height >> mip, 1u);
const auto mip_d = std::max(depth >> mip, 1u);
const auto [src_layers, dst_layers] = SanitizeCopyLayers(src_info, info, mip_d);
image_copies.emplace_back(vk::ImageCopy{
.srcSubresource{
.aspectMask = src_image.aspect_mask & ~vk::ImageAspectFlagBits::eStencil,
.mipLevel = mip,
.baseArrayLayer = 0,
.layerCount = src_layers,
},
.dstSubresource{
.aspectMask = aspect_mask & ~vk::ImageAspectFlagBits::eStencil,
.mipLevel = mip,
.baseArrayLayer = 0,
.layerCount = dst_layers,
},
.extent = {mip_w, mip_h, mip_d},
});
}
scheduler->EndRendering();
src_image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {});
Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {});
auto cmdbuf = scheduler->CommandBuffer();
cmdbuf.copyImage(src_image.GetImage(), src_image.backing->state.layout, GetImage(),
backing->state.layout, image_copies);
Transit(vk::ImageLayout::eGeneral,
vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {});
}
void Image::CopyImageWithBuffer(Image& src_image, vk::Buffer buffer, u64 offset) {
const auto& src_info = src_image.info;
const u32 num_mips = std::min(src_info.resources.levels, info.resources.levels);
const u32 num_layers = std::min(src_info.resources.layers, info.resources.layers);
ASSERT(src_info.resources.layers == info.resources.layers || num_mips == 1);
SetBackingSamples(info.num_samples, false);
src_image.SetBackingSamples(src_info.num_samples);
boost::container::small_vector<vk::BufferImageCopy, 8> buffer_copies;
for (u32 mip = 0; mip < num_mips; ++mip) {
const auto mip_w = std::max(src_info.size.width >> mip, 1u);
const auto mip_h = std::max(src_info.size.height >> mip, 1u);
const auto mip_d = std::max(src_info.size.depth >> mip, 1u);
buffer_copies.emplace_back(vk::BufferImageCopy{
.bufferOffset = offset,
.bufferRowLength = 0,
.bufferImageHeight = 0,
.imageSubresource{
.aspectMask = src_image.aspect_mask & ~vk::ImageAspectFlagBits::eStencil,
.mipLevel = mip,
.baseArrayLayer = 0,
.layerCount = num_layers,
},
.imageOffset = {0, 0, 0},
.imageExtent = {mip_w, mip_h, mip_d},
});
}
const vk::BufferMemoryBarrier2 pre_copy_barrier = {
.srcStageMask = vk::PipelineStageFlagBits2::eTransfer,
.srcAccessMask = vk::AccessFlagBits2::eTransferRead,
.dstStageMask = vk::PipelineStageFlagBits2::eTransfer,
.dstAccessMask = vk::AccessFlagBits2::eTransferWrite,
.buffer = buffer,
.offset = offset,
.size = VK_WHOLE_SIZE,
};
const vk::BufferMemoryBarrier2 post_copy_barrier = {
.srcStageMask = vk::PipelineStageFlagBits2::eTransfer,
.srcAccessMask = vk::AccessFlagBits2::eTransferWrite,
.dstStageMask = vk::PipelineStageFlagBits2::eTransfer,
.dstAccessMask = vk::AccessFlagBits2::eTransferRead,
.buffer = buffer,
.offset = offset,
.size = VK_WHOLE_SIZE,
};
scheduler->EndRendering();
src_image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {});
Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {});
auto cmdbuf = scheduler->CommandBuffer();
cmdbuf.pipelineBarrier2(vk::DependencyInfo{
.dependencyFlags = vk::DependencyFlagBits::eByRegion,
.bufferMemoryBarrierCount = 1,
.pBufferMemoryBarriers = &pre_copy_barrier,
});
cmdbuf.copyImageToBuffer(src_image.GetImage(), vk::ImageLayout::eTransferSrcOptimal, buffer,
buffer_copies);
cmdbuf.pipelineBarrier2(vk::DependencyInfo{
.dependencyFlags = vk::DependencyFlagBits::eByRegion,
.bufferMemoryBarrierCount = 1,
.pBufferMemoryBarriers = &post_copy_barrier,
});
for (auto& copy : buffer_copies) {
copy.imageSubresource.aspectMask = aspect_mask & ~vk::ImageAspectFlagBits::eStencil;
}
cmdbuf.copyBufferToImage(buffer, GetImage(), vk::ImageLayout::eTransferDstOptimal,
buffer_copies);
}
void Image::CopyMip(Image& src_image, u32 mip, u32 slice) {
const auto& src_info = src_image.info;
const auto mip_w = std::max(info.size.width >> mip, 1u);
const auto mip_h = std::max(info.size.height >> mip, 1u);
const auto mip_d = std::max(info.size.depth >> mip, 1u);
const auto [src_layers, dst_layers] = SanitizeCopyLayers(src_info, info, mip_d);
ASSERT(mip_w == src_info.size.width);
ASSERT(mip_h == src_info.size.height);
const vk::ImageCopy image_copy{
.srcSubresource{
.aspectMask = src_image.aspect_mask,
.mipLevel = 0,
.baseArrayLayer = 0,
.layerCount = src_layers,
},
.dstSubresource{
.aspectMask = src_image.aspect_mask,
.mipLevel = mip,
.baseArrayLayer = slice,
.layerCount = dst_layers,
},
.extent = {mip_w, mip_h, mip_d},
};
SetBackingSamples(info.num_samples);
src_image.SetBackingSamples(src_info.num_samples);
scheduler->EndRendering();
Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {});
src_image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {});
const auto cmdbuf = scheduler->CommandBuffer();
cmdbuf.copyImage(src_image.GetImage(), src_image.backing->state.layout, GetImage(),
backing->state.layout, image_copy);
}
void Image::Resolve(Image& src_image, const VideoCore::SubresourceRange& mrt0_range,
const VideoCore::SubresourceRange& mrt1_range) {
SetBackingSamples(1, false);
scheduler->EndRendering();
src_image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead,
mrt0_range);
Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, mrt1_range);
const auto [src_layers, dst_layers] = SanitizeCopyLayers(src_image.info, info, 1);
if (src_image.backing->num_samples == 1) {
const vk::ImageCopy region = {
.srcSubresource{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.mipLevel = 0,
.baseArrayLayer = mrt0_range.base.layer,
.layerCount = src_layers,
},
.srcOffset = {0, 0, 0},
.dstSubresource{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.mipLevel = 0,
.baseArrayLayer = mrt1_range.base.layer,
.layerCount = dst_layers,
},
.dstOffset = {0, 0, 0},
.extent = {info.size.width, info.size.height, 1},
};
scheduler->CommandBuffer().copyImage(src_image.GetImage(),
vk::ImageLayout::eTransferSrcOptimal, GetImage(),
vk::ImageLayout::eTransferDstOptimal, region);
} else {
const vk::ImageResolve region = {
.srcSubresource{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.mipLevel = 0,
.baseArrayLayer = mrt0_range.base.layer,
.layerCount = src_layers,
},
.srcOffset = {0, 0, 0},
.dstSubresource{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.mipLevel = 0,
.baseArrayLayer = mrt1_range.base.layer,
.layerCount = dst_layers,
},
.dstOffset = {0, 0, 0},
.extent = {info.size.width, info.size.height, 1},
};
scheduler->CommandBuffer().resolveImage(src_image.GetImage(),
vk::ImageLayout::eTransferSrcOptimal, GetImage(),
vk::ImageLayout::eTransferDstOptimal, region);
}
flags |= VideoCore::ImageFlagBits::GpuModified;
flags &= ~VideoCore::ImageFlagBits::Dirty;
}
void Image::Clear(const vk::ClearValue& clear_value, const VideoCore::SubresourceRange& range) {
const vk::ImageSubresourceRange vk_range = {
.aspectMask = vk::ImageAspectFlagBits::eColor,
.baseMipLevel = range.base.level,
.levelCount = range.extent.levels,
.baseArrayLayer = range.base.layer,
.layerCount = range.extent.layers,
};
scheduler->EndRendering();
Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {});
const auto cmdbuf = scheduler->CommandBuffer();
cmdbuf.clearColorImage(GetImage(), vk::ImageLayout::eTransferDstOptimal, clear_value.color,
vk_range);
}
void Image::SetBackingSamples(u32 num_samples, bool copy_backing) {
if (!backing || backing->num_samples == num_samples) {
return;
}
ASSERT_MSG(!info.props.is_depth, "Swapping samples is only valid for color images");
BackingImage* new_backing;
auto it = std::ranges::find(backing_images, num_samples, &BackingImage::num_samples);
if (it == backing_images.end()) {
auto new_image_ci = backing->image.image_ci;
new_image_ci.samples = LiverpoolToVK::NumSamples(num_samples, supported_samples);
new_backing = &backing_images.emplace_back();
new_backing->num_samples = num_samples;
new_backing->image = UniqueImage{instance->GetDevice(), instance->GetAllocator()};
new_backing->image.Create(new_image_ci);
Vulkan::SetObjectName(instance->GetDevice(), new_backing->image.image,
"Image {}x{}x{} {} {} {:#x}:{:#x} L:{} M:{} S:{} (backing)",
info.size.width, info.size.height, info.size.depth,
AmdGpu::NameOf(info.tile_mode), vk::to_string(info.pixel_format),
info.guest_address, info.guest_size, info.resources.layers,
info.resources.levels, num_samples);
} else {
new_backing = std::addressof(*it);
}
if (copy_backing) {
scheduler->EndRendering();
ASSERT(info.resources.levels == 1 && info.resources.layers == 1);
// Transition current backing to shader read layout
auto barriers =
GetBarriers(vk::ImageLayout::eShaderReadOnlyOptimal, vk::AccessFlagBits2::eShaderRead,
vk::PipelineStageFlagBits2::eFragmentShader, std::nullopt);
// Transition dest backing to color attachment layout, not caring of previous contents
constexpr auto dst_stage = vk::PipelineStageFlagBits2::eColorAttachmentOutput;
constexpr auto dst_access = vk::AccessFlagBits2::eColorAttachmentWrite;
constexpr auto dst_layout = vk::ImageLayout::eColorAttachmentOptimal;
barriers.push_back(vk::ImageMemoryBarrier2{
.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands,
.srcAccessMask = vk::AccessFlagBits2::eNone,
.dstStageMask = dst_stage,
.dstAccessMask = dst_access,
.oldLayout = vk::ImageLayout::eUndefined,
.newLayout = dst_layout,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = new_backing->image,
.subresourceRange{
.aspectMask = aspect_mask,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = info.resources.layers,
},
});
const auto cmdbuf = scheduler->CommandBuffer();
cmdbuf.pipelineBarrier2(vk::DependencyInfo{
.imageMemoryBarrierCount = static_cast<u32>(barriers.size()),
.pImageMemoryBarriers = barriers.data(),
});
// Copy between ms and non ms backing images
blit_helper->CopyBetweenMsImages(
info.size.width, info.size.height, new_backing->num_samples, info.pixel_format,
backing->num_samples > 1, backing->image, new_backing->image);
// Update current layout in tracker to new backings layout
new_backing->state.layout = dst_layout;
new_backing->state.access_mask = dst_access;
new_backing->state.pl_stage = dst_stage;
}
backing = new_backing;
}
} // namespace VideoCore