Implement Maxwell 3D Textures

Implements parsing for the Maxwell 3D TIC pool and conversion of a TIC into a `GuestTexture`, support is limited to pitch-linear RGB565/A8R8G8B8 textures at the moment but will be extended as games utilize more formats and layouts. Support for 1D buffers is also omitted at the moment since they need special handling with them effectively being treated as buffers in Vulkan rather than images.
This commit is contained in:
PixelyIon 2022-01-06 00:54:24 +05:30
parent a7b90e7825
commit adb0a16873
5 changed files with 498 additions and 5 deletions

View File

@ -12,6 +12,7 @@
#include "command_executor.h"
#include "types/tsc.h"
#include "types/tic.h"
namespace skyline::gpu::interconnect {
namespace maxwell3d = soc::gm20b::engine::maxwell3d::type;
@ -1548,6 +1549,146 @@ namespace skyline::gpu::interconnect {
private:
u32 bindlessTextureConstantBufferIndex{};
struct PoolTexture : public FenceCycleDependency {
GuestTexture guest;
std::weak_ptr<TextureView> view;
};
struct TexturePool {
IOVA iova;
u32 maximumIndex;
span<TextureImageControl> imageControls;
std::unordered_map<TextureImageControl, PoolTexture, util::ObjectHash<TextureImageControl>> textures;
} texturePool{};
public:
void SetBindlessTextureConstantBufferIndex(u32 index) {
bindlessTextureConstantBufferIndex = index;
}
void SetTexturePoolIovaHigh(u32 high) {
texturePool.iova.high = high;
texturePool.imageControls = nullptr;
}
void SetTexturePoolIovaLow(u32 low) {
texturePool.iova.low = low;
texturePool.imageControls = nullptr;
}
void SetTexturePoolMaximumIndex(u32 index) {
texturePool.maximumIndex = index;
texturePool.imageControls = nullptr;
}
private:
texture::Format ConvertTicFormat(TextureImageControl::FormatWord format) {
using TIC = TextureImageControl;
#define TIC_FORMAT(format, componentR, componentG, componentB, componentA, swizzleX, swizzleY, swizzleZ, swizzleW) TIC::FormatWord{TIC::ImageFormat::format, TIC::ImageComponent::componentR, TIC::ImageComponent::componentG, TIC::ImageComponent::componentB, TIC::ImageComponent::componentA, TIC::ImageSwizzle::swizzleX, TIC::ImageSwizzle::swizzleY, TIC::ImageSwizzle::swizzleZ, TIC::ImageSwizzle::swizzleW}.Raw()
#define TIC_FORMAT_SC(format, component, swizzleX, swizzleY, swizzleZ, swizzleW) TIC_FORMAT(format, component, component, component, component, swizzleX, swizzleY, swizzleZ, swizzleW)
switch (format.Raw()) {
case TIC_FORMAT_SC(B5G6R5, Unorm, B, G, R, OneFloat):
return format::R5G6B5Unorm;
case TIC_FORMAT_SC(A8R8G8B8, Unorm, R, G, B, A):
return format::A8B8G8R8Unorm;
default:
throw exception("Cannot translate TIC format: 0x{:X}", static_cast<u32>(format.Raw()));
}
#undef TIC_FORMAT_SC
#undef TIC_FORMAT
}
std::shared_ptr<TextureView> GetPoolTextureView(u32 index) {
if (!texturePool.imageControls.valid()) {
auto mappings{channelCtx.asCtx->gmmu.TranslateRange(texturePool.iova, texturePool.maximumIndex * sizeof(TextureImageControl))};
if (mappings.size() != 1)
throw exception("Texture pool mapping count is unexpected: {}", mappings.size());
texturePool.imageControls = mappings.front().cast<TextureImageControl>();
}
TextureImageControl &textureControl{texturePool.imageControls[index]};
auto textureIt{texturePool.textures.insert({textureControl, {}})};
auto &poolTexture{textureIt.first->second};
if (textureIt.second) {
// If the entry didn't exist prior then we need to convert the TIC to a GuestTexture
auto &guest{poolTexture.guest};
guest.format = ConvertTicFormat(textureControl.formatWord);
constexpr size_t CubeFaceCount{6}; //!< The amount of faces of a cube
guest.baseArrayLayer = static_cast<u16>(textureControl.BaseLayer());
guest.dimensions = texture::Dimensions(textureControl.widthMinusOne + 1, textureControl.heightMinusOne + 1, 1);
u16 depth{static_cast<u16>(textureControl.depthMinusOne + 1)};
using TicType = TextureImageControl::TextureType;
using TexType = texture::TextureType;
switch (textureControl.textureType) {
case TicType::e1D:
guest.type = TexType::e1D;
guest.layerCount = 1;
break;
case TicType::e1DArray:
guest.type = TexType::e1D;
guest.layerCount = depth;
break;
case TicType::e1DBuffer:
throw exception("1D Buffers are not supported");
case TicType::e2D:
case TicType::e2DNoMipmap:
guest.type = TexType::e2D;
guest.layerCount = 1;
break;
case TicType::e2DArray:
guest.type = TexType::e2D;
guest.layerCount = depth;
break;
case TicType::e3D:
guest.type = TexType::e3D;
guest.layerCount = 1;
guest.dimensions.depth = depth;
break;
case TicType::eCubemap:
guest.type = TexType::e2D;
guest.layerCount = CubeFaceCount;
break;
case TicType::eCubeArray:
guest.type = TexType::e2D;
guest.layerCount = depth * CubeFaceCount;
break;
}
size_t size; //!< The size of the texture in bytes
if (textureControl.headerType == TextureImageControl::HeaderType::Pitch) {
u32 pitch{static_cast<u32>(textureControl.tileConfig.pitchHigh) << TextureImageControl::TileConfig::PitchAlignmentBits};
guest.tileConfig = {
.mode = texture::TileMode::Pitch,
.pitch = pitch,
};
size = pitch * guest.dimensions.height * guest.dimensions.depth * guest.layerCount;
} else {
throw exception("Unsupported TIC Header Type: {}", static_cast<u32>(textureControl.headerType));
}
auto mappings{channelCtx.asCtx->gmmu.TranslateRange(textureControl.Iova(), size)};
guest.mappings.assign(mappings.begin(), mappings.end());
} else if (auto textureView{poolTexture.view.lock()}; textureView != nullptr) {
// If the entry already exists and the view is still valid then we return it directly
return textureView;
}
auto textureView{gpu.texture.FindOrCreate(poolTexture.guest)};
poolTexture.view = textureView;
return textureView;
}
/* Samplers */
private:
struct Sampler : public vk::raii::Sampler, public FenceCycleDependency {
@ -1577,6 +1718,7 @@ namespace skyline::gpu::interconnect {
samplerPool.samplerControls = nullptr;
}
private:
vk::Filter ConvertSamplerFilter(TextureSamplerControl::Filter filter) {
using TscFilter = TextureSamplerControl::Filter;
using VkFilter = vk::Filter;
@ -1753,11 +1895,6 @@ namespace skyline::gpu::interconnect {
return sampler = std::make_shared<Sampler>(gpu.vkDevice, samplerInfo.get<vk::SamplerCreateInfo>());
}
public:
void SetBindlessTextureConstantBufferIndex(u32 index) {
bindlessTextureConstantBufferIndex = index;
}
public:
void SetIndexBufferStartIovaHigh(u32 high) {
indexBuffer.start.high = high;

View File

@ -0,0 +1,341 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
// Copyright © 2018-2020 fincs (https://github.com/devkitPro/deko3d)
#pragma once
#include <bit>
#include <common/base.h>
namespace skyline::gpu::interconnect {
#pragma pack(push, 1)
/**
* @brief The Texture Image Control is a descriptor used to configure the texture unit in Maxwell GPUs
* @url https://github.com/envytools/envytools/blob/master/rnndb/graph/gm200_texture.xml
* @url https://github.com/devkitPro/deko3d/blob/00c12d1f4809014f1cc22719dd2e3476735eec64/source/maxwell/texture_image_control_block.h
* @note Any members with underscore number suffixes represent a bitfield range of a value that member represents
* @note Any enumerations that have numerical enumerants are prefixed with 'e'
*/
struct TextureImageControl {
/**
* @note An underscore may be used to describe a different block in a format
*/
enum class ImageFormat : u32 {
R32G32B32A32 = 0x01,
R32G32B32 = 0x02,
R16G16B16A16 = 0x03,
R32G32 = 0x04,
R32B24G8 = 0x05,
Etc2Rgb = 0x06,
X8B8G8R8 = 0x07,
A8R8G8B8 = 0x08,
A2B10G10R10 = 0x09,
Etc2RgbPta = 0x0A,
Etc2Rgba = 0x0B,
R16G16 = 0x0C,
G8R24 = 0x0D,
G24R8 = 0x0E,
R32 = 0x0F,
BC6hSf16 = 0x10,
BC6hUf16 = 0x11,
A4B4G4R4 = 0x12,
A5B5G5R1 = 0x13,
A1B5G5R5 = 0x14,
B5G6R5 = 0x15,
B6G5R5 = 0x16,
BC7U = 0x17,
G8R8 = 0x18,
Eac = 0x19,
EacX2 = 0x1A,
R16 = 0x1B,
Y8Video = 0x1C,
R8 = 0x1D,
G4R4 = 0x1E,
R1 = 0x1F,
E5B9G9R9SharedExponent = 0x20,
BF10GF11RF11 = 0x21,
G8B8G8R8 = 0x22,
B8G8R8G8 = 0x23,
Dxt1 = 0x24,
Dxt23 = 0x25,
Dxt45 = 0x26,
Dxn1 = 0x27,
Dxn2 = 0x28,
Z24S8 = 0x29,
X8Z24 = 0x2A,
S8Z24 = 0x2B,
X4V4Z24_Cov4R4V = 0x2C,
X4V4Z24_Cov8R8V = 0x2D,
V8Z24_Cov4R12V = 0x2E,
ZF32 = 0x2F,
ZF32_X24S8 = 0x30,
X8Z24_X20V4S8_Cov4R4V = 0x31,
X8Z24_X20V4S8_Cov8R8V = 0x32,
ZF32_X20V4X8_Cov4R4V = 0x33,
ZF32_X20V4X8_Cov8R8V = 0x34,
ZF32_X20V4S8_Cov4R4V = 0x35,
ZF32_X20V4S8_Cov8R8V = 0x36,
X8Z24_X16V8S8_Cov4R12V = 0x37,
ZF32_X16V8X8_Cov4R12V = 0x38,
ZF32_X16V8S8_Cov4R12V = 0x39,
Z16 = 0x3A,
V8Z24_Cov8R24V = 0x3B,
X8Z24_X16V8S8_Cov8R24V = 0x3C,
ZF32_X16V8X8_Cov8R24V = 0x3D,
ZF32_X16V8S8_Cov8R24V = 0x3E,
Astc4x4 = 0x40,
Astc5x5 = 0x41,
Astc6x6 = 0x42,
Astc8x8 = 0x44,
Astc10x10 = 0x45,
Astc12x12 = 0x46,
Astc5x4 = 0x50,
Astc6x5 = 0x51,
Astc8x6 = 0x52,
Astc10x8 = 0x53,
Astc12x10 = 0x54,
Astc8x5 = 0x55,
Astc10x5 = 0x56,
Astc10x6 = 0x57,
};
enum class ImageComponent : u32 {
Snorm = 1,
Unorm = 2,
Sint = 3,
Uint = 4,
SnormForceFp16 = 5,
UnormForceFp16 = 6,
Float = 7,
};
enum class ImageSwizzle : u32 {
Zero = 0,
R = 2,
G = 3,
B = 4,
A = 5,
OneInt = 6,
OneFloat = 7,
};
enum class HeaderType : u32 {
Buffer1D = 0,
PitchColorKey = 1,
Pitch = 2,
BlockLinear = 3,
BlockLinearColorKey = 4,
};
enum class TextureType : u32 {
e1D = 0,
e2D = 1,
e3D = 2,
eCubemap = 3,
e1DArray = 4,
e2DArray = 5,
e1DBuffer = 6,
e2DNoMipmap = 7,
eCubeArray = 8,
};
enum class MsaaMode : u32 {
e1x1 = 0,
e2x1 = 1,
e2x2 = 2,
e4x2 = 3,
e4x2D3D = 4,
e2x1D3D = 5,
e4x4 = 6,
e2x2Vc4 = 8,
e2x2Vc12 = 9,
e4x2Vc8 = 10,
e4x2Vc24 = 11,
};
enum class LodQuality : u32 {
Low = 0,
High = 1,
};
enum class SectorPromotion : u32 {
None = 0,
To2V = 1,
To2H = 2,
To4 = 3,
};
enum class BorderSize : u32 {
One = 0,
Two = 1,
Four = 2,
Eight = 3,
SamplerColor = 7,
};
enum class AnisotropySpreadModifier : u32 {
None = 0,
One = 1,
Two = 2,
Sqrt = 3,
};
enum class AnisotropySpread : u32 {
Half = 0,
One = 1,
Two = 2,
Max = 3,
};
enum class MaxAnisotropy : u32 {
e1to1 = 0,
e2to1 = 1,
e4to1 = 2,
e6to1 = 3,
e8to1 = 4,
e10to1 = 5,
e12to1 = 6,
e16to1 = 7,
};
// 0x00
struct FormatWord {
ImageFormat format : 7;
ImageComponent componentR : 3;
ImageComponent componentG : 3;
ImageComponent componentB : 3;
ImageComponent componentA : 3;
ImageSwizzle swizzleX : 3;
ImageSwizzle swizzleY : 3;
ImageSwizzle swizzleZ : 3;
ImageSwizzle swizzleW : 3;
u32 _pad_ : 1;
bool operator==(const FormatWord &) const = default;
constexpr u32 Raw() const {
if (std::is_constant_evaluated()) {
u32 raw{};
raw <<= 1;
raw |= static_cast<u32>(swizzleW);
raw <<= 3;
raw |= static_cast<u32>(swizzleZ);
raw <<= 3;
raw |= static_cast<u32>(swizzleY);
raw <<= 3;
raw |= static_cast<u32>(swizzleX);
raw <<= 3;
raw |= static_cast<u32>(componentA);
raw <<= 3;
raw |= static_cast<u32>(componentB);
raw <<= 3;
raw |= static_cast<u32>(componentG);
raw <<= 3;
raw |= static_cast<u32>(componentR);
raw <<= 7;
raw |= static_cast<u32>(format);
return raw;
} else {
return util::BitCast<u32>(*this);
}
}
} formatWord;
// 0x04
u32 addressLow;
// 0x08
u32 addressHigh : 16;
u32 viewLayerBase_3_7 : 5;
HeaderType headerType : 3;
u32 loadStoreHint : 1;
u32 viewCoherencyHash : 4;
u32 viewLayerBase_8_10 : 3;
// 0x0C
union TileConfig {
u16 widthMinusOne_16_31;
constexpr static size_t PitchAlignmentBits{5}; //!< The amount of bits that are 0 in the pitch due to alignment (Aligned to 32 bytes)
u16 pitchHigh; //!< Upper 16-bits of the 21-bit pitch, lower bits are implicitly zero due to alignment
struct {
u16 tileWidthGobsLog2 : 3;
u16 tileHeightGobsLog2 : 3;
u16 tileDepthGobsLog2 : 3;
u16 _pad0_ : 1;
u16 sparseTileWidthGobsLog2 : 3;
u16 gob3d : 1;
u16 _pad1_ : 2;
};
u16 raw;
bool operator==(const TileConfig &other) const {
return raw == other.raw;
}
} tileConfig;
u16 lodAnisotropyQuality_2 : 1;
LodQuality lodAnisotropyQuality : 1;
LodQuality lodIsotropyQuality : 1;
AnisotropySpreadModifier anisotropyCoarseSpreadModifier : 2;
u16 anisotropySpreadScale : 5;
u16 useHeaderOptControl : 1;
u16 depthTexture : 1;
u16 mipMaxLevels : 4;
// 0x10
u32 widthMinusOne : 16;
u32 viewLayerBase_0_2 : 3;
u32 anisotropySpreadMaxLog2 : 3;
u32 isSrgb : 1;
TextureType textureType : 4;
SectorPromotion sectorPromotion : 2;
BorderSize borderSize : 3;
// 0x14
u32 heightMinusOne : 16;
u32 depthMinusOne : 14;
u32 isSparse : 1;
u32 normalizedCoordinates : 1;
// 0x18
u32 colorKeyOp : 1;
u32 trilinOpt : 5;
u32 mipLodBias : 13;
u32 anisoBias : 4;
AnisotropySpread anisotropyFineSpread : 2;
AnisotropySpread anisotropyCoarseSpread : 2;
MaxAnisotropy maxAnisotropy : 3;
AnisotropySpreadModifier anisotropyFineSpreadModifier : 2;
// 0x1C
union ViewConfig {
u32 colorKeyValue;
struct {
u32 viewMipMinLevel : 4;
u32 viewMipMaxLevel : 4;
MsaaMode msaaMode : 4;
u32 minLodClamp : 12;
u32 _pad2_ : 8;
};
u32 raw;
bool operator==(const ViewConfig &other) const {
return raw == other.raw;
}
} viewConfig;
bool operator==(const TextureImageControl &) const = default;
u64 Iova() {
return (static_cast<u64>(addressHigh) << 32) | addressLow;
}
u32 BaseLayer() {
return static_cast<u32>(viewLayerBase_0_2 | (viewLayerBase_3_7 << 3) | (viewLayerBase_8_10 << 8));
}
};
static_assert(sizeof(TextureImageControl) == 0x20);
#pragma pack(pop)
}

View File

@ -7,6 +7,8 @@
#include <common/base.h>
namespace skyline::gpu::interconnect {
#pragma pack(push, 1)
/**
* @brief The Texture Sampler Control is a descriptor used to configure the texture sampler in Maxwell GPUs
* @url https://github.com/envytools/envytools/blob/master/rnndb/graph/g80_texture.xml#L367
@ -133,4 +135,6 @@ namespace skyline::gpu::interconnect {
}
};
static_assert(sizeof(TextureSamplerControl) == 0x20);
#pragma pack(pop)
}

View File

@ -16,6 +16,7 @@ namespace skyline::gpu::format {
constexpr Format R5G6B5Unorm{sizeof(u16), vkf::eR5G6B5UnormPack16};
constexpr Format A2B10G10R10Unorm{sizeof(u32), vkf::eA2B10G10R10UnormPack32};
constexpr Format A8B8G8R8Srgb{sizeof(u32), vkf::eA8B8G8R8SrgbPack32};
constexpr Format A8B8G8R8Unorm{sizeof(u32), vkf::eA8B8G8R8UnormPack32};
constexpr Format A8B8G8R8Snorm{sizeof(u32), vkf::eA8B8G8R8SnormPack32};
constexpr Format R16G16Unorm{sizeof(u32), vkf::eR16G16Unorm};
constexpr Format R16G16Snorm{sizeof(u32), vkf::eR16G16Snorm};

View File

@ -477,6 +477,16 @@ namespace skyline::soc::gm20b::engine::maxwell3d {
context.SetSamplerPoolMaximumIndex(maximumIndex);
})
MAXWELL3D_STRUCT_STRUCT_CASE(texturePool, address, high, {
context.SetTexturePoolIovaHigh(high);
})
MAXWELL3D_STRUCT_STRUCT_CASE(texturePool, address, low, {
context.SetTexturePoolIovaLow(low);
})
MAXWELL3D_STRUCT_CASE(texturePool, maximumIndex, {
context.SetTexturePoolMaximumIndex(maximumIndex);
})
default:
break;
}