summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar GPUCode2023-09-20 19:57:47 +0300
committerGravatar Liam2023-09-25 09:20:32 -0400
commit5e4938ab1a8dbc121b522a292d230a274a03cfed (patch)
treef6cac92fcf3f261dad3dd77285d6de6aef75df8e
parentMerge pull request #11225 from FernandoS27/no-laxatives-in-santas-cookies (diff)
downloadyuzu-5e4938ab1a8dbc121b522a292d230a274a03cfed.tar.gz
yuzu-5e4938ab1a8dbc121b522a292d230a274a03cfed.tar.xz
yuzu-5e4938ab1a8dbc121b522a292d230a274a03cfed.zip
renderer_vulkan: Implement MSAA copies
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pass.cpp139
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pass.h19
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp81
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.h7
-rw-r--r--src/video_core/vulkan_common/vulkan_device.h5
5 files changed, 219 insertions, 32 deletions
diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp
index 289d5b25c..617f92910 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp
+++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp
@@ -3,6 +3,7 @@
3 3
4#include <array> 4#include <array>
5#include <memory> 5#include <memory>
6#include <numeric>
6#include <optional> 7#include <optional>
7#include <utility> 8#include <utility>
8 9
@@ -11,7 +12,10 @@
11#include "common/assert.h" 12#include "common/assert.h"
12#include "common/common_types.h" 13#include "common/common_types.h"
13#include "common/div_ceil.h" 14#include "common/div_ceil.h"
15#include "common/vector_math.h"
14#include "video_core/host_shaders/astc_decoder_comp_spv.h" 16#include "video_core/host_shaders/astc_decoder_comp_spv.h"
17#include "video_core/host_shaders/convert_msaa_to_non_msaa_comp_spv.h"
18#include "video_core/host_shaders/convert_non_msaa_to_msaa_comp_spv.h"
15#include "video_core/host_shaders/queries_prefix_scan_sum_comp_spv.h" 19#include "video_core/host_shaders/queries_prefix_scan_sum_comp_spv.h"
16#include "video_core/host_shaders/queries_prefix_scan_sum_nosubgroups_comp_spv.h" 20#include "video_core/host_shaders/queries_prefix_scan_sum_nosubgroups_comp_spv.h"
17#include "video_core/host_shaders/resolve_conditional_render_comp_spv.h" 21#include "video_core/host_shaders/resolve_conditional_render_comp_spv.h"
@@ -131,6 +135,33 @@ constexpr DescriptorBankInfo ASTC_BANK_INFO{
131 .score = 2, 135 .score = 2,
132}; 136};
133 137
138constexpr std::array<VkDescriptorSetLayoutBinding, ASTC_NUM_BINDINGS> MSAA_DESCRIPTOR_SET_BINDINGS{{
139 {
140 .binding = 0,
141 .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
142 .descriptorCount = 1,
143 .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
144 .pImmutableSamplers = nullptr,
145 },
146 {
147 .binding = 1,
148 .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
149 .descriptorCount = 1,
150 .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
151 .pImmutableSamplers = nullptr,
152 },
153}};
154
155constexpr DescriptorBankInfo MSAA_BANK_INFO{
156 .uniform_buffers = 0,
157 .storage_buffers = 0,
158 .texture_buffers = 0,
159 .image_buffers = 0,
160 .textures = 0,
161 .images = 2,
162 .score = 2,
163};
164
134constexpr VkDescriptorUpdateTemplateEntry INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLATE{ 165constexpr VkDescriptorUpdateTemplateEntry INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLATE{
135 .dstBinding = 0, 166 .dstBinding = 0,
136 .dstArrayElement = 0, 167 .dstArrayElement = 0,
@@ -149,6 +180,15 @@ constexpr VkDescriptorUpdateTemplateEntry QUERIES_SCAN_DESCRIPTOR_UPDATE_TEMPLAT
149 .stride = sizeof(DescriptorUpdateEntry), 180 .stride = sizeof(DescriptorUpdateEntry),
150}; 181};
151 182
183constexpr VkDescriptorUpdateTemplateEntry MSAA_DESCRIPTOR_UPDATE_TEMPLATE{
184 .dstBinding = 0,
185 .dstArrayElement = 0,
186 .descriptorCount = 2,
187 .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
188 .offset = 0,
189 .stride = sizeof(DescriptorUpdateEntry),
190};
191
152constexpr std::array<VkDescriptorUpdateTemplateEntry, ASTC_NUM_BINDINGS> 192constexpr std::array<VkDescriptorUpdateTemplateEntry, ASTC_NUM_BINDINGS>
153 ASTC_PASS_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY{{ 193 ASTC_PASS_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY{{
154 { 194 {
@@ -224,6 +264,9 @@ ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool,
224 }); 264 });
225 descriptor_allocator = descriptor_pool.Allocator(*descriptor_set_layout, bank_info); 265 descriptor_allocator = descriptor_pool.Allocator(*descriptor_set_layout, bank_info);
226 } 266 }
267 if (code.empty()) {
268 return;
269 }
227 module = device.GetLogical().CreateShaderModule({ 270 module = device.GetLogical().CreateShaderModule({
228 .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, 271 .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
229 .pNext = nullptr, 272 .pNext = nullptr,
@@ -590,4 +633,100 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map,
590 scheduler.Finish(); 633 scheduler.Finish();
591} 634}
592 635
636MSAACopyPass::MSAACopyPass(const Device& device_, Scheduler& scheduler_,
637 DescriptorPool& descriptor_pool_,
638 StagingBufferPool& staging_buffer_pool_,
639 ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
640 : ComputePass(device_, descriptor_pool_, MSAA_DESCRIPTOR_SET_BINDINGS,
641 MSAA_DESCRIPTOR_UPDATE_TEMPLATE, MSAA_BANK_INFO, {},
642 CONVERT_NON_MSAA_TO_MSAA_COMP_SPV),
643 scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_},
644 compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {
645 const auto make_msaa_pipeline = [this](size_t i, std::span<const u32> code) {
646 modules[i] = device.GetLogical().CreateShaderModule({
647 .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
648 .pNext = nullptr,
649 .flags = 0,
650 .codeSize = static_cast<u32>(code.size_bytes()),
651 .pCode = code.data(),
652 });
653 pipelines[i] = device.GetLogical().CreateComputePipeline({
654 .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
655 .pNext = nullptr,
656 .flags = 0,
657 .stage{
658 .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
659 .pNext = nullptr,
660 .flags = 0,
661 .stage = VK_SHADER_STAGE_COMPUTE_BIT,
662 .module = *modules[i],
663 .pName = "main",
664 .pSpecializationInfo = nullptr,
665 },
666 .layout = *layout,
667 .basePipelineHandle = nullptr,
668 .basePipelineIndex = 0,
669 });
670 };
671 make_msaa_pipeline(0, CONVERT_NON_MSAA_TO_MSAA_COMP_SPV);
672 make_msaa_pipeline(1, CONVERT_MSAA_TO_NON_MSAA_COMP_SPV);
673}
674
675MSAACopyPass::~MSAACopyPass() = default;
676
677void MSAACopyPass::CopyImage(Image& dst_image, Image& src_image,
678 std::span<const VideoCommon::ImageCopy> copies,
679 bool msaa_to_non_msaa) {
680 const VkPipeline msaa_pipeline = *pipelines[msaa_to_non_msaa ? 1 : 0];
681 scheduler.RequestOutsideRenderPassOperationContext();
682 for (const VideoCommon::ImageCopy& copy : copies) {
683 ASSERT(copy.src_subresource.base_layer == 0);
684 ASSERT(copy.src_subresource.num_layers == 1);
685 ASSERT(copy.dst_subresource.base_layer == 0);
686 ASSERT(copy.dst_subresource.num_layers == 1);
687
688 compute_pass_descriptor_queue.Acquire();
689 compute_pass_descriptor_queue.AddImage(
690 src_image.StorageImageView(copy.src_subresource.base_level));
691 compute_pass_descriptor_queue.AddImage(
692 dst_image.StorageImageView(copy.dst_subresource.base_level));
693 const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()};
694
695 const Common::Vec3<u32> num_dispatches = {
696 Common::DivCeil(copy.extent.width, 8U),
697 Common::DivCeil(copy.extent.height, 8U),
698 copy.extent.depth,
699 };
700
701 scheduler.Record([this, dst = dst_image.Handle(), msaa_pipeline, num_dispatches,
702 descriptor_data](vk::CommandBuffer cmdbuf) {
703 const VkDescriptorSet set = descriptor_allocator.Commit();
704 device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
705 cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, msaa_pipeline);
706 cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
707 cmdbuf.Dispatch(num_dispatches.x, num_dispatches.y, num_dispatches.z);
708 const VkImageMemoryBarrier write_barrier{
709 .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
710 .pNext = nullptr,
711 .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
712 .dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
713 .oldLayout = VK_IMAGE_LAYOUT_GENERAL,
714 .newLayout = VK_IMAGE_LAYOUT_GENERAL,
715 .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
716 .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
717 .image = dst,
718 .subresourceRange{
719 .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
720 .baseMipLevel = 0,
721 .levelCount = VK_REMAINING_MIP_LEVELS,
722 .baseArrayLayer = 0,
723 .layerCount = VK_REMAINING_ARRAY_LAYERS,
724 },
725 };
726 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
727 VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, write_barrier);
728 });
729 }
730}
731
593} // namespace Vulkan 732} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.h b/src/video_core/renderer_vulkan/vk_compute_pass.h
index 3ff935639..7b8f938c1 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pass.h
+++ b/src/video_core/renderer_vulkan/vk_compute_pass.h
@@ -11,6 +11,7 @@
11#include "video_core/engines/maxwell_3d.h" 11#include "video_core/engines/maxwell_3d.h"
12#include "video_core/renderer_vulkan/vk_descriptor_pool.h" 12#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
13#include "video_core/renderer_vulkan/vk_update_descriptor.h" 13#include "video_core/renderer_vulkan/vk_update_descriptor.h"
14#include "video_core/texture_cache/types.h"
14#include "video_core/vulkan_common/vulkan_memory_allocator.h" 15#include "video_core/vulkan_common/vulkan_memory_allocator.h"
15#include "video_core/vulkan_common/vulkan_wrapper.h" 16#include "video_core/vulkan_common/vulkan_wrapper.h"
16 17
@@ -130,4 +131,22 @@ private:
130 MemoryAllocator& memory_allocator; 131 MemoryAllocator& memory_allocator;
131}; 132};
132 133
134class MSAACopyPass final : public ComputePass {
135public:
136 explicit MSAACopyPass(const Device& device_, Scheduler& scheduler_,
137 DescriptorPool& descriptor_pool_, StagingBufferPool& staging_buffer_pool_,
138 ComputePassDescriptorQueue& compute_pass_descriptor_queue_);
139 ~MSAACopyPass();
140
141 void CopyImage(Image& dst_image, Image& src_image,
142 std::span<const VideoCommon::ImageCopy> copies, bool msaa_to_non_msaa);
143
144private:
145 Scheduler& scheduler;
146 StagingBufferPool& staging_buffer_pool;
147 ComputePassDescriptorQueue& compute_pass_descriptor_queue;
148 std::array<vk::ShaderModule, 2> modules;
149 std::array<vk::Pipeline, 2> pipelines;
150};
151
133} // namespace Vulkan 152} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index 1f9e7acaa..71fdec809 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -176,6 +176,36 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
176 return allocator.CreateImage(image_ci); 176 return allocator.CreateImage(image_ci);
177} 177}
178 178
179[[nodiscard]] vk::ImageView MakeStorageView(const vk::Device& device, u32 level, VkImage image,
180 VkFormat format) {
181 static constexpr VkImageViewUsageCreateInfo storage_image_view_usage_create_info{
182 .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO,
183 .pNext = nullptr,
184 .usage = VK_IMAGE_USAGE_STORAGE_BIT,
185 };
186 return device.CreateImageView(VkImageViewCreateInfo{
187 .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
188 .pNext = &storage_image_view_usage_create_info,
189 .flags = 0,
190 .image = image,
191 .viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY,
192 .format = format,
193 .components{
194 .r = VK_COMPONENT_SWIZZLE_IDENTITY,
195 .g = VK_COMPONENT_SWIZZLE_IDENTITY,
196 .b = VK_COMPONENT_SWIZZLE_IDENTITY,
197 .a = VK_COMPONENT_SWIZZLE_IDENTITY,
198 },
199 .subresourceRange{
200 .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
201 .baseMipLevel = level,
202 .levelCount = 1,
203 .baseArrayLayer = 0,
204 .layerCount = VK_REMAINING_ARRAY_LAYERS,
205 },
206 });
207}
208
179[[nodiscard]] VkImageAspectFlags ImageAspectMask(PixelFormat format) { 209[[nodiscard]] VkImageAspectFlags ImageAspectMask(PixelFormat format) {
180 switch (VideoCore::Surface::GetFormatType(format)) { 210 switch (VideoCore::Surface::GetFormatType(format)) {
181 case VideoCore::Surface::SurfaceType::ColorTexture: 211 case VideoCore::Surface::SurfaceType::ColorTexture:
@@ -817,6 +847,10 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched
817 astc_decoder_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool, 847 astc_decoder_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool,
818 compute_pass_descriptor_queue, memory_allocator); 848 compute_pass_descriptor_queue, memory_allocator);
819 } 849 }
850 if (device.IsStorageImageMultisampleSupported()) {
851 msaa_copy_pass = std::make_unique<MSAACopyPass>(
852 device, scheduler, descriptor_pool, staging_buffer_pool, compute_pass_descriptor_queue);
853 }
820 if (!device.IsKhrImageFormatListSupported()) { 854 if (!device.IsKhrImageFormatListSupported()) {
821 return; 855 return;
822 } 856 }
@@ -1285,7 +1319,11 @@ void TextureCacheRuntime::CopyImage(Image& dst, Image& src,
1285 1319
1286void TextureCacheRuntime::CopyImageMSAA(Image& dst, Image& src, 1320void TextureCacheRuntime::CopyImageMSAA(Image& dst, Image& src,
1287 std::span<const VideoCommon::ImageCopy> copies) { 1321 std::span<const VideoCommon::ImageCopy> copies) {
1288 UNIMPLEMENTED_MSG("Copying images with different samples is not implemented in Vulkan."); 1322 const bool msaa_to_non_msaa = src.info.num_samples > 1 && dst.info.num_samples == 1;
1323 if (msaa_copy_pass) {
1324 return msaa_copy_pass->CopyImage(dst, src, copies, msaa_to_non_msaa);
1325 }
1326 UNIMPLEMENTED_MSG("Copying images with different samples is not supported.");
1289} 1327}
1290 1328
1291u64 TextureCacheRuntime::GetDeviceLocalMemory() const { 1329u64 TextureCacheRuntime::GetDeviceLocalMemory() const {
@@ -1333,39 +1371,15 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu
1333 if (runtime->device.HasDebuggingToolAttached()) { 1371 if (runtime->device.HasDebuggingToolAttached()) {
1334 original_image.SetObjectNameEXT(VideoCommon::Name(*this).c_str()); 1372 original_image.SetObjectNameEXT(VideoCommon::Name(*this).c_str());
1335 } 1373 }
1336 static constexpr VkImageViewUsageCreateInfo storage_image_view_usage_create_info{
1337 .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO,
1338 .pNext = nullptr,
1339 .usage = VK_IMAGE_USAGE_STORAGE_BIT,
1340 };
1341 current_image = *original_image; 1374 current_image = *original_image;
1375 storage_image_views.resize(info.resources.levels);
1342 if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported() && 1376 if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported() &&
1343 Settings::values.astc_recompression.GetValue() == 1377 Settings::values.astc_recompression.GetValue() ==
1344 Settings::AstcRecompression::Uncompressed) { 1378 Settings::AstcRecompression::Uncompressed) {
1345 const auto& device = runtime->device.GetLogical(); 1379 const auto& device = runtime->device.GetLogical();
1346 storage_image_views.reserve(info.resources.levels);
1347 for (s32 level = 0; level < info.resources.levels; ++level) { 1380 for (s32 level = 0; level < info.resources.levels; ++level) {
1348 storage_image_views.push_back(device.CreateImageView(VkImageViewCreateInfo{ 1381 storage_image_views[level] =
1349 .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, 1382 MakeStorageView(device, level, *original_image, VK_FORMAT_A8B8G8R8_UNORM_PACK32);
1350 .pNext = &storage_image_view_usage_create_info,
1351 .flags = 0,
1352 .image = *original_image,
1353 .viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY,
1354 .format = VK_FORMAT_A8B8G8R8_UNORM_PACK32,
1355 .components{
1356 .r = VK_COMPONENT_SWIZZLE_IDENTITY,
1357 .g = VK_COMPONENT_SWIZZLE_IDENTITY,
1358 .b = VK_COMPONENT_SWIZZLE_IDENTITY,
1359 .a = VK_COMPONENT_SWIZZLE_IDENTITY,
1360 },
1361 .subresourceRange{
1362 .aspectMask = aspect_mask,
1363 .baseMipLevel = static_cast<u32>(level),
1364 .levelCount = 1,
1365 .baseArrayLayer = 0,
1366 .layerCount = VK_REMAINING_ARRAY_LAYERS,
1367 },
1368 }));
1369 } 1383 }
1370 } 1384 }
1371} 1385}
@@ -1496,6 +1510,17 @@ void Image::DownloadMemory(const StagingBufferRef& map, std::span<const BufferIm
1496 DownloadMemory(buffers, offsets, copies); 1510 DownloadMemory(buffers, offsets, copies);
1497} 1511}
1498 1512
1513VkImageView Image::StorageImageView(s32 level) noexcept {
1514 auto& view = storage_image_views[level];
1515 if (!view) {
1516 const auto format_info =
1517 MaxwellToVK::SurfaceFormat(runtime->device, FormatType::Optimal, true, info.format);
1518 view =
1519 MakeStorageView(runtime->device.GetLogical(), level, current_image, format_info.format);
1520 }
1521 return *view;
1522}
1523
1499bool Image::IsRescaled() const noexcept { 1524bool Image::IsRescaled() const noexcept {
1500 return True(flags & ImageFlagBits::Rescaled); 1525 return True(flags & ImageFlagBits::Rescaled);
1501} 1526}
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h
index 565ce19a9..d6c5a15cc 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.h
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.h
@@ -117,6 +117,7 @@ public:
117 BlitImageHelper& blit_image_helper; 117 BlitImageHelper& blit_image_helper;
118 RenderPassCache& render_pass_cache; 118 RenderPassCache& render_pass_cache;
119 std::optional<ASTCDecoderPass> astc_decoder_pass; 119 std::optional<ASTCDecoderPass> astc_decoder_pass;
120 std::unique_ptr<MSAACopyPass> msaa_copy_pass;
120 const Settings::ResolutionScalingInfo& resolution; 121 const Settings::ResolutionScalingInfo& resolution;
121 std::array<std::vector<VkFormat>, VideoCore::Surface::MaxPixelFormat> view_formats; 122 std::array<std::vector<VkFormat>, VideoCore::Surface::MaxPixelFormat> view_formats;
122 123
@@ -161,15 +162,13 @@ public:
161 return aspect_mask; 162 return aspect_mask;
162 } 163 }
163 164
164 [[nodiscard]] VkImageView StorageImageView(s32 level) const noexcept {
165 return *storage_image_views[level];
166 }
167
168 /// Returns true when the image is already initialized and mark it as initialized 165 /// Returns true when the image is already initialized and mark it as initialized
169 [[nodiscard]] bool ExchangeInitialization() noexcept { 166 [[nodiscard]] bool ExchangeInitialization() noexcept {
170 return std::exchange(initialized, true); 167 return std::exchange(initialized, true);
171 } 168 }
172 169
170 VkImageView StorageImageView(s32 level) noexcept;
171
173 bool IsRescaled() const noexcept; 172 bool IsRescaled() const noexcept;
174 173
175 bool ScaleUp(bool ignore = false); 174 bool ScaleUp(bool ignore = false);
diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h
index 94f41266d..dd1e7ea8c 100644
--- a/src/video_core/vulkan_common/vulkan_device.h
+++ b/src/video_core/vulkan_common/vulkan_device.h
@@ -324,6 +324,11 @@ public:
324 return features.shader_float16_int8.shaderInt8; 324 return features.shader_float16_int8.shaderInt8;
325 } 325 }
326 326
327 /// Returns true if the device supports binding multisample images as storage images.
328 bool IsStorageImageMultisampleSupported() const {
329 return features.features.shaderStorageImageMultisample;
330 }
331
327 /// Returns true if the device warp size can potentially be bigger than guest's warp size. 332 /// Returns true if the device warp size can potentially be bigger than guest's warp size.
328 bool IsWarpSizePotentiallyBiggerThanGuest() const { 333 bool IsWarpSizePotentiallyBiggerThanGuest() const {
329 return is_warp_potentially_bigger; 334 return is_warp_potentially_bigger;