// SPDX-License-Identifier: MPL-2.0 // Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/) #include #include #include #include #include "textures.h" namespace skyline::gpu::interconnect { void TexturePoolState::EngineRegisters::DirtyBind(DirtyManager &manager, dirty::Handle handle) const { manager.Bind(handle, texHeaderPool); } TexturePoolState::TexturePoolState(dirty::Handle dirtyHandle, DirtyManager &manager, const EngineRegisters &engine) : engine{manager, dirtyHandle, engine} {} void TexturePoolState::Flush(InterconnectContext &ctx) { auto mapping{ctx.channelCtx.asCtx->gmmu.LookupBlock(engine->texHeaderPool.offset)}; textureHeaders = mapping.first.subspan(mapping.second).cast().first(engine->texHeaderPool.maximumIndex + 1); } void TexturePoolState::PurgeCaches() { textureHeaders = span{}; } Textures::Textures(DirtyManager &manager, const TexturePoolState::EngineRegisters &engine) : texturePool{manager, engine} {} void Textures::MarkAllDirty() { texturePool.MarkDirty(true); } static texture::Format ConvertTicFormat(TextureImageControl::FormatWord format, bool srgb) { using TIC = TextureImageControl; #define TIC_FORMAT(fmt, compR, compG, compB, compA, srgb) \ TIC::FormatWord{ .format = TIC::ImageFormat::fmt, \ .componentR = TIC::ImageComponent::compR, \ .componentG = TIC::ImageComponent::compG, \ .componentB = TIC::ImageComponent::compB, \ .componentA = TIC::ImageComponent::compA, \ ._pad_ = srgb }.Raw() // Reuse _pad_ to store if the texture is sRGB // For formats where all components are of the same type #define TIC_FORMAT_ST(format, component) \ TIC_FORMAT(format, component, component, component, component, false) #define TIC_FORMAT_ST_SRGB(format, component) \ TIC_FORMAT(format, component, component, component, component, true) #define TIC_FORMAT_CASE(ticFormat, skFormat, componentR, componentG, componentB, componentA) \ case TIC_FORMAT(ticFormat, componentR, componentG, componentB, componentA, false): \ return format::skFormat #define TIC_FORMAT_CASE_ST(ticFormat, skFormat, component) \ case TIC_FORMAT_ST(ticFormat, component): \ return format::skFormat ## component #define TIC_FORMAT_CASE_ST_SRGB(ticFormat, skFormat, component) \ case TIC_FORMAT_ST_SRGB(ticFormat, component): \ return format::skFormat ## Srgb #define TIC_FORMAT_CASE_NORM(ticFormat, skFormat) \ TIC_FORMAT_CASE_ST(ticFormat, skFormat, Unorm); \ TIC_FORMAT_CASE_ST(ticFormat, skFormat, Snorm) #define TIC_FORMAT_CASE_INT(ticFormat, skFormat) \ TIC_FORMAT_CASE_ST(ticFormat, skFormat, Uint); \ TIC_FORMAT_CASE_ST(ticFormat, skFormat, Sint) #define TIC_FORMAT_CASE_NORM_INT(ticFormat, skFormat) \ TIC_FORMAT_CASE_NORM(ticFormat, skFormat); \ TIC_FORMAT_CASE_INT(ticFormat, skFormat) #define TIC_FORMAT_CASE_NORM_INT_FLOAT(ticFormat, skFormat) \ TIC_FORMAT_CASE_NORM_INT(ticFormat, skFormat); \ TIC_FORMAT_CASE_ST(ticFormat, skFormat, Float) #define TIC_FORMAT_CASE_INT_FLOAT(ticFormat, skFormat) \ TIC_FORMAT_CASE_INT(ticFormat, skFormat); \ TIC_FORMAT_CASE_ST(ticFormat, skFormat, Float) // Ignore the swizzle components of the format word format._pad_ = srgb; // Reuse the _pad_ field to store the srgb flag switch ((format.Raw() & TextureImageControl::FormatWord::FormatColorComponentPadMask)) { TIC_FORMAT_CASE_NORM_INT(R8, R8); TIC_FORMAT_CASE_NORM_INT_FLOAT(R16, R16); TIC_FORMAT_CASE_ST(D16, D16, Unorm); TIC_FORMAT_CASE_NORM_INT(R8G8, R8G8); TIC_FORMAT_CASE_ST(B5G6R5, B5G6R5, Unorm); TIC_FORMAT_CASE_ST(R4G4B4A4, R4G4B4A4, Unorm); TIC_FORMAT_CASE_ST(A1B5G5R5, A1B5G5R5, Unorm); TIC_FORMAT_CASE_INT_FLOAT(R32, R32); TIC_FORMAT_CASE_ST(D32, D32, Float); TIC_FORMAT_CASE_NORM_INT_FLOAT(R16G16, R16G16); TIC_FORMAT_CASE(R8G24, D24UnormS8Uint, Uint, Unorm, Unorm, Unorm); TIC_FORMAT_CASE(S8D24, D24UnormS8Uint, Uint, Unorm, Uint, Uint); TIC_FORMAT_CASE(S8D24, D24UnormS8Uint, Uint, Unorm, Unorm, Unorm); TIC_FORMAT_CASE(D24S8, S8UintD24Unorm, Unorm, Uint, Uint, Uint); TIC_FORMAT_CASE_ST(B10G11R11, B10G11R11, Float); TIC_FORMAT_CASE_NORM_INT(A8B8G8R8, R8G8B8A8); TIC_FORMAT_CASE_ST_SRGB(A8B8G8R8, R8G8B8A8, Unorm); TIC_FORMAT_CASE_NORM_INT(A2B10G10R10, A2B10G10R10); TIC_FORMAT_CASE_ST(E5B9G9R9, E5B9G9R9, Float); TIC_FORMAT_CASE_ST(BC1, BC1, Unorm); TIC_FORMAT_CASE_ST_SRGB(BC1, BC1, Unorm); TIC_FORMAT_CASE_NORM(BC4, BC4); TIC_FORMAT_CASE_INT_FLOAT(R32G32, R32G32); TIC_FORMAT_CASE(D32S8, D32FloatS8Uint, Float, Uint, Uint, Unorm); TIC_FORMAT_CASE(D32S8, D32FloatS8Uint, Float, Uint, Unorm, Unorm); TIC_FORMAT_CASE(R32B24G8, D32FloatS8Uint, Float, Uint, Unorm, Unorm); TIC_FORMAT_CASE_NORM_INT_FLOAT(R16G16B16A16, R16G16B16A16); TIC_FORMAT_CASE_ST(Astc4x4, Astc4x4, Unorm); TIC_FORMAT_CASE_ST_SRGB(Astc4x4, Astc4x4, Unorm); TIC_FORMAT_CASE_ST(Astc5x5, Astc5x5, Unorm); TIC_FORMAT_CASE_ST_SRGB(Astc5x5, Astc5x5, Unorm); TIC_FORMAT_CASE_ST(Astc6x6, Astc6x6, Unorm); TIC_FORMAT_CASE_ST_SRGB(Astc6x6, Astc6x6, Unorm); TIC_FORMAT_CASE_ST(Astc8x8, Astc8x8, Unorm); TIC_FORMAT_CASE_ST_SRGB(Astc8x8, Astc8x8, Unorm); TIC_FORMAT_CASE_ST(Astc10x8, Astc10x8, Unorm); TIC_FORMAT_CASE_ST_SRGB(Astc10x8, Astc10x8, Unorm); TIC_FORMAT_CASE_ST(Astc10x10, Astc10x10, Unorm); TIC_FORMAT_CASE_ST_SRGB(Astc10x10, Astc10x10, Unorm); TIC_FORMAT_CASE_ST(BC2, BC2, Unorm); TIC_FORMAT_CASE_ST_SRGB(BC2, BC2, Unorm); TIC_FORMAT_CASE_ST(BC3, BC3, Unorm); TIC_FORMAT_CASE_ST_SRGB(BC3, BC3, Unorm); TIC_FORMAT_CASE_NORM(BC5, BC5); TIC_FORMAT_CASE(Bc6HUfloat, Bc6HUfloat, Float, Float, Float, Float); TIC_FORMAT_CASE(Bc6HSfloat, Bc6HSfloat, Float, Float, Float, Float); TIC_FORMAT_CASE_ST(BC7, BC7, Unorm); TIC_FORMAT_CASE_ST_SRGB(BC7, BC7, Unorm); TIC_FORMAT_CASE_INT_FLOAT(R32G32B32A32, R32G32B32A32); default: if (format.Raw()) Logger::Error("Cannot translate TIC format: 0x{:X}", static_cast(format.Raw())); return {}; } #undef TIC_FORMAT #undef TIC_FORMAT_ST #undef TIC_FORMAT_CASE #undef TIC_FORMAT_CASE_ST #undef TIC_FORMAT_CASE_NORM #undef TIC_FORMAT_CASE_INT #undef TIC_FORMAT_CASE_NORM_INT #undef TIC_FORMAT_CASE_NORM_INT_FLOAT } static vk::ComponentMapping ConvertTicSwizzleMapping(TextureImageControl::FormatWord format, vk::ComponentMapping swizzleMapping) { auto convertComponentSwizzle{[swizzleMapping](TextureImageControl::ImageSwizzle swizzle) { switch (swizzle) { case TextureImageControl::ImageSwizzle::R: return swizzleMapping.r; case TextureImageControl::ImageSwizzle::G: return swizzleMapping.g; case TextureImageControl::ImageSwizzle::B: return swizzleMapping.b; case TextureImageControl::ImageSwizzle::A: return swizzleMapping.a; case TextureImageControl::ImageSwizzle::Zero: return vk::ComponentSwizzle::eZero; case TextureImageControl::ImageSwizzle::OneFloat: case TextureImageControl::ImageSwizzle::OneInt: return vk::ComponentSwizzle::eOne; default: throw exception("Invalid swizzle: {:X}", static_cast(swizzle)); } }}; return vk::ComponentMapping{ .r = convertComponentSwizzle(format.swizzleX), .g = convertComponentSwizzle(format.swizzleY), .b = convertComponentSwizzle(format.swizzleZ), .a = convertComponentSwizzle(format.swizzleW) }; } static std::shared_ptr CreateNullTexture(InterconnectContext &ctx) { constexpr texture::Format NullImageFormat{format::R8G8B8A8Unorm}; constexpr texture::Dimensions NullImageDimensions{1, 1, 1}; constexpr vk::ImageLayout NullImageInitialLayout{vk::ImageLayout::eUndefined}; constexpr vk::ImageTiling NullImageTiling{vk::ImageTiling::eOptimal}; constexpr vk::ImageCreateFlags NullImageFlags{}; constexpr vk::ImageUsageFlags NullImageUsage{vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled}; auto vkImage{ctx.gpu.memory.AllocateImage( { .flags = NullImageFlags, .imageType = vk::ImageType::e2D, .format = NullImageFormat->vkFormat, .extent = NullImageDimensions, .mipLevels = 1, .arrayLayers = 1, .samples = vk::SampleCountFlagBits::e1, .tiling = NullImageTiling, .usage = NullImageUsage, .sharingMode = vk::SharingMode::eExclusive, .queueFamilyIndexCount = 1, .pQueueFamilyIndices = &ctx.gpu.vkQueueFamilyIndex, .initialLayout = NullImageInitialLayout } )}; auto nullTexture{std::make_shared(ctx.gpu, std::move(vkImage), NullImageDimensions, NullImageFormat, NullImageInitialLayout, NullImageTiling, NullImageFlags, NullImageUsage)}; nullTexture->TransitionLayout(vk::ImageLayout::eGeneral); return nullTexture->GetView(vk::ImageViewType::e2D, vk::ImageSubresourceRange{ .aspectMask = vk::ImageAspectFlagBits::eColor, .levelCount = 1, .layerCount = 1, });; } TextureView *Textures::GetTexture(InterconnectContext &ctx, u32 index, Shader::TextureType shaderType) { auto textureHeaders{texturePool.UpdateGet(ctx).textureHeaders}; if (textureHeaderCache.size() != textureHeaders.size()) { textureHeaderCache.resize(textureHeaders.size()); std::fill(textureHeaderCache.begin(), textureHeaderCache.end(), CacheEntry{}); } else if (auto &cached{textureHeaderCache[index]}; cached.view) { if (cached.executionTag == ctx.executor.executionTag) return cached.view; if (cached.tic == textureHeaders[index] && !cached.view->texture->replaced) { cached.executionTag = ctx.executor.executionTag; return cached.view; } } TextureImageControl &textureHeader{textureHeaders[index]}; auto &texture{textureHeaderStore[textureHeader]}; if (!texture || texture->texture->replaced) { // If the entry didn't exist prior then we need to convert the TIC to a GuestTexture GuestTexture guest{}; if (auto format{ConvertTicFormat(textureHeader.formatWord, textureHeader.isSrgb)}) { guest.format = format; } else { if (!nullTextureView) nullTextureView = CreateNullTexture(ctx); return nullTextureView.get(); } guest.aspect = guest.format->Aspect(textureHeader.formatWord.swizzleX == TextureImageControl::ImageSwizzle::R); guest.swizzle = ConvertTicSwizzleMapping(textureHeader.formatWord, guest.format->swizzleMapping); constexpr size_t CubeFaceCount{6}; guest.baseArrayLayer = static_cast(textureHeader.BaseLayer()); guest.dimensions = texture::Dimensions(textureHeader.widthMinusOne + 1, textureHeader.heightMinusOne + 1, 1); u16 depth{static_cast(textureHeader.depthMinusOne + 1)}; guest.mipLevelCount = textureHeader.mipMaxLevels + 1; guest.viewMipBase = textureHeader.viewConfig.mipMinLevel; guest.viewMipCount = textureHeader.viewConfig.mipMaxLevel - textureHeader.viewConfig.mipMinLevel + 1; switch (textureHeader.textureType) { case TextureImageControl::TextureType::e1D: guest.viewType = shaderType == Shader::TextureType::ColorArray1D ? vk::ImageViewType::e1DArray : vk::ImageViewType::e1D; guest.layerCount = 1; break; case TextureImageControl::TextureType::e1DArray: guest.viewType = vk::ImageViewType::e1DArray; guest.layerCount = depth; break; case TextureImageControl::TextureType::e1DBuffer: throw exception("1D Buffers are not supported"); case TextureImageControl::TextureType::e2DNoMipmap: guest.mipLevelCount = 1; guest.viewMipBase = 0; guest.viewMipCount = 1; case TextureImageControl::TextureType::e2D: guest.viewType = shaderType == Shader::TextureType::ColorArray2D ? vk::ImageViewType::e2DArray : vk::ImageViewType::e2D; guest.layerCount = 1; break; case TextureImageControl::TextureType::e2DArray: guest.viewType = vk::ImageViewType::e2DArray; guest.layerCount = depth; break; case TextureImageControl::TextureType::e3D: guest.viewType = vk::ImageViewType::e3D; guest.layerCount = 1; guest.dimensions.depth = depth; break; case TextureImageControl::TextureType::eCube: guest.viewType = shaderType == Shader::TextureType::ColorArrayCube ? vk::ImageViewType::eCubeArray : vk::ImageViewType::eCube; guest.layerCount = CubeFaceCount; break; case TextureImageControl::TextureType::eCubeArray: guest.viewType = vk::ImageViewType::eCubeArray; guest.layerCount = depth * CubeFaceCount; break; } size_t size; //!< The size of the texture in bytes if (textureHeader.headerType == TextureImageControl::HeaderType::Pitch) { guest.tileConfig = { .mode = texture::TileMode::Pitch, .pitch = static_cast(textureHeader.tileConfig.pitchHigh) << TextureImageControl::TileConfig::PitchAlignmentBits, }; } else if (textureHeader.headerType == TextureImageControl::HeaderType::BlockLinear) { guest.tileConfig = { .mode = texture::TileMode::Block, .blockHeight = static_cast(1U << textureHeader.tileConfig.tileHeightGobsLog2), .blockDepth = static_cast(1U << textureHeader.tileConfig.tileDepthGobsLog2), }; } else { throw exception("Unsupported TIC Header Type: {}", static_cast(textureHeader.headerType)); } auto mappings{ctx.channelCtx.asCtx->gmmu.TranslateRange(textureHeader.Iova(), guest.GetSize())}; guest.mappings.assign(mappings.begin(), mappings.end()); if (guest.mappings.empty() || !guest.mappings.front().valid()) { Logger::Warn("Unmapped texture in pool: 0x{:X}", textureHeader.Iova()); if (!nullTextureView) nullTextureView = CreateNullTexture(ctx); return nullTextureView.get(); } texture = ctx.gpu.texture.FindOrCreate(guest, ctx.executor.tag); } textureHeaderCache[index] = {textureHeader, texture.get(), ctx.executor.executionTag}; return texture.get(); } Shader::TextureType Textures::GetTextureType(InterconnectContext &ctx, u32 index) { auto textureHeaders{texturePool.UpdateGet(ctx).textureHeaders}; switch (textureHeaders[index].textureType) { case TextureImageControl::TextureType::e1D: return Shader::TextureType::Color1D; case TextureImageControl::TextureType::e1DArray: return Shader::TextureType::ColorArray1D; case TextureImageControl::TextureType::e1DBuffer: return Shader::TextureType::Buffer; case TextureImageControl::TextureType::e2DNoMipmap: case TextureImageControl::TextureType::e2D: return Shader::TextureType::Color2D; case TextureImageControl::TextureType::e2DArray: return Shader::TextureType::ColorArray2D; case TextureImageControl::TextureType::e3D: return Shader::TextureType::Color3D; case TextureImageControl::TextureType::eCube: return Shader::TextureType::ColorCube; case TextureImageControl::TextureType::eCubeArray: return Shader::TextureType::ColorArrayCube; } } }