code:
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <iterator>

#include "common/assert.h"
#include "common/common_types.h"
#include "common/logging/log.h"
#include "common/settings.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_vulkan/maxwell_to_vk.h"
#include "video_core/surface.h"
#include "video_core/vulkan_common/vulkan_device.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"

namespace Vulkan::MaxwellToVK {

using Maxwell = Tegra::Engines::Maxwell3D::Regs;

namespace Sampler {

VkFilter Filter(Tegra::Texture::TextureFilter filter) {
    switch (filter) {
    case Tegra::Texture::TextureFilter::Nearest:
        return VK_FILTER_NEAREST;
    case Tegra::Texture::TextureFilter::Linear:
        return VK_FILTER_LINEAR;
    }
    ASSERT_MSG(false, "Invalid sampler filter={}", filter);
    return {};
}

VkSamplerMipmapMode MipmapMode(Tegra::Texture::TextureMipmapFilter mipmap_filter) {
    switch (mipmap_filter) {
    case Tegra::Texture::TextureMipmapFilter::None:
        // There are no Vulkan filter modes that directly correspond to OpenGL minification filters
        // of GL_LINEAR or GL_NEAREST, but they can be emulated using
        // VK_SAMPLER_MIPMAP_MODE_NEAREST, minLod = 0, and maxLod = 0.25, and using minFilter =
        // VK_FILTER_LINEAR or minFilter = VK_FILTER_NEAREST, respectively.
        return VK_SAMPLER_MIPMAP_MODE_NEAREST;
    case Tegra::Texture::TextureMipmapFilter::Nearest:
        return VK_SAMPLER_MIPMAP_MODE_NEAREST;
    case Tegra::Texture::TextureMipmapFilter::Linear:
        return VK_SAMPLER_MIPMAP_MODE_LINEAR;
    }
    ASSERT_MSG(false, "Invalid sampler mipmap mode={}", mipmap_filter);
    return {};
}

VkSamplerAddressMode WrapMode(const Device& device, Tegra::Texture::WrapMode wrap_mode,
                              Tegra::Texture::TextureFilter filter) {
    switch (wrap_mode) {
    case Tegra::Texture::WrapMode::Wrap:
        return VK_SAMPLER_ADDRESS_MODE_REPEAT;
    case Tegra::Texture::WrapMode::Mirror:
        return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
    case Tegra::Texture::WrapMode::ClampToEdge:
        return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
    case Tegra::Texture::WrapMode::Border:
        return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
    case Tegra::Texture::WrapMode::Clamp:
        if (device.GetDriverID() == VK_DRIVER_ID_NVIDIA_PROPRIETARY) {
            // Nvidia's Vulkan driver defaults to GL_CLAMP on invalid enumerations, we can hack this
            // by sending an invalid enumeration.
            return static_cast<VkSamplerAddressMode>(0xcafe);
        }
        // TODO(Rodrigo): Emulate GL_CLAMP properly on other vendors
        switch (filter) {
        case Tegra::Texture::TextureFilter::Nearest:
            return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
        case Tegra::Texture::TextureFilter::Linear:
            return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
        }
        ASSERT(false);
        return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
    case Tegra::Texture::WrapMode::MirrorOnceClampToEdge:
        return VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE;
    case Tegra::Texture::WrapMode::MirrorOnceBorder:
        UNIMPLEMENTED();
        return VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE;
    default:
        UNIMPLEMENTED_MSG("Unimplemented wrap mode={}", wrap_mode);
        return {};
    }
}

VkCompareOp DepthCompareFunction(Tegra::Texture::DepthCompareFunc depth_compare_func) {
    switch (depth_compare_func) {
    case Tegra::Texture::DepthCompareFunc::Never:
        return VK_COMPARE_OP_NEVER;
    case Tegra::Texture::DepthCompareFunc::Less:
        return VK_COMPARE_OP_LESS;
    case Tegra::Texture::DepthCompareFunc::LessEqual:
        return VK_COMPARE_OP_LESS_OR_EQUAL;
    case Tegra::Texture::DepthCompareFunc::Equal:
        return VK_COMPARE_OP_EQUAL;
    case Tegra::Texture::DepthCompareFunc::NotEqual:
        return VK_COMPARE_OP_NOT_EQUAL;
    case Tegra::Texture::DepthCompareFunc::Greater:
        return VK_COMPARE_OP_GREATER;
    case Tegra::Texture::DepthCompareFunc::GreaterEqual:
        return VK_COMPARE_OP_GREATER_OR_EQUAL;
    case Tegra::Texture::DepthCompareFunc::Always:
        return VK_COMPARE_OP_ALWAYS;
    }
    UNIMPLEMENTED_MSG("Unimplemented sampler depth compare function={}", depth_compare_func);
    return {};
}

} // namespace Sampler

namespace {
constexpr u32 Attachable = 1 << 0;
constexpr u32 Storage = 1 << 1;

struct FormatTuple {
    VkFormat format; ///< Vulkan format
    int usage = 0;   ///< Describes image format usage
} constexpr tex_format_tuples[] = {
    {VK_FORMAT_A8B8G8R8_UNORM_PACK32, Attachable | Storage},    // A8B8G8R8_UNORM
    {VK_FORMAT_A8B8G8R8_SNORM_PACK32, Attachable | Storage},    // A8B8G8R8_SNORM
    {VK_FORMAT_A8B8G8R8_SINT_PACK32, Attachable | Storage},     // A8B8G8R8_SINT
    {VK_FORMAT_A8B8G8R8_UINT_PACK32, Attachable | Storage},     // A8B8G8R8_UINT
    {VK_FORMAT_R5G6B5_UNORM_PACK16, Attachable},                // R5G6B5_UNORM
    {VK_FORMAT_B5G6R5_UNORM_PACK16},                            // B5G6R5_UNORM
    {VK_FORMAT_A1R5G5B5_UNORM_PACK16, Attachable},              // A1R5G5B5_UNORM
    {VK_FORMAT_A2B10G10R10_UNORM_PACK32, Attachable | Storage}, // A2B10G10R10_UNORM
    {VK_FORMAT_A2B10G10R10_UINT_PACK32, Attachable | Storage},  // A2B10G10R10_UINT
    {VK_FORMAT_A2R10G10B10_UNORM_PACK32, Attachable},           // A2R10G10B10_UNORM
    {VK_FORMAT_A1R5G5B5_UNORM_PACK16, Attachable},         // A1B5G5R5_UNORM (flipped with swizzle)
    {VK_FORMAT_R5G5B5A1_UNORM_PACK16},                     // A5B5G5R1_UNORM (specially swizzled)
    {VK_FORMAT_R8_UNORM, Attachable | Storage},            // R8_UNORM
    {VK_FORMAT_R8_SNORM, Attachable | Storage},            // R8_SNORM
    {VK_FORMAT_R8_SINT, Attachable | Storage},             // R8_SINT
    {VK_FORMAT_R8_UINT, Attachable | Storage},             // R8_UINT
    {VK_FORMAT_R16G16B16A16_SFLOAT, Attachable | Storage}, // R16G16B16A16_FLOAT
    {VK_FORMAT_R16G16B16A16_UNORM, Attachable | Storage},  // R16G16B16A16_UNORM
    {VK_FORMAT_R16G16B16A16_SNORM, Attachable | Storage},  // R16G16B16A16_SNORM
    {VK_FORMAT_R16G16B16A16_SINT, Attachable | Storage},   // R16G16B16A16_SINT
    {VK_FORMAT_R16G16B16A16_UINT, Attachable | Storage},   // R16G16B16A16_UINT
    {VK_FORMAT_B10G11R11_UFLOAT_PACK32, Attachable | Storage}, // B10G11R11_FLOAT
    {VK_FORMAT_R32G32B32A32_UINT, Attachable | Storage},       // R32G32B32A32_UINT
    {VK_FORMAT_BC1_RGBA_UNORM_BLOCK},                          // BC1_RGBA_UNORM
    {VK_FORMAT_BC2_UNORM_BLOCK},                               // BC2_UNORM
    {VK_FORMAT_BC3_UNORM_BLOCK},                               // BC3_UNORM
    {VK_FORMAT_BC4_UNORM_BLOCK},                               // BC4_UNORM
    {VK_FORMAT_BC4_SNORM_BLOCK},                               // BC4_SNORM
    {VK_FORMAT_BC5_UNORM_BLOCK},                               // BC5_UNORM
    {VK_FORMAT_BC5_SNORM_BLOCK},                               // BC5_SNORM
    {VK_FORMAT_BC7_UNORM_BLOCK},                               // BC7_UNORM
    {VK_FORMAT_BC6H_UFLOAT_BLOCK},                             // BC6H_UFLOAT
    {VK_FORMAT_BC6H_SFLOAT_BLOCK},                             // BC6H_SFLOAT
    {VK_FORMAT_ASTC_4x4_UNORM_BLOCK},                          // ASTC_2D_4X4_UNORM
    {VK_FORMAT_B8G8R8A8_UNORM, Attachable | Storage},          // B8G8R8A8_UNORM
    {VK_FORMAT_R32G32B32A32_SFLOAT, Attachable | Storage},     // R32G32B32A32_FLOAT
    {VK_FORMAT_R32G32B32A32_SINT, Attachable | Storage},       // R32G32B32A32_SINT
    {VK_FORMAT_R32G32_SFLOAT, Attachable | Storage},           // R32G32_FLOAT
    {VK_FORMAT_R32G32_SINT, Attachable | Storage},             // R32G32_SINT
    {VK_FORMAT_R32_SFLOAT, Attachable | Storage},              // R32_FLOAT
    {VK_FORMAT_R16_SFLOAT, Attachable | Storage},              // R16_FLOAT
    {VK_FORMAT_R16_UNORM, Attachable | Storage},               // R16_UNORM
    {VK_FORMAT_R16_SNORM, Attachable | Storage},               // R16_SNORM
    {VK_FORMAT_R16_UINT, Attachable | Storage},                // R16_UINT
    {VK_FORMAT_R16_SINT, Attachable | Storage},                // R16_SINT
    {VK_FORMAT_R16G16_UNORM, Attachable | Storage},            // R16G16_UNORM
    {VK_FORMAT_R16G16_SFLOAT, Attachable | Storage},           // R16G16_FLOAT
    {VK_FORMAT_R16G16_UINT, Attachable | Storage},             // R16G16_UINT
    {VK_FORMAT_R16G16_SINT, Attachable | Storage},             // R16G16_SINT
    {VK_FORMAT_R16G16_SNORM, Attachable | Storage},            // R16G16_SNORM
    {VK_FORMAT_R32G32B32_SFLOAT},                              // R32G32B32_FLOAT
    {VK_FORMAT_A8B8G8R8_SRGB_PACK32, Attachable},              // A8B8G8R8_SRGB
    {VK_FORMAT_R8G8_UNORM, Attachable | Storage},              // R8G8_UNORM
    {VK_FORMAT_R8G8_SNORM, Attachable | Storage},              // R8G8_SNORM
    {VK_FORMAT_R8G8_SINT, Attachable | Storage},               // R8G8_SINT
    {VK_FORMAT_R8G8_UINT, Attachable | Storage},               // R8G8_UINT
    {VK_FORMAT_R32G32_UINT, Attachable | Storage},             // R32G32_UINT
    {VK_FORMAT_R16G16B16A16_SFLOAT, Attachable | Storage},     // R16G16B16X16_FLOAT
    {VK_FORMAT_R32_UINT, Attachable | Storage},                // R32_UINT
    {VK_FORMAT_R32_SINT, Attachable | Storage},                // R32_SINT
    {VK_FORMAT_ASTC_8x8_UNORM_BLOCK},                          // ASTC_2D_8X8_UNORM
    {VK_FORMAT_ASTC_8x5_UNORM_BLOCK},                          // ASTC_2D_8X5_UNORM
    {VK_FORMAT_ASTC_5x4_UNORM_BLOCK},                          // ASTC_2D_5X4_UNORM
    {VK_FORMAT_B8G8R8A8_SRGB, Attachable},                     // B8G8R8A8_SRGB
    {VK_FORMAT_BC1_RGBA_SRGB_BLOCK},                           // BC1_RGBA_SRGB
    {VK_FORMAT_BC2_SRGB_BLOCK},                                // BC2_SRGB
    {VK_FORMAT_BC3_SRGB_BLOCK},                                // BC3_SRGB
    {VK_FORMAT_BC7_SRGB_BLOCK},                                // BC7_SRGB
    {VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT},                     // A4B4G4R4_UNORM
    {VK_FORMAT_R4G4_UNORM_PACK8},                              // G4R4_UNORM
    {VK_FORMAT_ASTC_4x4_SRGB_BLOCK},                           // ASTC_2D_4X4_SRGB
    {VK_FORMAT_ASTC_8x8_SRGB_BLOCK},                           // ASTC_2D_8X8_SRGB
    {VK_FORMAT_ASTC_8x5_SRGB_BLOCK},                           // ASTC_2D_8X5_SRGB
    {VK_FORMAT_ASTC_5x4_SRGB_BLOCK},                           // ASTC_2D_5X4_SRGB
    {VK_FORMAT_ASTC_5x5_UNORM_BLOCK},                          // ASTC_2D_5X5_UNORM
    {VK_FORMAT_ASTC_5x5_SRGB_BLOCK},                           // ASTC_2D_5X5_SRGB
    {VK_FORMAT_ASTC_10x8_UNORM_BLOCK},                         // ASTC_2D_10X8_UNORM
    {VK_FORMAT_ASTC_10x8_SRGB_BLOCK},                          // ASTC_2D_10X8_SRGB
    {VK_FORMAT_ASTC_6x6_UNORM_BLOCK},                          // ASTC_2D_6X6_UNORM
    {VK_FORMAT_ASTC_6x6_SRGB_BLOCK},                           // ASTC_2D_6X6_SRGB
    {VK_FORMAT_ASTC_10x6_UNORM_BLOCK},                         // ASTC_2D_10X6_UNORM
    {VK_FORMAT_ASTC_10x6_SRGB_BLOCK},                          // ASTC_2D_10X6_SRGB
    {VK_FORMAT_ASTC_10x5_UNORM_BLOCK},                         // ASTC_2D_10X5_UNORM
    {VK_FORMAT_ASTC_10x5_SRGB_BLOCK},                          // ASTC_2D_10X5_SRGB
    {VK_FORMAT_ASTC_10x10_UNORM_BLOCK},                        // ASTC_2D_10X10_UNORM
    {VK_FORMAT_ASTC_10x10_SRGB_BLOCK},                         // ASTC_2D_10X10_SRGB
    {VK_FORMAT_ASTC_12x10_UNORM_BLOCK},                        // ASTC_2D_12X10_UNORM
    {VK_FORMAT_ASTC_12x10_SRGB_BLOCK},                         // ASTC_2D_12X10_SRGB
    {VK_FORMAT_ASTC_12x12_UNORM_BLOCK},                        // ASTC_2D_12X12_UNORM
    {VK_FORMAT_ASTC_12x12_SRGB_BLOCK},                         // ASTC_2D_12X12_SRGB
    {VK_FORMAT_ASTC_8x6_UNORM_BLOCK},                          // ASTC_2D_8X6_UNORM
    {VK_FORMAT_ASTC_8x6_SRGB_BLOCK},                           // ASTC_2D_8X6_SRGB
    {VK_FORMAT_ASTC_6x5_UNORM_BLOCK},                          // ASTC_2D_6X5_UNORM
    {VK_FORMAT_ASTC_6x5_SRGB_BLOCK},                           // ASTC_2D_6X5_SRGB
    {VK_FORMAT_E5B9G9R9_UFLOAT_PACK32},                        // E5B9G9R9_FLOAT

    // Depth formats
    {VK_FORMAT_D32_SFLOAT, Attachable},          // D32_FLOAT
    {VK_FORMAT_D16_UNORM, Attachable},           // D16_UNORM
    {VK_FORMAT_X8_D24_UNORM_PACK32, Attachable}, // X8_D24_UNORM

    // Stencil formats
    {VK_FORMAT_S8_UINT, Attachable}, // S8_UINT

    // DepthStencil formats
    {VK_FORMAT_D24_UNORM_S8_UINT, Attachable},  // D24_UNORM_S8_UINT
    {VK_FORMAT_D24_UNORM_S8_UINT, Attachable},  // S8_UINT_D24_UNORM (emulated)
    {VK_FORMAT_D32_SFLOAT_S8_UINT, Attachable}, // D32_FLOAT_S8_UINT
};
static_assert(std::size(tex_format_tuples) == VideoCore::Surface::MaxPixelFormat);

constexpr bool IsZetaFormat(PixelFormat pixel_format) {
    return pixel_format >= PixelFormat::MaxColorFormat &&
           pixel_format < PixelFormat::MaxDepthStencilFormat;
}

} // Anonymous namespace

FormatInfo SurfaceFormat(const Device& device, FormatType format_type, bool with_srgb,
                         PixelFormat pixel_format) {
    ASSERT(static_cast<size_t>(pixel_format) < std::size(tex_format_tuples));
    FormatTuple tuple = tex_format_tuples[static_cast<size_t>(pixel_format)];
    // Transcode on hardware that doesn't support ASTC natively
    if (!device.IsOptimalAstcSupported() && VideoCore::Surface::IsPixelFormatASTC(pixel_format)) {
        const bool is_srgb = with_srgb && VideoCore::Surface::IsPixelFormatSRGB(pixel_format);

        switch (Settings::values.astc_recompression.GetValue()) {
        case Settings::AstcRecompression::Uncompressed:
            if (is_srgb) {
                tuple.format = VK_FORMAT_A8B8G8R8_SRGB_PACK32;
            } else {
                tuple.format = VK_FORMAT_A8B8G8R8_UNORM_PACK32;
                tuple.usage |= Storage;
            }
            break;
        case Settings::AstcRecompression::Bc1:
            tuple.format = is_srgb ? VK_FORMAT_BC1_RGBA_SRGB_BLOCK : VK_FORMAT_BC1_RGBA_UNORM_BLOCK;
            break;
        case Settings::AstcRecompression::Bc3:
            tuple.format = is_srgb ? VK_FORMAT_BC3_SRGB_BLOCK : VK_FORMAT_BC3_UNORM_BLOCK;
            break;
        }
    }
    // Transcode on hardware that doesn't support BCn natively
    if (!device.IsOptimalBcnSupported() && VideoCore::Surface::IsPixelFormatBCn(pixel_format)) {
        const bool is_srgb = with_srgb && VideoCore::Surface::IsPixelFormatSRGB(pixel_format);
        if (pixel_format == PixelFormat::BC4_SNORM) {
            tuple.format = VK_FORMAT_R8_SNORM;
        } else if (pixel_format == PixelFormat::BC4_UNORM) {
            tuple.format = VK_FORMAT_R8_UNORM;
        } else if (pixel_format == PixelFormat::BC5_SNORM) {
            tuple.format = VK_FORMAT_R8G8_SNORM;
        } else if (pixel_format == PixelFormat::BC5_UNORM) {
            tuple.format = VK_FORMAT_R8G8_UNORM;
        } else if (pixel_format == PixelFormat::BC6H_SFLOAT ||
                   pixel_format == PixelFormat::BC6H_UFLOAT) {
            tuple.format = VK_FORMAT_R16G16B16A16_SFLOAT;
        } else if (is_srgb) {
            tuple.format = VK_FORMAT_A8B8G8R8_SRGB_PACK32;
        } else {
            tuple.format = VK_FORMAT_A8B8G8R8_UNORM_PACK32;
        }
    }
    const bool attachable = (tuple.usage & Attachable) != 0;
    const bool storage = (tuple.usage & Storage) != 0;

    VkFormatFeatureFlags usage{};
    switch (format_type) {
    case FormatType::Buffer:
        usage =
            VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT;
        break;
    case FormatType::Linear:
    case FormatType::Optimal:
        usage = VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT |
                VK_FORMAT_FEATURE_TRANSFER_SRC_BIT;
        if (attachable) {
            usage |= IsZetaFormat(pixel_format) ? VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT
                                                : VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT;
        }
        if (storage) {
            usage |= VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT;
        }
        break;
    }
    return {device.GetSupportedFormat(tuple.format, usage, format_type), attachable, storage};
}

VkShaderStageFlagBits ShaderStage(Shader::Stage stage) {
    switch (stage) {
    case Shader::Stage::VertexA:
    case Shader::Stage::VertexB:
        return VK_SHADER_STAGE_VERTEX_BIT;
    case Shader::Stage::TessellationControl:
        return VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT;
    case Shader::Stage::TessellationEval:
        return VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT;
    case Shader::Stage::Geometry:
        return VK_SHADER_STAGE_GEOMETRY_BIT;
    case Shader::Stage::Fragment:
        return VK_SHADER_STAGE_FRAGMENT_BIT;
    case Shader::Stage::Compute:
        return VK_SHADER_STAGE_COMPUTE_BIT;
    }
    UNIMPLEMENTED_MSG("Unimplemented shader stage={}", stage);
    return {};
}

VkPrimitiveTopology PrimitiveTopology([[maybe_unused]] const Device& device,
                                      Maxwell::PrimitiveTopology topology) {
    switch (topology) {
    case Maxwell::PrimitiveTopology::Points:
        return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
    case Maxwell::PrimitiveTopology::Lines:
        return VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
    case Maxwell::PrimitiveTopology::LineLoop:
        return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
    case Maxwell::PrimitiveTopology::LineStrip:
        return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
    case Maxwell::PrimitiveTopology::Triangles:
        return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
    case Maxwell::PrimitiveTopology::TriangleStrip:
        return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
    case Maxwell::PrimitiveTopology::TriangleFan:
        return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN;
    case Maxwell::PrimitiveTopology::LinesAdjacency:
        return VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY;
    case Maxwell::PrimitiveTopology::LineStripAdjacency:
        return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY;
    case Maxwell::PrimitiveTopology::TrianglesAdjacency:
        return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY;
    case Maxwell::PrimitiveTopology::TriangleStripAdjacency:
        return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY;
    case Maxwell::PrimitiveTopology::Quads:
    case Maxwell::PrimitiveTopology::QuadStrip:
        // TODO: Use VK_PRIMITIVE_TOPOLOGY_QUAD_LIST_EXT/VK_PRIMITIVE_TOPOLOGY_QUAD_STRIP_EXT
        // whenever it releases
        return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
    case Maxwell::PrimitiveTopology::Patches:
        return VK_PRIMITIVE_TOPOLOGY_PATCH_LIST;
    case Maxwell::PrimitiveTopology::Polygon:
        LOG_WARNING(Render_Vulkan, "Draw mode is Polygon with a polygon mode of lines should be a "
                                   "single body and not a bunch of triangles.");
        return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN;
    }
    UNIMPLEMENTED_MSG("Unimplemented topology={}", topology);
    return {};
}

VkFormat VertexFormat(const Device& device, Maxwell::VertexAttribute::Type type,
                      Maxwell::VertexAttribute::Size size) {
    if (device.MustEmulateScaledFormats()) {
        if (type == Maxwell::VertexAttribute::Type::SScaled) {
            type = Maxwell::VertexAttribute::Type::SInt;
        } else if (type == Maxwell::VertexAttribute::Type::UScaled) {
            type = Maxwell::VertexAttribute::Type::UInt;
        }
    }

    const VkFormat format{([&]() {
        switch (type) {
        case Maxwell::VertexAttribute::Type::UnusedEnumDoNotUseBecauseItWillGoAway:
            ASSERT_MSG(false, "Invalid vertex attribute type!");
            break;
        case Maxwell::VertexAttribute::Type::UNorm:
            switch (size) {
            case Maxwell::VertexAttribute::Size::Size_R8:
            case Maxwell::VertexAttribute::Size::Size_A8:
                return VK_FORMAT_R8_UNORM;
            case Maxwell::VertexAttribute::Size::Size_R8_G8:
            case Maxwell::VertexAttribute::Size::Size_G8_R8:
                return VK_FORMAT_R8G8_UNORM;
            case Maxwell::VertexAttribute::Size::Size_R8_G8_B8:
                return VK_FORMAT_R8G8B8_UNORM;
            case Maxwell::VertexAttribute::Size::Size_R8_G8_B8_A8:
            case Maxwell::VertexAttribute::Size::Size_X8_B8_G8_R8:
                return VK_FORMAT_R8G8B8A8_UNORM;
            case Maxwell::VertexAttribute::Size::Size_R16:
                return VK_FORMAT_R16_UNORM;
            case Maxwell::VertexAttribute::Size::Size_R16_G16:
                return VK_FORMAT_R16G16_UNORM;
            case Maxwell::VertexAttribute::Size::Size_R16_G16_B16:
                return VK_FORMAT_R16G16B16_UNORM;
            case Maxwell::VertexAttribute::Size::Size_R16_G16_B16_A16:
                return VK_FORMAT_R16G16B16A16_UNORM;
            case Maxwell::VertexAttribute::Size::Size_A2_B10_G10_R10:
                return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
            default:
                break;
            }
            break;
        case Maxwell::VertexAttribute::Type::SNorm:
            switch (size) {
            case Maxwell::VertexAttribute::Size::Size_R8:
            case Maxwell::VertexAttribute::Size::Size_A8:
                return VK_FORMAT_R8_SNORM;
            case Maxwell::VertexAttribute::Size::Size_R8_G8:
            case Maxwell::VertexAttribute::Size::Size_G8_R8:
                return VK_FORMAT_R8G8_SNORM;
            case Maxwell::VertexAttribute::Size::Size_R8_G8_B8:
                return VK_FORMAT_R8G8B8_SNORM;
            case Maxwell::VertexAttribute::Size::Size_R8_G8_B8_A8:
            case Maxwell::VertexAttribute::Size::Size_X8_B8_G8_R8:
                return VK_FORMAT_R8G8B8A8_SNORM;
            case Maxwell::VertexAttribute::Size::Size_R16:
                return VK_FORMAT_R16_SNORM;
            case Maxwell::VertexAttribute::Size::Size_R16_G16:
                return VK_FORMAT_R16G16_SNORM;
            case Maxwell::VertexAttribute::Size::Size_R16_G16_B16:
                return VK_FORMAT_R16G16B16_SNORM;
            case Maxwell::VertexAttribute::Size::Size_R16_G16_B16_A16:
                return VK_FORMAT_R16G16B16A16_SNORM;
            case Maxwell::VertexAttribute::Size::Size_A2_B10_G10_R10:
                return VK_FORMAT_A2B10G10R10_SNORM_PACK32;
            default:
                break;
            }
            break;
        case Maxwell::VertexAttribute::Type::UScaled:
            switch (size) {
            case Maxwell::VertexAttribute::Size::Size_R8:
            case Maxwell::VertexAttribute::Size::Size_A8:
                return VK_FORMAT_R8_USCALED;
            case Maxwell::VertexAttribute::Size::Size_R8_G8:
            case Maxwell::VertexAttribute::Size::Size_G8_R8:
                return VK_FORMAT_R8G8_USCALED;
            case Maxwell::VertexAttribute::Size::Size_R8_G8_B8:
                return VK_FORMAT_R8G8B8_USCALED;
            case Maxwell::VertexAttribute::Size::Size_R8_G8_B8_A8:
            case Maxwell::VertexAttribute::Size::Size_X8_B8_G8_R8:
                return VK_FORMAT_R8G8B8A8_USCALED;
            case Maxwell::VertexAttribute::Size::Size_R16:
                return VK_FORMAT_R16_USCALED;
            case Maxwell::VertexAttribute::Size::Size_R16_G16:
                return VK_FORMAT_R16G16_USCALED;
            case Maxwell::VertexAttribute::Size::Size_R16_G16_B16:
                return VK_FORMAT_R16G16B16_USCALED;
            case Maxwell::VertexAttribute::Size::Size_R16_G16_B16_A16:
                return VK_FORMAT_R16G16B16A16_USCALED;
            case Maxwell::VertexAttribute::Size::Size_A2_B10_G10_R10:
                return VK_FORMAT_A2B10G10R10_USCALED_PACK32;
            default:
                break;
            }
            break;
        case Maxwell::VertexAttribute::Type::SScaled:
            switch (size) {
            case Maxwell::VertexAttribute::Size::Size_R8:
            case Maxwell::VertexAttribute::Size::Size_A8:
                return VK_FORMAT_R8_SSCALED;
            case Maxwell::VertexAttribute::Size::Size_R8_G8:
            case Maxwell::VertexAttribute::Size::Size_G8_R8:
                return VK_FORMAT_R8G8_SSCALED;
            case Maxwell::VertexAttribute::Size::Size_R8_G8_B8:
                return VK_FORMAT_R8G8B8_SSCALED;
            case Maxwell::VertexAttribute::Size::Size_R8_G8_B8_A8:
            case Maxwell::VertexAttribute::Size::Size_X8_B8_G8_R8:
                return VK_FORMAT_R8G8B8A8_SSCALED;
            case Maxwell::VertexAttribute::Size::Size_R16:
                return VK_FORMAT_R16_SSCALED;
            case Maxwell::VertexAttribute::Size::Size_R16_G16:
                return VK_FORMAT_R16G16_SSCALED;
            case Maxwell::VertexAttribute::Size::Size_R16_G16_B16:
                return VK_FORMAT_R16G16B16_SSCALED;
            case Maxwell::VertexAttribute::Size::Size_R16_G16_B16_A16:
                return VK_FORMAT_R16G16B16A16_SSCALED;
            case Maxwell::VertexAttribute::Size::Size_A2_B10_G10_R10:
                return VK_FORMAT_A2B10G10R10_SSCALED_PACK32;
            default:
                break;
            }
            break;
        case Maxwell::VertexAttribute::Type::UInt:
            switch (size) {
            case Maxwell::VertexAttribute::Size::Size_R8:
            case Maxwell::VertexAttribute::Size::Size_A8:
                return VK_FORMAT_R8_UINT;
            case Maxwell::VertexAttribute::Size::Size_R8_G8:
            case Maxwell::VertexAttribute::Size::Size_G8_R8:
                return VK_FORMAT_R8G8_UINT;
            case Maxwell::VertexAttribute::Size::Size_R8_G8_B8:
                return VK_FORMAT_R8G8B8_UINT;
            case Maxwell::VertexAttribute::Size::Size_R8_G8_B8_A8:
            case Maxwell::VertexAttribute::Size::Size_X8_B8_G8_R8:
                return VK_FORMAT_R8G8B8A8_UINT;
            case Maxwell::VertexAttribute::Size::Size_R16:
                return VK_FORMAT_R16_UINT;
            case Maxwell::VertexAttribute::Size::Size_R16_G16:
                return VK_FORMAT_R16G16_UINT;
            case Maxwell::VertexAttribute::Size::Size_R16_G16_B16:
                return VK_FORMAT_R16G16B16_UINT;
            case Maxwell::VertexAttribute::Size::Size_R16_G16_B16_A16:
                return VK_FORMAT_R16G16B16A16_UINT;
            case Maxwell::VertexAttribute::Size::Size_R32:
                return VK_FORMAT_R32_UINT;
            case Maxwell::VertexAttribute::Size::Size_R32_G32:
                return VK_FORMAT_R32G32_UINT;
            case Maxwell::VertexAttribute::Size::Size_R32_G32_B32:
                return VK_FORMAT_R32G32B32_UINT;
            case Maxwell::VertexAttribute::Size::Size_R32_G32_B32_A32:
                return VK_FORMAT_R32G32B32A32_UINT;
            case Maxwell::VertexAttribute::Size::Size_A2_B10_G10_R10:
                return VK_FORMAT_A2B10G10R10_UINT_PACK32;
            default:
                break;
            }
            break;
        case Maxwell::VertexAttribute::Type::SInt:
            switch (size) {
            case Maxwell::VertexAttribute::Size::Size_R8:
            case Maxwell::VertexAttribute::Size::Size_A8:
                return VK_FORMAT_R8_SINT;
            case Maxwell::VertexAttribute::Size::Size_R8_G8:
            case Maxwell::VertexAttribute::Size::Size_G8_R8:
                return VK_FORMAT_R8G8_SINT;
            case Maxwell::VertexAttribute::Size::Size_R8_G8_B8:
                return VK_FORMAT_R8G8B8_SINT;
            case Maxwell::VertexAttribute::Size::Size_R8_G8_B8_A8:
            case Maxwell::VertexAttribute::Size::Size_X8_B8_G8_R8:
                return VK_FORMAT_R8G8B8A8_SINT;
            case Maxwell::VertexAttribute::Size::Size_R16:
                return VK_FORMAT_R16_SINT;
            case Maxwell::VertexAttribute::Size::Size_R16_G16:
                return VK_FORMAT_R16G16_SINT;
            case Maxwell::VertexAttribute::Size::Size_R16_G16_B16:
                return VK_FORMAT_R16G16B16_SINT;
            case Maxwell::VertexAttribute::Size::Size_R16_G16_B16_A16:
                return VK_FORMAT_R16G16B16A16_SINT;
            case Maxwell::VertexAttribute::Size::Size_R32:
                return VK_FORMAT_R32_SINT;
            case Maxwell::VertexAttribute::Size::Size_R32_G32:
                return VK_FORMAT_R32G32_SINT;
            case Maxwell::VertexAttribute::Size::Size_R32_G32_B32:
                return VK_FORMAT_R32G32B32_SINT;
            case Maxwell::VertexAttribute::Size::Size_R32_G32_B32_A32:
                return VK_FORMAT_R32G32B32A32_SINT;
            case Maxwell::VertexAttribute::Size::Size_A2_B10_G10_R10:
                return VK_FORMAT_A2B10G10R10_SINT_PACK32;
            default:
                break;
            }
            break;
        case Maxwell::VertexAttribute::Type::Float:
            switch (size) {
            case Maxwell::VertexAttribute::Size::Size_R16:
                return VK_FORMAT_R16_SFLOAT;
            case Maxwell::VertexAttribute::Size::Size_R16_G16:
                return VK_FORMAT_R16G16_SFLOAT;
            case Maxwell::VertexAttribute::Size::Size_R16_G16_B16:
                return VK_FORMAT_R16G16B16_SFLOAT;
            case Maxwell::VertexAttribute::Size::Size_R16_G16_B16_A16:
                return VK_FORMAT_R16G16B16A16_SFLOAT;
            case Maxwell::VertexAttribute::Size::Size_R32:
                return VK_FORMAT_R32_SFLOAT;
            case Maxwell::VertexAttribute::Size::Size_R32_G32:
                return VK_FORMAT_R32G32_SFLOAT;
            case Maxwell::VertexAttribute::Size::Size_R32_G32_B32:
                return VK_FORMAT_R32G32B32_SFLOAT;
            case Maxwell::VertexAttribute::Size::Size_R32_G32_B32_A32:
                return VK_FORMAT_R32G32B32A32_SFLOAT;
            case Maxwell::VertexAttribute::Size::Size_B10_G11_R11:
                return VK_FORMAT_B10G11R11_UFLOAT_PACK32;
            default:
                break;
            }
            break;
        }
        return VK_FORMAT_UNDEFINED;
    })()};

    if (format == VK_FORMAT_UNDEFINED) {
        UNIMPLEMENTED_MSG("Unimplemented vertex format of type={} and size={}", type, size);
    }

    return device.GetSupportedFormat(format, VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT,
                                     FormatType::Buffer);
}

VkCompareOp ComparisonOp(Maxwell::ComparisonOp comparison) {
    switch (comparison) {
    case Maxwell::ComparisonOp::Never_D3D:
    case Maxwell::ComparisonOp::Never_GL:
        return VK_COMPARE_OP_NEVER;
    case Maxwell::ComparisonOp::Less_D3D:
    case Maxwell::ComparisonOp::Less_GL:
        return VK_COMPARE_OP_LESS;
    case Maxwell::ComparisonOp::Equal_D3D:
    case Maxwell::ComparisonOp::Equal_GL:
        return VK_COMPARE_OP_EQUAL;
    case Maxwell::ComparisonOp::LessEqual_D3D:
    case Maxwell::ComparisonOp::LessEqual_GL:
        return VK_COMPARE_OP_LESS_OR_EQUAL;
    case Maxwell::ComparisonOp::Greater_D3D:
    case Maxwell::ComparisonOp::Greater_GL:
        return VK_COMPARE_OP_GREATER;
    case Maxwell::ComparisonOp::NotEqual_D3D:
    case Maxwell::ComparisonOp::NotEqual_GL:
        return VK_COMPARE_OP_NOT_EQUAL;
    case Maxwell::ComparisonOp::GreaterEqual_D3D:
    case Maxwell::ComparisonOp::GreaterEqual_GL:
        return VK_COMPARE_OP_GREATER_OR_EQUAL;
    case Maxwell::ComparisonOp::Always_D3D:
    case Maxwell::ComparisonOp::Always_GL:
        return VK_COMPARE_OP_ALWAYS;
    }
    UNIMPLEMENTED_MSG("Unimplemented comparison op={}", comparison);
    return {};
}

VkIndexType IndexFormat(Maxwell::IndexFormat index_format) {
    switch (index_format) {
    case Maxwell::IndexFormat::UnsignedByte:
        return VK_INDEX_TYPE_UINT8_EXT;
    case Maxwell::IndexFormat::UnsignedShort:
        return VK_INDEX_TYPE_UINT16;
    case Maxwell::IndexFormat::UnsignedInt:
        return VK_INDEX_TYPE_UINT32;
    }
    UNIMPLEMENTED_MSG("Unimplemented index_format={}", index_format);
    return {};
}

VkStencilOp StencilOp(Maxwell::StencilOp::Op stencil_op) {
    switch (stencil_op) {
    case Maxwell::StencilOp::Op::Keep_D3D:
    case Maxwell::StencilOp::Op::Keep_GL:
        return VK_STENCIL_OP_KEEP;
    case Maxwell::StencilOp::Op::Zero_D3D:
    case Maxwell::StencilOp::Op::Zero_GL:
        return VK_STENCIL_OP_ZERO;
    case Maxwell::StencilOp::Op::Replace_D3D:
    case Maxwell::StencilOp::Op::Replace_GL:
        return VK_STENCIL_OP_REPLACE;
    case Maxwell::StencilOp::Op::IncrSaturate_D3D:
    case Maxwell::StencilOp::Op::IncrSaturate_GL:
        return VK_STENCIL_OP_INCREMENT_AND_CLAMP;
    case Maxwell::StencilOp::Op::DecrSaturate_D3D:
    case Maxwell::StencilOp::Op::DecrSaturate_GL:
        return VK_STENCIL_OP_DECREMENT_AND_CLAMP;
    case Maxwell::StencilOp::Op::Invert_D3D:
    case Maxwell::StencilOp::Op::Invert_GL:
        return VK_STENCIL_OP_INVERT;
    case Maxwell::StencilOp::Op::Incr_D3D:
    case Maxwell::StencilOp::Op::Incr_GL:
        return VK_STENCIL_OP_INCREMENT_AND_WRAP;
    case Maxwell::StencilOp::Op::Decr_D3D:
    case Maxwell::StencilOp::Op::Decr_GL:
        return VK_STENCIL_OP_DECREMENT_AND_WRAP;
    }
    UNIMPLEMENTED_MSG("Unimplemented stencil op={}", stencil_op);
    return {};
}

VkBlendOp BlendEquation(Maxwell::Blend::Equation equation) {
    switch (equation) {
    case Maxwell::Blend::Equation::Add_D3D:
    case Maxwell::Blend::Equation::Add_GL:
        return VK_BLEND_OP_ADD;
    case Maxwell::Blend::Equation::Subtract_D3D:
    case Maxwell::Blend::Equation::Subtract_GL:
        return VK_BLEND_OP_SUBTRACT;
    case Maxwell::Blend::Equation::ReverseSubtract_D3D:
    case Maxwell::Blend::Equation::ReverseSubtract_GL:
        return VK_BLEND_OP_REVERSE_SUBTRACT;
    case Maxwell::Blend::Equation::Min_D3D:
    case Maxwell::Blend::Equation::Min_GL:
        return VK_BLEND_OP_MIN;
    case Maxwell::Blend::Equation::Max_D3D:
    case Maxwell::Blend::Equation::Max_GL:
        return VK_BLEND_OP_MAX;
    }
    UNIMPLEMENTED_MSG("Unimplemented blend equation={}", equation);
    return {};
}

VkBlendFactor BlendFactor(Maxwell::Blend::Factor factor) {
    switch (factor) {
    case Maxwell::Blend::Factor::Zero_D3D:
    case Maxwell::Blend::Factor::Zero_GL:
        return VK_BLEND_FACTOR_ZERO;
    case Maxwell::Blend::Factor::One_D3D:
    case Maxwell::Blend::Factor::One_GL:
        return VK_BLEND_FACTOR_ONE;
    case Maxwell::Blend::Factor::SourceColor_D3D:
    case Maxwell::Blend::Factor::SourceColor_GL:
        return VK_BLEND_FACTOR_SRC_COLOR;
    case Maxwell::Blend::Factor::OneMinusSourceColor_D3D:
    case Maxwell::Blend::Factor::OneMinusSourceColor_GL:
        return VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR;
    case Maxwell::Blend::Factor::SourceAlpha_D3D:
    case Maxwell::Blend::Factor::SourceAlpha_GL:
        return VK_BLEND_FACTOR_SRC_ALPHA;
    case Maxwell::Blend::Factor::OneMinusSourceAlpha_D3D:
    case Maxwell::Blend::Factor::OneMinusSourceAlpha_GL:
        return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
    case Maxwell::Blend::Factor::DestAlpha_D3D:
    case Maxwell::Blend::Factor::DestAlpha_GL:
        return VK_BLEND_FACTOR_DST_ALPHA;
    case Maxwell::Blend::Factor::OneMinusDestAlpha_D3D:
    case Maxwell::Blend::Factor::OneMinusDestAlpha_GL:
        return VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA;
    case Maxwell::Blend::Factor::DestColor_D3D:
    case Maxwell::Blend::Factor::DestColor_GL:
        return VK_BLEND_FACTOR_DST_COLOR;
    case Maxwell::Blend::Factor::OneMinusDestColor_D3D:
    case Maxwell::Blend::Factor::OneMinusDestColor_GL:
        return VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR;
    case Maxwell::Blend::Factor::SourceAlphaSaturate_D3D:
    case Maxwell::Blend::Factor::SourceAlphaSaturate_GL:
        return VK_BLEND_FACTOR_SRC_ALPHA_SATURATE;
    case Maxwell::Blend::Factor::Source1Color_D3D:
    case Maxwell::Blend::Factor::Source1Color_GL:
        return VK_BLEND_FACTOR_SRC1_COLOR;
    case Maxwell::Blend::Factor::OneMinusSource1Color_D3D:
    case Maxwell::Blend::Factor::OneMinusSource1Color_GL:
        return VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR;
    case Maxwell::Blend::Factor::Source1Alpha_D3D:
    case Maxwell::Blend::Factor::Source1Alpha_GL:
        return VK_BLEND_FACTOR_SRC1_ALPHA;
    case Maxwell::Blend::Factor::OneMinusSource1Alpha_D3D:
    case Maxwell::Blend::Factor::OneMinusSource1Alpha_GL:
        return VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA;
    case Maxwell::Blend::Factor::BlendFactor_D3D:
    case Maxwell::Blend::Factor::ConstantColor_GL:
        return VK_BLEND_FACTOR_CONSTANT_COLOR;
    case Maxwell::Blend::Factor::OneMinusBlendFactor_D3D:
    case Maxwell::Blend::Factor::OneMinusConstantColor_GL:
        return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR;
    case Maxwell::Blend::Factor::BothSourceAlpha_D3D:
    case Maxwell::Blend::Factor::ConstantAlpha_GL:
        return VK_BLEND_FACTOR_CONSTANT_ALPHA;
    case Maxwell::Blend::Factor::OneMinusBothSourceAlpha_D3D:
    case Maxwell::Blend::Factor::OneMinusConstantAlpha_GL:
        return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA;
    }
    UNIMPLEMENTED_MSG("Unimplemented blend factor={}", factor);
    return {};
}

VkFrontFace FrontFace(Maxwell::FrontFace front_face) {
    switch (front_face) {
    case Maxwell::FrontFace::ClockWise:
        return VK_FRONT_FACE_CLOCKWISE;
    case Maxwell::FrontFace::CounterClockWise:
        return VK_FRONT_FACE_COUNTER_CLOCKWISE;
    }
    UNIMPLEMENTED_MSG("Unimplemented front face={}", front_face);
    return {};
}

VkCullModeFlagBits CullFace(Maxwell::CullFace cull_face) {
    switch (cull_face) {
    case Maxwell::CullFace::Front:
        return VK_CULL_MODE_FRONT_BIT;
    case Maxwell::CullFace::Back:
        return VK_CULL_MODE_BACK_BIT;
    case Maxwell::CullFace::FrontAndBack:
        return VK_CULL_MODE_FRONT_AND_BACK;
    }
    UNIMPLEMENTED_MSG("Unimplemented cull face={}", cull_face);
    return {};
}

VkPolygonMode PolygonMode(Maxwell::PolygonMode polygon_mode) {
    switch (polygon_mode) {
    case Maxwell::PolygonMode::Point:
        return VK_POLYGON_MODE_POINT;
    case Maxwell::PolygonMode::Line:
        return VK_POLYGON_MODE_LINE;
    case Maxwell::PolygonMode::Fill:
        return VK_POLYGON_MODE_FILL;
    }
    UNIMPLEMENTED_MSG("Unimplemented polygon mode={}", polygon_mode);
    return {};
}

VkComponentSwizzle SwizzleSource(Tegra::Texture::SwizzleSource swizzle) {
    switch (swizzle) {
    case Tegra::Texture::SwizzleSource::Zero:
        return VK_COMPONENT_SWIZZLE_ZERO;
    case Tegra::Texture::SwizzleSource::R:
        return VK_COMPONENT_SWIZZLE_R;
    case Tegra::Texture::SwizzleSource::G:
        return VK_COMPONENT_SWIZZLE_G;
    case Tegra::Texture::SwizzleSource::B:
        return VK_COMPONENT_SWIZZLE_B;
    case Tegra::Texture::SwizzleSource::A:
        return VK_COMPONENT_SWIZZLE_A;
    case Tegra::Texture::SwizzleSource::OneInt:
    case Tegra::Texture::SwizzleSource::OneFloat:
        return VK_COMPONENT_SWIZZLE_ONE;
    }
    UNIMPLEMENTED_MSG("Unimplemented swizzle source={}", swizzle);
    return {};
}

VkViewportCoordinateSwizzleNV ViewportSwizzle(Maxwell::ViewportSwizzle swizzle) {
    switch (swizzle) {
    case Maxwell::ViewportSwizzle::PositiveX:
        return VK_VIEWPORT_COORDINATE_SWIZZLE_POSITIVE_X_NV;
    case Maxwell::ViewportSwizzle::NegativeX:
        return VK_VIEWPORT_COORDINATE_SWIZZLE_NEGATIVE_X_NV;
    case Maxwell::ViewportSwizzle::PositiveY:
        return VK_VIEWPORT_COORDINATE_SWIZZLE_POSITIVE_Y_NV;
    case Maxwell::ViewportSwizzle::NegativeY:
        return VK_VIEWPORT_COORDINATE_SWIZZLE_NEGATIVE_Y_NV;
    case Maxwell::ViewportSwizzle::PositiveZ:
        return VK_VIEWPORT_COORDINATE_SWIZZLE_POSITIVE_Z_NV;
    case Maxwell::ViewportSwizzle::NegativeZ:
        return VK_VIEWPORT_COORDINATE_SWIZZLE_NEGATIVE_Z_NV;
    case Maxwell::ViewportSwizzle::PositiveW:
        return VK_VIEWPORT_COORDINATE_SWIZZLE_POSITIVE_W_NV;
    case Maxwell::ViewportSwizzle::NegativeW:
        return VK_VIEWPORT_COORDINATE_SWIZZLE_NEGATIVE_W_NV;
    }
    ASSERT_MSG(false, "Invalid swizzle={}", swizzle);
    return {};
}

VkSamplerReductionMode SamplerReduction(Tegra::Texture::SamplerReduction reduction) {
    switch (reduction) {
    case Tegra::Texture::SamplerReduction::WeightedAverage:
        return VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE_EXT;
    case Tegra::Texture::SamplerReduction::Min:
        return VK_SAMPLER_REDUCTION_MODE_MIN_EXT;
    case Tegra::Texture::SamplerReduction::Max:
        return VK_SAMPLER_REDUCTION_MODE_MAX_EXT;
    }
    ASSERT_MSG(false, "Invalid sampler mode={}", static_cast<int>(reduction));
    return VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE_EXT;
}

VkSampleCountFlagBits MsaaMode(Tegra::Texture::MsaaMode msaa_mode) {
    switch (msaa_mode) {
    case Tegra::Texture::MsaaMode::Msaa1x1:
        return VK_SAMPLE_COUNT_1_BIT;
    case Tegra::Texture::MsaaMode::Msaa2x1:
    case Tegra::Texture::MsaaMode::Msaa2x1_D3D:
        return VK_SAMPLE_COUNT_2_BIT;
    case Tegra::Texture::MsaaMode::Msaa2x2:
    case Tegra::Texture::MsaaMode::Msaa2x2_VC4:
    case Tegra::Texture::MsaaMode::Msaa2x2_VC12:
        return VK_SAMPLE_COUNT_4_BIT;
    case Tegra::Texture::MsaaMode::Msaa4x2:
    case Tegra::Texture::MsaaMode::Msaa4x2_D3D:
    case Tegra::Texture::MsaaMode::Msaa4x2_VC8:
    case Tegra::Texture::MsaaMode::Msaa4x2_VC24:
        return VK_SAMPLE_COUNT_8_BIT;
    case Tegra::Texture::MsaaMode::Msaa4x4:
        return VK_SAMPLE_COUNT_16_BIT;
    default:
        ASSERT_MSG(false, "Invalid msaa_mode={}", static_cast<int>(msaa_mode));
        return VK_SAMPLE_COUNT_1_BIT;
    }
}

} // namespace Vulkan::MaxwellToVK


code:
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "shader_recompiler/stage.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/surface.h"
#include "video_core/textures/texture.h"
#include "video_core/vulkan_common/vulkan_device.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"

namespace Vulkan::MaxwellToVK {

using Maxwell = Tegra::Engines::Maxwell3D::Regs;
using PixelFormat = VideoCore::Surface::PixelFormat;

namespace Sampler {

VkFilter Filter(Tegra::Texture::TextureFilter filter);

VkSamplerMipmapMode MipmapMode(Tegra::Texture::TextureMipmapFilter mipmap_filter);

VkSamplerAddressMode WrapMode(const Device& device, Tegra::Texture::WrapMode wrap_mode,
                              Tegra::Texture::TextureFilter filter);

VkCompareOp DepthCompareFunction(Tegra::Texture::DepthCompareFunc depth_compare_func);

} // namespace Sampler

struct FormatInfo {
    VkFormat format;
    bool attachable;
    bool storage;
};

/**
 * Returns format properties supported in the host
 * @param device       Host device
 * @param format_type  Type of image the buffer will use
 * @param with_srgb    True when the format can be sRGB when converted to another format (ASTC)
 * @param pixel_format Guest pixel format to describe
 */
[[nodiscard]] FormatInfo SurfaceFormat(const Device& device, FormatType format_type, bool with_srgb,
                                       PixelFormat pixel_format);

VkShaderStageFlagBits ShaderStage(Shader::Stage stage);

VkPrimitiveTopology PrimitiveTopology(const Device& device, Maxwell::PrimitiveTopology topology);

VkFormat VertexFormat(const Device& device, Maxwell::VertexAttribute::Type type,
                      Maxwell::VertexAttribute::Size size);

VkCompareOp ComparisonOp(Maxwell::ComparisonOp comparison);

VkIndexType IndexFormat(Maxwell::IndexFormat index_format);

VkStencilOp StencilOp(Maxwell::StencilOp::Op stencil_op);

VkBlendOp BlendEquation(Maxwell::Blend::Equation equation);

VkBlendFactor BlendFactor(Maxwell::Blend::Factor factor);

VkFrontFace FrontFace(Maxwell::FrontFace front_face);

VkCullModeFlagBits CullFace(Maxwell::CullFace cull_face);

VkPolygonMode PolygonMode(Maxwell::PolygonMode polygon_mode);

VkComponentSwizzle SwizzleSource(Tegra::Texture::SwizzleSource swizzle);

VkViewportCoordinateSwizzleNV ViewportSwizzle(Maxwell::ViewportSwizzle swizzle);

VkSamplerReductionMode SamplerReduction(Tegra::Texture::SamplerReduction reduction);

VkSampleCountFlagBits MsaaMode(Tegra::Texture::MsaaMode msaa_mode);

} // namespace Vulkan::MaxwellToVK

repo_name: litucks/torzu

license: mit


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <cstddef>

#include <boost/container/small_vector.hpp>

#include "common/common_types.h"
#include "shader_recompiler/backend/spirv/emit_spirv.h"
#include "shader_recompiler/shader_info.h"
#include "video_core/renderer_vulkan/vk_texture_cache.h"
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
#include "video_core/texture_cache/types.h"
#include "video_core/vulkan_common/vulkan_device.h"

namespace Vulkan {

using Shader::Backend::SPIRV::NUM_TEXTURE_AND_IMAGE_SCALING_WORDS;

class DescriptorLayoutBuilder {
public:
    DescriptorLayoutBuilder(const Device& device_) : device{&device_} {}

    bool CanUsePushDescriptor() const noexcept {
        return device->IsKhrPushDescriptorSupported() &&
               num_descriptors <= device->MaxPushDescriptors();
    }

    vk::DescriptorSetLayout CreateDescriptorSetLayout(bool use_push_descriptor) const {
        if (bindings.empty()) {
            return nullptr;
        }
        const VkDescriptorSetLayoutCreateFlags flags =
            use_push_descriptor ? VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR : 0;
        return device->GetLogical().CreateDescriptorSetLayout({
            .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
            .pNext = nullptr,
            .flags = flags,
            .bindingCount = static_cast<u32>(bindings.size()),
            .pBindings = bindings.data(),
        });
    }

    vk::DescriptorUpdateTemplate CreateTemplate(VkDescriptorSetLayout descriptor_set_layout,
                                                VkPipelineLayout pipeline_layout,
                                                bool use_push_descriptor) const {
        if (entries.empty()) {
            return nullptr;
        }
        const VkDescriptorUpdateTemplateType type =
            use_push_descriptor ? VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_PUSH_DESCRIPTORS_KHR
                                : VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_DESCRIPTOR_SET;
        return device->GetLogical().CreateDescriptorUpdateTemplate({
            .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_CREATE_INFO,
            .pNext = nullptr,
            .flags = 0,
            .descriptorUpdateEntryCount = static_cast<u32>(entries.size()),
            .pDescriptorUpdateEntries = entries.data(),
            .templateType = type,
            .descriptorSetLayout = descriptor_set_layout,
            .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
            .pipelineLayout = pipeline_layout,
            .set = 0,
        });
    }

    vk::PipelineLayout CreatePipelineLayout(VkDescriptorSetLayout descriptor_set_layout) const {
        using Shader::Backend::SPIRV::RenderAreaLayout;
        using Shader::Backend::SPIRV::RescalingLayout;
        const u32 size_offset = is_compute ? sizeof(RescalingLayout::down_factor) : 0u;
        const VkPushConstantRange range{
            .stageFlags = static_cast<VkShaderStageFlags>(
                is_compute ? VK_SHADER_STAGE_COMPUTE_BIT : VK_SHADER_STAGE_ALL_GRAPHICS),
            .offset = 0,
            .size = static_cast<u32>(sizeof(RescalingLayout)) - size_offset +
                    static_cast<u32>(sizeof(RenderAreaLayout)),
        };
        return device->GetLogical().CreatePipelineLayout({
            .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
            .pNext = nullptr,
            .flags = 0,
            .setLayoutCount = descriptor_set_layout ? 1U : 0U,
            .pSetLayouts = bindings.empty() ? nullptr : &descriptor_set_layout,
            .pushConstantRangeCount = 1,
            .pPushConstantRanges = &range,
        });
    }

    void Add(const Shader::Info& info, VkShaderStageFlags stage) {
        is_compute |= (stage & VK_SHADER_STAGE_COMPUTE_BIT) != 0;

        Add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, stage, info.constant_buffer_descriptors);
        Add(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, stage, info.storage_buffers_descriptors);
        Add(VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, stage, info.texture_buffer_descriptors);
        Add(VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, stage, info.image_buffer_descriptors);
        Add(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, stage, info.texture_descriptors);
        Add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, stage, info.image_descriptors);
    }

private:
    template <typename Descriptors>
    void Add(VkDescriptorType type, VkShaderStageFlags stage, const Descriptors& descriptors) {
        const size_t num{descriptors.size()};
        for (size_t i = 0; i < num; ++i) {
            bindings.push_back({
                .binding = binding,
                .descriptorType = type,
                .descriptorCount = descriptors[i].count,
                .stageFlags = stage,
                .pImmutableSamplers = nullptr,
            });
            entries.push_back({
                .dstBinding = binding,
                .dstArrayElement = 0,
                .descriptorCount = descriptors[i].count,
                .descriptorType = type,
                .offset = offset,
                .stride = sizeof(DescriptorUpdateEntry),
            });
            ++binding;
            num_descriptors += descriptors[i].count;
            offset += sizeof(DescriptorUpdateEntry);
        }
    }

    const Device* device{};
    bool is_compute{};
    boost::container::small_vector<VkDescriptorSetLayoutBinding, 32> bindings;
    boost::container::small_vector<VkDescriptorUpdateTemplateEntry, 32> entries;
    u32 binding{};
    u32 num_descriptors{};
    size_t offset{};
};

class RescalingPushConstant {
public:
    explicit RescalingPushConstant() noexcept {}

    void PushTexture(bool is_rescaled) noexcept {
        *texture_ptr |= is_rescaled ? texture_bit : 0u;
        texture_bit <<= 1u;
        if (texture_bit == 0u) {
            texture_bit = 1u;
            ++texture_ptr;
        }
    }

    void PushImage(bool is_rescaled) noexcept {
        *image_ptr |= is_rescaled ? image_bit : 0u;
        image_bit <<= 1u;
        if (image_bit == 0u) {
            image_bit = 1u;
            ++image_ptr;
        }
    }

    const std::array<u32, NUM_TEXTURE_AND_IMAGE_SCALING_WORDS>& Data() const noexcept {
        return words;
    }

private:
    std::array<u32, NUM_TEXTURE_AND_IMAGE_SCALING_WORDS> words{};
    u32* texture_ptr{words.data()};
    u32* image_ptr{words.data() + Shader::Backend::SPIRV::NUM_TEXTURE_SCALING_WORDS};
    u32 texture_bit{1u};
    u32 image_bit{1u};
};

class RenderAreaPushConstant {
public:
    bool uses_render_area{};
    std::array<f32, 4> words{};
};

inline void PushImageDescriptors(TextureCache& texture_cache,
                                 GuestDescriptorQueue& guest_descriptor_queue,
                                 const Shader::Info& info, RescalingPushConstant& rescaling,
                                 const VideoCommon::SamplerId*& samplers,
                                 const VideoCommon::ImageViewInOut*& views) {
    const u32 num_texture_buffers = Shader::NumDescriptors(info.texture_buffer_descriptors);
    const u32 num_image_buffers = Shader::NumDescriptors(info.image_buffer_descriptors);
    views += num_texture_buffers;
    views += num_image_buffers;
    for (const auto& desc : info.texture_descriptors) {
        for (u32 index = 0; index < desc.count; ++index) {
            const VideoCommon::ImageViewId image_view_id{(views++)->id};
            const VideoCommon::SamplerId sampler_id{*(samplers++)};
            ImageView& image_view{texture_cache.GetImageView(image_view_id)};
            const VkImageView vk_image_view{image_view.Handle(desc.type)};
            const Sampler& sampler{texture_cache.GetSampler(sampler_id)};
            const bool use_fallback_sampler{sampler.HasAddedAnisotropy() &&
                                            !image_view.SupportsAnisotropy()};
            const VkSampler vk_sampler{use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy()
                                                            : sampler.Handle()};
            guest_descriptor_queue.AddSampledImage(vk_image_view, vk_sampler);
            rescaling.PushTexture(texture_cache.IsRescaling(image_view));
        }
    }
    for (const auto& desc : info.image_descriptors) {
        for (u32 index = 0; index < desc.count; ++index) {
            ImageView& image_view{texture_cache.GetImageView((views++)->id)};
            if (desc.is_written) {
                texture_cache.MarkModification(image_view.image_id);
            }
            const VkImageView vk_image_view{image_view.StorageView(desc.type, desc.format)};
            guest_descriptor_queue.AddImage(vk_image_view);
            rescaling.PushImage(texture_cache.IsRescaling(image_view));
        }
    }
}

} // namespace Vulkan


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <string_view>

#include <fmt/format.h>

#include "common/common_types.h"
#include "common/logging/log.h"
#include "video_core/renderer_vulkan/pipeline_statistics.h"
#include "video_core/vulkan_common/vulkan_device.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"

namespace Vulkan {

using namespace std::string_view_literals;

static u64 GetUint64(const VkPipelineExecutableStatisticKHR& statistic) {
    switch (statistic.format) {
    case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_INT64_KHR:
        return static_cast<u64>(statistic.value.i64);
    case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_UINT64_KHR:
        return statistic.value.u64;
    case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_FLOAT64_KHR:
        return static_cast<u64>(statistic.value.f64);
    default:
        return 0;
    }
}

PipelineStatistics::PipelineStatistics(const Device& device_) : device{device_} {}

void PipelineStatistics::Collect(VkPipeline pipeline) {
    const auto& dev{device.GetLogical()};
    const std::vector properties{dev.GetPipelineExecutablePropertiesKHR(pipeline)};
    const u32 num_executables{static_cast<u32>(properties.size())};
    for (u32 executable = 0; executable < num_executables; ++executable) {
        const auto statistics{dev.GetPipelineExecutableStatisticsKHR(pipeline, executable)};
        if (statistics.empty()) {
            continue;
        }
        Stats stage_stats;
        for (const auto& statistic : statistics) {
            const char* const name{statistic.name};
            if (name == "Binary Size"sv || name == "Code size"sv || name == "Instruction Count"sv) {
                stage_stats.code_size = GetUint64(statistic);
            } else if (name == "Register Count"sv) {
                stage_stats.register_count = GetUint64(statistic);
            } else if (name == "SGPRs"sv || name == "numUsedSgprs"sv) {
                stage_stats.sgpr_count = GetUint64(statistic);
            } else if (name == "VGPRs"sv || name == "numUsedVgprs"sv) {
                stage_stats.vgpr_count = GetUint64(statistic);
            } else if (name == "Branches"sv) {
                stage_stats.branches_count = GetUint64(statistic);
            } else if (name == "Basic Block Count"sv) {
                stage_stats.basic_block_count = GetUint64(statistic);
            }
        }
        std::scoped_lock lock{mutex};
        collected_stats.push_back(stage_stats);
    }
}

void PipelineStatistics::Report() const {
    double num{};
    Stats total;
    {
        std::scoped_lock lock{mutex};
        for (const Stats& stats : collected_stats) {
            total.code_size += stats.code_size;
            total.register_count += stats.register_count;
            total.sgpr_count += stats.sgpr_count;
            total.vgpr_count += stats.vgpr_count;
            total.branches_count += stats.branches_count;
            total.basic_block_count += stats.basic_block_count;
        }
        num = static_cast<double>(collected_stats.size());
    }
    std::string report;
    const auto add = [&](const char* fmt, u64 value) {
        if (value > 0) {
            report += fmt::format(fmt::runtime(fmt), static_cast<double>(value) / num);
        }
    };
    add("Code size:      {:9.03f}\n", total.code_size);
    add("Register count: {:9.03f}\n", total.register_count);
    add("SGPRs:          {:9.03f}\n", total.sgpr_count);
    add("VGPRs:          {:9.03f}\n", total.vgpr_count);
    add("Branches count: {:9.03f}\n", total.branches_count);
    add("Basic blocks:   {:9.03f}\n", total.basic_block_count);

    LOG_INFO(Render_Vulkan,
             "\nAverage pipeline statistics\n"
             "==========================================\n"
             "{}\n",
             report);
}

} // namespace Vulkan


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <mutex>
#include <vector>

#include "common/common_types.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"

namespace Vulkan {

class Device;

class PipelineStatistics {
public:
    explicit PipelineStatistics(const Device& device_);

    void Collect(VkPipeline pipeline);

    void Report() const;

private:
    struct Stats {
        u64 code_size{};
        u64 register_count{};
        u64 sgpr_count{};
        u64 vgpr_count{};
        u64 branches_count{};
        u64 basic_block_count{};
    };

    const Device& device;
    mutable std::mutex mutex;
    std::vector<Stats> collected_stats;
};

} // namespace Vulkan


code:
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "video_core/vulkan_common/vulkan_wrapper.h"

namespace Vulkan {

class Scheduler;

class AntiAliasPass {
public:
    virtual ~AntiAliasPass() = default;
    virtual void Draw(Scheduler& scheduler, size_t image_index, VkImage* inout_image,
                      VkImageView* inout_image_view) = 0;
};

class NoAA final : public AntiAliasPass {
public:
    void Draw(Scheduler& scheduler, size_t image_index, VkImage* inout_image,
              VkImageView* inout_image_view) override {}
};

} // namespace Vulkan


code:
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "common/common_types.h"

#include "video_core/host_shaders/present_bicubic_frag_spv.h"
#include "video_core/host_shaders/present_gaussian_frag_spv.h"
#include "video_core/host_shaders/vulkan_present_frag_spv.h"
#include "video_core/host_shaders/vulkan_present_scaleforce_fp16_frag_spv.h"
#include "video_core/host_shaders/vulkan_present_scaleforce_fp32_frag_spv.h"
#include "video_core/renderer_vulkan/present/filters.h"
#include "video_core/renderer_vulkan/present/util.h"
#include "video_core/renderer_vulkan/vk_shader_util.h"
#include "video_core/vulkan_common/vulkan_device.h"

namespace Vulkan {

namespace {

vk::ShaderModule SelectScaleForceShader(const Device& device) {
    if (device.IsFloat16Supported()) {
        return BuildShader(device, VULKAN_PRESENT_SCALEFORCE_FP16_FRAG_SPV);
    } else {
        return BuildShader(device, VULKAN_PRESENT_SCALEFORCE_FP32_FRAG_SPV);
    }
}

} // Anonymous namespace

std::unique_ptr<WindowAdaptPass> MakeNearestNeighbor(const Device& device, VkFormat frame_format) {
    return std::make_unique<WindowAdaptPass>(device, frame_format,
                                             CreateNearestNeighborSampler(device),
                                             BuildShader(device, VULKAN_PRESENT_FRAG_SPV));
}

std::unique_ptr<WindowAdaptPass> MakeBilinear(const Device& device, VkFormat frame_format) {
    return std::make_unique<WindowAdaptPass>(device, frame_format, CreateBilinearSampler(device),
                                             BuildShader(device, VULKAN_PRESENT_FRAG_SPV));
}

std::unique_ptr<WindowAdaptPass> MakeBicubic(const Device& device, VkFormat frame_format) {
    return std::make_unique<WindowAdaptPass>(device, frame_format, CreateBilinearSampler(device),
                                             BuildShader(device, PRESENT_BICUBIC_FRAG_SPV));
}

std::unique_ptr<WindowAdaptPass> MakeGaussian(const Device& device, VkFormat frame_format) {
    return std::make_unique<WindowAdaptPass>(device, frame_format, CreateBilinearSampler(device),
                                             BuildShader(device, PRESENT_GAUSSIAN_FRAG_SPV));
}

std::unique_ptr<WindowAdaptPass> MakeScaleForce(const Device& device, VkFormat frame_format) {
    return std::make_unique<WindowAdaptPass>(device, frame_format, CreateBilinearSampler(device),
                                             SelectScaleForceShader(device));
}

} // namespace Vulkan


code:
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "video_core/renderer_vulkan/present/window_adapt_pass.h"

namespace Vulkan {

class MemoryAllocator;

std::unique_ptr<WindowAdaptPass> MakeNearestNeighbor(const Device& device, VkFormat frame_format);
std::unique_ptr<WindowAdaptPass> MakeBilinear(const Device& device, VkFormat frame_format);
std::unique_ptr<WindowAdaptPass> MakeBicubic(const Device& device, VkFormat frame_format);
std::unique_ptr<WindowAdaptPass> MakeGaussian(const Device& device, VkFormat frame_format);
std::unique_ptr<WindowAdaptPass> MakeScaleForce(const Device& device, VkFormat frame_format);

} // namespace Vulkan


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "common/common_types.h"
#include "common/div_ceil.h"
#include "common/settings.h"

#include "video_core/fsr.h"
#include "video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp16_frag_spv.h"
#include "video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp32_frag_spv.h"
#include "video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp16_frag_spv.h"
#include "video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp32_frag_spv.h"
#include "video_core/host_shaders/vulkan_fidelityfx_fsr_vert_spv.h"
#include "video_core/renderer_vulkan/present/fsr.h"
#include "video_core/renderer_vulkan/present/util.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_shader_util.h"
#include "video_core/vulkan_common/vulkan_device.h"

namespace Vulkan {
using namespace FSR;

using PushConstants = std::array<u32, 4 * 4>;

FSR::FSR(const Device& device, MemoryAllocator& memory_allocator, size_t image_count,
         VkExtent2D extent)
    : m_device{device}, m_memory_allocator{memory_allocator},
      m_image_count{image_count}, m_extent{extent} {

    CreateImages();
    CreateRenderPasses();
    CreateSampler();
    CreateShaders();
    CreateDescriptorPool();
    CreateDescriptorSetLayout();
    CreateDescriptorSets();
    CreatePipelineLayouts();
    CreatePipelines();
}

void FSR::CreateImages() {
    m_dynamic_images.resize(m_image_count);
    for (auto& images : m_dynamic_images) {
        images.images[Easu] =
            CreateWrappedImage(m_memory_allocator, m_extent, VK_FORMAT_R16G16B16A16_SFLOAT);
        images.images[Rcas] =
            CreateWrappedImage(m_memory_allocator, m_extent, VK_FORMAT_R16G16B16A16_SFLOAT);
        images.image_views[Easu] =
            CreateWrappedImageView(m_device, images.images[Easu], VK_FORMAT_R16G16B16A16_SFLOAT);
        images.image_views[Rcas] =
            CreateWrappedImageView(m_device, images.images[Rcas], VK_FORMAT_R16G16B16A16_SFLOAT);
    }
}

void FSR::CreateRenderPasses() {
    m_renderpass = CreateWrappedRenderPass(m_device, VK_FORMAT_R16G16B16A16_SFLOAT);

    for (auto& images : m_dynamic_images) {
        images.framebuffers[Easu] =
            CreateWrappedFramebuffer(m_device, m_renderpass, images.image_views[Easu], m_extent);
        images.framebuffers[Rcas] =
            CreateWrappedFramebuffer(m_device, m_renderpass, images.image_views[Rcas], m_extent);
    }
}

void FSR::CreateSampler() {
    m_sampler = CreateBilinearSampler(m_device);
}

void FSR::CreateShaders() {
    m_vert_shader = BuildShader(m_device, VULKAN_FIDELITYFX_FSR_VERT_SPV);

    if (m_device.IsFloat16Supported()) {
        m_easu_shader = BuildShader(m_device, VULKAN_FIDELITYFX_FSR_EASU_FP16_FRAG_SPV);
        m_rcas_shader = BuildShader(m_device, VULKAN_FIDELITYFX_FSR_RCAS_FP16_FRAG_SPV);
    } else {
        m_easu_shader = BuildShader(m_device, VULKAN_FIDELITYFX_FSR_EASU_FP32_FRAG_SPV);
        m_rcas_shader = BuildShader(m_device, VULKAN_FIDELITYFX_FSR_RCAS_FP32_FRAG_SPV);
    }
}

void FSR::CreateDescriptorPool() {
    // EASU: 1 descriptor
    // RCAS: 1 descriptor
    // 2 descriptors, 2 descriptor sets per invocation
    m_descriptor_pool = CreateWrappedDescriptorPool(m_device, 2 * m_image_count, 2 * m_image_count);
}

void FSR::CreateDescriptorSetLayout() {
    m_descriptor_set_layout =
        CreateWrappedDescriptorSetLayout(m_device, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER});
}

void FSR::CreateDescriptorSets() {
    std::vector<VkDescriptorSetLayout> layouts(MaxFsrStage, *m_descriptor_set_layout);

    for (auto& images : m_dynamic_images) {
        images.descriptor_sets = CreateWrappedDescriptorSets(m_descriptor_pool, layouts);
    }
}

void FSR::CreatePipelineLayouts() {
    const VkPushConstantRange range{
        .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
        .offset = 0,
        .size = sizeof(PushConstants),
    };
    VkPipelineLayoutCreateInfo ci{
        .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
        .pNext = nullptr,
        .flags = 0,
        .setLayoutCount = 1,
        .pSetLayouts = m_descriptor_set_layout.address(),
        .pushConstantRangeCount = 1,
        .pPushConstantRanges = &range,
    };

    m_pipeline_layout = m_device.GetLogical().CreatePipelineLayout(ci);
}

void FSR::CreatePipelines() {
    m_easu_pipeline = CreateWrappedPipeline(m_device, m_renderpass, m_pipeline_layout,
                                            std::tie(m_vert_shader, m_easu_shader));
    m_rcas_pipeline = CreateWrappedPipeline(m_device, m_renderpass, m_pipeline_layout,
                                            std::tie(m_vert_shader, m_rcas_shader));
}

void FSR::UpdateDescriptorSets(VkImageView image_view, size_t image_index) {
    Images& images = m_dynamic_images[image_index];
    std::vector<VkDescriptorImageInfo> image_infos;
    std::vector<VkWriteDescriptorSet> updates;
    image_infos.reserve(2);

    updates.push_back(CreateWriteDescriptorSet(image_infos, *m_sampler, image_view,
                                               images.descriptor_sets[Easu], 0));
    updates.push_back(CreateWriteDescriptorSet(image_infos, *m_sampler, *images.image_views[Easu],
                                               images.descriptor_sets[Rcas], 0));

    m_device.GetLogical().UpdateDescriptorSets(updates, {});
}

void FSR::UploadImages(Scheduler& scheduler) {
    if (m_images_ready) {
        return;
    }

    scheduler.Record([&](vk::CommandBuffer cmdbuf) {
        for (auto& image : m_dynamic_images) {
            ClearColorImage(cmdbuf, *image.images[Easu]);
            ClearColorImage(cmdbuf, *image.images[Rcas]);
        }
    });
    scheduler.Finish();

    m_images_ready = true;
}

VkImageView FSR::Draw(Scheduler& scheduler, size_t image_index, VkImage source_image,
                      VkImageView source_image_view, VkExtent2D input_image_extent,
                      const Common::Rectangle<f32>& crop_rect) {
    Images& images = m_dynamic_images[image_index];

    VkImage easu_image = *images.images[Easu];
    VkImage rcas_image = *images.images[Rcas];
    VkDescriptorSet easu_descriptor_set = images.descriptor_sets[Easu];
    VkDescriptorSet rcas_descriptor_set = images.descriptor_sets[Rcas];
    VkFramebuffer easu_framebuffer = *images.framebuffers[Easu];
    VkFramebuffer rcas_framebuffer = *images.framebuffers[Rcas];
    VkPipeline easu_pipeline = *m_easu_pipeline;
    VkPipeline rcas_pipeline = *m_rcas_pipeline;
    VkPipelineLayout pipeline_layout = *m_pipeline_layout;
    VkRenderPass renderpass = *m_renderpass;
    VkExtent2D extent = m_extent;

    const f32 input_image_width = static_cast<f32>(input_image_extent.width);
    const f32 input_image_height = static_cast<f32>(input_image_extent.height);
    const f32 output_image_width = static_cast<f32>(extent.width);
    const f32 output_image_height = static_cast<f32>(extent.height);
    const f32 viewport_width = (crop_rect.right - crop_rect.left) * input_image_width;
    const f32 viewport_x = crop_rect.left * input_image_width;
    const f32 viewport_height = (crop_rect.bottom - crop_rect.top) * input_image_height;
    const f32 viewport_y = crop_rect.top * input_image_height;

    PushConstants easu_con{};
    PushConstants rcas_con{};
    FsrEasuConOffset(easu_con.data() + 0, easu_con.data() + 4, easu_con.data() + 8,
                     easu_con.data() + 12, viewport_width, viewport_height, input_image_width,
                     input_image_height, output_image_width, output_image_height, viewport_x,
                     viewport_y);

    const float sharpening =
        static_cast<float>(Settings::values.fsr_sharpening_slider.GetValue()) / 100.0f;
    FsrRcasCon(rcas_con.data(), sharpening);

    UploadImages(scheduler);
    UpdateDescriptorSets(source_image_view, image_index);

    scheduler.RequestOutsideRenderPassOperationContext();
    scheduler.Record([=](vk::CommandBuffer cmdbuf) {
        TransitionImageLayout(cmdbuf, source_image, VK_IMAGE_LAYOUT_GENERAL);
        TransitionImageLayout(cmdbuf, easu_image, VK_IMAGE_LAYOUT_GENERAL);
        BeginRenderPass(cmdbuf, renderpass, easu_framebuffer, extent);
        cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, easu_pipeline);
        cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0,
                                  easu_descriptor_set, {});
        cmdbuf.PushConstants(pipeline_layout, VK_SHADER_STAGE_FRAGMENT_BIT, easu_con);
        cmdbuf.Draw(3, 1, 0, 0);
        cmdbuf.EndRenderPass();

        TransitionImageLayout(cmdbuf, easu_image, VK_IMAGE_LAYOUT_GENERAL);
        TransitionImageLayout(cmdbuf, rcas_image, VK_IMAGE_LAYOUT_GENERAL);
        BeginRenderPass(cmdbuf, renderpass, rcas_framebuffer, extent);
        cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, rcas_pipeline);
        cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0,
                                  rcas_descriptor_set, {});
        cmdbuf.PushConstants(pipeline_layout, VK_SHADER_STAGE_FRAGMENT_BIT, rcas_con);
        cmdbuf.Draw(3, 1, 0, 0);
        cmdbuf.EndRenderPass();

        TransitionImageLayout(cmdbuf, rcas_image, VK_IMAGE_LAYOUT_GENERAL);
    });

    return *images.image_views[Rcas];
}

} // namespace Vulkan


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "common/math_util.h"
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"

namespace Vulkan {

class Device;
class Scheduler;

class FSR {
public:
    explicit FSR(const Device& device, MemoryAllocator& memory_allocator, size_t image_count,
                 VkExtent2D extent);
    VkImageView Draw(Scheduler& scheduler, size_t image_index, VkImage source_image,
                     VkImageView source_image_view, VkExtent2D input_image_extent,
                     const Common::Rectangle<f32>& crop_rect);

private:
    void CreateImages();
    void CreateRenderPasses();
    void CreateSampler();
    void CreateShaders();
    void CreateDescriptorPool();
    void CreateDescriptorSetLayout();
    void CreateDescriptorSets();
    void CreatePipelineLayouts();
    void CreatePipelines();

    void UploadImages(Scheduler& scheduler);
    void UpdateDescriptorSets(VkImageView image_view, size_t image_index);

    const Device& m_device;
    MemoryAllocator& m_memory_allocator;
    const size_t m_image_count;
    const VkExtent2D m_extent;

    enum FsrStage {
        Easu,
        Rcas,
        MaxFsrStage,
    };

    vk::DescriptorPool m_descriptor_pool;
    vk::DescriptorSetLayout m_descriptor_set_layout;
    vk::PipelineLayout m_pipeline_layout;
    vk::ShaderModule m_vert_shader;
    vk::ShaderModule m_easu_shader;
    vk::ShaderModule m_rcas_shader;
    vk::Pipeline m_easu_pipeline;
    vk::Pipeline m_rcas_pipeline;
    vk::RenderPass m_renderpass;
    vk::Sampler m_sampler;

    struct Images {
        vk::DescriptorSets descriptor_sets;
        std::array<vk::Image, MaxFsrStage> images;
        std::array<vk::ImageView, MaxFsrStage> image_views;
        std::array<vk::Framebuffer, MaxFsrStage> framebuffers;
    };
    std::vector<Images> m_dynamic_images;
    bool m_images_ready{};
};

} // namespace Vulkan


code:
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "common/common_types.h"

#include "video_core/host_shaders/fxaa_frag_spv.h"
#include "video_core/host_shaders/fxaa_vert_spv.h"
#include "video_core/renderer_vulkan/present/fxaa.h"
#include "video_core/renderer_vulkan/present/util.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_shader_util.h"
#include "video_core/vulkan_common/vulkan_device.h"

namespace Vulkan {

FXAA::FXAA(const Device& device, MemoryAllocator& allocator, size_t image_count, VkExtent2D extent)
    : m_device(device), m_allocator(allocator), m_extent(extent),
      m_image_count(static_cast<u32>(image_count)) {
    CreateImages();
    CreateRenderPasses();
    CreateSampler();
    CreateShaders();
    CreateDescriptorPool();
    CreateDescriptorSetLayouts();
    CreateDescriptorSets();
    CreatePipelineLayouts();
    CreatePipelines();
}

FXAA::~FXAA() = default;

void FXAA::CreateImages() {
    for (u32 i = 0; i < m_image_count; i++) {
        Image& image = m_dynamic_images.emplace_back();

        image.image = CreateWrappedImage(m_allocator, m_extent, VK_FORMAT_R16G16B16A16_SFLOAT);
        image.image_view =
            CreateWrappedImageView(m_device, image.image, VK_FORMAT_R16G16B16A16_SFLOAT);
    }
}

void FXAA::CreateRenderPasses() {
    m_renderpass = CreateWrappedRenderPass(m_device, VK_FORMAT_R16G16B16A16_SFLOAT);

    for (auto& image : m_dynamic_images) {
        image.framebuffer =
            CreateWrappedFramebuffer(m_device, m_renderpass, image.image_view, m_extent);
    }
}

void FXAA::CreateSampler() {
    m_sampler = CreateWrappedSampler(m_device);
}

void FXAA::CreateShaders() {
    m_vertex_shader = CreateWrappedShaderModule(m_device, FXAA_VERT_SPV);
    m_fragment_shader = CreateWrappedShaderModule(m_device, FXAA_FRAG_SPV);
}

void FXAA::CreateDescriptorPool() {
    // 2 descriptors, 1 descriptor set per image
    m_descriptor_pool = CreateWrappedDescriptorPool(m_device, 2 * m_image_count, m_image_count);
}

void FXAA::CreateDescriptorSetLayouts() {
    m_descriptor_set_layout =
        CreateWrappedDescriptorSetLayout(m_device, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
                                                    VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER});
}

void FXAA::CreateDescriptorSets() {
    VkDescriptorSetLayout layout = *m_descriptor_set_layout;

    for (auto& images : m_dynamic_images) {
        images.descriptor_sets = CreateWrappedDescriptorSets(m_descriptor_pool, {layout});
    }
}

void FXAA::CreatePipelineLayouts() {
    m_pipeline_layout = CreateWrappedPipelineLayout(m_device, m_descriptor_set_layout);
}

void FXAA::CreatePipelines() {
    m_pipeline = CreateWrappedPipeline(m_device, m_renderpass, m_pipeline_layout,
                                       std::tie(m_vertex_shader, m_fragment_shader));
}

void FXAA::UpdateDescriptorSets(VkImageView image_view, size_t image_index) {
    Image& image = m_dynamic_images[image_index];
    std::vector<VkDescriptorImageInfo> image_infos;
    std::vector<VkWriteDescriptorSet> updates;
    image_infos.reserve(2);

    updates.push_back(
        CreateWriteDescriptorSet(image_infos, *m_sampler, image_view, image.descriptor_sets[0], 0));
    updates.push_back(
        CreateWriteDescriptorSet(image_infos, *m_sampler, image_view, image.descriptor_sets[0], 1));

    m_device.GetLogical().UpdateDescriptorSets(updates, {});
}

void FXAA::UploadImages(Scheduler& scheduler) {
    if (m_images_ready) {
        return;
    }

    scheduler.Record([&](vk::CommandBuffer cmdbuf) {
        for (auto& image : m_dynamic_images) {
            ClearColorImage(cmdbuf, *image.image);
        }
    });
    scheduler.Finish();

    m_images_ready = true;
}

void FXAA::Draw(Scheduler& scheduler, size_t image_index, VkImage* inout_image,
                VkImageView* inout_image_view) {
    const Image& image{m_dynamic_images[image_index]};
    const VkImage input_image{*inout_image};
    const VkImage output_image{*image.image};
    const VkDescriptorSet descriptor_set{image.descriptor_sets[0]};
    const VkFramebuffer framebuffer{*image.framebuffer};
    const VkRenderPass renderpass{*m_renderpass};
    const VkPipeline pipeline{*m_pipeline};
    const VkPipelineLayout layout{*m_pipeline_layout};
    const VkExtent2D extent{m_extent};

    UploadImages(scheduler);
    UpdateDescriptorSets(*inout_image_view, image_index);

    scheduler.RequestOutsideRenderPassOperationContext();
    scheduler.Record([=](vk::CommandBuffer cmdbuf) {
        TransitionImageLayout(cmdbuf, input_image, VK_IMAGE_LAYOUT_GENERAL);
        TransitionImageLayout(cmdbuf, output_image, VK_IMAGE_LAYOUT_GENERAL);
        BeginRenderPass(cmdbuf, renderpass, framebuffer, extent);
        cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
        cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set, {});
        cmdbuf.Draw(3, 1, 0, 0);
        cmdbuf.EndRenderPass();
        TransitionImageLayout(cmdbuf, output_image, VK_IMAGE_LAYOUT_GENERAL);
    });

    *inout_image = *image.image;
    *inout_image_view = *image.image_view;
}

} // namespace Vulkan


code:
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "video_core/renderer_vulkan/present/anti_alias_pass.h"
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"

namespace Vulkan {

class Device;
class Scheduler;
class StagingBufferPool;

class FXAA final : public AntiAliasPass {
public:
    explicit FXAA(const Device& device, MemoryAllocator& allocator, size_t image_count,
                  VkExtent2D extent);
    ~FXAA() override;

    void Draw(Scheduler& scheduler, size_t image_index, VkImage* inout_image,
              VkImageView* inout_image_view) override;

private:
    void CreateImages();
    void CreateRenderPasses();
    void CreateSampler();
    void CreateShaders();
    void CreateDescriptorPool();
    void CreateDescriptorSetLayouts();
    void CreateDescriptorSets();
    void CreatePipelineLayouts();
    void CreatePipelines();
    void UpdateDescriptorSets(VkImageView image_view, size_t image_index);
    void UploadImages(Scheduler& scheduler);

    const Device& m_device;
    MemoryAllocator& m_allocator;
    const VkExtent2D m_extent;
    const u32 m_image_count;

    vk::ShaderModule m_vertex_shader{};
    vk::ShaderModule m_fragment_shader{};
    vk::DescriptorPool m_descriptor_pool{};
    vk::DescriptorSetLayout m_descriptor_set_layout{};
    vk::PipelineLayout m_pipeline_layout{};
    vk::Pipeline m_pipeline{};
    vk::RenderPass m_renderpass{};

    struct Image {
        vk::DescriptorSets descriptor_sets{};
        vk::Framebuffer framebuffer{};
        vk::Image image{};
        vk::ImageView image_view{};
    };
    std::vector<Image> m_dynamic_images{};
    bool m_images_ready{};

    vk::Sampler m_sampler{};
};

} // namespace Vulkan


code:
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "video_core/present.h"
#include "video_core/renderer_vulkan/vk_rasterizer.h"

#include "common/settings.h"
#include "video_core/framebuffer_config.h"
#include "video_core/renderer_vulkan/present/fsr.h"
#include "video_core/renderer_vulkan/present/fxaa.h"
#include "video_core/renderer_vulkan/present/layer.h"
#include "video_core/renderer_vulkan/present/present_push_constants.h"
#include "video_core/renderer_vulkan/present/smaa.h"
#include "video_core/renderer_vulkan/present/util.h"
#include "video_core/renderer_vulkan/vk_blit_screen.h"
#include "video_core/textures/decoders.h"

namespace Vulkan {

namespace {

u32 GetBytesPerPixel(const Tegra::FramebufferConfig& framebuffer) {
    using namespace VideoCore::Surface;
    return BytesPerBlock(PixelFormatFromGPUPixelFormat(framebuffer.pixel_format));
}

std::size_t GetSizeInBytes(const Tegra::FramebufferConfig& framebuffer) {
    return static_cast<std::size_t>(framebuffer.stride) *
           static_cast<std::size_t>(framebuffer.height) * GetBytesPerPixel(framebuffer);
}

VkFormat GetFormat(const Tegra::FramebufferConfig& framebuffer) {
    switch (framebuffer.pixel_format) {
    case Service::android::PixelFormat::Rgba8888:
    case Service::android::PixelFormat::Rgbx8888:
        return VK_FORMAT_A8B8G8R8_UNORM_PACK32;
    case Service::android::PixelFormat::Rgb565:
        return VK_FORMAT_R5G6B5_UNORM_PACK16;
    case Service::android::PixelFormat::Bgra8888:
        return VK_FORMAT_B8G8R8A8_UNORM;
    default:
        UNIMPLEMENTED_MSG("Unknown framebuffer pixel format: {}",
                          static_cast<u32>(framebuffer.pixel_format));
        return VK_FORMAT_A8B8G8R8_UNORM_PACK32;
    }
}

} // Anonymous namespace

Layer::Layer(const Device& device_, MemoryAllocator& memory_allocator_, Scheduler& scheduler_,
             Tegra::MaxwellDeviceMemoryManager& device_memory_, size_t image_count_,
             VkExtent2D output_size, VkDescriptorSetLayout layout, const PresentFilters& filters_)
    : device(device_), memory_allocator(memory_allocator_), scheduler(scheduler_),
      device_memory(device_memory_), filters(filters_), image_count(image_count_) {
    CreateDescriptorPool();
    CreateDescriptorSets(layout);
    if (filters.get_scaling_filter() == Settings::ScalingFilter::Fsr) {
        CreateFSR(output_size);
    }
}

Layer::~Layer() {
    ReleaseRawImages();
}

void Layer::ConfigureDraw(PresentPushConstants* out_push_constants,
                          VkDescriptorSet* out_descriptor_set, RasterizerVulkan& rasterizer,
                          VkSampler sampler, size_t image_index,
                          const Tegra::FramebufferConfig& framebuffer,
                          const Layout::FramebufferLayout& layout) {
    const auto texture_info = rasterizer.AccelerateDisplay(
        framebuffer, framebuffer.address + framebuffer.offset, framebuffer.stride);
    const u32 texture_width = texture_info ? texture_info->width : framebuffer.width;
    const u32 texture_height = texture_info ? texture_info->height : framebuffer.height;
    const u32 scaled_width = texture_info ? texture_info->scaled_width : texture_width;
    const u32 scaled_height = texture_info ? texture_info->scaled_height : texture_height;
    const bool use_accelerated = texture_info.has_value();

    RefreshResources(framebuffer);
    SetAntiAliasPass();

    // Finish any pending renderpass
    scheduler.RequestOutsideRenderPassOperationContext();
    scheduler.Wait(resource_ticks[image_index]);
    SCOPE_EXIT {
        resource_ticks[image_index] = scheduler.CurrentTick();
    };

    if (!use_accelerated) {
        UpdateRawImage(framebuffer, image_index);
    }

    VkImage source_image = texture_info ? texture_info->image : *raw_images[image_index];
    VkImageView source_image_view =
        texture_info ? texture_info->image_view : *raw_image_views[image_index];

    anti_alias->Draw(scheduler, image_index, &source_image, &source_image_view);

    auto crop_rect = Tegra::NormalizeCrop(framebuffer, texture_width, texture_height);
    const VkExtent2D render_extent{
        .width = scaled_width,
        .height = scaled_height,
    };

    if (fsr) {
        source_image_view = fsr->Draw(scheduler, image_index, source_image, source_image_view,
                                      render_extent, crop_rect);
        crop_rect = {0, 0, 1, 1};
    }

    SetMatrixData(*out_push_constants, layout);
    SetVertexData(*out_push_constants, layout, crop_rect);

    UpdateDescriptorSet(source_image_view, sampler, image_index);
    *out_descriptor_set = descriptor_sets[image_index];
}

void Layer::CreateDescriptorPool() {
    descriptor_pool = CreateWrappedDescriptorPool(device, image_count, image_count);
}

void Layer::CreateDescriptorSets(VkDescriptorSetLayout layout) {
    const std::vector layouts(image_count, layout);
    descriptor_sets = CreateWrappedDescriptorSets(descriptor_pool, layouts);
}

void Layer::CreateStagingBuffer(const Tegra::FramebufferConfig& framebuffer) {
    const VkBufferCreateInfo ci{
        .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
        .pNext = nullptr,
        .flags = 0,
        .size = CalculateBufferSize(framebuffer),
        .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
                 VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
        .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
        .queueFamilyIndexCount = 0,
        .pQueueFamilyIndices = nullptr,
    };

    buffer = memory_allocator.CreateBuffer(ci, MemoryUsage::Upload);
}

void Layer::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
    const auto format = GetFormat(framebuffer);
    resource_ticks.resize(image_count);
    raw_images.resize(image_count);
    raw_image_views.resize(image_count);

    for (size_t i = 0; i < image_count; ++i) {
        raw_images[i] =
            CreateWrappedImage(memory_allocator, {framebuffer.width, framebuffer.height}, format);
        raw_image_views[i] = CreateWrappedImageView(device, raw_images[i], format);
    }
}

void Layer::CreateFSR(VkExtent2D output_size) {
    fsr = std::make_unique<FSR>(device, memory_allocator, image_count, output_size);
}

void Layer::RefreshResources(const Tegra::FramebufferConfig& framebuffer) {
    if (framebuffer.width == raw_width && framebuffer.height == raw_height &&
        framebuffer.pixel_format == pixel_format && !raw_images.empty()) {
        return;
    }

    raw_width = framebuffer.width;
    raw_height = framebuffer.height;
    pixel_format = framebuffer.pixel_format;
    anti_alias.reset();

    ReleaseRawImages();
    CreateStagingBuffer(framebuffer);
    CreateRawImages(framebuffer);
}

void Layer::SetAntiAliasPass() {
    if (anti_alias && anti_alias_setting == filters.get_anti_aliasing()) {
        return;
    }

    anti_alias_setting = filters.get_anti_aliasing();

    const VkExtent2D render_area{
        .width = Settings::values.resolution_info.ScaleUp(raw_width),
        .height = Settings::values.resolution_info.ScaleUp(raw_height),
    };

    switch (anti_alias_setting) {
    case Settings::AntiAliasing::Fxaa:
        anti_alias = std::make_unique<FXAA>(device, memory_allocator, image_count, render_area);
        break;
    case Settings::AntiAliasing::Smaa:
        anti_alias = std::make_unique<SMAA>(device, memory_allocator, image_count, render_area);
        break;
    default:
        anti_alias = std::make_unique<NoAA>();
        break;
    }
}

void Layer::ReleaseRawImages() {
    for (const u64 tick : resource_ticks) {
        scheduler.Wait(tick);
    }
    raw_images.clear();
    buffer.reset();
}

u64 Layer::CalculateBufferSize(const Tegra::FramebufferConfig& framebuffer) const {
    return GetSizeInBytes(framebuffer) * image_count;
}

u64 Layer::GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer,
                             size_t image_index) const {
    return GetSizeInBytes(framebuffer) * image_index;
}

void Layer::SetMatrixData(PresentPushConstants& data,
                          const Layout::FramebufferLayout& layout) const {
    data.modelview_matrix =
        MakeOrthographicMatrix(static_cast<f32>(layout.width), static_cast<f32>(layout.height));
}

void Layer::SetVertexData(PresentPushConstants& data, const Layout::FramebufferLayout& layout,
                          const Common::Rectangle<f32>& crop) const {
    // Map the coordinates to the screen.
    const auto& screen = layout.screen;
    const auto x = static_cast<f32>(screen.left);
    const auto y = static_cast<f32>(screen.top);
    const auto w = static_cast<f32>(screen.GetWidth());
    const auto h = static_cast<f32>(screen.GetHeight());

    data.vertices[0] = ScreenRectVertex(x, y, crop.left, crop.top);
    data.vertices[1] = ScreenRectVertex(x + w, y, crop.right, crop.top);
    data.vertices[2] = ScreenRectVertex(x, y + h, crop.left, crop.bottom);
    data.vertices[3] = ScreenRectVertex(x + w, y + h, crop.right, crop.bottom);
}

void Layer::UpdateDescriptorSet(VkImageView image_view, VkSampler sampler, size_t image_index) {
    const VkDescriptorImageInfo image_info{
        .sampler = sampler,
        .imageView = image_view,
        .imageLayout = VK_IMAGE_LAYOUT_GENERAL,
    };

    const VkWriteDescriptorSet sampler_write{
        .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
        .pNext = nullptr,
        .dstSet = descriptor_sets[image_index],
        .dstBinding = 0,
        .dstArrayElement = 0,
        .descriptorCount = 1,
        .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
        .pImageInfo = &image_info,
        .pBufferInfo = nullptr,
        .pTexelBufferView = nullptr,
    };

    device.GetLogical().UpdateDescriptorSets(std::array{sampler_write}, {});
}

void Layer::UpdateRawImage(const Tegra::FramebufferConfig& framebuffer, size_t image_index) {
    const std::span<u8> mapped_span = buffer.Mapped();

    const u64 image_offset = GetRawImageOffset(framebuffer, image_index);

    const DAddr framebuffer_addr = framebuffer.address + framebuffer.offset;
    const u8* const host_ptr = device_memory.GetPointer<u8>(framebuffer_addr);

    // TODO(Rodrigo): Read this from HLE
    constexpr u32 block_height_log2 = 4;
    const u32 bytes_per_pixel = GetBytesPerPixel(framebuffer);
    const u64 linear_size{GetSizeInBytes(framebuffer)};
    const u64 tiled_size{Tegra::Texture::CalculateSize(
        true, bytes_per_pixel, framebuffer.stride, framebuffer.height, 1, block_height_log2, 0)};
    if (host_ptr) {
        Tegra::Texture::UnswizzleTexture(
            mapped_span.subspan(image_offset, linear_size), std::span(host_ptr, tiled_size),
            bytes_per_pixel, framebuffer.width, framebuffer.height, 1, block_height_log2, 0);
    }

    const VkBufferImageCopy copy{
        .bufferOffset = image_offset,
        .bufferRowLength = 0,
        .bufferImageHeight = 0,
        .imageSubresource =
            {
                .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
                .mipLevel = 0,
                .baseArrayLayer = 0,
                .layerCount = 1,
            },
        .imageOffset = {.x = 0, .y = 0, .z = 0},
        .imageExtent =
            {
                .width = framebuffer.width,
                .height = framebuffer.height,
                .depth = 1,
            },
    };
    scheduler.Record([this, copy, index = image_index](vk::CommandBuffer cmdbuf) {
        const VkImage image = *raw_images[index];
        const VkImageMemoryBarrier base_barrier{
            .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
            .pNext = nullptr,
            .srcAccessMask = 0,
            .dstAccessMask = 0,
            .oldLayout = VK_IMAGE_LAYOUT_GENERAL,
            .newLayout = VK_IMAGE_LAYOUT_GENERAL,
            .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
            .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
            .image = image,
            .subresourceRange{
                .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
                .baseMipLevel = 0,
                .levelCount = 1,
                .baseArrayLayer = 0,
                .layerCount = 1,
            },
        };
        VkImageMemoryBarrier read_barrier = base_barrier;
        read_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
        read_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
        read_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;

        VkImageMemoryBarrier write_barrier = base_barrier;
        write_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
        write_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
        write_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;

        cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
                               read_barrier);
        cmdbuf.CopyBufferToImage(*buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, copy);
        cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
                               VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
                                   VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
                               0, write_barrier);
    });
}

} // namespace Vulkan


code:
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "common/math_util.h"
#include "video_core/host1x/gpu_device_memory_manager.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"

namespace Layout {
struct FramebufferLayout;
}

struct PresentFilters;

namespace Tegra {
struct FramebufferConfig;
}

namespace Service::android {
enum class PixelFormat : u32;
}

namespace Settings {
enum class AntiAliasing : u32;
}

namespace Vulkan {

class AntiAliasPass;
class Device;
class FSR;
class MemoryAllocator;
struct PresentPushConstants;
class RasterizerVulkan;
class Scheduler;

class Layer final {
public:
    explicit Layer(const Device& device, MemoryAllocator& memory_allocator, Scheduler& scheduler,
                   Tegra::MaxwellDeviceMemoryManager& device_memory, size_t image_count,
                   VkExtent2D output_size, VkDescriptorSetLayout layout,
                   const PresentFilters& filters);
    ~Layer();

    void ConfigureDraw(PresentPushConstants* out_push_constants,
                       VkDescriptorSet* out_descriptor_set, RasterizerVulkan& rasterizer,
                       VkSampler sampler, size_t image_index,
                       const Tegra::FramebufferConfig& framebuffer,
                       const Layout::FramebufferLayout& layout);

private:
    void CreateDescriptorPool();
    void CreateDescriptorSets(VkDescriptorSetLayout layout);
    void CreateStagingBuffer(const Tegra::FramebufferConfig& framebuffer);
    void CreateRawImages(const Tegra::FramebufferConfig& framebuffer);
    void CreateFSR(VkExtent2D output_size);

    void RefreshResources(const Tegra::FramebufferConfig& framebuffer);
    void SetAntiAliasPass();
    void ReleaseRawImages();

    u64 CalculateBufferSize(const Tegra::FramebufferConfig& framebuffer) const;
    u64 GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer, size_t image_index) const;

    void SetMatrixData(PresentPushConstants& data, const Layout::FramebufferLayout& layout) const;
    void SetVertexData(PresentPushConstants& data, const Layout::FramebufferLayout& layout,
                       const Common::Rectangle<f32>& crop) const;
    void UpdateDescriptorSet(VkImageView image_view, VkSampler sampler, size_t image_index);
    void UpdateRawImage(const Tegra::FramebufferConfig& framebuffer, size_t image_index);

private:
    const Device& device;
    MemoryAllocator& memory_allocator;
    Scheduler& scheduler;
    Tegra::MaxwellDeviceMemoryManager& device_memory;
    const PresentFilters& filters;
    const size_t image_count{};
    vk::DescriptorPool descriptor_pool{};
    vk::DescriptorSets descriptor_sets{};

    vk::Buffer buffer{};
    std::vector<vk::Image> raw_images{};
    std::vector<vk::ImageView> raw_image_views{};
    u32 raw_width{};
    u32 raw_height{};
    Service::android::PixelFormat pixel_format{};

    Settings::AntiAliasing anti_alias_setting{};
    std::unique_ptr<AntiAliasPass> anti_alias{};

    std::unique_ptr<FSR> fsr{};
    std::vector<u64> resource_ticks{};
};

} // namespace Vulkan


code:
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "common/common_types.h"

namespace Vulkan {

struct ScreenRectVertex {
    ScreenRectVertex() = default;
    explicit ScreenRectVertex(f32 x, f32 y, f32 u, f32 v) : position{{x, y}}, tex_coord{{u, v}} {}

    std::array<f32, 2> position;
    std::array<f32, 2> tex_coord;
};

static inline std::array<f32, 4 * 4> MakeOrthographicMatrix(f32 width, f32 height) {
    // clang-format off
    return { 2.f / width, 0.f,          0.f, 0.f,
             0.f,         2.f / height, 0.f, 0.f,
             0.f,         0.f,          1.f, 0.f,
            -1.f,        -1.f,          0.f, 1.f};
    // clang-format on
}

struct PresentPushConstants {
    std::array<f32, 4 * 4> modelview_matrix;
    std::array<ScreenRectVertex, 4> vertices;
};

static_assert(sizeof(PresentPushConstants) <= 128, "Push constants are too large");

} // namespace Vulkan