[Vulkan] Add resolution scaling mips support
This commit is contained in:
@@ -164,6 +164,17 @@ void DeferredCommandBuffer::Execute(VkCommandBuffer command_buffer) {
|
||||
alignof(VkBufferImageCopy))));
|
||||
} break;
|
||||
|
||||
case Command::kVkBlitImage: {
|
||||
auto& args = *reinterpret_cast<const ArgsVkBlitImage*>(stream);
|
||||
dfn.vkCmdBlitImage(
|
||||
command_buffer, args.src_image, args.src_image_layout,
|
||||
args.dst_image, args.dst_image_layout, args.region_count,
|
||||
reinterpret_cast<const VkImageBlit*>(
|
||||
reinterpret_cast<const uint8_t*>(stream) +
|
||||
xe::align(sizeof(ArgsVkBlitImage), alignof(VkImageBlit))),
|
||||
args.filter);
|
||||
} break;
|
||||
|
||||
case Command::kVkDispatch: {
|
||||
auto& args = *reinterpret_cast<const ArgsVkDispatch*>(stream);
|
||||
dfn.vkCmdDispatch(command_buffer, args.group_count_x,
|
||||
|
||||
@@ -232,6 +232,34 @@ class DeferredCommandBuffer {
|
||||
regions, sizeof(VkBufferImageCopy) * region_count);
|
||||
}
|
||||
|
||||
VkImageBlit* CmdBlitImageEmplace(VkImage src_image,
|
||||
VkImageLayout src_image_layout,
|
||||
VkImage dst_image,
|
||||
VkImageLayout dst_image_layout,
|
||||
uint32_t region_count, VkFilter filter) {
|
||||
const size_t header_size =
|
||||
xe::align(sizeof(ArgsVkBlitImage), alignof(VkImageBlit));
|
||||
uint8_t* args_ptr = reinterpret_cast<uint8_t*>(
|
||||
WriteCommand(Command::kVkBlitImage,
|
||||
header_size + sizeof(VkImageBlit) * region_count));
|
||||
auto& args = *reinterpret_cast<ArgsVkBlitImage*>(args_ptr);
|
||||
args.src_image = src_image;
|
||||
args.src_image_layout = src_image_layout;
|
||||
args.dst_image = dst_image;
|
||||
args.dst_image_layout = dst_image_layout;
|
||||
args.region_count = region_count;
|
||||
args.filter = filter;
|
||||
return reinterpret_cast<VkImageBlit*>(args_ptr + header_size);
|
||||
}
|
||||
void CmdVkBlitImage(VkImage src_image, VkImageLayout src_image_layout,
|
||||
VkImage dst_image, VkImageLayout dst_image_layout,
|
||||
uint32_t region_count, const VkImageBlit* regions,
|
||||
VkFilter filter) {
|
||||
std::memcpy(CmdBlitImageEmplace(src_image, src_image_layout, dst_image,
|
||||
dst_image_layout, region_count, filter),
|
||||
regions, sizeof(VkImageBlit) * region_count);
|
||||
}
|
||||
|
||||
void CmdVkDispatch(uint32_t group_count_x, uint32_t group_count_y,
|
||||
uint32_t group_count_z) {
|
||||
auto& args = *reinterpret_cast<ArgsVkDispatch*>(
|
||||
@@ -369,6 +397,7 @@ class DeferredCommandBuffer {
|
||||
kVkClearColorImage,
|
||||
kVkCopyBuffer,
|
||||
kVkCopyBufferToImage,
|
||||
kVkBlitImage,
|
||||
kVkDispatch,
|
||||
kVkDraw,
|
||||
kVkDrawIndexed,
|
||||
@@ -464,6 +493,17 @@ class DeferredCommandBuffer {
|
||||
static_assert(alignof(VkBufferImageCopy) <= alignof(uintmax_t));
|
||||
};
|
||||
|
||||
struct ArgsVkBlitImage {
|
||||
VkImage src_image;
|
||||
VkImageLayout src_image_layout;
|
||||
VkImage dst_image;
|
||||
VkImageLayout dst_image_layout;
|
||||
uint32_t region_count;
|
||||
VkFilter filter;
|
||||
// Followed by aligned VkImageBlit[].
|
||||
static_assert(alignof(VkImageBlit) <= alignof(uintmax_t));
|
||||
};
|
||||
|
||||
struct ArgsVkDispatch {
|
||||
uint32_t group_count_x;
|
||||
uint32_t group_count_y;
|
||||
|
||||
@@ -1079,6 +1079,11 @@ std::unique_ptr<TextureCache::Texture> VulkanTextureCache::CreateTexture(
|
||||
image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
image_create_info.usage =
|
||||
VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
// For scaled resolve textures with mips, we need transfer source to generate
|
||||
// mip levels via blit from the base level.
|
||||
if (key.scaled_resolve && key.mip_max_level > 0) {
|
||||
image_create_info.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||
}
|
||||
image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
image_create_info.queueFamilyIndexCount = 0;
|
||||
image_create_info.pQueueFamilyIndices = nullptr;
|
||||
@@ -1157,7 +1162,14 @@ bool VulkanTextureCache::LoadTextureDataFromResidentMemoryImpl(Texture& texture,
|
||||
uint32_t bytes_per_block = guest_format_info->bytes_per_block();
|
||||
uint32_t level_first = load_base ? 0 : 1;
|
||||
uint32_t level_last = load_mips ? texture_key.mip_max_level : 0;
|
||||
assert_true(level_first <= level_last);
|
||||
// For scaled resolve textures, we only load level 0 from the scaled buffer -
|
||||
// mips will be generated via blit.
|
||||
uint32_t level_last_for_blit_gen = 0;
|
||||
if (texture_key.scaled_resolve && level_last > 0) {
|
||||
level_last_for_blit_gen = level_last;
|
||||
level_last = 0; // Only load base level from buffer
|
||||
}
|
||||
assert_true(level_first <= level_last || level_last_for_blit_gen > 0);
|
||||
uint32_t level_packed = guest_layout.packed_level;
|
||||
uint32_t level_stored_first = std::min(level_first, level_packed);
|
||||
uint32_t level_stored_last = std::min(level_last, level_packed);
|
||||
@@ -1385,7 +1397,10 @@ bool VulkanTextureCache::LoadTextureDataFromResidentMemoryImpl(Texture& texture,
|
||||
&write_descriptor_set_source_base_buffer_info;
|
||||
write_descriptor_set_source_base.pTexelBufferView = nullptr;
|
||||
}
|
||||
if (level_last != 0) {
|
||||
// For scaled resolve textures, we don't load mips from buffers - they will
|
||||
// be generated via blit from the base level. For unscaled textures, load
|
||||
// mips from shared memory as usual.
|
||||
if (level_last != 0 && !texture_key.scaled_resolve) {
|
||||
descriptor_set_source_mips =
|
||||
command_processor_.AllocateSingleTransientDescriptor(
|
||||
VulkanCommandProcessor::SingleTransientDescriptorLayout ::
|
||||
@@ -1393,10 +1408,7 @@ bool VulkanTextureCache::LoadTextureDataFromResidentMemoryImpl(Texture& texture,
|
||||
if (!descriptor_set_source_mips) {
|
||||
return false;
|
||||
}
|
||||
// TODO: Implement scaled mips support similar to D3D12.
|
||||
// Currently mips are always loaded from unscaled shared memory even when
|
||||
// the base texture is scaled. D3D12 properly handles scaled mips in
|
||||
// D3D12TextureCache::LoadTextureDataFromResidentMemoryImpl.
|
||||
// Regular unscaled texture - use shared memory
|
||||
write_descriptor_set_source_mips_buffer_info.buffer =
|
||||
vulkan_shared_memory.buffer();
|
||||
write_descriptor_set_source_mips_buffer_info.offset = texture_key.mip_page
|
||||
@@ -1615,6 +1627,97 @@ bool VulkanTextureCache::LoadTextureDataFromResidentMemoryImpl(Texture& texture,
|
||||
copy_region.imageExtent.depth = std::max(depth >> level, UINT32_C(1));
|
||||
}
|
||||
|
||||
// Generate mip levels for scaled resolve textures via blit.
|
||||
if (level_last_for_blit_gen > 0) {
|
||||
VkImage image = vulkan_texture.image();
|
||||
uint32_t scaled_width = width * texture_resolution_scale_x;
|
||||
uint32_t scaled_height = height * texture_resolution_scale_y;
|
||||
|
||||
// Generate each mip level by blitting from the previous level.
|
||||
for (uint32_t level = 1; level <= level_last_for_blit_gen; ++level) {
|
||||
uint32_t src_width = std::max(scaled_width >> (level - 1), UINT32_C(1));
|
||||
uint32_t src_height = std::max(scaled_height >> (level - 1), UINT32_C(1));
|
||||
uint32_t src_depth = std::max(depth >> (level - 1), UINT32_C(1));
|
||||
uint32_t dst_width = std::max(scaled_width >> level, UINT32_C(1));
|
||||
uint32_t dst_height = std::max(scaled_height >> level, UINT32_C(1));
|
||||
uint32_t dst_depth = std::max(depth >> level, UINT32_C(1));
|
||||
// VkImageBlit offsets are int32_t - ensure dimensions fit.
|
||||
assert_true(src_width <= INT32_MAX && src_height <= INT32_MAX &&
|
||||
src_depth <= INT32_MAX);
|
||||
assert_true(dst_width <= INT32_MAX && dst_height <= INT32_MAX &&
|
||||
dst_depth <= INT32_MAX);
|
||||
|
||||
// Transition source mip (level - 1) to TRANSFER_SRC_OPTIMAL.
|
||||
{
|
||||
VkImageMemoryBarrier src_barrier = {};
|
||||
src_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
src_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
src_barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||||
src_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
src_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
||||
src_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
src_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
src_barrier.image = image;
|
||||
src_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
src_barrier.subresourceRange.baseMipLevel = level - 1;
|
||||
src_barrier.subresourceRange.levelCount = 1;
|
||||
src_barrier.subresourceRange.baseArrayLayer = 0;
|
||||
src_barrier.subresourceRange.layerCount = array_size;
|
||||
command_buffer.CmdVkPipelineBarrier(
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
|
||||
0, nullptr, 0, nullptr, 1, &src_barrier);
|
||||
}
|
||||
|
||||
// Blit from level - 1 to level.
|
||||
VkImageBlit blit_region = {};
|
||||
blit_region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
blit_region.srcSubresource.mipLevel = level - 1;
|
||||
blit_region.srcSubresource.baseArrayLayer = 0;
|
||||
blit_region.srcSubresource.layerCount = array_size;
|
||||
blit_region.srcOffsets[0] = {0, 0, 0};
|
||||
blit_region.srcOffsets[1] = {static_cast<int32_t>(src_width),
|
||||
static_cast<int32_t>(src_height),
|
||||
static_cast<int32_t>(src_depth)};
|
||||
blit_region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
blit_region.dstSubresource.mipLevel = level;
|
||||
blit_region.dstSubresource.baseArrayLayer = 0;
|
||||
blit_region.dstSubresource.layerCount = array_size;
|
||||
blit_region.dstOffsets[0] = {0, 0, 0};
|
||||
blit_region.dstOffsets[1] = {static_cast<int32_t>(dst_width),
|
||||
static_cast<int32_t>(dst_height),
|
||||
static_cast<int32_t>(dst_depth)};
|
||||
|
||||
command_buffer.CmdVkBlitImage(image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
1, &blit_region, VK_FILTER_LINEAR);
|
||||
}
|
||||
|
||||
// Transition all mip levels to the final layout (TRANSFER_DST for now,
|
||||
// will be transitioned to shader read when used).
|
||||
// Level 0 to level_last_for_blit_gen - 1 are in TRANSFER_SRC_OPTIMAL.
|
||||
// Level level_last_for_blit_gen is in TRANSFER_DST_OPTIMAL (no change
|
||||
// needed).
|
||||
if (level_last_for_blit_gen > 0) {
|
||||
VkImageMemoryBarrier final_barrier = {};
|
||||
final_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
final_barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||||
final_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
final_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
||||
final_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
final_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
final_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
final_barrier.image = image;
|
||||
final_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
final_barrier.subresourceRange.baseMipLevel = 0;
|
||||
final_barrier.subresourceRange.levelCount = level_last_for_blit_gen;
|
||||
final_barrier.subresourceRange.baseArrayLayer = 0;
|
||||
final_barrier.subresourceRange.layerCount = array_size;
|
||||
command_buffer.CmdVkPipelineBarrier(
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0,
|
||||
nullptr, 0, nullptr, 1, &final_barrier);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ XE_UI_VULKAN_FUNCTION(vkCmdBindDescriptorSets)
|
||||
XE_UI_VULKAN_FUNCTION(vkCmdBindIndexBuffer)
|
||||
XE_UI_VULKAN_FUNCTION(vkCmdBindPipeline)
|
||||
XE_UI_VULKAN_FUNCTION(vkCmdBindVertexBuffers)
|
||||
XE_UI_VULKAN_FUNCTION(vkCmdBlitImage)
|
||||
XE_UI_VULKAN_FUNCTION(vkCmdClearAttachments)
|
||||
XE_UI_VULKAN_FUNCTION(vkCmdClearColorImage)
|
||||
XE_UI_VULKAN_FUNCTION(vkCmdCopyBuffer)
|
||||
|
||||
Reference in New Issue
Block a user