renderer_vulkan: Centralize pixel format trait management. (#18)

* renderer_vulkan: Centralize pixel format trait management.

* renderer_vulkan: Add D24 <-> D32 conversion support.
This commit is contained in:
Steveice10
2023-01-12 00:43:40 -08:00
committed by GitHub
parent c8d614139c
commit a3a8964d46
9 changed files with 155 additions and 160 deletions

View File

@ -18,51 +18,3 @@
#define VMA_STATIC_VULKAN_FUNCTIONS 0
#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1
namespace Vulkan {
/// Return the image aspect associated on the provided format
constexpr vk::ImageAspectFlags GetImageAspect(vk::Format format) {
switch (format) {
case vk::Format::eD16UnormS8Uint:
case vk::Format::eD24UnormS8Uint:
case vk::Format::eD32SfloatS8Uint:
return vk::ImageAspectFlagBits::eStencil | vk::ImageAspectFlagBits::eDepth;
break;
case vk::Format::eD16Unorm:
case vk::Format::eX8D24UnormPack32:
case vk::Format::eD32Sfloat:
return vk::ImageAspectFlagBits::eDepth;
break;
default:
return vk::ImageAspectFlagBits::eColor;
}
}
/// Returns a bit mask with the required usage of a format with a particular aspect
constexpr vk::ImageUsageFlags GetImageUsage(vk::ImageAspectFlags aspect) {
auto usage = vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst |
vk::ImageUsageFlagBits::eTransferSrc;
if (aspect & vk::ImageAspectFlagBits::eDepth) {
return usage | vk::ImageUsageFlagBits::eDepthStencilAttachment;
} else {
return usage | vk::ImageUsageFlagBits::eStorage | vk::ImageUsageFlagBits::eColorAttachment;
}
}
/// Returns a bit mask with the required features of a format with a particular aspect
constexpr vk::FormatFeatureFlags GetFormatFeatures(vk::ImageAspectFlags aspect) {
auto usage = vk::FormatFeatureFlagBits::eSampledImage |
vk::FormatFeatureFlagBits::eTransferDst | vk::FormatFeatureFlagBits::eTransferSrc |
vk::FormatFeatureFlagBits::eBlitSrc | vk::FormatFeatureFlagBits::eBlitDst;
if (aspect & vk::ImageAspectFlagBits::eDepth) {
return usage | vk::FormatFeatureFlagBits::eDepthStencilAttachment;
} else {
return usage | vk::FormatFeatureFlagBits::eStorageImage |
vk::FormatFeatureFlagBits::eColorAttachment;
}
}
} // namespace Vulkan

View File

@ -276,6 +276,80 @@ FormatTraits Instance::GetTraits(VideoCore::PixelFormat pixel_format) const {
return format_table[index];
}
vk::ImageAspectFlags MakeAspect(VideoCore::SurfaceType type) {
switch (type) {
case VideoCore::SurfaceType::Color:
case VideoCore::SurfaceType::Texture:
case VideoCore::SurfaceType::Fill:
return vk::ImageAspectFlagBits::eColor;
case VideoCore::SurfaceType::Depth:
return vk::ImageAspectFlagBits::eDepth;
case VideoCore::SurfaceType::DepthStencil:
return vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil;
default:
LOG_CRITICAL(Render_Vulkan, "Invalid surface type {}", type);
UNREACHABLE();
}
return vk::ImageAspectFlagBits::eColor;
}
FormatTraits Instance::DetermineTraits(VideoCore::PixelFormat pixel_format, vk::Format format) {
const vk::ImageAspectFlags format_aspect =
MakeAspect(VideoCore::GetFormatType(pixel_format));
const vk::FormatProperties format_properties = physical_device.getFormatProperties(format);
const vk::FormatFeatureFlagBits attachment_usage =
(format_aspect & vk::ImageAspectFlagBits::eDepth)
? vk::FormatFeatureFlagBits::eDepthStencilAttachment
: vk::FormatFeatureFlagBits::eColorAttachmentBlend;
const vk::FormatFeatureFlags storage_usage = vk::FormatFeatureFlagBits::eStorageImage;
const vk::FormatFeatureFlags transfer_usage = vk::FormatFeatureFlagBits::eSampledImage;
const vk::FormatFeatureFlags blit_usage =
vk::FormatFeatureFlagBits::eBlitSrc | vk::FormatFeatureFlagBits::eBlitDst;
const bool supports_transfer =
(format_properties.optimalTilingFeatures & transfer_usage) == transfer_usage;
const bool supports_blit = (format_properties.optimalTilingFeatures & blit_usage) == blit_usage;
const bool supports_attachment =
(format_properties.optimalTilingFeatures & attachment_usage) == attachment_usage &&
pixel_format != VideoCore::PixelFormat::RGB8;
const bool supports_storage =
(format_properties.optimalTilingFeatures & storage_usage) == storage_usage;
const bool requires_conversion =
// Requires component flip.
pixel_format == VideoCore::PixelFormat::RGBA8 ||
// Requires (de)interleaving.
pixel_format == VideoCore::PixelFormat::D24S8;
// Find the most inclusive usage flags for this format
vk::ImageUsageFlags best_usage;
if (supports_blit || supports_transfer) {
best_usage |= vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst |
vk::ImageUsageFlagBits::eTransferSrc;
}
if (supports_attachment) {
best_usage |= (format_aspect & vk::ImageAspectFlagBits::eDepth)
? vk::ImageUsageFlagBits::eDepthStencilAttachment
: vk::ImageUsageFlagBits::eColorAttachment;
}
if (supports_storage) {
best_usage |= vk::ImageUsageFlagBits::eStorage;
}
return FormatTraits{
.transfer_support = supports_transfer,
.blit_support = supports_blit,
.attachment_support = supports_attachment,
.storage_support = supports_storage,
.requires_conversion = requires_conversion,
.usage = best_usage,
.aspect = format_aspect,
.native = format,
};
}
void Instance::CreateFormatTable() {
constexpr std::array pixel_formats = {
VideoCore::PixelFormat::RGBA8, VideoCore::PixelFormat::RGB8,
@ -288,73 +362,32 @@ void Instance::CreateFormatTable() {
VideoCore::PixelFormat::D16, VideoCore::PixelFormat::D24,
VideoCore::PixelFormat::D24S8};
const vk::FormatFeatureFlags storage_usage = vk::FormatFeatureFlagBits::eStorageImage;
const vk::FormatFeatureFlags transfer_usage = vk::FormatFeatureFlagBits::eSampledImage;
const vk::FormatFeatureFlags blit_usage =
vk::FormatFeatureFlagBits::eBlitSrc | vk::FormatFeatureFlagBits::eBlitDst;
for (const auto& pixel_format : pixel_formats) {
const vk::Format format = ToVkFormat(pixel_format);
const vk::FormatProperties properties = physical_device.getFormatProperties(format);
const vk::ImageAspectFlags aspect = GetImageAspect(format);
const auto format = ToVkFormat(pixel_format);
FormatTraits traits = DetermineTraits(pixel_format, format);
const vk::FormatFeatureFlagBits attachment_usage =
(aspect & vk::ImageAspectFlagBits::eDepth)
? vk::FormatFeatureFlagBits::eDepthStencilAttachment
: vk::FormatFeatureFlagBits::eColorAttachmentBlend;
const bool supports_transfer =
(properties.optimalTilingFeatures & transfer_usage) == transfer_usage;
const bool supports_blit = (properties.optimalTilingFeatures & blit_usage) == blit_usage;
bool supports_attachment =
(properties.optimalTilingFeatures & attachment_usage) == attachment_usage;
const bool supports_storage =
(properties.optimalTilingFeatures & storage_usage) == storage_usage;
// Find the most inclusive usage flags for this format
vk::ImageUsageFlags best_usage;
if (supports_blit || supports_transfer) {
best_usage |= vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst |
vk::ImageUsageFlagBits::eTransferSrc;
}
if (supports_attachment) {
best_usage |= (aspect & vk::ImageAspectFlagBits::eDepth)
? vk::ImageUsageFlagBits::eDepthStencilAttachment
: vk::ImageUsageFlagBits::eColorAttachment;
}
if (supports_storage) {
best_usage |= vk::ImageUsageFlagBits::eStorage;
}
// Always fallback to RGBA8 or D32(S8) for convenience
vk::Format fallback = vk::Format::eR8G8B8A8Unorm;
if (aspect & vk::ImageAspectFlagBits::eDepth) {
fallback = vk::Format::eD32Sfloat;
if (aspect & vk::ImageAspectFlagBits::eStencil) {
fallback = vk::Format::eD32SfloatS8Uint;
const bool is_suitable =
traits.transfer_support && traits.attachment_support &&
(traits.blit_support || traits.aspect & vk::ImageAspectFlagBits::eDepth);
// Fall back if the native format is not suitable.
if (!is_suitable) {
// Always fallback to RGBA8 or D32(S8) for convenience
auto fallback = vk::Format::eR8G8B8A8Unorm;
if (traits.aspect & vk::ImageAspectFlagBits::eDepth) {
fallback = vk::Format::eD32Sfloat;
if (traits.aspect & vk::ImageAspectFlagBits::eStencil) {
fallback = vk::Format::eD32SfloatS8Uint;
}
}
}
// Report completely unsupported formats
if (!supports_blit && !supports_attachment && !supports_storage) {
LOG_WARNING(Render_Vulkan, "Format {} unsupported, falling back unconditionally to {}",
vk::to_string(format), vk::to_string(fallback));
}
if (pixel_format == VideoCore::PixelFormat::RGB8) {
supports_attachment = false;
traits = DetermineTraits(pixel_format, fallback);
// Always requires conversion if backing format does not match.
traits.requires_conversion = true;
}
const u32 index = static_cast<u32>(pixel_format);
format_table[index] = FormatTraits{
.transfer_support = supports_transfer,
.blit_support = supports_blit,
.attachment_support = supports_attachment,
.storage_support = supports_storage,
.usage = best_usage,
.native = format,
.fallback = fallback,
};
format_table[index] = traits;
}
}

View File

@ -23,9 +23,11 @@ struct FormatTraits {
bool blit_support = false; ///< True if the format supports blit operations
bool attachment_support = false; ///< True if the format supports being used as an attachment
bool storage_support = false; ///< True if the format supports storage operations
vk::ImageUsageFlags usage{}; ///< Most supported usage for the native format
vk::Format native = vk::Format::eUndefined; ///< Closest possible native format
vk::Format fallback = vk::Format::eUndefined; ///< Best fallback format
bool requires_conversion =
false; ///< True if the format requires conversion to the native format
vk::ImageUsageFlags usage{}; ///< Most supported usage for the native format
vk::ImageAspectFlags aspect; ///< Aspect flags of the format
vk::Format native = vk::Format::eUndefined; ///< Closest possible native format
};
/// The global Vulkan instance
@ -202,7 +204,8 @@ public:
private:
/// Returns the optimal supported usage for the requested format
vk::FormatFeatureFlags GetFormatFeatures(vk::Format format);
[[nodiscard]] FormatTraits DetermineTraits(VideoCore::PixelFormat pixel_format,
vk::Format format);
/// Creates the format compatibility table for the current device
void CreateFormatTable();

View File

@ -63,8 +63,10 @@ RasterizerVulkan::RasterizerVulkan(Frontend::EmuWindow& emu_window, const Instan
: instance{instance}, scheduler{scheduler}, runtime{runtime},
renderpass_cache{renderpass_cache}, desc_manager{desc_manager}, res_cache{*this, runtime},
pipeline_cache{instance, scheduler, renderpass_cache, desc_manager},
null_surface{NULL_PARAMS, vk::Format::eR8G8B8A8Unorm, NULL_USAGE, runtime},
null_storage_surface{NULL_PARAMS, vk::Format::eR32Uint, NULL_STORAGE_USAGE, runtime},
null_surface{NULL_PARAMS, vk::Format::eR8G8B8A8Unorm, NULL_USAGE,
vk::ImageAspectFlagBits::eColor, runtime},
null_storage_surface{NULL_PARAMS, vk::Format::eR32Uint, NULL_STORAGE_USAGE,
vk::ImageAspectFlagBits::eColor, runtime},
stream_buffer{instance, scheduler, BUFFER_USAGE, STREAM_BUFFER_SIZE},
texture_buffer{instance, scheduler, TEX_BUFFER_USAGE, TextureBufferSize(instance)},
texture_lf_buffer{instance, scheduler, TEX_BUFFER_USAGE, TextureBufferSize(instance)} {

View File

@ -47,15 +47,8 @@ RenderpassCache::RenderpassCache(const Instance& instance, Scheduler& scheduler)
const FormatTraits color_traits = instance.GetTraits(ToFormatColor(color));
const FormatTraits depth_traits = instance.GetTraits(ToFormatDepth(depth));
const vk::Format color_format = color_traits.transfer_support &&
color_traits.blit_support &&
color_traits.attachment_support
? color_traits.native
: color_traits.fallback;
const vk::Format depth_format =
depth_traits.transfer_support && depth_traits.attachment_support
? depth_traits.native
: depth_traits.fallback;
const vk::Format color_format = color_traits.native;
const vk::Format depth_format = depth_traits.native;
if (color_format == vk::Format::eUndefined && depth_format == vk::Format::eUndefined) {
continue;

View File

@ -26,24 +26,6 @@ struct RecordParams {
vk::Image dst_image;
};
[[nodiscard]] vk::ImageAspectFlags MakeAspect(VideoCore::SurfaceType type) {
switch (type) {
case VideoCore::SurfaceType::Color:
case VideoCore::SurfaceType::Texture:
case VideoCore::SurfaceType::Fill:
return vk::ImageAspectFlagBits::eColor;
case VideoCore::SurfaceType::Depth:
return vk::ImageAspectFlagBits::eDepth;
case VideoCore::SurfaceType::DepthStencil:
return vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil;
default:
LOG_CRITICAL(Render_Vulkan, "Invalid surface type {}", type);
UNREACHABLE();
}
return vk::ImageAspectFlagBits::eColor;
}
[[nodiscard]] vk::Filter MakeFilter(VideoCore::PixelFormat pixel_format) {
switch (pixel_format) {
case VideoCore::PixelFormat::D16:
@ -93,6 +75,17 @@ u32 UnpackDepthStencil(const StagingData& data, vk::Format dest) {
}
break;
}
case vk::Format::eD32SfloatS8Uint: {
for (; stencil_offset < data.size; depth_offset += 4) {
std::byte* ptr = mapped.data() + depth_offset;
const u32 d24s8 = VideoCore::MakeInt<u32>(ptr);
const float d32 = (d24s8 >> 8) / 16777215.f;
mapped[stencil_offset] = static_cast<std::byte>(d24s8 & 0xFF);
std::memcpy(ptr, &d32, 4);
stencil_offset++;
}
break;
}
default:
LOG_ERROR(Render_Vulkan, "Unimplemtend convertion for depth format {}",
vk::to_string(dest));
@ -172,26 +165,18 @@ void TextureRuntime::Finish() {
ImageAlloc TextureRuntime::Allocate(u32 width, u32 height, VideoCore::PixelFormat format,
VideoCore::TextureType type) {
const FormatTraits traits = instance.GetTraits(format);
const vk::ImageAspectFlags aspect = MakeAspect(VideoCore::GetFormatType(format));
// Depth buffers are not supposed to support blit by the spec so don't require it.
const bool is_suitable = traits.transfer_support && traits.attachment_support &&
(traits.blit_support || aspect & vk::ImageAspectFlagBits::eDepth);
const vk::Format vk_format = is_suitable ? traits.native : traits.fallback;
const vk::ImageUsageFlags vk_usage = is_suitable ? traits.usage : GetImageUsage(aspect);
return Allocate(width, height, format, type, vk_format, vk_usage);
return Allocate(width, height, format, type, traits.native, traits.usage, traits.aspect);
}
MICROPROFILE_DEFINE(Vulkan_ImageAlloc, "Vulkan", "TextureRuntime Finish", MP_RGB(192, 52, 235));
ImageAlloc TextureRuntime::Allocate(u32 width, u32 height, VideoCore::PixelFormat pixel_format,
VideoCore::TextureType type, vk::Format format,
vk::ImageUsageFlags usage) {
vk::ImageUsageFlags usage, vk::ImageAspectFlags aspect) {
MICROPROFILE_SCOPE(Vulkan_ImageAlloc);
ImageAlloc alloc{};
alloc.format = format;
alloc.aspect = GetImageAspect(format);
alloc.aspect = aspect;
// The internal format does not provide enough guarantee of texture uniqueness
// especially when many pixel formats fallback to RGBA8
@ -384,6 +369,8 @@ void TextureRuntime::FormatConvert(const Surface& surface, bool upload, std::spa
return Pica::Texture::ConvertBGRToRGBA(source, dest);
case VideoCore::PixelFormat::RGBA4:
return Pica::Texture::ConvertRGBA4ToRGBA8(source, dest);
case VideoCore::PixelFormat::D24:
return Pica::Texture::ConvertD24ToD32(source, dest);
default:
break;
}
@ -395,6 +382,8 @@ void TextureRuntime::FormatConvert(const Surface& surface, bool upload, std::spa
return Pica::Texture::ConvertRGBA8ToRGBA4(source, dest);
case VideoCore::PixelFormat::RGB8:
return Pica::Texture::ConvertRGBAToBGR(source, dest);
case VideoCore::PixelFormat::D24:
return Pica::Texture::ConvertD32ToD24(source, dest);
default:
break;
}
@ -850,10 +839,9 @@ const ReinterpreterList& TextureRuntime::GetPossibleReinterpretations(
bool TextureRuntime::NeedsConvertion(VideoCore::PixelFormat format) const {
const FormatTraits traits = instance.GetTraits(format);
const VideoCore::SurfaceType type = VideoCore::GetFormatType(format);
return type == VideoCore::SurfaceType::Color &&
(format == VideoCore::PixelFormat::RGBA8 || !traits.blit_support ||
!traits.attachment_support);
return traits.requires_conversion &&
// DepthStencil formats are handled elsewhere due to de-interleaving.
traits.aspect != (vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil);
}
Surface::Surface(TextureRuntime& runtime)
@ -870,12 +858,12 @@ Surface::Surface(const VideoCore::SurfaceParams& params, TextureRuntime& runtime
}
Surface::Surface(const VideoCore::SurfaceParams& params, vk::Format format,
vk::ImageUsageFlags usage, TextureRuntime& runtime)
vk::ImageUsageFlags usage, vk::ImageAspectFlags aspect, TextureRuntime& runtime)
: VideoCore::SurfaceBase<Surface>{params}, runtime{runtime}, instance{runtime.GetInstance()},
scheduler{runtime.GetScheduler()} {
if (format != vk::Format::eUndefined) {
alloc = runtime.Allocate(GetScaledWidth(), GetScaledHeight(), pixel_format, texture_type,
format, usage);
format, usage, aspect);
}
}
@ -1214,7 +1202,7 @@ void Surface::DepthStencilDownload(const VideoCore::BufferTextureCopy& download,
Surface r32_surface{r32_params, vk::Format::eR32Uint,
vk::ImageUsageFlagBits::eTransferSrc |
vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eStorage,
runtime};
vk::ImageAspectFlagBits::eColor, runtime};
const VideoCore::TextureBlit blit = {
.src_level = download.texture_level,

View File

@ -106,7 +106,7 @@ public:
/// Allocates a vulkan image
[[nodiscard]] ImageAlloc Allocate(u32 width, u32 height, VideoCore::PixelFormat pixel_format,
VideoCore::TextureType type, vk::Format format,
vk::ImageUsageFlags usage);
vk::ImageUsageFlags usage, vk::ImageAspectFlags aspect);
/// Performs required format convertions on the staging data
void FormatConvert(const Surface& surface, bool upload, std::span<std::byte> source,
@ -173,7 +173,7 @@ public:
Surface(TextureRuntime& runtime);
Surface(const VideoCore::SurfaceParams& params, TextureRuntime& runtime);
Surface(const VideoCore::SurfaceParams& params, vk::Format format, vk::ImageUsageFlags usage,
TextureRuntime& runtime);
vk::ImageAspectFlags aspect, TextureRuntime& runtime);
~Surface() override;
/// Uploads pixel data in staging to a rectangle region of the surface texture

View File

@ -300,6 +300,26 @@ void ConvertRGBA8ToRGB5A1(std::span<const std::byte> source, std::span<std::byte
}
}
void ConvertD24ToD32(std::span<const std::byte> source, std::span<std::byte> dest) {
u32 j = 0;
for (std::size_t i = 0; i < dest.size(); i += 4) {
auto d32 =
Common::Color::DecodeD24(reinterpret_cast<const u8*>(source.data() + j)) / 16777215.f;
std::memcpy(dest.data() + i, &d32, sizeof(d32));
j += 3;
}
}
void ConvertD32ToD24(std::span<const std::byte> source, std::span<std::byte> dest) {
u32 j = 0;
for (std::size_t i = 0; i < dest.size(); i += 3) {
float d32;
std::memcpy(&d32, source.data() + j, sizeof(d32));
Common::Color::EncodeD24(d32 * 0xFFFFFF, reinterpret_cast<u8*>(dest.data() + i));
j += 4;
}
}
void ConvertD32S8ToD24S8(std::span<const std::byte> source, std::span<std::byte> dest) {
std::size_t depth_offset = 0;
std::size_t stencil_offset = 4 * source.size() / 5;

View File

@ -89,6 +89,10 @@ void ConvertRGB5A1ToRGBA8(std::span<const std::byte> source, std::span<std::byte
void ConvertRGBA8ToRGB5A1(std::span<const std::byte> source, std::span<std::byte> dest);
void ConvertD24ToD32(std::span<const std::byte> source, std::span<std::byte> dest);
void ConvertD32ToD24(std::span<const std::byte> source, std::span<std::byte> dest);
void ConvertD32S8ToD24S8(std::span<const std::byte> source, std::span<std::byte> dest);
void InterleaveD24S8(std::span<const std::byte> source, std::span<std::byte> dest);