Move solution and projects to src

This commit is contained in:
TSR Berry 2023-04-08 01:22:00 +02:00 committed by Mary
parent cd124bda58
commit cee7121058
3466 changed files with 55 additions and 55 deletions

View file

@ -0,0 +1,256 @@
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
/// An entry on the short duration texture cache.
/// </summary>
class ShortTextureCacheEntry
{
public readonly TextureDescriptor Descriptor;
public readonly int InvalidatedSequence;
public readonly Texture Texture;
/// <summary>
/// Create a new entry on the short duration texture cache.
/// </summary>
/// <param name="descriptor">Last descriptor that referenced the texture</param>
/// <param name="texture">The texture</param>
public ShortTextureCacheEntry(TextureDescriptor descriptor, Texture texture)
{
Descriptor = descriptor;
InvalidatedSequence = texture.InvalidatedSequence;
Texture = texture;
}
}
/// <summary>
/// A texture cache that automatically removes older textures that are not used for some time.
/// The cache works with a rotated list with a fixed size. When new textures are added, the
/// old ones at the bottom of the list are deleted.
/// </summary>
class AutoDeleteCache : IEnumerable<Texture>
{
private const int MinCountForDeletion = 32;
private const int MaxCapacity = 2048;
private const ulong MaxTextureSizeCapacity = 512 * 1024 * 1024; // MB;
private readonly LinkedList<Texture> _textures;
private ulong _totalSize;
private HashSet<ShortTextureCacheEntry> _shortCacheBuilder;
private HashSet<ShortTextureCacheEntry> _shortCache;
private Dictionary<TextureDescriptor, ShortTextureCacheEntry> _shortCacheLookup;
/// <summary>
/// Creates a new instance of the automatic deletion cache.
/// </summary>
public AutoDeleteCache()
{
_textures = new LinkedList<Texture>();
_shortCacheBuilder = new HashSet<ShortTextureCacheEntry>();
_shortCache = new HashSet<ShortTextureCacheEntry>();
_shortCacheLookup = new Dictionary<TextureDescriptor, ShortTextureCacheEntry>();
}
/// <summary>
/// Adds a new texture to the cache, even if the texture added is already on the cache.
/// </summary>
/// <remarks>
/// Using this method is only recommended if you know that the texture is not yet on the cache,
/// otherwise it would store the same texture more than once.
/// </remarks>
/// <param name="texture">The texture to be added to the cache</param>
public void Add(Texture texture)
{
_totalSize += texture.Size;
texture.IncrementReferenceCount();
texture.CacheNode = _textures.AddLast(texture);
if (_textures.Count > MaxCapacity ||
(_totalSize > MaxTextureSizeCapacity && _textures.Count >= MinCountForDeletion))
{
RemoveLeastUsedTexture();
}
}
/// <summary>
/// Adds a new texture to the cache, or just moves it to the top of the list if the
/// texture is already on the cache.
/// </summary>
/// <remarks>
/// Moving the texture to the top of the list prevents it from being deleted,
/// as the textures on the bottom of the list are deleted when new ones are added.
/// </remarks>
/// <param name="texture">The texture to be added, or moved to the top</param>
public void Lift(Texture texture)
{
if (texture.CacheNode != null)
{
if (texture.CacheNode != _textures.Last)
{
_textures.Remove(texture.CacheNode);
texture.CacheNode = _textures.AddLast(texture);
}
if (_totalSize > MaxTextureSizeCapacity && _textures.Count >= MinCountForDeletion)
{
RemoveLeastUsedTexture();
}
}
else
{
Add(texture);
}
}
/// <summary>
/// Removes the least used texture from the cache.
/// </summary>
private void RemoveLeastUsedTexture()
{
Texture oldestTexture = _textures.First.Value;
_totalSize -= oldestTexture.Size;
if (!oldestTexture.CheckModified(false))
{
// The texture must be flushed if it falls out of the auto delete cache.
// Flushes out of the auto delete cache do not trigger write tracking,
// as it is expected that other overlapping textures exist that have more up-to-date contents.
oldestTexture.Group.SynchronizeDependents(oldestTexture);
oldestTexture.FlushModified(false);
}
_textures.RemoveFirst();
oldestTexture.DecrementReferenceCount();
oldestTexture.CacheNode = null;
}
/// <summary>
/// Removes a texture from the cache.
/// </summary>
/// <param name="texture">The texture to be removed from the cache</param>
/// <param name="flush">True to remove the texture if it was on the cache</param>
/// <returns>True if the texture was found and removed, false otherwise</returns>
public bool Remove(Texture texture, bool flush)
{
if (texture.CacheNode == null)
{
return false;
}
// Remove our reference to this texture.
if (flush)
{
texture.FlushModified(false);
}
_textures.Remove(texture.CacheNode);
_totalSize -= texture.Size;
texture.CacheNode = null;
return texture.DecrementReferenceCount();
}
/// <summary>
/// Attempt to find a texture on the short duration cache.
/// </summary>
/// <param name="descriptor">The texture descriptor</param>
/// <returns>The texture if found, null otherwise</returns>
public Texture FindShortCache(in TextureDescriptor descriptor)
{
if (_shortCacheLookup.Count > 0 && _shortCacheLookup.TryGetValue(descriptor, out var entry))
{
if (entry.InvalidatedSequence == entry.Texture.InvalidatedSequence)
{
return entry.Texture;
}
else
{
_shortCacheLookup.Remove(descriptor);
}
}
return null;
}
/// <summary>
/// Removes a texture from the short duration cache.
/// </summary>
/// <param name="texture">Texture to remove from the short cache</param>
public void RemoveShortCache(Texture texture)
{
bool removed = _shortCache.Remove(texture.ShortCacheEntry);
removed |= _shortCacheBuilder.Remove(texture.ShortCacheEntry);
if (removed)
{
texture.DecrementReferenceCount();
_shortCacheLookup.Remove(texture.ShortCacheEntry.Descriptor);
texture.ShortCacheEntry = null;
}
}
/// <summary>
/// Adds a texture to the short duration cache.
/// It starts in the builder set, and it is moved into the deletion set on next process.
/// </summary>
/// <param name="texture">Texture to add to the short cache</param>
/// <param name="descriptor">Last used texture descriptor</param>
public void AddShortCache(Texture texture, ref TextureDescriptor descriptor)
{
var entry = new ShortTextureCacheEntry(descriptor, texture);
_shortCacheBuilder.Add(entry);
_shortCacheLookup.Add(entry.Descriptor, entry);
texture.ShortCacheEntry = entry;
texture.IncrementReferenceCount();
}
/// <summary>
/// Delete textures from the short duration cache.
/// Moves the builder set to be deleted on next process.
/// </summary>
public void ProcessShortCache()
{
HashSet<ShortTextureCacheEntry> toRemove = _shortCache;
foreach (var entry in toRemove)
{
entry.Texture.DecrementReferenceCount();
_shortCacheLookup.Remove(entry.Descriptor);
entry.Texture.ShortCacheEntry = null;
}
toRemove.Clear();
_shortCache = _shortCacheBuilder;
_shortCacheBuilder = toRemove;
}
public IEnumerator<Texture> GetEnumerator()
{
return _textures.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return _textures.GetEnumerator();
}
}
}

View file

@ -0,0 +1,72 @@
using Ryujinx.Graphics.GAL;
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
/// Represents texture format information.
/// </summary>
readonly struct FormatInfo
{
/// <summary>
/// A default, generic RGBA8 texture format.
/// </summary>
public static FormatInfo Default { get; } = new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4);
/// <summary>
/// The format of the texture data.
/// </summary>
public Format Format { get; }
/// <summary>
/// The block width for compressed formats.
/// </summary>
/// <remarks>
/// Must be 1 for non-compressed formats.
/// </remarks>
public int BlockWidth { get; }
/// <summary>
/// The block height for compressed formats.
/// </summary>
/// <remarks>
/// Must be 1 for non-compressed formats.
/// </remarks>
public int BlockHeight { get; }
/// <summary>
/// The number of bytes occupied by a single pixel in memory of the texture data.
/// </summary>
public int BytesPerPixel { get; }
/// <summary>
/// The maximum number of components this format has defined (in RGBA order).
/// </summary>
public int Components { get; }
/// <summary>
/// Whenever or not the texture format is a compressed format. Determined from block size.
/// </summary>
public bool IsCompressed => (BlockWidth | BlockHeight) != 1;
/// <summary>
/// Constructs the texture format info structure.
/// </summary>
/// <param name="format">The format of the texture data</param>
/// <param name="blockWidth">The block width for compressed formats. Must be 1 for non-compressed formats</param>
/// <param name="blockHeight">The block height for compressed formats. Must be 1 for non-compressed formats</param>
/// <param name="bytesPerPixel">The number of bytes occupied by a single pixel in memory of the texture data</param>
public FormatInfo(
Format format,
int blockWidth,
int blockHeight,
int bytesPerPixel,
int components)
{
Format = format;
BlockWidth = blockWidth;
BlockHeight = blockHeight;
BytesPerPixel = bytesPerPixel;
Components = components;
}
}
}

View file

@ -0,0 +1,578 @@
using Ryujinx.Graphics.GAL;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
/// Contains format tables, for texture and vertex attribute formats.
/// </summary>
static class FormatTable
{
private enum TextureFormat : uint
{
// Formats
R32G32B32A32 = 0x01,
R32G32B32 = 0x02,
R16G16B16A16 = 0x03,
R32G32 = 0x04,
R32B24G8 = 0x05,
X8B8G8R8 = 0x07,
A8B8G8R8 = 0x08,
A2B10G10R10 = 0x09,
R16G16 = 0x0c,
G8R24 = 0x0d,
G24R8 = 0x0e,
R32 = 0x0f,
A4B4G4R4 = 0x12,
A5B5G5R1 = 0x13,
A1B5G5R5 = 0x14,
B5G6R5 = 0x15,
B6G5R5 = 0x16,
G8R8 = 0x18,
R16 = 0x1b,
Y8Video = 0x1c,
R8 = 0x1d,
G4R4 = 0x1e,
R1 = 0x1f,
E5B9G9R9SharedExp = 0x20,
Bf10Gf11Rf11 = 0x21,
G8B8G8R8 = 0x22,
B8G8R8G8 = 0x23,
Bc1 = 0x24,
Bc2 = 0x25,
Bc3 = 0x26,
Bc4 = 0x27,
Bc5 = 0x28,
Bc6HSf16 = 0x10,
Bc6HUf16 = 0x11,
Bc7U = 0x17,
Etc2Rgb = 0x06,
Etc2RgbPta = 0x0a,
Etc2Rgba = 0x0b,
Eac = 0x19,
Eacx2 = 0x1a,
Z24S8 = 0x29,
X8Z24 = 0x2a,
S8Z24 = 0x2b,
X4V4Z24Cov4R4V = 0x2c,
X4V4Z24Cov8R8V = 0x2d,
V8Z24Cov4R12V = 0x2e,
Zf32 = 0x2f,
Zf32X24S8 = 0x30,
X8Z24X20V4S8Cov4R4V = 0x31,
X8Z24X20V4S8Cov8R8V = 0x32,
Zf32X20V4X8Cov4R4V = 0x33,
Zf32X20V4X8Cov8R8V = 0x34,
Zf32X20V4S8Cov4R4V = 0x35,
Zf32X20V4S8Cov8R8V = 0x36,
X8Z24X16V8S8Cov4R12V = 0x37,
Zf32X16V8X8Cov4R12V = 0x38,
Zf32X16V8S8Cov4R12V = 0x39,
Z16 = 0x3a,
V8Z24Cov8R24V = 0x3b,
X8Z24X16V8S8Cov8R24V = 0x3c,
Zf32X16V8X8Cov8R24V = 0x3d,
Zf32X16V8S8Cov8R24V = 0x3e,
Astc2D4x4 = 0x40,
Astc2D5x4 = 0x50,
Astc2D5x5 = 0x41,
Astc2D6x5 = 0x51,
Astc2D6x6 = 0x42,
Astc2D8x5 = 0x55,
Astc2D8x6 = 0x52,
Astc2D8x8 = 0x44,
Astc2D10x5 = 0x56,
Astc2D10x6 = 0x57,
Astc2D10x8 = 0x53,
Astc2D10x10 = 0x45,
Astc2D12x10 = 0x54,
Astc2D12x12 = 0x46,
// Types
Snorm = 0x1,
Unorm = 0x2,
Sint = 0x3,
Uint = 0x4,
SnormForceFp16 = 0x5,
UnormForceFp16 = 0x6,
Float = 0x7,
// Component Types
RSnorm = Snorm << 7,
GSnorm = Snorm << 10,
BSnorm = Snorm << 13,
ASnorm = Snorm << 16,
RUnorm = Unorm << 7,
GUnorm = Unorm << 10,
BUnorm = Unorm << 13,
AUnorm = Unorm << 16,
RSint = Sint << 7,
GSint = Sint << 10,
BSint = Sint << 13,
ASint = Sint << 16,
RUint = Uint << 7,
GUint = Uint << 10,
BUint = Uint << 13,
AUint = Uint << 16,
RSnormForceFp16 = SnormForceFp16 << 7,
GSnormForceFp16 = SnormForceFp16 << 10,
BSnormForceFp16 = SnormForceFp16 << 13,
ASnormForceFp16 = SnormForceFp16 << 16,
RUnormForceFp16 = UnormForceFp16 << 7,
GUnormForceFp16 = UnormForceFp16 << 10,
BUnormForceFp16 = UnormForceFp16 << 13,
AUnormForceFp16 = UnormForceFp16 << 16,
RFloat = Float << 7,
GFloat = Float << 10,
BFloat = Float << 13,
AFloat = Float << 16,
Srgb = 0x1 << 19, // Custom encoding
// Combinations
R8Unorm = R8 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x2491d
R8Snorm = R8 | RSnorm | GSnorm | BSnorm | ASnorm, // 0x1249d
R8Uint = R8 | RUint | GUint | BUint | AUint, // 0x4921d
R8Sint = R8 | RSint | GSint | BSint | ASint, // 0x36d9d
R16Float = R16 | RFloat | GFloat | BFloat | AFloat, // 0x7ff9b
R16Unorm = R16 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x2491b
R16Snorm = R16 | RSnorm | GSnorm | BSnorm | ASnorm, // 0x1249b
R16Uint = R16 | RUint | GUint | BUint | AUint, // 0x4921b
R16Sint = R16 | RSint | GSint | BSint | ASint, // 0x36d9b
R32Float = R32 | RFloat | GFloat | BFloat | AFloat, // 0x7ff8f
R32Uint = R32 | RUint | GUint | BUint | AUint, // 0x4920f
R32Sint = R32 | RSint | GSint | BSint | ASint, // 0x36d8f
G8R8Unorm = G8R8 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24918
G8R8Snorm = G8R8 | RSnorm | GSnorm | BSnorm | ASnorm, // 0x12498
G8R8Uint = G8R8 | RUint | GUint | BUint | AUint, // 0x49218
G8R8Sint = G8R8 | RSint | GSint | BSint | ASint, // 0x36d98
R16G16Float = R16G16 | RFloat | GFloat | BFloat | AFloat, // 0x7ff8c
R16G16Unorm = R16G16 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x2490c
R16G16Snorm = R16G16 | RSnorm | GSnorm | BSnorm | ASnorm, // 0x1248c
R16G16Uint = R16G16 | RUint | GUint | BUint | AUint, // 0x4920c
R16G16Sint = R16G16 | RSint | GSint | BSint | ASint, // 0x36d8c
R32G32Float = R32G32 | RFloat | GFloat | BFloat | AFloat, // 0x7ff84
R32G32Uint = R32G32 | RUint | GUint | BUint | AUint, // 0x49204
R32G32Sint = R32G32 | RSint | GSint | BSint | ASint, // 0x36d84
R32G32B32Float = R32G32B32 | RFloat | GFloat | BFloat | AFloat, // 0x7ff82
R32G32B32Uint = R32G32B32 | RUint | GUint | BUint | AUint, // 0x49202
R32G32B32Sint = R32G32B32 | RSint | GSint | BSint | ASint, // 0x36d82
A8B8G8R8Unorm = A8B8G8R8 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24908
A8B8G8R8Snorm = A8B8G8R8 | RSnorm | GSnorm | BSnorm | ASnorm, // 0x12488
A8B8G8R8Uint = A8B8G8R8 | RUint | GUint | BUint | AUint, // 0x49208
A8B8G8R8Sint = A8B8G8R8 | RSint | GSint | BSint | ASint, // 0x36d88
R16G16B16A16Float = R16G16B16A16 | RFloat | GFloat | BFloat | AFloat, // 0x7ff83
R16G16B16A16Unorm = R16G16B16A16 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24903
R16G16B16A16Snorm = R16G16B16A16 | RSnorm | GSnorm | BSnorm | ASnorm, // 0x12483
R16G16B16A16Uint = R16G16B16A16 | RUint | GUint | BUint | AUint, // 0x49203
R16G16B16A16Sint = R16G16B16A16 | RSint | GSint | BSint | ASint, // 0x36d83
R32G32B32A32Float = R32G32B32A32 | RFloat | GFloat | BFloat | AFloat, // 0x7ff81
R32G32B32A32Uint = R32G32B32A32 | RUint | GUint | BUint | AUint, // 0x49201
R32G32B32A32Sint = R32G32B32A32 | RSint | GSint | BSint | ASint, // 0x36d81
Z16Unorm = Z16 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x2493a
Zf32RFloatGUintBUintAUint = Zf32 | RFloat | GUint | BUint | AUint, // 0x493af
Zf32Float = Zf32 | RFloat | GFloat | BFloat | AFloat, // 0x7ffaf
G24R8RUintGUnormBUnormAUnorm = G24R8 | RUint | GUnorm | BUnorm | AUnorm, // 0x24a0e
Z24S8RUintGUnormBUnormAUnorm = Z24S8 | RUint | GUnorm | BUnorm | AUnorm, // 0x24a29
Z24S8RUintGUnormBUintAUint = Z24S8 | RUint | GUnorm | BUint | AUint, // 0x48a29
S8Z24RUnormGUintBUintAUint = S8Z24 | RUnorm | GUint | BUint | AUint, // 0x4912b
R32B24G8RFloatGUintBUnormAUnorm = R32B24G8 | RFloat | GUint | BUnorm | AUnorm, // 0x25385
Zf32X24S8RFloatGUintBUnormAUnorm = Zf32X24S8 | RFloat | GUint | BUnorm | AUnorm, // 0x253b0
A8B8G8R8UnormSrgb = A8B8G8R8 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4908
G4R4Unorm = G4R4 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x2491e
A4B4G4R4Unorm = A4B4G4R4 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24912
A1B5G5R5Unorm = A1B5G5R5 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24914
B5G6R5Unorm = B5G6R5 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24915
A2B10G10R10Unorm = A2B10G10R10 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24909
A2B10G10R10Uint = A2B10G10R10 | RUint | GUint | BUint | AUint, // 0x49209
Bf10Gf11Rf11Float = Bf10Gf11Rf11 | RFloat | GFloat | BFloat | AFloat, // 0x7ffa1
E5B9G9R9SharedExpFloat = E5B9G9R9SharedExp | RFloat | GFloat | BFloat | AFloat, // 0x7ffa0
Bc1Unorm = Bc1 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24924
Bc2Unorm = Bc2 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24925
Bc3Unorm = Bc3 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24926
Bc1UnormSrgb = Bc1 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4924
Bc2UnormSrgb = Bc2 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4925
Bc3UnormSrgb = Bc3 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4926
Bc4Unorm = Bc4 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24927
Bc4Snorm = Bc4 | RSnorm | GSnorm | BSnorm | ASnorm, // 0x124a7
Bc5Unorm = Bc5 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24928
Bc5Snorm = Bc5 | RSnorm | GSnorm | BSnorm | ASnorm, // 0x124a8
Bc7UUnorm = Bc7U | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24917
Bc7UUnormSrgb = Bc7U | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4917
Bc6HSf16Float = Bc6HSf16 | RFloat | GFloat | BFloat | AFloat, // 0x7ff90
Bc6HUf16Float = Bc6HUf16 | RFloat | GFloat | BFloat | AFloat, // 0x7ff91
Etc2RgbUnorm = Etc2Rgb | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24906
Etc2RgbPtaUnorm = Etc2RgbPta | RUnorm | GUnorm | BUnorm | AUnorm, // 0x2490a
Etc2RgbaUnorm = Etc2Rgba | RUnorm | GUnorm | BUnorm | AUnorm, // 0x2490b
Etc2RgbUnormSrgb = Etc2Rgb | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4906
Etc2RgbPtaUnormSrgb = Etc2RgbPta | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa490a
Etc2RgbaUnormSrgb = Etc2Rgba | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa490b
Astc2D4x4Unorm = Astc2D4x4 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24940
Astc2D5x4Unorm = Astc2D5x4 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24950
Astc2D5x5Unorm = Astc2D5x5 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24941
Astc2D6x5Unorm = Astc2D6x5 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24951
Astc2D6x6Unorm = Astc2D6x6 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24942
Astc2D8x5Unorm = Astc2D8x5 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24955
Astc2D8x6Unorm = Astc2D8x6 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24952
Astc2D8x8Unorm = Astc2D8x8 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24944
Astc2D10x5Unorm = Astc2D10x5 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24956
Astc2D10x6Unorm = Astc2D10x6 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24957
Astc2D10x8Unorm = Astc2D10x8 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24953
Astc2D10x10Unorm = Astc2D10x10 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24945
Astc2D12x10Unorm = Astc2D12x10 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24954
Astc2D12x12Unorm = Astc2D12x12 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24946
Astc2D4x4UnormSrgb = Astc2D4x4 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4940
Astc2D5x4UnormSrgb = Astc2D5x4 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4950
Astc2D5x5UnormSrgb = Astc2D5x5 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4941
Astc2D6x5UnormSrgb = Astc2D6x5 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4951
Astc2D6x6UnormSrgb = Astc2D6x6 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4942
Astc2D8x5UnormSrgb = Astc2D8x5 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4955
Astc2D8x6UnormSrgb = Astc2D8x6 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4952
Astc2D8x8UnormSrgb = Astc2D8x8 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4944
Astc2D10x5UnormSrgb = Astc2D10x5 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4956
Astc2D10x6UnormSrgb = Astc2D10x6 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4957
Astc2D10x8UnormSrgb = Astc2D10x8 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4953
Astc2D10x10UnormSrgb = Astc2D10x10 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4945
Astc2D12x10UnormSrgb = Astc2D12x10 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4954
Astc2D12x12UnormSrgb = Astc2D12x12 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4946
A5B5G5R1Unorm = A5B5G5R1 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24913
}
private enum VertexAttributeFormat : uint
{
// Width
R32G32B32A32 = 0x01,
R32G32B32 = 0x02,
R16G16B16A16 = 0x03,
R32G32 = 0x04,
R16G16B16 = 0x05,
A8B8G8R8 = 0x2f,
R8G8B8A8 = 0x0a,
X8B8G8R8 = 0x33,
A2B10G10R10 = 0x30,
B10G11R11 = 0x31,
R16G16 = 0x0f,
R32 = 0x12,
R8G8B8 = 0x13,
G8R8 = 0x32,
R8G8 = 0x18,
R16 = 0x1b,
R8 = 0x1d,
A8 = 0x34,
// Type
Snorm = 0x01,
Unorm = 0x02,
Sint = 0x03,
Uint = 0x04,
Uscaled = 0x05,
Sscaled = 0x06,
Float = 0x07,
// Combinations
R8Unorm = (R8 << 21) | (Unorm << 27), // 0x13a00000
R8Snorm = (R8 << 21) | (Snorm << 27), // 0x0ba00000
R8Uint = (R8 << 21) | (Uint << 27), // 0x23a00000
R8Sint = (R8 << 21) | (Sint << 27), // 0x1ba00000
R16Float = (R16 << 21) | (Float << 27), // 0x3b600000
R16Unorm = (R16 << 21) | (Unorm << 27), // 0x13600000
R16Snorm = (R16 << 21) | (Snorm << 27), // 0x0b600000
R16Uint = (R16 << 21) | (Uint << 27), // 0x23600000
R16Sint = (R16 << 21) | (Sint << 27), // 0x1b600000
R32Float = (R32 << 21) | (Float << 27), // 0x3a400000
R32Uint = (R32 << 21) | (Uint << 27), // 0x22400000
R32Sint = (R32 << 21) | (Sint << 27), // 0x1a400000
R8G8Unorm = (R8G8 << 21) | (Unorm << 27), // 0x13000000
R8G8Snorm = (R8G8 << 21) | (Snorm << 27), // 0x0b000000
R8G8Uint = (R8G8 << 21) | (Uint << 27), // 0x23000000
R8G8Sint = (R8G8 << 21) | (Sint << 27), // 0x1b000000
R16G16Float = (R16G16 << 21) | (Float << 27), // 0x39e00000
R16G16Unorm = (R16G16 << 21) | (Unorm << 27), // 0x11e00000
R16G16Snorm = (R16G16 << 21) | (Snorm << 27), // 0x09e00000
R16G16Uint = (R16G16 << 21) | (Uint << 27), // 0x21e00000
R16G16Sint = (R16G16 << 21) | (Sint << 27), // 0x19e00000
R32G32Float = (R32G32 << 21) | (Float << 27), // 0x38800000
R32G32Uint = (R32G32 << 21) | (Uint << 27), // 0x20800000
R32G32Sint = (R32G32 << 21) | (Sint << 27), // 0x18800000
R8G8B8Unorm = (R8G8B8 << 21) | (Unorm << 27), // 0x12600000
R8G8B8Snorm = (R8G8B8 << 21) | (Snorm << 27), // 0x0a600000
R8G8B8Uint = (R8G8B8 << 21) | (Uint << 27), // 0x22600000
R8G8B8Sint = (R8G8B8 << 21) | (Sint << 27), // 0x1a600000
R16G16B16Float = (R16G16B16 << 21) | (Float << 27), // 0x38a00000
R16G16B16Unorm = (R16G16B16 << 21) | (Unorm << 27), // 0x10a00000
R16G16B16Snorm = (R16G16B16 << 21) | (Snorm << 27), // 0x08a00000
R16G16B16Uint = (R16G16B16 << 21) | (Uint << 27), // 0x20a00000
R16G16B16Sint = (R16G16B16 << 21) | (Sint << 27), // 0x18a00000
R32G32B32Float = (R32G32B32 << 21) | (Float << 27), // 0x38400000
R32G32B32Uint = (R32G32B32 << 21) | (Uint << 27), // 0x20400000
R32G32B32Sint = (R32G32B32 << 21) | (Sint << 27), // 0x18400000
R8G8B8A8Unorm = (R8G8B8A8 << 21) | (Unorm << 27), // 0x11400000
R8G8B8A8Snorm = (R8G8B8A8 << 21) | (Snorm << 27), // 0x09400000
R8G8B8A8Uint = (R8G8B8A8 << 21) | (Uint << 27), // 0x21400000
R8G8B8A8Sint = (R8G8B8A8 << 21) | (Sint << 27), // 0x19400000
R16G16B16A16Float = (R16G16B16A16 << 21) | (Float << 27), // 0x38600000
R16G16B16A16Unorm = (R16G16B16A16 << 21) | (Unorm << 27), // 0x10600000
R16G16B16A16Snorm = (R16G16B16A16 << 21) | (Snorm << 27), // 0x08600000
R16G16B16A16Uint = (R16G16B16A16 << 21) | (Uint << 27), // 0x20600000
R16G16B16A16Sint = (R16G16B16A16 << 21) | (Sint << 27), // 0x18600000
R32G32B32A32Float = (R32G32B32A32 << 21) | (Float << 27), // 0x38200000
R32G32B32A32Uint = (R32G32B32A32 << 21) | (Uint << 27), // 0x20200000
R32G32B32A32Sint = (R32G32B32A32 << 21) | (Sint << 27), // 0x18200000
A2B10G10R10Unorm = (A2B10G10R10 << 21) | (Unorm << 27), // 0x16000000
A2B10G10R10Uint = (A2B10G10R10 << 21) | (Uint << 27), // 0x26000000
B10G11R11Float = (B10G11R11 << 21) | (Float << 27), // 0x3e200000
R8Uscaled = (R8 << 21) | (Uscaled << 27), // 0x2ba00000
R8Sscaled = (R8 << 21) | (Sscaled << 27), // 0x33a00000
R16Uscaled = (R16 << 21) | (Uscaled << 27), // 0x2b600000
R16Sscaled = (R16 << 21) | (Sscaled << 27), // 0x33600000
R32Uscaled = (R32 << 21) | (Uscaled << 27), // 0x2a400000
R32Sscaled = (R32 << 21) | (Sscaled << 27), // 0x32400000
R8G8Uscaled = (R8G8 << 21) | (Uscaled << 27), // 0x2b000000
R8G8Sscaled = (R8G8 << 21) | (Sscaled << 27), // 0x33000000
R16G16Uscaled = (R16G16 << 21) | (Uscaled << 27), // 0x29e00000
R16G16Sscaled = (R16G16 << 21) | (Sscaled << 27), // 0x31e00000
R32G32Uscaled = (R32G32 << 21) | (Uscaled << 27), // 0x28800000
R32G32Sscaled = (R32G32 << 21) | (Sscaled << 27), // 0x30800000
R8G8B8Uscaled = (R8G8B8 << 21) | (Uscaled << 27), // 0x2a600000
R8G8B8Sscaled = (R8G8B8 << 21) | (Sscaled << 27), // 0x32600000
R16G16B16Uscaled = (R16G16B16 << 21) | (Uscaled << 27), // 0x28a00000
R16G16B16Sscaled = (R16G16B16 << 21) | (Sscaled << 27), // 0x30a00000
R32G32B32Uscaled = (R32G32B32 << 21) | (Uscaled << 27), // 0x28400000
R32G32B32Sscaled = (R32G32B32 << 21) | (Sscaled << 27), // 0x30400000
R8G8B8A8Uscaled = (R8G8B8A8 << 21) | (Uscaled << 27), // 0x29400000
R8G8B8A8Sscaled = (R8G8B8A8 << 21) | (Sscaled << 27), // 0x31400000
R16G16B16A16Uscaled = (R16G16B16A16 << 21) | (Uscaled << 27), // 0x28600000
R16G16B16A16Sscaled = (R16G16B16A16 << 21) | (Sscaled << 27), // 0x30600000
R32G32B32A32Uscaled = (R32G32B32A32 << 21) | (Uscaled << 27), // 0x28200000
R32G32B32A32Sscaled = (R32G32B32A32 << 21) | (Sscaled << 27), // 0x30200000
A2B10G10R10Snorm = (A2B10G10R10 << 21) | (Snorm << 27), // 0x0e000000
A2B10G10R10Sint = (A2B10G10R10 << 21) | (Sint << 27), // 0x1e000000
A2B10G10R10Uscaled = (A2B10G10R10 << 21) | (Uscaled << 27), // 0x2e000000
A2B10G10R10Sscaled = (A2B10G10R10 << 21) | (Sscaled << 27), // 0x36000000
}
private static readonly Dictionary<TextureFormat, FormatInfo> _textureFormats = new Dictionary<TextureFormat, FormatInfo>()
{
{ TextureFormat.R8Unorm, new FormatInfo(Format.R8Unorm, 1, 1, 1, 1) },
{ TextureFormat.R8Snorm, new FormatInfo(Format.R8Snorm, 1, 1, 1, 1) },
{ TextureFormat.R8Uint, new FormatInfo(Format.R8Uint, 1, 1, 1, 1) },
{ TextureFormat.R8Sint, new FormatInfo(Format.R8Sint, 1, 1, 1, 1) },
{ TextureFormat.R16Float, new FormatInfo(Format.R16Float, 1, 1, 2, 1) },
{ TextureFormat.R16Unorm, new FormatInfo(Format.R16Unorm, 1, 1, 2, 1) },
{ TextureFormat.R16Snorm, new FormatInfo(Format.R16Snorm, 1, 1, 2, 1) },
{ TextureFormat.R16Uint, new FormatInfo(Format.R16Uint, 1, 1, 2, 1) },
{ TextureFormat.R16Sint, new FormatInfo(Format.R16Sint, 1, 1, 2, 1) },
{ TextureFormat.R32Float, new FormatInfo(Format.R32Float, 1, 1, 4, 1) },
{ TextureFormat.R32Uint, new FormatInfo(Format.R32Uint, 1, 1, 4, 1) },
{ TextureFormat.R32Sint, new FormatInfo(Format.R32Sint, 1, 1, 4, 1) },
{ TextureFormat.G8R8Unorm, new FormatInfo(Format.R8G8Unorm, 1, 1, 2, 2) },
{ TextureFormat.G8R8Snorm, new FormatInfo(Format.R8G8Snorm, 1, 1, 2, 2) },
{ TextureFormat.G8R8Uint, new FormatInfo(Format.R8G8Uint, 1, 1, 2, 2) },
{ TextureFormat.G8R8Sint, new FormatInfo(Format.R8G8Sint, 1, 1, 2, 2) },
{ TextureFormat.R16G16Float, new FormatInfo(Format.R16G16Float, 1, 1, 4, 2) },
{ TextureFormat.R16G16Unorm, new FormatInfo(Format.R16G16Unorm, 1, 1, 4, 2) },
{ TextureFormat.R16G16Snorm, new FormatInfo(Format.R16G16Snorm, 1, 1, 4, 2) },
{ TextureFormat.R16G16Uint, new FormatInfo(Format.R16G16Uint, 1, 1, 4, 2) },
{ TextureFormat.R16G16Sint, new FormatInfo(Format.R16G16Sint, 1, 1, 4, 2) },
{ TextureFormat.R32G32Float, new FormatInfo(Format.R32G32Float, 1, 1, 8, 2) },
{ TextureFormat.R32G32Uint, new FormatInfo(Format.R32G32Uint, 1, 1, 8, 2) },
{ TextureFormat.R32G32Sint, new FormatInfo(Format.R32G32Sint, 1, 1, 8, 2) },
{ TextureFormat.R32G32B32Float, new FormatInfo(Format.R32G32B32Float, 1, 1, 12, 3) },
{ TextureFormat.R32G32B32Uint, new FormatInfo(Format.R32G32B32Uint, 1, 1, 12, 3) },
{ TextureFormat.R32G32B32Sint, new FormatInfo(Format.R32G32B32Sint, 1, 1, 12, 3) },
{ TextureFormat.A8B8G8R8Unorm, new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4) },
{ TextureFormat.A8B8G8R8Snorm, new FormatInfo(Format.R8G8B8A8Snorm, 1, 1, 4, 4) },
{ TextureFormat.A8B8G8R8Uint, new FormatInfo(Format.R8G8B8A8Uint, 1, 1, 4, 4) },
{ TextureFormat.A8B8G8R8Sint, new FormatInfo(Format.R8G8B8A8Sint, 1, 1, 4, 4) },
{ TextureFormat.R16G16B16A16Float, new FormatInfo(Format.R16G16B16A16Float, 1, 1, 8, 4) },
{ TextureFormat.R16G16B16A16Unorm, new FormatInfo(Format.R16G16B16A16Unorm, 1, 1, 8, 4) },
{ TextureFormat.R16G16B16A16Snorm, new FormatInfo(Format.R16G16B16A16Snorm, 1, 1, 8, 4) },
{ TextureFormat.R16G16B16A16Uint, new FormatInfo(Format.R16G16B16A16Uint, 1, 1, 8, 4) },
{ TextureFormat.R16G16B16A16Sint, new FormatInfo(Format.R16G16B16A16Sint, 1, 1, 8, 4) },
{ TextureFormat.R32G32B32A32Float, new FormatInfo(Format.R32G32B32A32Float, 1, 1, 16, 4) },
{ TextureFormat.R32G32B32A32Uint, new FormatInfo(Format.R32G32B32A32Uint, 1, 1, 16, 4) },
{ TextureFormat.R32G32B32A32Sint, new FormatInfo(Format.R32G32B32A32Sint, 1, 1, 16, 4) },
{ TextureFormat.Z16Unorm, new FormatInfo(Format.D16Unorm, 1, 1, 2, 1) },
{ TextureFormat.Zf32RFloatGUintBUintAUint, new FormatInfo(Format.D32Float, 1, 1, 4, 1) },
{ TextureFormat.Zf32Float, new FormatInfo(Format.D32Float, 1, 1, 4, 1) },
{ TextureFormat.G24R8RUintGUnormBUnormAUnorm, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2) },
{ TextureFormat.Z24S8RUintGUnormBUnormAUnorm, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2) },
{ TextureFormat.Z24S8RUintGUnormBUintAUint, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2) },
{ TextureFormat.S8Z24RUnormGUintBUintAUint, new FormatInfo(Format.S8UintD24Unorm, 1, 1, 4, 2) },
{ TextureFormat.R32B24G8RFloatGUintBUnormAUnorm, new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2) },
{ TextureFormat.Zf32X24S8RFloatGUintBUnormAUnorm, new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2) },
{ TextureFormat.A8B8G8R8UnormSrgb, new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4) },
{ TextureFormat.G4R4Unorm, new FormatInfo(Format.R4G4Unorm, 1, 1, 1, 2) },
{ TextureFormat.A4B4G4R4Unorm, new FormatInfo(Format.R4G4B4A4Unorm, 1, 1, 2, 4) },
{ TextureFormat.A1B5G5R5Unorm, new FormatInfo(Format.R5G5B5A1Unorm, 1, 1, 2, 4) },
{ TextureFormat.B5G6R5Unorm, new FormatInfo(Format.R5G6B5Unorm, 1, 1, 2, 3) },
{ TextureFormat.A2B10G10R10Unorm, new FormatInfo(Format.R10G10B10A2Unorm, 1, 1, 4, 4) },
{ TextureFormat.A2B10G10R10Uint, new FormatInfo(Format.R10G10B10A2Uint, 1, 1, 4, 4) },
{ TextureFormat.Bf10Gf11Rf11Float, new FormatInfo(Format.R11G11B10Float, 1, 1, 4, 3) },
{ TextureFormat.E5B9G9R9SharedExpFloat, new FormatInfo(Format.R9G9B9E5Float, 1, 1, 4, 4) },
{ TextureFormat.Bc1Unorm, new FormatInfo(Format.Bc1RgbaUnorm, 4, 4, 8, 4) },
{ TextureFormat.Bc2Unorm, new FormatInfo(Format.Bc2Unorm, 4, 4, 16, 4) },
{ TextureFormat.Bc3Unorm, new FormatInfo(Format.Bc3Unorm, 4, 4, 16, 4) },
{ TextureFormat.Bc1UnormSrgb, new FormatInfo(Format.Bc1RgbaSrgb, 4, 4, 8, 4) },
{ TextureFormat.Bc2UnormSrgb, new FormatInfo(Format.Bc2Srgb, 4, 4, 16, 4) },
{ TextureFormat.Bc3UnormSrgb, new FormatInfo(Format.Bc3Srgb, 4, 4, 16, 4) },
{ TextureFormat.Bc4Unorm, new FormatInfo(Format.Bc4Unorm, 4, 4, 8, 1) },
{ TextureFormat.Bc4Snorm, new FormatInfo(Format.Bc4Snorm, 4, 4, 8, 1) },
{ TextureFormat.Bc5Unorm, new FormatInfo(Format.Bc5Unorm, 4, 4, 16, 2) },
{ TextureFormat.Bc5Snorm, new FormatInfo(Format.Bc5Snorm, 4, 4, 16, 2) },
{ TextureFormat.Bc7UUnorm, new FormatInfo(Format.Bc7Unorm, 4, 4, 16, 4) },
{ TextureFormat.Bc7UUnormSrgb, new FormatInfo(Format.Bc7Srgb, 4, 4, 16, 4) },
{ TextureFormat.Bc6HSf16Float, new FormatInfo(Format.Bc6HSfloat, 4, 4, 16, 4) },
{ TextureFormat.Bc6HUf16Float, new FormatInfo(Format.Bc6HUfloat, 4, 4, 16, 4) },
{ TextureFormat.Etc2RgbUnorm, new FormatInfo(Format.Etc2RgbUnorm, 4, 4, 8, 3) },
{ TextureFormat.Etc2RgbPtaUnorm, new FormatInfo(Format.Etc2RgbPtaUnorm, 4, 4, 8, 4) },
{ TextureFormat.Etc2RgbaUnorm, new FormatInfo(Format.Etc2RgbaUnorm, 4, 4, 16, 4) },
{ TextureFormat.Etc2RgbUnormSrgb, new FormatInfo(Format.Etc2RgbSrgb, 4, 4, 8, 3) },
{ TextureFormat.Etc2RgbPtaUnormSrgb, new FormatInfo(Format.Etc2RgbPtaSrgb, 4, 4, 8, 4) },
{ TextureFormat.Etc2RgbaUnormSrgb, new FormatInfo(Format.Etc2RgbaSrgb, 4, 4, 16, 4) },
{ TextureFormat.Astc2D4x4Unorm, new FormatInfo(Format.Astc4x4Unorm, 4, 4, 16, 4) },
{ TextureFormat.Astc2D5x4Unorm, new FormatInfo(Format.Astc5x4Unorm, 5, 4, 16, 4) },
{ TextureFormat.Astc2D5x5Unorm, new FormatInfo(Format.Astc5x5Unorm, 5, 5, 16, 4) },
{ TextureFormat.Astc2D6x5Unorm, new FormatInfo(Format.Astc6x5Unorm, 6, 5, 16, 4) },
{ TextureFormat.Astc2D6x6Unorm, new FormatInfo(Format.Astc6x6Unorm, 6, 6, 16, 4) },
{ TextureFormat.Astc2D8x5Unorm, new FormatInfo(Format.Astc8x5Unorm, 8, 5, 16, 4) },
{ TextureFormat.Astc2D8x6Unorm, new FormatInfo(Format.Astc8x6Unorm, 8, 6, 16, 4) },
{ TextureFormat.Astc2D8x8Unorm, new FormatInfo(Format.Astc8x8Unorm, 8, 8, 16, 4) },
{ TextureFormat.Astc2D10x5Unorm, new FormatInfo(Format.Astc10x5Unorm, 10, 5, 16, 4) },
{ TextureFormat.Astc2D10x6Unorm, new FormatInfo(Format.Astc10x6Unorm, 10, 6, 16, 4) },
{ TextureFormat.Astc2D10x8Unorm, new FormatInfo(Format.Astc10x8Unorm, 10, 8, 16, 4) },
{ TextureFormat.Astc2D10x10Unorm, new FormatInfo(Format.Astc10x10Unorm, 10, 10, 16, 4) },
{ TextureFormat.Astc2D12x10Unorm, new FormatInfo(Format.Astc12x10Unorm, 12, 10, 16, 4) },
{ TextureFormat.Astc2D12x12Unorm, new FormatInfo(Format.Astc12x12Unorm, 12, 12, 16, 4) },
{ TextureFormat.Astc2D4x4UnormSrgb, new FormatInfo(Format.Astc4x4Srgb, 4, 4, 16, 4) },
{ TextureFormat.Astc2D5x4UnormSrgb, new FormatInfo(Format.Astc5x4Srgb, 5, 4, 16, 4) },
{ TextureFormat.Astc2D5x5UnormSrgb, new FormatInfo(Format.Astc5x5Srgb, 5, 5, 16, 4) },
{ TextureFormat.Astc2D6x5UnormSrgb, new FormatInfo(Format.Astc6x5Srgb, 6, 5, 16, 4) },
{ TextureFormat.Astc2D6x6UnormSrgb, new FormatInfo(Format.Astc6x6Srgb, 6, 6, 16, 4) },
{ TextureFormat.Astc2D8x5UnormSrgb, new FormatInfo(Format.Astc8x5Srgb, 8, 5, 16, 4) },
{ TextureFormat.Astc2D8x6UnormSrgb, new FormatInfo(Format.Astc8x6Srgb, 8, 6, 16, 4) },
{ TextureFormat.Astc2D8x8UnormSrgb, new FormatInfo(Format.Astc8x8Srgb, 8, 8, 16, 4) },
{ TextureFormat.Astc2D10x5UnormSrgb, new FormatInfo(Format.Astc10x5Srgb, 10, 5, 16, 4) },
{ TextureFormat.Astc2D10x6UnormSrgb, new FormatInfo(Format.Astc10x6Srgb, 10, 6, 16, 4) },
{ TextureFormat.Astc2D10x8UnormSrgb, new FormatInfo(Format.Astc10x8Srgb, 10, 8, 16, 4) },
{ TextureFormat.Astc2D10x10UnormSrgb, new FormatInfo(Format.Astc10x10Srgb, 10, 10, 16, 4) },
{ TextureFormat.Astc2D12x10UnormSrgb, new FormatInfo(Format.Astc12x10Srgb, 12, 10, 16, 4) },
{ TextureFormat.Astc2D12x12UnormSrgb, new FormatInfo(Format.Astc12x12Srgb, 12, 12, 16, 4) },
{ TextureFormat.A5B5G5R1Unorm, new FormatInfo(Format.A1B5G5R5Unorm, 1, 1, 2, 4) }
};
private static readonly Dictionary<VertexAttributeFormat, Format> _attribFormats = new Dictionary<VertexAttributeFormat, Format>()
{
{ VertexAttributeFormat.R8Unorm, Format.R8Unorm },
{ VertexAttributeFormat.R8Snorm, Format.R8Snorm },
{ VertexAttributeFormat.R8Uint, Format.R8Uint },
{ VertexAttributeFormat.R8Sint, Format.R8Sint },
{ VertexAttributeFormat.R16Float, Format.R16Float },
{ VertexAttributeFormat.R16Unorm, Format.R16Unorm },
{ VertexAttributeFormat.R16Snorm, Format.R16Snorm },
{ VertexAttributeFormat.R16Uint, Format.R16Uint },
{ VertexAttributeFormat.R16Sint, Format.R16Sint },
{ VertexAttributeFormat.R32Float, Format.R32Float },
{ VertexAttributeFormat.R32Uint, Format.R32Uint },
{ VertexAttributeFormat.R32Sint, Format.R32Sint },
{ VertexAttributeFormat.R8G8Unorm, Format.R8G8Unorm },
{ VertexAttributeFormat.R8G8Snorm, Format.R8G8Snorm },
{ VertexAttributeFormat.R8G8Uint, Format.R8G8Uint },
{ VertexAttributeFormat.R8G8Sint, Format.R8G8Sint },
{ VertexAttributeFormat.R16G16Float, Format.R16G16Float },
{ VertexAttributeFormat.R16G16Unorm, Format.R16G16Unorm },
{ VertexAttributeFormat.R16G16Snorm, Format.R16G16Snorm },
{ VertexAttributeFormat.R16G16Uint, Format.R16G16Uint },
{ VertexAttributeFormat.R16G16Sint, Format.R16G16Sint },
{ VertexAttributeFormat.R32G32Float, Format.R32G32Float },
{ VertexAttributeFormat.R32G32Uint, Format.R32G32Uint },
{ VertexAttributeFormat.R32G32Sint, Format.R32G32Sint },
{ VertexAttributeFormat.R8G8B8Unorm, Format.R8G8B8Unorm },
{ VertexAttributeFormat.R8G8B8Snorm, Format.R8G8B8Snorm },
{ VertexAttributeFormat.R8G8B8Uint, Format.R8G8B8Uint },
{ VertexAttributeFormat.R8G8B8Sint, Format.R8G8B8Sint },
{ VertexAttributeFormat.R16G16B16Float, Format.R16G16B16Float },
{ VertexAttributeFormat.R16G16B16Unorm, Format.R16G16B16Unorm },
{ VertexAttributeFormat.R16G16B16Snorm, Format.R16G16B16Snorm },
{ VertexAttributeFormat.R16G16B16Uint, Format.R16G16B16Uint },
{ VertexAttributeFormat.R16G16B16Sint, Format.R16G16B16Sint },
{ VertexAttributeFormat.R32G32B32Float, Format.R32G32B32Float },
{ VertexAttributeFormat.R32G32B32Uint, Format.R32G32B32Uint },
{ VertexAttributeFormat.R32G32B32Sint, Format.R32G32B32Sint },
{ VertexAttributeFormat.R8G8B8A8Unorm, Format.R8G8B8A8Unorm },
{ VertexAttributeFormat.R8G8B8A8Snorm, Format.R8G8B8A8Snorm },
{ VertexAttributeFormat.R8G8B8A8Uint, Format.R8G8B8A8Uint },
{ VertexAttributeFormat.R8G8B8A8Sint, Format.R8G8B8A8Sint },
{ VertexAttributeFormat.R16G16B16A16Float, Format.R16G16B16A16Float },
{ VertexAttributeFormat.R16G16B16A16Unorm, Format.R16G16B16A16Unorm },
{ VertexAttributeFormat.R16G16B16A16Snorm, Format.R16G16B16A16Snorm },
{ VertexAttributeFormat.R16G16B16A16Uint, Format.R16G16B16A16Uint },
{ VertexAttributeFormat.R16G16B16A16Sint, Format.R16G16B16A16Sint },
{ VertexAttributeFormat.R32G32B32A32Float, Format.R32G32B32A32Float },
{ VertexAttributeFormat.R32G32B32A32Uint, Format.R32G32B32A32Uint },
{ VertexAttributeFormat.R32G32B32A32Sint, Format.R32G32B32A32Sint },
{ VertexAttributeFormat.A2B10G10R10Unorm, Format.R10G10B10A2Unorm },
{ VertexAttributeFormat.A2B10G10R10Uint, Format.R10G10B10A2Uint },
{ VertexAttributeFormat.B10G11R11Float, Format.R11G11B10Float },
{ VertexAttributeFormat.R8Uscaled, Format.R8Uscaled },
{ VertexAttributeFormat.R8Sscaled, Format.R8Sscaled },
{ VertexAttributeFormat.R16Uscaled, Format.R16Uscaled },
{ VertexAttributeFormat.R16Sscaled, Format.R16Sscaled },
{ VertexAttributeFormat.R32Uscaled, Format.R32Uscaled },
{ VertexAttributeFormat.R32Sscaled, Format.R32Sscaled },
{ VertexAttributeFormat.R8G8Uscaled, Format.R8G8Uscaled },
{ VertexAttributeFormat.R8G8Sscaled, Format.R8G8Sscaled },
{ VertexAttributeFormat.R16G16Uscaled, Format.R16G16Uscaled },
{ VertexAttributeFormat.R16G16Sscaled, Format.R16G16Sscaled },
{ VertexAttributeFormat.R32G32Uscaled, Format.R32G32Uscaled },
{ VertexAttributeFormat.R32G32Sscaled, Format.R32G32Sscaled },
{ VertexAttributeFormat.R8G8B8Uscaled, Format.R8G8B8Uscaled },
{ VertexAttributeFormat.R8G8B8Sscaled, Format.R8G8B8Sscaled },
{ VertexAttributeFormat.R16G16B16Uscaled, Format.R16G16B16Uscaled },
{ VertexAttributeFormat.R16G16B16Sscaled, Format.R16G16B16Sscaled },
{ VertexAttributeFormat.R32G32B32Uscaled, Format.R32G32B32Uscaled },
{ VertexAttributeFormat.R32G32B32Sscaled, Format.R32G32B32Sscaled },
{ VertexAttributeFormat.R8G8B8A8Uscaled, Format.R8G8B8A8Uscaled },
{ VertexAttributeFormat.R8G8B8A8Sscaled, Format.R8G8B8A8Sscaled },
{ VertexAttributeFormat.R16G16B16A16Uscaled, Format.R16G16B16A16Uscaled },
{ VertexAttributeFormat.R16G16B16A16Sscaled, Format.R16G16B16A16Sscaled },
{ VertexAttributeFormat.R32G32B32A32Uscaled, Format.R32G32B32A32Uscaled },
{ VertexAttributeFormat.R32G32B32A32Sscaled, Format.R32G32B32A32Sscaled },
{ VertexAttributeFormat.A2B10G10R10Snorm, Format.R10G10B10A2Snorm },
{ VertexAttributeFormat.A2B10G10R10Sint, Format.R10G10B10A2Sint },
{ VertexAttributeFormat.A2B10G10R10Uscaled, Format.R10G10B10A2Uscaled },
{ VertexAttributeFormat.A2B10G10R10Sscaled, Format.R10G10B10A2Sscaled }
};
/// <summary>
/// Try getting the texture format from an encoded format integer from the Maxwell texture descriptor.
/// </summary>
/// <param name="encoded">The encoded format integer from the texture descriptor</param>
/// <param name="isSrgb">Indicates if the format is a sRGB format</param>
/// <param name="format">The output texture format</param>
/// <returns>True if the format is valid, false otherwise</returns>
public static bool TryGetTextureFormat(uint encoded, bool isSrgb, out FormatInfo format)
{
encoded |= (isSrgb ? 1u << 19 : 0u);
return _textureFormats.TryGetValue((TextureFormat)encoded, out format);
}
/// <summary>
/// Try getting the vertex attribute format from an encoded format integer from Maxwell attribute registers.
/// </summary>
/// <param name="encoded">The encoded format integer from the attribute registers</param>
/// <param name="format">The output vertex attribute format</param>
/// <returns>True if the format is valid, false otherwise</returns>
public static bool TryGetAttribFormat(uint encoded, out Format format)
{
return _attribFormats.TryGetValue((VertexAttributeFormat)encoded, out format);
}
}
}

View file

@ -0,0 +1,10 @@
namespace Ryujinx.Graphics.Gpu.Image
{
interface ITextureDescriptor
{
public uint UnpackFormat();
public TextureTarget UnpackTextureTarget();
public bool UnpackSrgb();
public bool UnpackTextureCoordNormalized();
}
}

View file

@ -0,0 +1,222 @@
using Ryujinx.Cpu.Tracking;
using Ryujinx.Graphics.Gpu.Memory;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
/// Represents a pool of GPU resources, such as samplers or textures.
/// </summary>
/// <typeparam name="T1">Type of the GPU resource</typeparam>
/// <typeparam name="T2">Type of the descriptor</typeparam>
abstract class Pool<T1, T2> : IDisposable where T2 : unmanaged
{
protected const int DescriptorSize = 0x20;
protected GpuContext Context;
protected PhysicalMemory PhysicalMemory;
protected int SequenceNumber;
protected int ModifiedSequenceNumber;
protected T1[] Items;
protected T2[] DescriptorCache;
/// <summary>
/// The maximum ID value of resources on the pool (inclusive).
/// </summary>
/// <remarks>
/// The maximum amount of resources on the pool is equal to this value plus one.
/// </remarks>
public int MaximumId { get; }
/// <summary>
/// The address of the pool in guest memory.
/// </summary>
public ulong Address { get; }
/// <summary>
/// The size of the pool in bytes.
/// </summary>
public ulong Size { get; }
private readonly CpuMultiRegionHandle _memoryTracking;
private readonly Action<ulong, ulong> _modifiedDelegate;
private int _modifiedSequenceOffset;
private bool _modified;
/// <summary>
/// Creates a new instance of the GPU resource pool.
/// </summary>
/// <param name="context">GPU context that the pool belongs to</param>
/// <param name="physicalMemory">Physical memory where the resource descriptors are mapped</param>
/// <param name="address">Address of the pool in physical memory</param>
/// <param name="maximumId">Maximum index of an item on the pool (inclusive)</param>
public Pool(GpuContext context, PhysicalMemory physicalMemory, ulong address, int maximumId)
{
Context = context;
PhysicalMemory = physicalMemory;
MaximumId = maximumId;
int count = maximumId + 1;
ulong size = (ulong)(uint)count * DescriptorSize;
Items = new T1[count];
DescriptorCache = new T2[count];
Address = address;
Size = size;
_memoryTracking = physicalMemory.BeginGranularTracking(address, size, ResourceKind.Pool);
_memoryTracking.RegisterPreciseAction(address, size, PreciseAction);
_modifiedDelegate = RegionModified;
}
/// <summary>
/// Gets the descriptor for a given ID.
/// </summary>
/// <param name="id">ID of the descriptor. This is effectively a zero-based index</param>
/// <returns>The descriptor</returns>
public T2 GetDescriptor(int id)
{
return PhysicalMemory.Read<T2>(Address + (ulong)id * DescriptorSize);
}
/// <summary>
/// Gets a reference to the descriptor for a given ID.
/// </summary>
/// <param name="id">ID of the descriptor. This is effectively a zero-based index</param>
/// <returns>A reference to the descriptor</returns>
public ref readonly T2 GetDescriptorRef(int id)
{
return ref GetDescriptorRefAddress(Address + (ulong)id * DescriptorSize);
}
/// <summary>
/// Gets a reference to the descriptor for a given address.
/// </summary>
/// <param name="address">Address of the descriptor</param>
/// <returns>A reference to the descriptor</returns>
public ref readonly T2 GetDescriptorRefAddress(ulong address)
{
return ref MemoryMarshal.Cast<byte, T2>(PhysicalMemory.GetSpan(address, DescriptorSize))[0];
}
/// <summary>
/// Gets the GPU resource with the given ID.
/// </summary>
/// <param name="id">ID of the resource. This is effectively a zero-based index</param>
/// <returns>The GPU resource with the given ID</returns>
public abstract T1 Get(int id);
/// <summary>
/// Checks if a given ID is valid and inside the range of the pool.
/// </summary>
/// <param name="id">ID of the descriptor. This is effectively a zero-based index</param>
/// <returns>True if the specified ID is valid, false otherwise</returns>
public bool IsValidId(int id)
{
return (uint)id <= MaximumId;
}
/// <summary>
/// Synchronizes host memory with guest memory.
/// This causes invalidation of pool entries,
/// if a modification of entries by the CPU is detected.
/// </summary>
public void SynchronizeMemory()
{
_modified = false;
_memoryTracking.QueryModified(_modifiedDelegate);
if (_modified)
{
UpdateModifiedSequence();
}
}
/// <summary>
/// Indicate that a region of the pool was modified, and must be loaded from memory.
/// </summary>
/// <param name="mAddress">Start address of the modified region</param>
/// <param name="mSize">Size of the modified region</param>
private void RegionModified(ulong mAddress, ulong mSize)
{
_modified = true;
if (mAddress < Address)
{
mAddress = Address;
}
ulong maxSize = Address + Size - mAddress;
if (mSize > maxSize)
{
mSize = maxSize;
}
InvalidateRangeImpl(mAddress, mSize);
}
/// <summary>
/// Updates the modified sequence number using the current sequence number and offset,
/// indicating that it has been modified.
/// </summary>
protected void UpdateModifiedSequence()
{
ModifiedSequenceNumber = SequenceNumber + _modifiedSequenceOffset;
}
/// <summary>
/// An action to be performed when a precise memory access occurs to this resource.
/// Makes sure that the dirty flags are checked.
/// </summary>
/// <param name="address">Address of the memory action</param>
/// <param name="size">Size in bytes</param>
/// <param name="write">True if the access was a write, false otherwise</param>
private bool PreciseAction(ulong address, ulong size, bool write)
{
if (write && Context.SequenceNumber == SequenceNumber)
{
if (ModifiedSequenceNumber == SequenceNumber + _modifiedSequenceOffset)
{
// The modified sequence number is offset when PreciseActions occur so that
// users checking it will see an increment and know the pool has changed since
// their last look, even though the main SequenceNumber has not been changed.
_modifiedSequenceOffset++;
}
// Force the pool to be checked again the next time it is used.
SequenceNumber--;
}
return false;
}
protected abstract void InvalidateRangeImpl(ulong address, ulong size);
protected abstract void Delete(T1 item);
/// <summary>
/// Performs the disposal of all resources stored on the pool.
/// It's an error to try using the pool after disposal.
/// </summary>
public virtual void Dispose()
{
if (Items != null)
{
for (int index = 0; index < Items.Length; index++)
{
Delete(Items[index]);
}
Items = null;
}
_memoryTracking.Dispose();
}
}
}

View file

@ -0,0 +1,129 @@
using System;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
/// Resource pool interface.
/// </summary>
/// <typeparam name="T">Resource pool type</typeparam>
interface IPool<T>
{
/// <summary>
/// Start address of the pool in memory.
/// </summary>
ulong Address { get; }
/// <summary>
/// Linked list node used on the texture pool cache.
/// </summary>
LinkedListNode<T> CacheNode { get; set; }
/// <summary>
/// Timestamp set on the last use of the pool by the cache.
/// </summary>
ulong CacheTimestamp { get; set; }
}
/// <summary>
/// Pool cache.
/// This can keep multiple pools, and return the current one as needed.
/// </summary>
abstract class PoolCache<T> : IDisposable where T : IPool<T>, IDisposable
{
private const int MaxCapacity = 2;
private const ulong MinDeltaForRemoval = 20000;
private readonly GpuContext _context;
private readonly LinkedList<T> _pools;
private ulong _currentTimestamp;
/// <summary>
/// Constructs a new instance of the pool.
/// </summary>
/// <param name="context">GPU context that the texture pool belongs to</param>
public PoolCache(GpuContext context)
{
_context = context;
_pools = new LinkedList<T>();
}
/// <summary>
/// Increments the internal timestamp of the cache that is used to decide when old resources will be deleted.
/// </summary>
public void Tick()
{
_currentTimestamp++;
}
/// <summary>
/// Finds a cache texture pool, or creates a new one if not found.
/// </summary>
/// <param name="channel">GPU channel that the texture pool cache belongs to</param>
/// <param name="address">Start address of the texture pool</param>
/// <param name="maximumId">Maximum ID of the texture pool</param>
/// <returns>The found or newly created texture pool</returns>
public T FindOrCreate(GpuChannel channel, ulong address, int maximumId)
{
// Remove old entries from the cache, if possible.
while (_pools.Count > MaxCapacity && (_currentTimestamp - _pools.First.Value.CacheTimestamp) >= MinDeltaForRemoval)
{
T oldestPool = _pools.First.Value;
_pools.RemoveFirst();
oldestPool.Dispose();
oldestPool.CacheNode = null;
}
T pool;
// Try to find the pool on the cache.
for (LinkedListNode<T> node = _pools.First; node != null; node = node.Next)
{
pool = node.Value;
if (pool.Address == address)
{
if (pool.CacheNode != _pools.Last)
{
_pools.Remove(pool.CacheNode);
pool.CacheNode = _pools.AddLast(pool);
}
pool.CacheTimestamp = _currentTimestamp;
return pool;
}
}
// If not found, create a new one.
pool = CreatePool(_context, channel, address, maximumId);
pool.CacheNode = _pools.AddLast(pool);
pool.CacheTimestamp = _currentTimestamp;
return pool;
}
/// <summary>
/// Creates a new instance of the pool.
/// </summary>
/// <param name="context">GPU context that the pool belongs to</param>
/// <param name="channel">GPU channel that the pool belongs to</param>
/// <param name="address">Address of the pool in guest memory</param>
/// <param name="maximumId">Maximum ID of the pool (equal to maximum minus one)</param>
protected abstract T CreatePool(GpuContext context, GpuChannel channel, ulong address, int maximumId);
public void Dispose()
{
foreach (T pool in _pools)
{
pool.Dispose();
pool.CacheNode = null;
}
_pools.Clear();
}
}
}

View file

@ -0,0 +1,15 @@
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
/// Represents a filter used with texture minification linear filtering.
/// </summary>
/// <remarks>
/// This feature is only supported on NVIDIA GPUs.
/// </remarks>
enum ReductionFilter
{
Average,
Minimum,
Maximum
}
}

View file

@ -0,0 +1,115 @@
using Ryujinx.Graphics.GAL;
using System;
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
/// Cached sampler entry for sampler pools.
/// </summary>
class Sampler : IDisposable
{
/// <summary>
/// True if the sampler is disposed, false otherwise.
/// </summary>
public bool IsDisposed { get; private set; }
/// <summary>
/// Host sampler object.
/// </summary>
private readonly ISampler _hostSampler;
/// <summary>
/// Host sampler object, with anisotropy forced.
/// </summary>
private readonly ISampler _anisoSampler;
/// <summary>
/// Creates a new instance of the cached sampler.
/// </summary>
/// <param name="context">The GPU context the sampler belongs to</param>
/// <param name="descriptor">The Maxwell sampler descriptor</param>
public Sampler(GpuContext context, SamplerDescriptor descriptor)
{
MinFilter minFilter = descriptor.UnpackMinFilter();
MagFilter magFilter = descriptor.UnpackMagFilter();
bool seamlessCubemap = descriptor.UnpackSeamlessCubemap();
AddressMode addressU = descriptor.UnpackAddressU();
AddressMode addressV = descriptor.UnpackAddressV();
AddressMode addressP = descriptor.UnpackAddressP();
CompareMode compareMode = descriptor.UnpackCompareMode();
CompareOp compareOp = descriptor.UnpackCompareOp();
ColorF color = new ColorF(
descriptor.BorderColorR,
descriptor.BorderColorG,
descriptor.BorderColorB,
descriptor.BorderColorA);
float minLod = descriptor.UnpackMinLod();
float maxLod = descriptor.UnpackMaxLod();
float mipLodBias = descriptor.UnpackMipLodBias();
float maxRequestedAnisotropy = descriptor.UnpackMaxAnisotropy();
float maxSupportedAnisotropy = context.Capabilities.MaximumSupportedAnisotropy;
_hostSampler = context.Renderer.CreateSampler(new SamplerCreateInfo(
minFilter,
magFilter,
seamlessCubemap,
addressU,
addressV,
addressP,
compareMode,
compareOp,
color,
minLod,
maxLod,
mipLodBias,
Math.Min(maxRequestedAnisotropy, maxSupportedAnisotropy)));
if (GraphicsConfig.MaxAnisotropy >= 0 && GraphicsConfig.MaxAnisotropy <= 16 && (minFilter == MinFilter.LinearMipmapNearest || minFilter == MinFilter.LinearMipmapLinear))
{
maxRequestedAnisotropy = GraphicsConfig.MaxAnisotropy;
_anisoSampler = context.Renderer.CreateSampler(new SamplerCreateInfo(
minFilter,
magFilter,
seamlessCubemap,
addressU,
addressV,
addressP,
compareMode,
compareOp,
color,
minLod,
maxLod,
mipLodBias,
Math.Min(maxRequestedAnisotropy, maxSupportedAnisotropy)));
}
}
/// <summary>
/// Gets a host sampler for the given texture.
/// </summary>
/// <param name="texture">Texture to be sampled</param>
/// <returns>A host sampler</returns>
public ISampler GetHostSampler(Texture texture)
{
return _anisoSampler != null && texture?.CanForceAnisotropy == true ? _anisoSampler : _hostSampler;
}
/// <summary>
/// Disposes the host sampler object.
/// </summary>
public void Dispose()
{
IsDisposed = true;
_hostSampler.Dispose();
_anisoSampler?.Dispose();
}
}
}

View file

@ -0,0 +1,260 @@
using Ryujinx.Graphics.GAL;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
/// Maxwell sampler descriptor structure.
/// This structure defines the sampler descriptor as it is packed on the GPU sampler pool region.
/// </summary>
struct SamplerDescriptor
{
private static readonly float[] _f5ToF32ConversionLut = new float[]
{
0.0f,
0.055555556f,
0.1f,
0.13636364f,
0.16666667f,
0.1923077f,
0.21428572f,
0.23333333f,
0.25f,
0.2777778f,
0.3f,
0.3181818f,
0.33333334f,
0.34615386f,
0.35714287f,
0.36666667f,
0.375f,
0.3888889f,
0.4f,
0.4090909f,
0.41666666f,
0.42307693f,
0.42857143f,
0.43333334f,
0.4375f,
0.44444445f,
0.45f,
0.45454547f,
0.45833334f,
0.46153846f,
0.4642857f,
0.46666667f
};
private static readonly float[] _maxAnisotropyLut = new float[]
{
1, 2, 4, 6, 8, 10, 12, 16
};
private const float Frac8ToF32 = 1.0f / 256.0f;
#pragma warning disable CS0649
public uint Word0;
public uint Word1;
public uint Word2;
public uint Word3;
public float BorderColorR;
public float BorderColorG;
public float BorderColorB;
public float BorderColorA;
#pragma warning restore CS0649
/// <summary>
/// Unpacks the texture wrap mode along the X axis.
/// </summary>
/// <returns>The texture wrap mode enum</returns>
public AddressMode UnpackAddressU()
{
return (AddressMode)(Word0 & 7);
}
// <summary>
/// Unpacks the texture wrap mode along the Y axis.
/// </summary>
/// <returns>The texture wrap mode enum</returns>
public AddressMode UnpackAddressV()
{
return (AddressMode)((Word0 >> 3) & 7);
}
// <summary>
/// Unpacks the texture wrap mode along the Z axis.
/// </summary>
/// <returns>The texture wrap mode enum</returns>
public AddressMode UnpackAddressP()
{
return (AddressMode)((Word0 >> 6) & 7);
}
/// <summary>
/// Unpacks the compare mode used for depth comparison on the shader, for
/// depth buffer texture.
/// This is only relevant for shaders with shadow samplers.
/// </summary>
/// <returns>The depth comparison mode enum</returns>
public CompareMode UnpackCompareMode()
{
return (CompareMode)((Word0 >> 9) & 1);
}
/// <summary>
/// Unpacks the compare operation used for depth comparison on the shader, for
/// depth buffer texture.
/// This is only relevant for shaders with shadow samplers.
/// </summary>
/// <returns>The depth comparison operation enum</returns>
public CompareOp UnpackCompareOp()
{
return (CompareOp)(((Word0 >> 10) & 7) + 1);
}
/// <summary>
/// Unpacks and converts the maximum anisotropy value used for texture anisotropic filtering.
/// </summary>
/// <returns>The maximum anisotropy</returns>
public float UnpackMaxAnisotropy()
{
return _maxAnisotropyLut[(Word0 >> 20) & 7];
}
/// <summary>
/// Unpacks the texture magnification filter.
/// This defines the filtering used when the texture covers an area on the screen
/// that is larger than the texture size.
/// </summary>
/// <returns>The magnification filter</returns>
public MagFilter UnpackMagFilter()
{
return (MagFilter)(Word1 & 3);
}
/// <summary>
/// Unpacks the texture minification filter.
/// This defines the filtering used when the texture covers an area on the screen
/// that is smaller than the texture size.
/// </summary>
/// <returns>The minification filter</returns>
public MinFilter UnpackMinFilter()
{
SamplerMinFilter minFilter = (SamplerMinFilter)((Word1 >> 4) & 3);
SamplerMipFilter mipFilter = (SamplerMipFilter)((Word1 >> 6) & 3);
return ConvertFilter(minFilter, mipFilter);
}
/// <summary>
/// Converts two minification and filter enum, to a single minification enum,
/// including mipmap filtering information, as expected from the host API.
/// </summary>
/// <param name="minFilter">The minification filter</param>
/// <param name="mipFilter">The mipmap level filter</param>
/// <returns>The combined, host API compatible filter enum</returns>
private static MinFilter ConvertFilter(SamplerMinFilter minFilter, SamplerMipFilter mipFilter)
{
switch (mipFilter)
{
case SamplerMipFilter.None:
switch (minFilter)
{
case SamplerMinFilter.Nearest: return MinFilter.Nearest;
case SamplerMinFilter.Linear: return MinFilter.Linear;
}
break;
case SamplerMipFilter.Nearest:
switch (minFilter)
{
case SamplerMinFilter.Nearest: return MinFilter.NearestMipmapNearest;
case SamplerMinFilter.Linear: return MinFilter.LinearMipmapNearest;
}
break;
case SamplerMipFilter.Linear:
switch (minFilter)
{
case SamplerMinFilter.Nearest: return MinFilter.NearestMipmapLinear;
case SamplerMinFilter.Linear: return MinFilter.LinearMipmapLinear;
}
break;
}
return MinFilter.Nearest;
}
/// <summary>
/// Unpacks the seamless cubemap flag.
/// </summary>
/// <returns>The seamless cubemap flag</returns>
public bool UnpackSeamlessCubemap()
{
return (Word1 & (1 << 9)) != 0;
}
/// <summary>
/// Unpacks the reduction filter, used with texture minification linear filtering.
/// This describes how the final value will be computed from neighbouring pixels.
/// </summary>
/// <returns>The reduction filter</returns>
public ReductionFilter UnpackReductionFilter()
{
return (ReductionFilter)((Word1 >> 10) & 3);
}
/// <summary>
/// Unpacks the level-of-detail bias value.
/// This is a bias added to the level-of-detail value as computed by the GPU, used to select
/// which mipmap level to use from a given texture.
/// </summary>
/// <returns>The level-of-detail bias value</returns>
public float UnpackMipLodBias()
{
int fixedValue = (int)(Word1 >> 12) & 0x1fff;
fixedValue = (fixedValue << 19) >> 19;
return fixedValue * Frac8ToF32;
}
/// <summary>
/// Unpacks the level-of-detail snap value.
/// </summary>
/// <returns>The level-of-detail snap value</returns>
public float UnpackLodSnap()
{
return _f5ToF32ConversionLut[(Word1 >> 26) & 0x1f];
}
/// <summary>
/// Unpacks the minimum level-of-detail value.
/// </summary>
/// <returns>The minimum level-of-detail value</returns>
public float UnpackMinLod()
{
return (Word2 & 0xfff) * Frac8ToF32;
}
/// <summary>
/// Unpacks the maximum level-of-detail value.
/// </summary>
/// <returns>The maximum level-of-detail value</returns>
public float UnpackMaxLod()
{
return ((Word2 >> 12) & 0xfff) * Frac8ToF32;
}
/// <summary>
/// Check if two descriptors are equal.
/// </summary>
/// <param name="other">The descriptor to compare against</param>
/// <returns>True if they are equal, false otherwise</returns>
public bool Equals(ref SamplerDescriptor other)
{
return Unsafe.As<SamplerDescriptor, Vector256<byte>>(ref this).Equals(Unsafe.As<SamplerDescriptor, Vector256<byte>>(ref other));
}
}
}

View file

@ -0,0 +1,11 @@
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
/// Sampler texture minification filter.
/// </summary>
enum SamplerMinFilter
{
Nearest = 1,
Linear
}
}

View file

@ -0,0 +1,12 @@
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
/// Sampler texture mipmap level filter.
/// </summary>
enum SamplerMipFilter
{
None = 1,
Nearest,
Linear
}
}

View file

@ -0,0 +1,162 @@
using Ryujinx.Graphics.Gpu.Memory;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
/// Sampler pool.
/// </summary>
class SamplerPool : Pool<Sampler, SamplerDescriptor>, IPool<SamplerPool>
{
private float _forcedAnisotropy;
/// <summary>
/// Linked list node used on the sampler pool cache.
/// </summary>
public LinkedListNode<SamplerPool> CacheNode { get; set; }
/// <summary>
/// Timestamp used by the sampler pool cache, updated on every use of this sampler pool.
/// </summary>
public ulong CacheTimestamp { get; set; }
/// <summary>
/// Creates a new instance of the sampler pool.
/// </summary>
/// <param name="context">GPU context that the sampler pool belongs to</param>
/// <param name="physicalMemory">Physical memory where the sampler descriptors are mapped</param>
/// <param name="address">Address of the sampler pool in guest memory</param>
/// <param name="maximumId">Maximum sampler ID of the sampler pool (equal to maximum samplers minus one)</param>
public SamplerPool(GpuContext context, PhysicalMemory physicalMemory, ulong address, int maximumId) : base(context, physicalMemory, address, maximumId)
{
_forcedAnisotropy = GraphicsConfig.MaxAnisotropy;
}
/// <summary>
/// Gets the sampler with the given ID.
/// </summary>
/// <param name="id">ID of the sampler. This is effectively a zero-based index</param>
/// <returns>The sampler with the given ID</returns>
public override Sampler Get(int id)
{
if ((uint)id >= Items.Length)
{
return null;
}
if (SequenceNumber != Context.SequenceNumber)
{
if (_forcedAnisotropy != GraphicsConfig.MaxAnisotropy)
{
_forcedAnisotropy = GraphicsConfig.MaxAnisotropy;
for (int i = 0; i < Items.Length; i++)
{
if (Items[i] != null)
{
Items[i].Dispose();
Items[i] = null;
}
}
UpdateModifiedSequence();
}
SequenceNumber = Context.SequenceNumber;
SynchronizeMemory();
}
Sampler sampler = Items[id];
if (sampler == null)
{
SamplerDescriptor descriptor = GetDescriptor(id);
sampler = new Sampler(Context, descriptor);
Items[id] = sampler;
DescriptorCache[id] = descriptor;
}
return sampler;
}
/// <summary>
/// Checks if the pool was modified, and returns the last sequence number where a modification was detected.
/// </summary>
/// <returns>A number that increments each time a modification is detected</returns>
public int CheckModified()
{
if (SequenceNumber != Context.SequenceNumber)
{
SequenceNumber = Context.SequenceNumber;
if (_forcedAnisotropy != GraphicsConfig.MaxAnisotropy)
{
_forcedAnisotropy = GraphicsConfig.MaxAnisotropy;
for (int i = 0; i < Items.Length; i++)
{
if (Items[i] != null)
{
Items[i].Dispose();
Items[i] = null;
}
}
UpdateModifiedSequence();
}
SynchronizeMemory();
}
return ModifiedSequenceNumber;
}
/// <summary>
/// Implementation of the sampler pool range invalidation.
/// </summary>
/// <param name="address">Start address of the range of the sampler pool</param>
/// <param name="size">Size of the range being invalidated</param>
protected override void InvalidateRangeImpl(ulong address, ulong size)
{
ulong endAddress = address + size;
for (; address < endAddress; address += DescriptorSize)
{
int id = (int)((address - Address) / DescriptorSize);
Sampler sampler = Items[id];
if (sampler != null)
{
SamplerDescriptor descriptor = GetDescriptor(id);
// If the descriptors are the same, the sampler is still valid.
if (descriptor.Equals(ref DescriptorCache[id]))
{
continue;
}
sampler.Dispose();
Items[id] = null;
}
}
}
/// <summary>
/// Deletes a given sampler pool entry.
/// The host memory used by the sampler is released by the driver.
/// </summary>
/// <param name="item">The entry to be deleted</param>
protected override void Delete(Sampler item)
{
item?.Dispose();
}
}
}

View file

@ -0,0 +1,30 @@
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
/// Sampler pool cache.
/// This can keep multiple sampler pools, and return the current one as needed.
/// It is useful for applications that uses multiple sampler pools.
/// </summary>
class SamplerPoolCache : PoolCache<SamplerPool>
{
/// <summary>
/// Constructs a new instance of the texture pool.
/// </summary>
/// <param name="context">GPU context that the texture pool belongs to</param>
public SamplerPoolCache(GpuContext context) : base(context)
{
}
/// <summary>
/// Creates a new instance of the sampler pool.
/// </summary>
/// <param name="context">GPU context that the sampler pool belongs to</param>
/// <param name="channel">GPU channel that the texture pool belongs to</param>
/// <param name="address">Address of the sampler pool in guest memory</param>
/// <param name="maximumId">Maximum sampler ID of the sampler pool (equal to maximum samplers minus one)</param>
protected override SamplerPool CreatePool(GpuContext context, GpuChannel channel, ulong address, int maximumId)
{
return new SamplerPool(context, channel.MemoryManager.Physical, address, maximumId);
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,73 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
/// Texture binding information.
/// This is used for textures that needs to be accessed from shaders.
/// </summary>
readonly struct TextureBindingInfo
{
/// <summary>
/// Shader sampler target type.
/// </summary>
public Target Target { get; }
/// <summary>
/// For images, indicates the format specified on the shader.
/// </summary>
public Format Format { get; }
/// <summary>
/// Shader texture host binding point.
/// </summary>
public int Binding { get; }
/// <summary>
/// Constant buffer slot with the texture handle.
/// </summary>
public int CbufSlot { get; }
/// <summary>
/// Index of the texture handle on the constant buffer at slot <see cref="CbufSlot"/>.
/// </summary>
public int Handle { get; }
/// <summary>
/// Flags from the texture descriptor that indicate how the texture is used.
/// </summary>
public TextureUsageFlags Flags { get; }
/// <summary>
/// Constructs the texture binding information structure.
/// </summary>
/// <param name="target">The shader sampler target type</param>
/// <param name="format">Format of the image as declared on the shader</param>
/// <param name="binding">The shader texture binding point</param>
/// <param name="cbufSlot">Constant buffer slot where the texture handle is located</param>
/// <param name="handle">The shader texture handle (read index into the texture constant buffer)</param>
/// <param name="flags">The texture's usage flags, indicating how it is used in the shader</param>
public TextureBindingInfo(Target target, Format format, int binding, int cbufSlot, int handle, TextureUsageFlags flags)
{
Target = target;
Format = format;
Binding = binding;
CbufSlot = cbufSlot;
Handle = handle;
Flags = flags;
}
/// <summary>
/// Constructs the texture binding information structure.
/// </summary>
/// <param name="target">The shader sampler target type</param>
/// <param name="binding">The shader texture binding point</param>
/// <param name="cbufSlot">Constant buffer slot where the texture handle is located</param>
/// <param name="handle">The shader texture handle (read index into the texture constant buffer)</param>
/// <param name="flags">The texture's usage flags, indicating how it is used in the shader</param>
public TextureBindingInfo(Target target, int binding, int cbufSlot, int handle, TextureUsageFlags flags) : this(target, (Format)0, binding, cbufSlot, handle, flags)
{
}
}
}

View file

@ -0,0 +1,882 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Types;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Gpu.Shader;
using Ryujinx.Graphics.Shader;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
/// Texture bindings manager.
/// </summary>
class TextureBindingsManager
{
private const int InitialTextureStateSize = 32;
private const int InitialImageStateSize = 8;
private readonly GpuContext _context;
private readonly bool _isCompute;
private ulong _texturePoolGpuVa;
private int _texturePoolMaximumId;
private TexturePool _texturePool;
private ulong _samplerPoolGpuVa;
private int _samplerPoolMaximumId;
private SamplerIndex _samplerIndex;
private SamplerPool _samplerPool;
private readonly GpuChannel _channel;
private readonly TexturePoolCache _texturePoolCache;
private readonly SamplerPoolCache _samplerPoolCache;
private TexturePool _cachedTexturePool;
private SamplerPool _cachedSamplerPool;
private TextureBindingInfo[][] _textureBindings;
private TextureBindingInfo[][] _imageBindings;
private struct TextureState
{
public ITexture Texture;
public ISampler Sampler;
public int TextureHandle;
public int SamplerHandle;
public Format ImageFormat;
public int InvalidatedSequence;
public Texture CachedTexture;
public Sampler CachedSampler;
}
private TextureState[] _textureState;
private TextureState[] _imageState;
private int _texturePoolSequence;
private int _samplerPoolSequence;
private int _textureBufferIndex;
private readonly float[] _scales;
private bool _scaleChanged;
private int _lastFragmentTotal;
/// <summary>
/// Constructs a new instance of the texture bindings manager.
/// </summary>
/// <param name="context">The GPU context that the texture bindings manager belongs to</param>
/// <param name="channel">The GPU channel that the texture bindings manager belongs to</param>
/// <param name="texturePoolCache">Texture pools cache used to get texture pools from</param>
/// <param name="samplerPoolCache">Sampler pools cache used to get sampler pools from</param>
/// <param name="scales">Array where the scales for the currently bound textures are stored</param>
/// <param name="isCompute">True if the bindings manager is used for the compute engine</param>
public TextureBindingsManager(
GpuContext context,
GpuChannel channel,
TexturePoolCache texturePoolCache,
SamplerPoolCache samplerPoolCache,
float[] scales,
bool isCompute)
{
_context = context;
_channel = channel;
_texturePoolCache = texturePoolCache;
_samplerPoolCache = samplerPoolCache;
_scales = scales;
_isCompute = isCompute;
int stages = isCompute ? 1 : Constants.ShaderStages;
_textureBindings = new TextureBindingInfo[stages][];
_imageBindings = new TextureBindingInfo[stages][];
_textureState = new TextureState[InitialTextureStateSize];
_imageState = new TextureState[InitialImageStateSize];
for (int stage = 0; stage < stages; stage++)
{
_textureBindings[stage] = new TextureBindingInfo[InitialTextureStateSize];
_imageBindings[stage] = new TextureBindingInfo[InitialImageStateSize];
}
}
/// <summary>
/// Sets the texture and image bindings.
/// </summary>
/// <param name="bindings">Bindings for the active shader</param>
public void SetBindings(CachedShaderBindings bindings)
{
_textureBindings = bindings.TextureBindings;
_imageBindings = bindings.ImageBindings;
SetMaxBindings(bindings.MaxTextureBinding, bindings.MaxImageBinding);
}
/// <summary>
/// Sets the max binding indexes for textures and images.
/// </summary>
/// <param name="maxTextureBinding">The maximum texture binding</param>
/// <param name="maxImageBinding">The maximum image binding</param>
public void SetMaxBindings(int maxTextureBinding, int maxImageBinding)
{
if (maxTextureBinding >= _textureState.Length)
{
Array.Resize(ref _textureState, maxTextureBinding + 1);
}
if (maxImageBinding >= _imageState.Length)
{
Array.Resize(ref _imageState, maxImageBinding + 1);
}
}
/// <summary>
/// Sets the textures constant buffer index.
/// The constant buffer specified holds the texture handles.
/// </summary>
/// <param name="index">Constant buffer index</param>
public void SetTextureBufferIndex(int index)
{
_textureBufferIndex = index;
}
/// <summary>
/// Sets the current texture sampler pool to be used.
/// </summary>
/// <param name="gpuVa">Start GPU virtual address of the pool</param>
/// <param name="maximumId">Maximum ID of the pool (total count minus one)</param>
/// <param name="samplerIndex">Type of the sampler pool indexing used for bound samplers</param>
public void SetSamplerPool(ulong gpuVa, int maximumId, SamplerIndex samplerIndex)
{
_samplerPoolGpuVa = gpuVa;
_samplerPoolMaximumId = maximumId;
_samplerIndex = samplerIndex;
_samplerPool = null;
}
/// <summary>
/// Sets the current texture pool to be used.
/// </summary>
/// <param name="gpuVa">Start GPU virtual address of the pool</param>
/// <param name="maximumId">Maximum ID of the pool (total count minus one)</param>
public void SetTexturePool(ulong gpuVa, int maximumId)
{
_texturePoolGpuVa = gpuVa;
_texturePoolMaximumId = maximumId;
_texturePool = null;
}
/// <summary>
/// Gets a texture and a sampler from their respective pools from a texture ID and a sampler ID.
/// </summary>
/// <param name="textureId">ID of the texture</param>
/// <param name="samplerId">ID of the sampler</param>
public (Texture, Sampler) GetTextureAndSampler(int textureId, int samplerId)
{
(TexturePool texturePool, SamplerPool samplerPool) = GetPools();
return (texturePool.Get(textureId), samplerPool.Get(samplerId));
}
/// <summary>
/// Updates the texture scale for a given texture or image.
/// </summary>
/// <param name="texture">Start GPU virtual address of the pool</param>
/// <param name="usageFlags">The related texture usage flags</param>
/// <param name="index">The texture/image binding index</param>
/// <param name="stage">The active shader stage</param>
/// <returns>True if the given texture has become blacklisted, indicating that its host texture may have changed.</returns>
private bool UpdateScale(Texture texture, TextureUsageFlags usageFlags, int index, ShaderStage stage)
{
float result = 1f;
bool changed = false;
if ((usageFlags & TextureUsageFlags.NeedsScaleValue) != 0 && texture != null)
{
if ((usageFlags & TextureUsageFlags.ResScaleUnsupported) != 0)
{
changed = texture.ScaleMode != TextureScaleMode.Blacklisted;
texture.BlacklistScale();
}
else
{
switch (stage)
{
case ShaderStage.Fragment:
float scale = texture.ScaleFactor;
if (scale != 1)
{
Texture activeTarget = _channel.TextureManager.GetAnyRenderTarget();
if (activeTarget != null && (activeTarget.Info.Width / (float)texture.Info.Width) == (activeTarget.Info.Height / (float)texture.Info.Height))
{
// If the texture's size is a multiple of the sampler size, enable interpolation using gl_FragCoord. (helps "invent" new integer values between scaled pixels)
result = -scale;
break;
}
}
result = scale;
break;
case ShaderStage.Vertex:
int fragmentIndex = (int)ShaderStage.Fragment - 1;
index += _textureBindings[fragmentIndex].Length + _imageBindings[fragmentIndex].Length;
result = texture.ScaleFactor;
break;
case ShaderStage.Compute:
result = texture.ScaleFactor;
break;
}
}
}
if (result != _scales[index])
{
_scaleChanged = true;
_scales[index] = result;
}
return changed;
}
/// <summary>
/// Determines if the vertex stage requires a scale value.
/// </summary>
private bool VertexRequiresScale()
{
for (int i = 0; i < _textureBindings[0].Length; i++)
{
if ((_textureBindings[0][i].Flags & TextureUsageFlags.NeedsScaleValue) != 0)
{
return true;
}
}
for (int i = 0; i < _imageBindings[0].Length; i++)
{
if ((_imageBindings[0][i].Flags & TextureUsageFlags.NeedsScaleValue) != 0)
{
return true;
}
}
return false;
}
/// <summary>
/// Uploads texture and image scales to the backend when they are used.
/// </summary>
private void CommitRenderScale()
{
// Stage 0 total: Compute or Vertex.
int total = _textureBindings[0].Length + _imageBindings[0].Length;
int fragmentIndex = (int)ShaderStage.Fragment - 1;
int fragmentTotal = _isCompute ? 0 : (_textureBindings[fragmentIndex].Length + _imageBindings[fragmentIndex].Length);
if (total != 0 && fragmentTotal != _lastFragmentTotal && VertexRequiresScale())
{
// Must update scales in the support buffer if:
// - Vertex stage has bindings that require scale.
// - Fragment stage binding count has been updated since last render scale update.
_scaleChanged = true;
}
if (_scaleChanged)
{
if (!_isCompute)
{
total += fragmentTotal; // Add the fragment bindings to the total.
}
_lastFragmentTotal = fragmentTotal;
_context.Renderer.Pipeline.UpdateRenderScale(_scales, total, fragmentTotal);
_scaleChanged = false;
}
}
/// <summary>
/// Ensures that the bindings are visible to the host GPU.
/// Note: this actually performs the binding using the host graphics API.
/// </summary>
/// <param name="specState">Specialization state for the bound shader</param>
/// <returns>True if all bound textures match the current shader specialiation state, false otherwise</returns>
public bool CommitBindings(ShaderSpecializationState specState)
{
(TexturePool texturePool, SamplerPool samplerPool) = GetPools();
// Check if the texture pool has been modified since bindings were last committed.
// If it wasn't, then it's possible to avoid looking up textures again when the handle remains the same.
if (_cachedTexturePool != texturePool || _cachedSamplerPool != samplerPool)
{
Rebind();
_cachedTexturePool = texturePool;
_cachedSamplerPool = samplerPool;
}
bool poolModified = false;
if (texturePool != null)
{
int texturePoolSequence = texturePool.CheckModified();
if (_texturePoolSequence != texturePoolSequence)
{
poolModified = true;
_texturePoolSequence = texturePoolSequence;
}
}
if (samplerPool != null)
{
int samplerPoolSequence = samplerPool.CheckModified();
if (_samplerPoolSequence != samplerPoolSequence)
{
poolModified = true;
_samplerPoolSequence = samplerPoolSequence;
}
}
bool specStateMatches = true;
if (_isCompute)
{
specStateMatches &= CommitTextureBindings(texturePool, samplerPool, ShaderStage.Compute, 0, poolModified, specState);
specStateMatches &= CommitImageBindings(texturePool, ShaderStage.Compute, 0, poolModified, specState);
}
else
{
for (ShaderStage stage = ShaderStage.Vertex; stage <= ShaderStage.Fragment; stage++)
{
int stageIndex = (int)stage - 1;
specStateMatches &= CommitTextureBindings(texturePool, samplerPool, stage, stageIndex, poolModified, specState);
specStateMatches &= CommitImageBindings(texturePool, stage, stageIndex, poolModified, specState);
}
}
CommitRenderScale();
return specStateMatches;
}
/// <summary>
/// Fetch the constant buffers used for a texture to cache.
/// </summary>
/// <param name="stageIndex">Stage index of the constant buffer</param>
/// <param name="cachedTextureBufferIndex">The currently cached texture buffer index</param>
/// <param name="cachedSamplerBufferIndex">The currently cached sampler buffer index</param>
/// <param name="cachedTextureBuffer">The currently cached texture buffer data</param>
/// <param name="cachedSamplerBuffer">The currently cached sampler buffer data</param>
/// <param name="textureBufferIndex">The new texture buffer index</param>
/// <param name="samplerBufferIndex">The new sampler buffer index</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void UpdateCachedBuffer(
int stageIndex,
scoped ref int cachedTextureBufferIndex,
scoped ref int cachedSamplerBufferIndex,
scoped ref ReadOnlySpan<int> cachedTextureBuffer,
scoped ref ReadOnlySpan<int> cachedSamplerBuffer,
int textureBufferIndex,
int samplerBufferIndex)
{
if (textureBufferIndex != cachedTextureBufferIndex)
{
ref BufferBounds bounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, textureBufferIndex);
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(bounds.Address, (int)bounds.Size));
cachedTextureBufferIndex = textureBufferIndex;
if (samplerBufferIndex == textureBufferIndex)
{
cachedSamplerBuffer = cachedTextureBuffer;
cachedSamplerBufferIndex = samplerBufferIndex;
}
}
if (samplerBufferIndex != cachedSamplerBufferIndex)
{
ref BufferBounds bounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, samplerBufferIndex);
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(bounds.Address, (int)bounds.Size));
cachedSamplerBufferIndex = samplerBufferIndex;
}
}
/// <summary>
/// Counts the total number of texture bindings used by all shader stages.
/// </summary>
/// <returns>The total amount of textures used</returns>
private int GetTextureBindingsCount()
{
int count = 0;
for (int i = 0; i < _textureBindings.Length; i++)
{
if (_textureBindings[i] != null)
{
count += _textureBindings[i].Length;
}
}
return count;
}
/// <summary>
/// Ensures that the texture bindings are visible to the host GPU.
/// Note: this actually performs the binding using the host graphics API.
/// </summary>
/// <param name="texturePool">The current texture pool</param>
/// <param name="samplerPool">The current sampler pool</param>
/// <param name="stage">The shader stage using the textures to be bound</param>
/// <param name="stageIndex">The stage number of the specified shader stage</param
/// <param name="poolModified">True if either the texture or sampler pool was modified, false otherwise</param>
/// <param name="specState">Specialization state for the bound shader</param>
/// <returns>True if all bound textures match the current shader specialiation state, false otherwise</returns>
private bool CommitTextureBindings(
TexturePool texturePool,
SamplerPool samplerPool,
ShaderStage stage,
int stageIndex,
bool poolModified,
ShaderSpecializationState specState)
{
int textureCount = _textureBindings[stageIndex].Length;
if (textureCount == 0)
{
return true;
}
if (texturePool == null)
{
Logger.Error?.Print(LogClass.Gpu, $"Shader stage \"{stage}\" uses textures, but texture pool was not set.");
return true;
}
bool specStateMatches = true;
int cachedTextureBufferIndex = -1;
int cachedSamplerBufferIndex = -1;
ReadOnlySpan<int> cachedTextureBuffer = Span<int>.Empty;
ReadOnlySpan<int> cachedSamplerBuffer = Span<int>.Empty;
for (int index = 0; index < textureCount; index++)
{
TextureBindingInfo bindingInfo = _textureBindings[stageIndex][index];
TextureUsageFlags usageFlags = bindingInfo.Flags;
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex);
UpdateCachedBuffer(stageIndex, ref cachedTextureBufferIndex, ref cachedSamplerBufferIndex, ref cachedTextureBuffer, ref cachedSamplerBuffer, textureBufferIndex, samplerBufferIndex);
int packedId = TextureHandle.ReadPackedId(bindingInfo.Handle, cachedTextureBuffer, cachedSamplerBuffer);
int textureId = TextureHandle.UnpackTextureId(packedId);
int samplerId;
if (_samplerIndex == SamplerIndex.ViaHeaderIndex)
{
samplerId = textureId;
}
else
{
samplerId = TextureHandle.UnpackSamplerId(packedId);
}
ref TextureState state = ref _textureState[bindingInfo.Binding];
if (!poolModified &&
state.TextureHandle == textureId &&
state.SamplerHandle == samplerId &&
state.CachedTexture != null &&
state.CachedTexture.InvalidatedSequence == state.InvalidatedSequence &&
state.CachedSampler?.IsDisposed != true)
{
// The texture is already bound.
state.CachedTexture.SynchronizeMemory();
if ((usageFlags & TextureUsageFlags.NeedsScaleValue) != 0 &&
UpdateScale(state.CachedTexture, usageFlags, index, stage))
{
ITexture hostTextureRebind = state.CachedTexture.GetTargetTexture(bindingInfo.Target);
state.Texture = hostTextureRebind;
_context.Renderer.Pipeline.SetTextureAndSampler(stage, bindingInfo.Binding, hostTextureRebind, state.Sampler);
}
continue;
}
state.TextureHandle = textureId;
state.SamplerHandle = samplerId;
ref readonly TextureDescriptor descriptor = ref texturePool.GetForBinding(textureId, out Texture texture);
specStateMatches &= specState.MatchesTexture(stage, index, descriptor);
Sampler sampler = samplerPool?.Get(samplerId);
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
ISampler hostSampler = sampler?.GetHostSampler(texture);
if (hostTexture != null && texture.Target == Target.TextureBuffer)
{
// Ensure that the buffer texture is using the correct buffer as storage.
// Buffers are frequently re-created to accomodate larger data, so we need to re-bind
// to ensure we're not using a old buffer that was already deleted.
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, bindingInfo.Format, false);
// Cache is not used for buffer texture, it must always rebind.
state.CachedTexture = null;
}
else
{
bool textureOrSamplerChanged = state.Texture != hostTexture || state.Sampler != hostSampler;
if ((usageFlags & TextureUsageFlags.NeedsScaleValue) != 0 &&
UpdateScale(texture, usageFlags, index, stage))
{
hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
textureOrSamplerChanged = true;
}
if (textureOrSamplerChanged)
{
state.Texture = hostTexture;
state.Sampler = hostSampler;
_context.Renderer.Pipeline.SetTextureAndSampler(stage, bindingInfo.Binding, hostTexture, hostSampler);
}
state.CachedTexture = texture;
state.CachedSampler = sampler;
state.InvalidatedSequence = texture?.InvalidatedSequence ?? 0;
}
}
return specStateMatches;
}
/// <summary>
/// Ensures that the image bindings are visible to the host GPU.
/// Note: this actually performs the binding using the host graphics API.
/// </summary>
/// <param name="pool">The current texture pool</param>
/// <param name="stage">The shader stage using the textures to be bound</param>
/// <param name="stageIndex">The stage number of the specified shader stage</param>
/// <param name="poolModified">True if either the texture or sampler pool was modified, false otherwise</param>
/// <param name="specState">Specialization state for the bound shader</param>
/// <returns>True if all bound images match the current shader specialiation state, false otherwise</returns>
private bool CommitImageBindings(TexturePool pool, ShaderStage stage, int stageIndex, bool poolModified, ShaderSpecializationState specState)
{
int imageCount = _imageBindings[stageIndex].Length;
if (imageCount == 0)
{
return true;
}
if (pool == null)
{
Logger.Error?.Print(LogClass.Gpu, $"Shader stage \"{stage}\" uses images, but texture pool was not set.");
return true;
}
// Scales for images appear after the texture ones.
int baseScaleIndex = _textureBindings[stageIndex].Length;
int cachedTextureBufferIndex = -1;
int cachedSamplerBufferIndex = -1;
ReadOnlySpan<int> cachedTextureBuffer = Span<int>.Empty;
ReadOnlySpan<int> cachedSamplerBuffer = Span<int>.Empty;
bool specStateMatches = true;
for (int index = 0; index < imageCount; index++)
{
TextureBindingInfo bindingInfo = _imageBindings[stageIndex][index];
TextureUsageFlags usageFlags = bindingInfo.Flags;
int scaleIndex = baseScaleIndex + index;
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex);
UpdateCachedBuffer(stageIndex, ref cachedTextureBufferIndex, ref cachedSamplerBufferIndex, ref cachedTextureBuffer, ref cachedSamplerBuffer, textureBufferIndex, samplerBufferIndex);
int packedId = TextureHandle.ReadPackedId(bindingInfo.Handle, cachedTextureBuffer, cachedSamplerBuffer);
int textureId = TextureHandle.UnpackTextureId(packedId);
ref TextureState state = ref _imageState[bindingInfo.Binding];
bool isStore = bindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
if (!poolModified &&
state.TextureHandle == textureId &&
state.CachedTexture != null &&
state.CachedTexture.InvalidatedSequence == state.InvalidatedSequence)
{
Texture cachedTexture = state.CachedTexture;
// The texture is already bound.
cachedTexture.SynchronizeMemory();
if (isStore)
{
cachedTexture?.SignalModified();
}
Format format = bindingInfo.Format == 0 ? cachedTexture.Format : bindingInfo.Format;
if (state.ImageFormat != format ||
((usageFlags & TextureUsageFlags.NeedsScaleValue) != 0 &&
UpdateScale(state.CachedTexture, usageFlags, scaleIndex, stage)))
{
ITexture hostTextureRebind = state.CachedTexture.GetTargetTexture(bindingInfo.Target);
state.Texture = hostTextureRebind;
state.ImageFormat = format;
_context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTextureRebind, format);
}
continue;
}
state.TextureHandle = textureId;
ref readonly TextureDescriptor descriptor = ref pool.GetForBinding(textureId, out Texture texture);
specStateMatches &= specState.MatchesImage(stage, index, descriptor);
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
if (hostTexture != null && texture.Target == Target.TextureBuffer)
{
// Ensure that the buffer texture is using the correct buffer as storage.
// Buffers are frequently re-created to accomodate larger data, so we need to re-bind
// to ensure we're not using a old buffer that was already deleted.
Format format = bindingInfo.Format;
if (format == 0 && texture != null)
{
format = texture.Format;
}
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, format, true);
// Cache is not used for buffer texture, it must always rebind.
state.CachedTexture = null;
}
else
{
if (isStore)
{
texture?.SignalModified();
}
if ((usageFlags & TextureUsageFlags.NeedsScaleValue) != 0 &&
UpdateScale(texture, usageFlags, scaleIndex, stage))
{
hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
}
if (state.Texture != hostTexture)
{
state.Texture = hostTexture;
Format format = bindingInfo.Format;
if (format == 0 && texture != null)
{
format = texture.Format;
}
state.ImageFormat = format;
_context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTexture, format);
}
state.CachedTexture = texture;
state.InvalidatedSequence = texture?.InvalidatedSequence ?? 0;
}
}
return specStateMatches;
}
/// <summary>
/// Gets the texture descriptor for a given texture handle.
/// </summary>
/// <param name="poolGpuVa">GPU virtual address of the texture pool</param>
/// <param name="bufferIndex">Index of the constant buffer with texture handles</param>
/// <param name="maximumId">Maximum ID of the texture pool</param>
/// <param name="stageIndex">The stage number where the texture is bound</param>
/// <param name="handle">The texture handle</param>
/// <param name="cbufSlot">The texture handle's constant buffer slot</param>
/// <returns>The texture descriptor for the specified texture</returns>
public TextureDescriptor GetTextureDescriptor(
ulong poolGpuVa,
int bufferIndex,
int maximumId,
int stageIndex,
int handle,
int cbufSlot)
{
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(cbufSlot, bufferIndex);
int packedId = ReadPackedId(stageIndex, handle, textureBufferIndex, samplerBufferIndex);
int textureId = TextureHandle.UnpackTextureId(packedId);
ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa);
TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId);
TextureDescriptor descriptor;
if (texturePool.IsValidId(textureId))
{
descriptor = texturePool.GetDescriptor(textureId);
}
else
{
// If the ID is not valid, we just return a default descriptor with the most common state.
// Since this is used for shader specialization, doing so might avoid the need for recompilations.
descriptor = new TextureDescriptor();
descriptor.Word4 |= (uint)TextureTarget.Texture2D << 23;
descriptor.Word5 |= 1u << 31; // Coords normalized.
}
return descriptor;
}
/// <summary>
/// Reads a packed texture and sampler ID (basically, the real texture handle)
/// from the texture constant buffer.
/// </summary>
/// <param name="stageIndex">The number of the shader stage where the texture is bound</param>
/// <param name="wordOffset">A word offset of the handle on the buffer (the "fake" shader handle)</param>
/// <param name="textureBufferIndex">Index of the constant buffer holding the texture handles</param>
/// <param name="samplerBufferIndex">Index of the constant buffer holding the sampler handles</param>
/// <returns>The packed texture and sampler ID (the real texture handle)</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int ReadPackedId(int stageIndex, int wordOffset, int textureBufferIndex, int samplerBufferIndex)
{
(int textureWordOffset, int samplerWordOffset, TextureHandleType handleType) = TextureHandle.UnpackOffsets(wordOffset);
ulong textureBufferAddress = _isCompute
? _channel.BufferManager.GetComputeUniformBufferAddress(textureBufferIndex)
: _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, textureBufferIndex);
int handle = textureBufferAddress != 0
? _channel.MemoryManager.Physical.Read<int>(textureBufferAddress + (uint)textureWordOffset * 4)
: 0;
// The "wordOffset" (which is really the immediate value used on texture instructions on the shader)
// is a 13-bit value. However, in order to also support separate samplers and textures (which uses
// bindless textures on the shader), we extend it with another value on the higher 16 bits with
// another offset for the sampler.
// The shader translator has code to detect separate texture and sampler uses with a bindless texture,
// turn that into a regular texture access and produce those special handles with values on the higher 16 bits.
if (handleType != TextureHandleType.CombinedSampler)
{
int samplerHandle;
if (handleType != TextureHandleType.SeparateConstantSamplerHandle)
{
ulong samplerBufferAddress = _isCompute
? _channel.BufferManager.GetComputeUniformBufferAddress(samplerBufferIndex)
: _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, samplerBufferIndex);
samplerHandle = samplerBufferAddress != 0
? _channel.MemoryManager.Physical.Read<int>(samplerBufferAddress + (uint)samplerWordOffset * 4)
: 0;
}
else
{
samplerHandle = samplerWordOffset;
}
if (handleType == TextureHandleType.SeparateSamplerId ||
handleType == TextureHandleType.SeparateConstantSamplerHandle)
{
samplerHandle <<= 20;
}
handle |= samplerHandle;
}
return handle;
}
/// <summary>
/// Gets the texture and sampler pool for the GPU virtual address that are currently set.
/// </summary>
/// <returns>The texture and sampler pools</returns>
private (TexturePool, SamplerPool) GetPools()
{
MemoryManager memoryManager = _channel.MemoryManager;
TexturePool texturePool = _texturePool;
SamplerPool samplerPool = _samplerPool;
if (texturePool == null)
{
ulong poolAddress = memoryManager.Translate(_texturePoolGpuVa);
if (poolAddress != MemoryManager.PteUnmapped)
{
texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, _texturePoolMaximumId);
_texturePool = texturePool;
}
}
if (samplerPool == null)
{
ulong poolAddress = memoryManager.Translate(_samplerPoolGpuVa);
if (poolAddress != MemoryManager.PteUnmapped)
{
samplerPool = _samplerPoolCache.FindOrCreate(_channel, poolAddress, _samplerPoolMaximumId);
_samplerPool = samplerPool;
}
}
return (texturePool, samplerPool);
}
/// <summary>
/// Forces the texture and sampler pools to be re-loaded from the cache on next use.
/// </summary>
/// <remarks>
/// This should be called if the memory mappings change, to ensure the correct pools are being used.
/// </remarks>
public void ReloadPools()
{
_samplerPool = null;
_texturePool = null;
}
/// <summary>
/// Force all bound textures and images to be rebound the next time CommitBindings is called.
/// </summary>
public void Rebind()
{
Array.Clear(_textureState);
Array.Clear(_imageState);
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,911 @@
using Ryujinx.Common;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Texture;
using System;
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
/// Texture format compatibility checks.
/// </summary>
static class TextureCompatibility
{
private enum FormatClass
{
Unclassified,
Bc1Rgba,
Bc2,
Bc3,
Bc4,
Bc5,
Bc6,
Bc7,
Etc2Rgb,
Etc2Rgba,
Astc4x4,
Astc5x4,
Astc5x5,
Astc6x5,
Astc6x6,
Astc8x5,
Astc8x6,
Astc8x8,
Astc10x5,
Astc10x6,
Astc10x8,
Astc10x10,
Astc12x10,
Astc12x12
}
/// <summary>
/// Checks if a format is host incompatible.
/// </summary>
/// <remarks>
/// Host incompatible formats can't be used directly, the texture data needs to be converted
/// to a compatible format first.
/// </remarks>
/// <param name="info">Texture information</param>
/// <param name="caps">Host GPU capabilities</param>
/// <returns>True if the format is incompatible, false otherwise</returns>
public static bool IsFormatHostIncompatible(TextureInfo info, Capabilities caps)
{
Format originalFormat = info.FormatInfo.Format;
return ToHostCompatibleFormat(info, caps).Format != originalFormat;
}
/// <summary>
/// Converts a incompatible format to a host compatible format, or return the format directly
/// if it is already host compatible.
/// </summary>
/// <remarks>
/// This can be used to convert a incompatible compressed format to the decompressor
/// output format.
/// </remarks>
/// <param name="info">Texture information</param>
/// <param name="caps">Host GPU capabilities</param>
/// <returns>A host compatible format</returns>
public static FormatInfo ToHostCompatibleFormat(TextureInfo info, Capabilities caps)
{
// The host API does not support those compressed formats.
// We assume software decompression will be done for those textures,
// and so we adjust the format here to match the decompressor output.
if (!caps.SupportsAstcCompression)
{
if (info.FormatInfo.Format.IsAstcUnorm())
{
return GraphicsConfig.EnableTextureRecompression
? new FormatInfo(Format.Bc7Unorm, 4, 4, 16, 4)
: new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4);
}
else if (info.FormatInfo.Format.IsAstcSrgb())
{
return GraphicsConfig.EnableTextureRecompression
? new FormatInfo(Format.Bc7Srgb, 4, 4, 16, 4)
: new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4);
}
}
if (!HostSupportsBcFormat(info.FormatInfo.Format, info.Target, caps))
{
switch (info.FormatInfo.Format)
{
case Format.Bc1RgbaSrgb:
case Format.Bc2Srgb:
case Format.Bc3Srgb:
case Format.Bc7Srgb:
return new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4);
case Format.Bc1RgbaUnorm:
case Format.Bc2Unorm:
case Format.Bc3Unorm:
case Format.Bc7Unorm:
return new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4);
case Format.Bc4Unorm:
return new FormatInfo(Format.R8Unorm, 1, 1, 1, 1);
case Format.Bc4Snorm:
return new FormatInfo(Format.R8Snorm, 1, 1, 1, 1);
case Format.Bc5Unorm:
return new FormatInfo(Format.R8G8Unorm, 1, 1, 2, 2);
case Format.Bc5Snorm:
return new FormatInfo(Format.R8G8Snorm, 1, 1, 2, 2);
case Format.Bc6HSfloat:
case Format.Bc6HUfloat:
return new FormatInfo(Format.R16G16B16A16Float, 1, 1, 8, 4);
}
}
if (!caps.SupportsEtc2Compression)
{
switch (info.FormatInfo.Format)
{
case Format.Etc2RgbaSrgb:
case Format.Etc2RgbPtaSrgb:
case Format.Etc2RgbSrgb:
return new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4);
case Format.Etc2RgbaUnorm:
case Format.Etc2RgbPtaUnorm:
case Format.Etc2RgbUnorm:
return new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4);
}
}
if (!caps.SupportsR4G4Format && info.FormatInfo.Format == Format.R4G4Unorm)
{
if (caps.SupportsR4G4B4A4Format)
{
return new FormatInfo(Format.R4G4B4A4Unorm, 1, 1, 2, 4);
}
else
{
return new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4);
}
}
if (info.FormatInfo.Format == Format.R4G4B4A4Unorm)
{
if (!caps.SupportsR4G4B4A4Format)
{
return new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4);
}
}
else if (!caps.Supports5BitComponentFormat && info.FormatInfo.Format.Is16BitPacked())
{
return new FormatInfo(info.FormatInfo.Format.IsBgr() ? Format.B8G8R8A8Unorm : Format.R8G8B8A8Unorm, 1, 1, 4, 4);
}
return info.FormatInfo;
}
/// <summary>
/// Checks if the host API supports a given texture compression format of the BC family.
/// </summary>
/// <param name="format">BC format to be checked</param>
/// <param name="target">Target usage of the texture</param>
/// <param name="caps">Host GPU Capabilities</param>
/// <returns>True if the texture host supports the format with the given target usage, false otherwise</returns>
public static bool HostSupportsBcFormat(Format format, Target target, Capabilities caps)
{
bool not3DOr3DCompressionSupported = target != Target.Texture3D || caps.Supports3DTextureCompression;
switch (format)
{
case Format.Bc1RgbaSrgb:
case Format.Bc1RgbaUnorm:
case Format.Bc2Srgb:
case Format.Bc2Unorm:
case Format.Bc3Srgb:
case Format.Bc3Unorm:
return caps.SupportsBc123Compression && not3DOr3DCompressionSupported;
case Format.Bc4Unorm:
case Format.Bc4Snorm:
case Format.Bc5Unorm:
case Format.Bc5Snorm:
return caps.SupportsBc45Compression && not3DOr3DCompressionSupported;
case Format.Bc6HSfloat:
case Format.Bc6HUfloat:
case Format.Bc7Srgb:
case Format.Bc7Unorm:
return caps.SupportsBc67Compression && not3DOr3DCompressionSupported;
}
return true;
}
/// <summary>
/// Determines whether a texture can flush its data back to guest memory.
/// </summary>
/// <param name="info">Texture information</param>
/// <param name="caps">Host GPU Capabilities</param>
/// <returns>True if the texture can flush, false otherwise</returns>
public static bool CanTextureFlush(TextureInfo info, Capabilities caps)
{
if (IsFormatHostIncompatible(info, caps))
{
return false; // Flushing this format is not supported, as it may have been converted to another host format.
}
if (info.Target == Target.Texture2DMultisample ||
info.Target == Target.Texture2DMultisampleArray)
{
return false; // Flushing multisample textures is not supported, the host does not allow getting their data.
}
return true;
}
/// <summary>
/// Checks if the texture format matches with the specified texture information.
/// </summary>
/// <param name="lhs">Texture information to compare</param>
/// <param name="rhs">Texture information to compare with</param>
/// <param name="forSampler">Indicates that the texture will be used for shader sampling</param>
/// <param name="forCopy">Indicates that the texture will be used as copy source or target</param>
/// <returns>A value indicating how well the formats match</returns>
public static TextureMatchQuality FormatMatches(TextureInfo lhs, TextureInfo rhs, bool forSampler, bool forCopy)
{
// D32F and R32F texture have the same representation internally,
// however the R32F format is used to sample from depth textures.
if (lhs.FormatInfo.Format == Format.D32Float && rhs.FormatInfo.Format == Format.R32Float && (forSampler || forCopy))
{
return TextureMatchQuality.FormatAlias;
}
if (forCopy)
{
// The 2D engine does not support depth-stencil formats, so it will instead
// use equivalent color formats. We must also consider them as compatible.
if (lhs.FormatInfo.Format == Format.S8Uint && rhs.FormatInfo.Format == Format.R8Unorm)
{
return TextureMatchQuality.FormatAlias;
}
if (lhs.FormatInfo.Format == Format.D16Unorm && rhs.FormatInfo.Format == Format.R16Unorm)
{
return TextureMatchQuality.FormatAlias;
}
if ((lhs.FormatInfo.Format == Format.D24UnormS8Uint ||
lhs.FormatInfo.Format == Format.S8UintD24Unorm) && rhs.FormatInfo.Format == Format.B8G8R8A8Unorm)
{
return TextureMatchQuality.FormatAlias;
}
}
return lhs.FormatInfo.Format == rhs.FormatInfo.Format ? TextureMatchQuality.Perfect : TextureMatchQuality.NoMatch;
}
/// <summary>
/// Checks if the texture layout specified matches with this texture layout.
/// The layout information is composed of the Stride for linear textures, or GOB block size
/// for block linear textures.
/// </summary>
/// <param name="lhs">Texture information to compare</param>
/// <param name="rhs">Texture information to compare with</param>
/// <returns>True if the layout matches, false otherwise</returns>
public static bool LayoutMatches(TextureInfo lhs, TextureInfo rhs)
{
if (lhs.IsLinear != rhs.IsLinear)
{
return false;
}
// For linear textures, gob block sizes are ignored.
// For block linear textures, the stride is ignored.
if (rhs.IsLinear)
{
return lhs.Stride == rhs.Stride;
}
else
{
return lhs.GobBlocksInY == rhs.GobBlocksInY &&
lhs.GobBlocksInZ == rhs.GobBlocksInZ;
}
}
/// <summary>
/// Obtain the minimum compatibility level of two provided view compatibility results.
/// </summary>
/// <param name="first">The first compatibility level</param>
/// <param name="second">The second compatibility level</param>
/// <returns>The minimum compatibility level of two provided view compatibility results</returns>
public static TextureViewCompatibility PropagateViewCompatibility(TextureViewCompatibility first, TextureViewCompatibility second)
{
if (first == TextureViewCompatibility.Incompatible || second == TextureViewCompatibility.Incompatible)
{
return TextureViewCompatibility.Incompatible;
}
else if (first == TextureViewCompatibility.LayoutIncompatible || second == TextureViewCompatibility.LayoutIncompatible)
{
return TextureViewCompatibility.LayoutIncompatible;
}
else if (first == TextureViewCompatibility.CopyOnly || second == TextureViewCompatibility.CopyOnly)
{
return TextureViewCompatibility.CopyOnly;
}
else
{
return TextureViewCompatibility.Full;
}
}
/// <summary>
/// Checks if the sizes of two texture levels are copy compatible.
/// </summary>
/// <param name="lhs">Texture information of the texture view</param>
/// <param name="rhs">Texture information of the texture view to match against</param>
/// <param name="lhsLevel">Mipmap level of the texture view in relation to this texture</param>
/// <param name="rhsLevel">Mipmap level of the texture view in relation to the second texture</param>
/// <returns>True if both levels are view compatible</returns>
public static bool CopySizeMatches(TextureInfo lhs, TextureInfo rhs, int lhsLevel, int rhsLevel)
{
Size size = GetAlignedSize(lhs, lhsLevel);
Size otherSize = GetAlignedSize(rhs, rhsLevel);
if (size.Width == otherSize.Width && size.Height == otherSize.Height)
{
return true;
}
else if (lhs.IsLinear && rhs.IsLinear)
{
// Copy between linear textures with matching stride.
int stride = BitUtils.AlignUp(Math.Max(1, lhs.Stride >> lhsLevel), Constants.StrideAlignment);
return stride == rhs.Stride;
}
else
{
return false;
}
}
/// <summary>
/// Checks if the sizes of two given textures are view compatible.
/// </summary>
/// <param name="lhs">Texture information of the texture view</param>
/// <param name="rhs">Texture information of the texture view to match against</param>
/// <param name="exact">Indicates if the sizes must be exactly equal</param>
/// <param name="level">Mipmap level of the texture view in relation to this texture</param>
/// <returns>The view compatibility level of the view sizes</returns>
public static TextureViewCompatibility ViewSizeMatches(TextureInfo lhs, TextureInfo rhs, bool exact, int level)
{
Size lhsAlignedSize = GetAlignedSize(lhs, level);
Size rhsAlignedSize = GetAlignedSize(rhs);
Size lhsSize = GetSizeInBlocks(lhs, level);
Size rhsSize = GetSizeInBlocks(rhs);
bool alignedWidthMatches = lhsAlignedSize.Width == rhsAlignedSize.Width;
if (lhs.FormatInfo.BytesPerPixel != rhs.FormatInfo.BytesPerPixel && IsIncompatibleFormatAliasingAllowed(lhs.FormatInfo, rhs.FormatInfo))
{
alignedWidthMatches = lhsSize.Width * lhs.FormatInfo.BytesPerPixel == rhsSize.Width * rhs.FormatInfo.BytesPerPixel;
}
TextureViewCompatibility result = TextureViewCompatibility.Full;
// For copies, we can copy a subset of the 3D texture slices,
// so the depth may be different in this case.
if (rhs.Target == Target.Texture3D && lhsSize.Depth != rhsSize.Depth)
{
result = TextureViewCompatibility.CopyOnly;
}
// Some APIs align the width for copy and render target textures,
// so the width may not match in this case for different uses of the same texture.
// To account for this, we compare the aligned width here.
// We expect height to always match exactly, if the texture is the same.
if (alignedWidthMatches && lhsSize.Height == rhsSize.Height)
{
return (exact && lhsSize.Width != rhsSize.Width) || lhsSize.Width < rhsSize.Width
? TextureViewCompatibility.CopyOnly
: result;
}
else if (lhs.IsLinear && rhs.IsLinear && lhsSize.Height == rhsSize.Height)
{
// Copy between linear textures with matching stride.
int stride = BitUtils.AlignUp(Math.Max(1, lhs.Stride >> level), Constants.StrideAlignment);
return stride == rhs.Stride ? TextureViewCompatibility.CopyOnly : TextureViewCompatibility.LayoutIncompatible;
}
else
{
return TextureViewCompatibility.LayoutIncompatible;
}
}
/// <summary>
/// Checks if the potential child texture fits within the level and layer bounds of the parent.
/// </summary>
/// <param name="parent">Texture information for the parent</param>
/// <param name="child">Texture information for the child</param>
/// <param name="layer">Base layer of the child texture</param>
/// <param name="level">Base level of the child texture</param>
/// <returns>Full compatiblity if the child's layer and level count fit within the parent, incompatible otherwise</returns>
public static TextureViewCompatibility ViewSubImagesInBounds(TextureInfo parent, TextureInfo child, int layer, int level)
{
if (level + child.Levels <= parent.Levels &&
layer + child.GetSlices() <= parent.GetSlices())
{
return TextureViewCompatibility.Full;
}
else
{
return TextureViewCompatibility.LayoutIncompatible;
}
}
/// <summary>
/// Checks if the texture sizes of the supplied texture informations match.
/// </summary>
/// <param name="lhs">Texture information to compare</param>
/// <param name="rhs">Texture information to compare with</param>
/// <param name="exact">Indicates if the size must be exactly equal between the textures, or if <paramref name="rhs"/> is allowed to be larger</param>
/// <returns>True if the sizes matches, false otherwise</returns>
public static bool SizeMatches(TextureInfo lhs, TextureInfo rhs, bool exact)
{
if (lhs.GetLayers() != rhs.GetLayers())
{
return false;
}
Size lhsSize = GetSizeInBlocks(lhs);
Size rhsSize = GetSizeInBlocks(rhs);
if (exact || lhs.IsLinear || rhs.IsLinear)
{
return lhsSize.Width == rhsSize.Width &&
lhsSize.Height == rhsSize.Height &&
lhsSize.Depth == rhsSize.Depth;
}
else
{
Size lhsAlignedSize = GetAlignedSize(lhs);
Size rhsAlignedSize = GetAlignedSize(rhs);
return lhsAlignedSize.Width == rhsAlignedSize.Width &&
lhsSize.Width >= rhsSize.Width &&
lhsSize.Height == rhsSize.Height &&
lhsSize.Depth == rhsSize.Depth;
}
}
/// <summary>
/// Gets the aligned sizes for the given dimensions, using the specified texture information.
/// The alignment depends on the texture layout and format bytes per pixel.
/// </summary>
/// <param name="info">Texture information to calculate the aligned size from</param>
/// <param name="width">The width to be aligned</param>
/// <param name="height">The height to be aligned</param>
/// <param name="depth">The depth to be aligned</param>
/// <returns>The aligned texture size</returns>
private static Size GetAlignedSize(TextureInfo info, int width, int height, int depth)
{
if (info.IsLinear)
{
return SizeCalculator.GetLinearAlignedSize(
width,
height,
info.FormatInfo.BlockWidth,
info.FormatInfo.BlockHeight,
info.FormatInfo.BytesPerPixel);
}
else
{
return SizeCalculator.GetBlockLinearAlignedSize(
width,
height,
depth,
info.FormatInfo.BlockWidth,
info.FormatInfo.BlockHeight,
info.FormatInfo.BytesPerPixel,
info.GobBlocksInY,
info.GobBlocksInZ,
info.GobBlocksInTileX);
}
}
/// <summary>
/// Gets the aligned sizes of the specified texture information.
/// The alignment depends on the texture layout and format bytes per pixel.
/// </summary>
/// <param name="info">Texture information to calculate the aligned size from</param>
/// <param name="level">Mipmap level for texture views</param>
/// <returns>The aligned texture size</returns>
public static Size GetAlignedSize(TextureInfo info, int level = 0)
{
int width = Math.Max(1, info.Width >> level);
int height = Math.Max(1, info.Height >> level);
int depth = Math.Max(1, info.GetDepth() >> level);
return GetAlignedSize(info, width, height, depth);
}
/// <summary>
/// Gets the size in blocks for the given texture information.
/// For non-compressed formats, that's the same as the regular size.
/// </summary>
/// <param name="info">Texture information to calculate the aligned size from</param>
/// <param name="level">Mipmap level for texture views</param>
/// <returns>The texture size in blocks</returns>
public static Size GetSizeInBlocks(TextureInfo info, int level = 0)
{
int width = Math.Max(1, info.Width >> level);
int height = Math.Max(1, info.Height >> level);
int depth = Math.Max(1, info.GetDepth() >> level);
return new Size(
BitUtils.DivRoundUp(width, info.FormatInfo.BlockWidth),
BitUtils.DivRoundUp(height, info.FormatInfo.BlockHeight),
depth);
}
/// <summary>
/// Check if it's possible to create a view with the layout of the second texture information from the first.
/// The layout information is composed of the Stride for linear textures, or GOB block size
/// for block linear textures.
/// </summary>
/// <param name="lhs">Texture information of the texture view</param>
/// <param name="rhs">Texture information of the texture view to compare against</param>
/// <param name="level">Start level of the texture view, in relation with the first texture</param>
/// <returns>True if the layout is compatible, false otherwise</returns>
public static bool ViewLayoutCompatible(TextureInfo lhs, TextureInfo rhs, int level)
{
if (lhs.IsLinear != rhs.IsLinear)
{
return false;
}
// For linear textures, gob block sizes are ignored.
// For block linear textures, the stride is ignored.
if (rhs.IsLinear)
{
int stride = Math.Max(1, lhs.Stride >> level);
stride = BitUtils.AlignUp(stride, Constants.StrideAlignment);
return stride == rhs.Stride;
}
else
{
int height = Math.Max(1, lhs.Height >> level);
int depth = Math.Max(1, lhs.GetDepth() >> level);
(int gobBlocksInY, int gobBlocksInZ) = SizeCalculator.GetMipGobBlockSizes(
height,
depth,
lhs.FormatInfo.BlockHeight,
lhs.GobBlocksInY,
lhs.GobBlocksInZ);
return gobBlocksInY == rhs.GobBlocksInY &&
gobBlocksInZ == rhs.GobBlocksInZ;
}
}
/// <summary>
/// Check if it's possible to create a view with the layout of the second texture information from the first.
/// The layout information is composed of the Stride for linear textures, or GOB block size
/// for block linear textures.
/// </summary>
/// <param name="lhs">Texture information of the texture view</param>
/// <param name="rhs">Texture information of the texture view to compare against</param>
/// <param name="lhsLevel">Start level of the texture view, in relation with the first texture</param>
/// <param name="rhsLevel">Start level of the texture view, in relation with the second texture</param>
/// <returns>True if the layout is compatible, false otherwise</returns>
public static bool ViewLayoutCompatible(TextureInfo lhs, TextureInfo rhs, int lhsLevel, int rhsLevel)
{
if (lhs.IsLinear != rhs.IsLinear)
{
return false;
}
// For linear textures, gob block sizes are ignored.
// For block linear textures, the stride is ignored.
if (rhs.IsLinear)
{
int lhsStride = Math.Max(1, lhs.Stride >> lhsLevel);
lhsStride = BitUtils.AlignUp(lhsStride, Constants.StrideAlignment);
int rhsStride = Math.Max(1, rhs.Stride >> rhsLevel);
rhsStride = BitUtils.AlignUp(rhsStride, Constants.StrideAlignment);
return lhsStride == rhsStride;
}
else
{
int lhsHeight = Math.Max(1, lhs.Height >> lhsLevel);
int lhsDepth = Math.Max(1, lhs.GetDepth() >> lhsLevel);
(int lhsGobBlocksInY, int lhsGobBlocksInZ) = SizeCalculator.GetMipGobBlockSizes(
lhsHeight,
lhsDepth,
lhs.FormatInfo.BlockHeight,
lhs.GobBlocksInY,
lhs.GobBlocksInZ);
int rhsHeight = Math.Max(1, rhs.Height >> rhsLevel);
int rhsDepth = Math.Max(1, rhs.GetDepth() >> rhsLevel);
(int rhsGobBlocksInY, int rhsGobBlocksInZ) = SizeCalculator.GetMipGobBlockSizes(
rhsHeight,
rhsDepth,
rhs.FormatInfo.BlockHeight,
rhs.GobBlocksInY,
rhs.GobBlocksInZ);
return lhsGobBlocksInY == rhsGobBlocksInY &&
lhsGobBlocksInZ == rhsGobBlocksInZ;
}
}
/// <summary>
/// Checks if the view format of the first texture format is compatible with the format of the second.
/// In general, the formats are considered compatible if the bytes per pixel values are equal,
/// but there are more complex rules for some formats, like compressed or depth-stencil formats.
/// This follows the host API copy compatibility rules.
/// </summary>
/// <param name="lhs">Texture information of the texture view</param>
/// <param name="rhs">Texture information of the texture view</param>
/// <param name="caps">Host GPU capabilities</param>
/// <returns>The view compatibility level of the texture formats</returns>
public static TextureViewCompatibility ViewFormatCompatible(TextureInfo lhs, TextureInfo rhs, Capabilities caps)
{
FormatInfo lhsFormat = lhs.FormatInfo;
FormatInfo rhsFormat = rhs.FormatInfo;
if (lhsFormat.Format.IsDepthOrStencil() || rhsFormat.Format.IsDepthOrStencil())
{
return lhsFormat.Format == rhsFormat.Format ? TextureViewCompatibility.Full : TextureViewCompatibility.Incompatible;
}
if (IsFormatHostIncompatible(lhs, caps) || IsFormatHostIncompatible(rhs, caps))
{
return lhsFormat.Format == rhsFormat.Format ? TextureViewCompatibility.Full : TextureViewCompatibility.Incompatible;
}
if (lhsFormat.IsCompressed && rhsFormat.IsCompressed)
{
FormatClass lhsClass = GetFormatClass(lhsFormat.Format);
FormatClass rhsClass = GetFormatClass(rhsFormat.Format);
return lhsClass == rhsClass ? TextureViewCompatibility.Full : TextureViewCompatibility.Incompatible;
}
else if (lhsFormat.BytesPerPixel == rhsFormat.BytesPerPixel)
{
return lhs.FormatInfo.IsCompressed == rhs.FormatInfo.IsCompressed
? TextureViewCompatibility.Full
: TextureViewCompatibility.CopyOnly;
}
else if (IsIncompatibleFormatAliasingAllowed(lhsFormat, rhsFormat))
{
return TextureViewCompatibility.CopyOnly;
}
return TextureViewCompatibility.Incompatible;
}
/// <summary>
/// Checks if aliasing of two formats that would normally be considered incompatible be allowed,
/// using copy dependencies.
/// </summary>
/// <param name="lhsFormat">Format information of the first texture</param
/// <param name="rhsFormat">Format information of the second texture</param>
/// <returns>True if aliasing should be allowed, false otherwise</returns>
private static bool IsIncompatibleFormatAliasingAllowed(FormatInfo lhsFormat, FormatInfo rhsFormat)
{
// Some games will try to alias textures with incompatible foramts, with different BPP (bytes per pixel).
// We allow that in some cases as long Width * BPP is equal on both textures.
// This is very conservative right now as we want to avoid copies as much as possible,
// so we only consider the formats we have seen being aliased.
if (rhsFormat.BytesPerPixel < lhsFormat.BytesPerPixel)
{
(lhsFormat, rhsFormat) = (rhsFormat, lhsFormat);
}
return lhsFormat.Format == Format.R8Unorm && rhsFormat.Format == Format.R8G8B8A8Unorm;
}
/// <summary>
/// Check if the target of the first texture view information is compatible with the target of the second texture view information.
/// This follows the host API target compatibility rules.
/// </summary>
/// <param name="lhs">Texture information of the texture view</param
/// <param name="rhs">Texture information of the texture view</param>
/// <param name="caps">Host GPU capabilities</param>
/// <returns>True if the targets are compatible, false otherwise</returns>
public static TextureViewCompatibility ViewTargetCompatible(TextureInfo lhs, TextureInfo rhs, ref Capabilities caps)
{
bool result = false;
switch (lhs.Target)
{
case Target.Texture1D:
case Target.Texture1DArray:
result = rhs.Target == Target.Texture1D ||
rhs.Target == Target.Texture1DArray;
break;
case Target.Texture2D:
result = rhs.Target == Target.Texture2D ||
rhs.Target == Target.Texture2DArray;
break;
case Target.Texture2DArray:
result = rhs.Target == Target.Texture2D ||
rhs.Target == Target.Texture2DArray;
if (rhs.Target == Target.Cubemap || rhs.Target == Target.CubemapArray)
{
return caps.SupportsCubemapView ? TextureViewCompatibility.Full : TextureViewCompatibility.CopyOnly;
}
break;
case Target.Cubemap:
case Target.CubemapArray:
result = rhs.Target == Target.Cubemap ||
rhs.Target == Target.CubemapArray;
if (rhs.Target == Target.Texture2D || rhs.Target == Target.Texture2DArray)
{
return caps.SupportsCubemapView ? TextureViewCompatibility.Full : TextureViewCompatibility.CopyOnly;
}
break;
case Target.Texture2DMultisample:
case Target.Texture2DMultisampleArray:
if (rhs.Target == Target.Texture2D || rhs.Target == Target.Texture2DArray)
{
return TextureViewCompatibility.CopyOnly;
}
result = rhs.Target == Target.Texture2DMultisample ||
rhs.Target == Target.Texture2DMultisampleArray;
break;
case Target.Texture3D:
if (rhs.Target == Target.Texture2D)
{
return TextureViewCompatibility.CopyOnly;
}
result = rhs.Target == Target.Texture3D;
break;
}
return result ? TextureViewCompatibility.Full : TextureViewCompatibility.Incompatible;
}
/// <summary>
/// Checks if a swizzle component in two textures functionally match, taking into account if the components are defined.
/// </summary>
/// <param name="lhs">Texture information to compare</param>
/// <param name="rhs">Texture information to compare with</param>
/// <param name="swizzleLhs">Swizzle component for the first texture</param>
/// <param name="swizzleRhs">Swizzle component for the second texture</param>
/// <param name="component">Component index, starting at 0 for red</param>
/// <returns>True if the swizzle components functionally match, false othersize</returns>
private static bool SwizzleComponentMatches(TextureInfo lhs, TextureInfo rhs, SwizzleComponent swizzleLhs, SwizzleComponent swizzleRhs, int component)
{
int lhsComponents = lhs.FormatInfo.Components;
int rhsComponents = rhs.FormatInfo.Components;
if (lhsComponents == 4 && rhsComponents == 4)
{
return swizzleLhs == swizzleRhs;
}
// Swizzles after the number of components a format defines are "undefined".
// We allow these to not be equal under certain circumstances.
// This can only happen when there are less than 4 components in a format.
// It tends to happen when float depth textures are sampled.
bool lhsDefined = (swizzleLhs - SwizzleComponent.Red) < lhsComponents;
bool rhsDefined = (swizzleRhs - SwizzleComponent.Red) < rhsComponents;
if (lhsDefined == rhsDefined)
{
// If both are undefined, return true. Otherwise just check if they're equal.
return lhsDefined ? swizzleLhs == swizzleRhs : true;
}
else
{
SwizzleComponent defined = lhsDefined ? swizzleLhs : swizzleRhs;
SwizzleComponent undefined = lhsDefined ? swizzleRhs : swizzleLhs;
// Undefined swizzle can be matched by a forced value (0, 1), exact equality, or expected value.
// For example, R___ matches R001, RGBA but not RBGA.
return defined == undefined || defined < SwizzleComponent.Red || defined == SwizzleComponent.Red + component;
}
}
/// <summary>
/// Checks if the texture shader sampling parameters of two texture informations match.
/// </summary>
/// <param name="lhs">Texture information to compare</param>
/// <param name="rhs">Texture information to compare with</param>
/// <returns>True if the texture shader sampling parameters matches, false otherwise</returns>
public static bool SamplerParamsMatches(TextureInfo lhs, TextureInfo rhs)
{
return lhs.DepthStencilMode == rhs.DepthStencilMode &&
SwizzleComponentMatches(lhs, rhs, lhs.SwizzleR, rhs.SwizzleR, 0) &&
SwizzleComponentMatches(lhs, rhs, lhs.SwizzleG, rhs.SwizzleG, 1) &&
SwizzleComponentMatches(lhs, rhs, lhs.SwizzleB, rhs.SwizzleB, 2) &&
SwizzleComponentMatches(lhs, rhs, lhs.SwizzleA, rhs.SwizzleA, 3);
}
/// <summary>
/// Check if the texture target and samples count (for multisampled textures) matches.
/// </summary>
/// <param name="first">Texture information to compare with</param>
/// <param name="rhs">Texture information to compare with</param>
/// <returns>True if the texture target and samples count matches, false otherwise</returns>
public static bool TargetAndSamplesCompatible(TextureInfo lhs, TextureInfo rhs)
{
return lhs.Target == rhs.Target &&
lhs.SamplesInX == rhs.SamplesInX &&
lhs.SamplesInY == rhs.SamplesInY;
}
/// <summary>
/// Gets the texture format class, for compressed textures, or Unclassified otherwise.
/// </summary>
/// <param name="format">The format</param>
/// <returns>Format class</returns>
private static FormatClass GetFormatClass(Format format)
{
switch (format)
{
case Format.Bc1RgbaSrgb:
case Format.Bc1RgbaUnorm:
return FormatClass.Bc1Rgba;
case Format.Bc2Srgb:
case Format.Bc2Unorm:
return FormatClass.Bc2;
case Format.Bc3Srgb:
case Format.Bc3Unorm:
return FormatClass.Bc3;
case Format.Bc4Snorm:
case Format.Bc4Unorm:
return FormatClass.Bc4;
case Format.Bc5Snorm:
case Format.Bc5Unorm:
return FormatClass.Bc5;
case Format.Bc6HSfloat:
case Format.Bc6HUfloat:
return FormatClass.Bc6;
case Format.Bc7Srgb:
case Format.Bc7Unorm:
return FormatClass.Bc7;
case Format.Etc2RgbSrgb:
case Format.Etc2RgbUnorm:
return FormatClass.Etc2Rgb;
case Format.Etc2RgbaSrgb:
case Format.Etc2RgbaUnorm:
return FormatClass.Etc2Rgba;
case Format.Astc4x4Srgb:
case Format.Astc4x4Unorm:
return FormatClass.Astc4x4;
case Format.Astc5x4Srgb:
case Format.Astc5x4Unorm:
return FormatClass.Astc5x4;
case Format.Astc5x5Srgb:
case Format.Astc5x5Unorm:
return FormatClass.Astc5x5;
case Format.Astc6x5Srgb:
case Format.Astc6x5Unorm:
return FormatClass.Astc6x5;
case Format.Astc6x6Srgb:
case Format.Astc6x6Unorm:
return FormatClass.Astc6x6;
case Format.Astc8x5Srgb:
case Format.Astc8x5Unorm:
return FormatClass.Astc8x5;
case Format.Astc8x6Srgb:
case Format.Astc8x6Unorm:
return FormatClass.Astc8x6;
case Format.Astc8x8Srgb:
case Format.Astc8x8Unorm:
return FormatClass.Astc8x8;
case Format.Astc10x5Srgb:
case Format.Astc10x5Unorm:
return FormatClass.Astc10x5;
case Format.Astc10x6Srgb:
case Format.Astc10x6Unorm:
return FormatClass.Astc10x6;
case Format.Astc10x8Srgb:
case Format.Astc10x8Unorm:
return FormatClass.Astc10x8;
case Format.Astc10x10Srgb:
case Format.Astc10x10Unorm:
return FormatClass.Astc10x10;
case Format.Astc12x10Srgb:
case Format.Astc12x10Unorm:
return FormatClass.Astc12x10;
case Format.Astc12x12Srgb:
case Format.Astc12x12Unorm:
return FormatClass.Astc12x12;
}
return FormatClass.Unclassified;
}
}
}

View file

@ -0,0 +1,43 @@
using Ryujinx.Graphics.GAL;
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
/// Texture swizzle color component.
/// </summary>
enum TextureComponent
{
Zero = 0,
Red = 2,
Green = 3,
Blue = 4,
Alpha = 5,
OneSI = 6,
OneF = 7
}
static class TextureComponentConverter
{
/// <summary>
/// Converts the texture swizzle color component enum to the respective Graphics Abstraction Layer enum.
/// </summary>
/// <param name="component">Texture swizzle color component</param>
/// <returns>Converted enum</returns>
public static SwizzleComponent Convert(this TextureComponent component)
{
switch (component)
{
case TextureComponent.Zero: return SwizzleComponent.Zero;
case TextureComponent.Red: return SwizzleComponent.Red;
case TextureComponent.Green: return SwizzleComponent.Green;
case TextureComponent.Blue: return SwizzleComponent.Blue;
case TextureComponent.Alpha: return SwizzleComponent.Alpha;
case TextureComponent.OneSI:
case TextureComponent.OneF:
return SwizzleComponent.One;
}
return SwizzleComponent.Zero;
}
}
}

View file

@ -0,0 +1,37 @@
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
/// One side of a two-way dependency between one texture view and another.
/// Contains a reference to the handle owning the dependency, and the other dependency.
/// </summary>
class TextureDependency
{
/// <summary>
/// The handle that owns this dependency.
/// </summary>
public TextureGroupHandle Handle;
/// <summary>
/// The other dependency linked to this one, which belongs to another handle.
/// </summary>
public TextureDependency Other;
/// <summary>
/// Create a new texture dependency.
/// </summary>
/// <param name="handle">The handle that owns the dependency</param>
public TextureDependency(TextureGroupHandle handle)
{
Handle = handle;
}
/// <summary>
/// Signal that the owner of this dependency has been modified,
/// meaning that the other dependency's handle must defer a copy from it.
/// </summary>
public void SignalModified()
{
Other.Handle.DeferCopy(Handle);
}
}
}

View file

@ -0,0 +1,273 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
/// Maxwell texture descriptor, as stored on the GPU texture pool memory region.
/// </summary>
struct TextureDescriptor : ITextureDescriptor, IEquatable<TextureDescriptor>
{
#pragma warning disable CS0649
public uint Word0;
public uint Word1;
public uint Word2;
public uint Word3;
public uint Word4;
public uint Word5;
public uint Word6;
public uint Word7;
#pragma warning restore CS0649
/// <summary>
/// Unpacks Maxwell texture format integer.
/// </summary>
/// <returns>The texture format integer</returns>
public uint UnpackFormat()
{
return Word0 & 0x8007ffff;
}
/// <summary>
/// Unpacks the swizzle component for the texture red color channel.
/// </summary>
/// <returns>The swizzle component</returns>
public TextureComponent UnpackSwizzleR()
{
return(TextureComponent)((Word0 >> 19) & 7);
}
/// <summary>
/// Unpacks the swizzle component for the texture green color channel.
/// </summary>
/// <returns>The swizzle component</returns>
public TextureComponent UnpackSwizzleG()
{
return(TextureComponent)((Word0 >> 22) & 7);
}
/// <summary>
/// Unpacks the swizzle component for the texture blue color channel.
/// </summary>
/// <returns>The swizzle component</returns>
public TextureComponent UnpackSwizzleB()
{
return(TextureComponent)((Word0 >> 25) & 7);
}
/// <summary>
/// Unpacks the swizzle component for the texture alpha color channel.
/// </summary>
/// <returns>The swizzle component</returns>
public TextureComponent UnpackSwizzleA()
{
return(TextureComponent)((Word0 >> 28) & 7);
}
/// <summary>
/// Unpacks the 40-bits texture GPU virtual address.
/// </summary>
/// <returns>The GPU virtual address</returns>
public ulong UnpackAddress()
{
return Word1 | ((ulong)(Word2 & 0xffff) << 32);
}
/// <summary>
/// Unpacks texture descriptor type for this texture descriptor.
/// This defines the texture layout, among other things.
/// </summary>
/// <returns>The texture descriptor type</returns>
public TextureDescriptorType UnpackTextureDescriptorType()
{
return (TextureDescriptorType)((Word2 >> 21) & 7);
}
/// <summary>
/// Unpacks the texture stride (bytes per line) for linear textures only.
/// Always 32-bytes aligned.
/// </summary>
/// <returns>The linear texture stride</returns>
public int UnpackStride()
{
return (int)(Word3 & 0xffff) << 5;
}
/// <summary>
/// Unpacks the GOB block size in X (width) for block linear textures.
/// Must be always 1, ignored by the GPU.
/// </summary>
/// <returns>THe GOB block X size</returns>
public int UnpackGobBlocksInX()
{
return 1 << (int)(Word3 & 7);
}
/// <summary>
/// Unpacks the GOB block size in Y (height) for block linear textures.
/// Must be always a power of 2, with a maximum value of 32.
/// </summary>
/// <returns>THe GOB block Y size</returns>
public int UnpackGobBlocksInY()
{
return 1 << (int)((Word3 >> 3) & 7);
}
/// <summary>
/// Unpacks the GOB block size in Z (depth) for block linear textures.
/// Must be always a power of 2, with a maximum value of 32.
/// Must be 1 for any texture target other than 3D textures.
/// </summary>
/// <returns>The GOB block Z size</returns>
public int UnpackGobBlocksInZ()
{
return 1 << (int)((Word3 >> 6) & 7);
}
/// <summary>
/// Number of GOB blocks per tile in the X direction.
/// This is only used for sparse textures, should be 1 otherwise.
/// </summary>
/// <returns>The number of GOB blocks per tile</returns>
public int UnpackGobBlocksInTileX()
{
return 1 << (int)((Word3 >> 10) & 7);
}
/// <summary>
/// Unpacks the number of mipmap levels of the texture.
/// </summary>
/// <returns>The number of mipmap levels</returns>
public int UnpackLevels()
{
return (int)(Word3 >> 28) + 1;
}
/// <summary>
/// Unpack the base level texture width size.
/// </summary>
/// <returns>The texture width</returns>
public int UnpackWidth()
{
return (int)(Word4 & 0xffff) + 1;
}
/// <summary>
/// Unpack the width of a buffer texture.
/// </summary>
/// <returns>The texture width</returns>
public int UnpackBufferTextureWidth()
{
return (int)((Word4 & 0xffff) | (Word3 << 16)) + 1;
}
/// <summary>
/// Unpacks the texture sRGB format flag.
/// </summary>
/// <returns>True if the texture is sRGB, false otherwise</returns>
public bool UnpackSrgb()
{
return (Word4 & (1 << 22)) != 0;
}
/// <summary>
/// Unpacks the texture target.
/// </summary>
/// <returns>The texture target</returns>
public TextureTarget UnpackTextureTarget()
{
return (TextureTarget)((Word4 >> 23) & 0xf);
}
/// <summary>
/// Unpack the base level texture height size, or array layers for 1D array textures.
/// Should be ignored for 1D or buffer textures.
/// </summary>
/// <returns>The texture height or layers count</returns>
public int UnpackHeight()
{
return (int)(Word5 & 0xffff) + 1;
}
/// <summary>
/// Unpack the base level texture depth size, number of array layers or cubemap faces.
/// The meaning of this value depends on the texture target.
/// </summary>
/// <returns>The texture depth, layer or faces count</returns>
public int UnpackDepth()
{
return (int)((Word5 >> 16) & 0x3fff) + 1;
}
/// <summary>
/// Unpacks the texture coordinates normalized flag.
/// When this is true, texture coordinates are expected to be in the [0, 1] range on the shader.
/// When this is false, texture coordinates are expected to be in the [0, W], [0, H] and [0, D] range.
/// It must be set to false (by the guest driver) for rectangle textures.
/// </summary>
/// <returns>The texture coordinates normalized flag</returns>
public bool UnpackTextureCoordNormalized()
{
return (Word5 & (1 << 31)) != 0;
}
/// <summary>
/// Unpacks the base mipmap level of the texture.
/// </summary>
/// <returns>The base mipmap level of the texture</returns>
public int UnpackBaseLevel()
{
return (int)(Word7 & 0xf);
}
/// <summary>
/// Unpacks the maximum mipmap level (inclusive) of the texture.
/// Usually equal to Levels minus 1.
/// </summary>
/// <returns>The maximum mipmap level (inclusive) of the texture</returns>
public int UnpackMaxLevelInclusive()
{
return (int)((Word7 >> 4) & 0xf);
}
/// <summary>
/// Unpacks the multisampled texture samples count in each direction.
/// Must be ignored for non-multisample textures.
/// </summary>
/// <returns>The multisample counts enum</returns>
public TextureMsaaMode UnpackTextureMsaaMode()
{
return (TextureMsaaMode)((Word7 >> 8) & 0xf);
}
/// <summary>
/// Check if two descriptors are equal.
/// </summary>
/// <param name="other">The descriptor to compare against</param>
/// <returns>True if they are equal, false otherwise</returns>
public bool Equals(ref TextureDescriptor other)
{
return Unsafe.As<TextureDescriptor, Vector256<byte>>(ref this).Equals(Unsafe.As<TextureDescriptor, Vector256<byte>>(ref other));
}
/// <summary>
/// Check if two descriptors are equal.
/// </summary>
/// <param name="other">The descriptor to compare against</param>
/// <returns>True if they are equal, false otherwise</returns>
public bool Equals(TextureDescriptor other)
{
return Equals(ref other);
}
/// <summary>
/// Gets a hash code for this descriptor.
/// </summary>
/// <returns>The hash code for this descriptor.</returns>
public override int GetHashCode()
{
return Unsafe.As<TextureDescriptor, Vector256<byte>>(ref this).GetHashCode();
}
}
}

View file

@ -0,0 +1,16 @@
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
/// The texture descriptor type.
/// This specifies the texture memory layout.
/// The texture descriptor structure depends on the type.
/// </summary>
enum TextureDescriptorType
{
Buffer,
LinearColorKey,
Linear,
BlockLinear,
BlockLinearColorKey
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,554 @@
using Ryujinx.Cpu.Tracking;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
/// A tracking handle for a texture group, which represents a range of views in a storage texture.
/// Retains a list of overlapping texture views, a modified flag, and tracking for each
/// CPU VA range that the views cover.
/// Also tracks copy dependencies for the handle - references to other handles that must be kept
/// in sync with this one before use.
/// </summary>
class TextureGroupHandle : IDisposable
{
private TextureGroup _group;
private int _bindCount;
private int _firstLevel;
private int _firstLayer;
// Sync state for texture flush.
/// <summary>
/// The sync number last registered.
/// </summary>
private ulong _registeredSync;
/// <summary>
/// The sync number when the texture was last modified by GPU.
/// </summary>
private ulong _modifiedSync;
/// <summary>
/// Whether a tracking action is currently registered or not. (0/1)
/// </summary>
private int _actionRegistered;
/// <summary>
/// Whether a sync action is currently registered or not.
/// </summary>
private bool _syncActionRegistered;
/// <summary>
/// The byte offset from the start of the storage of this handle.
/// </summary>
public int Offset { get; }
/// <summary>
/// The size in bytes covered by this handle.
/// </summary>
public int Size { get; }
/// <summary>
/// The base slice index for this handle.
/// </summary>
public int BaseSlice { get; }
/// <summary>
/// The number of slices covered by this handle.
/// </summary>
public int SliceCount { get; }
/// <summary>
/// The textures which this handle overlaps with.
/// </summary>
public List<Texture> Overlaps { get; }
/// <summary>
/// The CPU memory tracking handles that cover this handle.
/// </summary>
public CpuRegionHandle[] Handles { get; }
/// <summary>
/// True if a texture overlapping this handle has been modified. Is set false when the flush action is called.
/// </summary>
public bool Modified { get; set; }
/// <summary>
/// Dependencies to handles from other texture groups.
/// </summary>
public List<TextureDependency> Dependencies { get; }
/// <summary>
/// A flag indicating that a copy is required from one of the dependencies.
/// </summary>
public bool NeedsCopy => DeferredCopy != null;
/// <summary>
/// A data copy that must be acknowledged the next time this handle is used.
/// </summary>
public TextureGroupHandle DeferredCopy { get; set; }
/// <summary>
/// Create a new texture group handle, representing a range of views in a storage texture.
/// </summary>
/// <param name="group">The TextureGroup that the handle belongs to</param>
/// <param name="offset">The byte offset from the start of the storage of the handle</param>
/// <param name="size">The size in bytes covered by the handle</param>
/// <param name="views">All views of the storage texture, used to calculate overlaps</param>
/// <param name="firstLayer">The first layer of this handle in the storage texture</param>
/// <param name="firstLevel">The first level of this handle in the storage texture</param>
/// <param name="baseSlice">The base slice index of this handle</param>
/// <param name="sliceCount">The number of slices this handle covers</param>
/// <param name="handles">The memory tracking handles that cover this handle</param>
public TextureGroupHandle(TextureGroup group,
int offset,
ulong size,
List<Texture> views,
int firstLayer,
int firstLevel,
int baseSlice,
int sliceCount,
CpuRegionHandle[] handles)
{
_group = group;
_firstLayer = firstLayer;
_firstLevel = firstLevel;
Offset = offset;
Size = (int)size;
Overlaps = new List<Texture>();
Dependencies = new List<TextureDependency>();
BaseSlice = baseSlice;
SliceCount = sliceCount;
if (views != null)
{
RecalculateOverlaps(group, views);
}
Handles = handles;
}
/// <summary>
/// Calculate a list of which views overlap this handle.
/// </summary>
/// <param name="group">The parent texture group, used to find a view's base CPU VA offset</param>
/// <param name="views">The list of views to search for overlaps</param>
public void RecalculateOverlaps(TextureGroup group, List<Texture> views)
{
// Overlaps can be accessed from the memory tracking signal handler, so access must be atomic.
lock (Overlaps)
{
int endOffset = Offset + Size;
Overlaps.Clear();
foreach (Texture view in views)
{
int viewOffset = group.FindOffset(view);
if (viewOffset < endOffset && Offset < viewOffset + (int)view.Size)
{
Overlaps.Add(view);
}
}
}
}
/// <summary>
/// Adds a single texture view as an overlap if its range overlaps.
/// </summary>
/// <param name="offset">The offset of the view in the group</param>
/// <param name="view">The texture to add as an overlap</param>
public void AddOverlap(int offset, Texture view)
{
// Overlaps can be accessed from the memory tracking signal handler, so access must be atomic.
if (OverlapsWith(offset, (int)view.Size))
{
lock (Overlaps)
{
Overlaps.Add(view);
}
}
}
/// <summary>
/// Removes a single texture view as an overlap if its range overlaps.
/// </summary>
/// <param name="offset">The offset of the view in the group</param>
/// <param name="view">The texture to add as an overlap</param>
public void RemoveOverlap(int offset, Texture view)
{
// Overlaps can be accessed from the memory tracking signal handler, so access must be atomic.
if (OverlapsWith(offset, (int)view.Size))
{
lock (Overlaps)
{
Overlaps.Remove(view);
}
}
}
/// <summary>
/// Registers a sync action to happen for this handle, and an interim flush action on the tracking handle.
/// </summary>
/// <param name="context">The GPU context to register a sync action on</param>
private void RegisterSync(GpuContext context)
{
if (!_syncActionRegistered)
{
_modifiedSync = context.SyncNumber;
context.RegisterSyncAction(SyncAction, true);
_syncActionRegistered = true;
}
if (Interlocked.Exchange(ref _actionRegistered, 1) == 0)
{
_group.RegisterAction(this);
}
}
/// <summary>
/// Signal that this handle has been modified to any existing dependencies, and set the modified flag.
/// </summary>
/// <param name="context">The GPU context to register a sync action on</param>
public void SignalModified(GpuContext context)
{
Modified = true;
// If this handle has any copy dependencies, notify the other handle that a copy needs to be performed.
foreach (TextureDependency dependency in Dependencies)
{
dependency.SignalModified();
}
RegisterSync(context);
}
/// <summary>
/// Signal that this handle has either started or ended being modified.
/// </summary>
/// <param name="bound">True if this handle is being bound, false if unbound</param>
/// <param name="context">The GPU context to register a sync action on</param>
public void SignalModifying(bool bound, GpuContext context)
{
SignalModified(context);
// Note: Bind count currently resets to 0 on inherit for safety, as the handle <-> view relationship can change.
_bindCount = Math.Max(0, _bindCount + (bound ? 1 : -1));
}
/// <summary>
/// Synchronize dependent textures, if any of them have deferred a copy from this texture.
/// </summary>
public void SynchronizeDependents()
{
foreach (TextureDependency dependency in Dependencies)
{
TextureGroupHandle otherHandle = dependency.Other.Handle;
if (otherHandle.DeferredCopy == this)
{
otherHandle._group.Storage.SynchronizeMemory();
}
}
}
/// <summary>
/// Wait for the latest sync number that the texture handle was written to,
/// removing the modified flag if it was reached, or leaving it set if it has not yet been created.
/// </summary>
/// <param name="context">The GPU context used to wait for sync</param>
public void Sync(GpuContext context)
{
ulong registeredSync = _registeredSync;
long diff = (long)(context.SyncNumber - registeredSync);
if (diff > 0)
{
context.Renderer.WaitSync(registeredSync);
if ((long)(_modifiedSync - registeredSync) > 0)
{
// Flush the data in a previous state. Do not remove the modified flag - it will be removed to ignore following writes.
return;
}
Modified = false;
}
// If the difference is <= 0, no data is not ready yet. Flush any data we can without waiting or removing modified flag.
}
/// <summary>
/// Clears the action registered variable, indicating that the tracking action should be
/// re-registered on the next modification.
/// </summary>
public void ClearActionRegistered()
{
Interlocked.Exchange(ref _actionRegistered, 0);
}
/// <summary>
/// Action to perform when a sync number is registered after modification.
/// This action will register a read tracking action on the memory tracking handle so that a flush from CPU can happen.
/// </summary>
private void SyncAction()
{
// The storage will need to signal modified again to update the sync number in future.
_group.Storage.SignalModifiedDirty();
lock (Overlaps)
{
foreach (Texture texture in Overlaps)
{
texture.SignalModifiedDirty();
}
}
// Register region tracking for CPU? (again)
_registeredSync = _modifiedSync;
_syncActionRegistered = false;
if (Interlocked.Exchange(ref _actionRegistered, 1) == 0)
{
_group.RegisterAction(this);
}
}
/// <summary>
/// Signal that a copy dependent texture has been modified, and must have its data copied to this one.
/// </summary>
/// <param name="copyFrom">The texture handle that must defer a copy to this one</param>
public void DeferCopy(TextureGroupHandle copyFrom)
{
Modified = false;
DeferredCopy = copyFrom;
_group.Storage.SignalGroupDirty();
foreach (Texture overlap in Overlaps)
{
overlap.SignalGroupDirty();
}
}
/// <summary>
/// Create a copy dependency between this handle, and another.
/// </summary>
/// <param name="other">The handle to create a copy dependency to</param>
/// <param name="copyToOther">True if a copy should be deferred to all of the other handle's dependencies</param>
public void CreateCopyDependency(TextureGroupHandle other, bool copyToOther = false)
{
// Does this dependency already exist?
foreach (TextureDependency existing in Dependencies)
{
if (existing.Other.Handle == other)
{
// Do not need to create it again. May need to set the dirty flag.
return;
}
}
_group.HasCopyDependencies = true;
other._group.HasCopyDependencies = true;
TextureDependency dependency = new TextureDependency(this);
TextureDependency otherDependency = new TextureDependency(other);
dependency.Other = otherDependency;
otherDependency.Other = dependency;
Dependencies.Add(dependency);
other.Dependencies.Add(otherDependency);
// Recursively create dependency:
// All of this handle's dependencies must depend on the other.
foreach (TextureDependency existing in Dependencies.ToArray())
{
if (existing != dependency && existing.Other.Handle != other)
{
existing.Other.Handle.CreateCopyDependency(other);
}
}
// All of the other handle's dependencies must depend on this.
foreach (TextureDependency existing in other.Dependencies.ToArray())
{
if (existing != otherDependency && existing.Other.Handle != this)
{
existing.Other.Handle.CreateCopyDependency(this);
if (copyToOther)
{
existing.Other.Handle.DeferCopy(this);
}
}
}
}
/// <summary>
/// Remove a dependency from this handle's dependency list.
/// </summary>
/// <param name="dependency">The dependency to remove</param>
public void RemoveDependency(TextureDependency dependency)
{
Dependencies.Remove(dependency);
}
/// <summary>
/// Check if any of this handle's memory tracking handles are dirty.
/// </summary>
/// <returns>True if at least one of the handles is dirty</returns>
private bool CheckDirty()
{
return Handles.Any(handle => handle.Dirty);
}
/// <summary>
/// Perform a copy from the provided handle to this one, or perform a deferred copy if none is provided.
/// </summary>
/// <param name="context">GPU context to register sync for modified handles</param>
/// <param name="fromHandle">The handle to copy from. If not provided, this method will copy from and clear the deferred copy instead</param>
/// <returns>True if the copy was performed, false otherwise</returns>
public bool Copy(GpuContext context, TextureGroupHandle fromHandle = null)
{
bool result = false;
bool shouldCopy = false;
if (fromHandle == null)
{
fromHandle = DeferredCopy;
if (fromHandle != null)
{
// Only copy if the copy texture is still modified.
// It will be set as unmodified if new data is written from CPU, as the data previously in the texture will flush.
// It will also set as unmodified if a copy is deferred to it.
shouldCopy = fromHandle.Modified;
if (fromHandle._bindCount == 0)
{
// Repeat the copy in future if the bind count is greater than 0.
DeferredCopy = null;
}
}
}
else
{
// Copies happen directly when initializing a copy dependency.
// If dirty, do not copy. Its data no longer matters, and this handle should also be dirty.
// Also, only direct copy if the data in this handle is not already modified (can be set by copies from modified handles).
shouldCopy = !fromHandle.CheckDirty() && (fromHandle.Modified || !Modified);
}
if (shouldCopy)
{
Texture from = fromHandle._group.Storage;
Texture to = _group.Storage;
if (from.ScaleFactor != to.ScaleFactor)
{
to.PropagateScale(from);
}
from.HostTexture.CopyTo(
to.HostTexture,
fromHandle._firstLayer,
_firstLayer,
fromHandle._firstLevel,
_firstLevel);
if (fromHandle.Modified)
{
Modified = true;
RegisterSync(context);
}
result = true;
}
return result;
}
/// <summary>
/// Check if this handle has a dependency to a given texture group.
/// </summary>
/// <param name="group">The texture group to check for</param>
/// <returns>True if there is a dependency, false otherwise</returns>
public bool HasDependencyTo(TextureGroup group)
{
foreach (TextureDependency dep in Dependencies)
{
if (dep.Other.Handle._group == group)
{
return true;
}
}
return false;
}
/// <summary>
/// Inherit modified flags and dependencies from another texture handle.
/// </summary>
/// <param name="old">The texture handle to inherit from</param>
/// <param name="withCopies">Whether the handle should inherit copy dependencies or not</param>
public void Inherit(TextureGroupHandle old, bool withCopies)
{
Modified |= old.Modified;
if (withCopies)
{
foreach (TextureDependency dependency in old.Dependencies.ToArray())
{
CreateCopyDependency(dependency.Other.Handle);
if (dependency.Other.Handle.DeferredCopy == old)
{
dependency.Other.Handle.DeferredCopy = this;
}
}
DeferredCopy = old.DeferredCopy;
}
}
/// <summary>
/// Check if this region overlaps with another.
/// </summary>
/// <param name="address">Base address</param>
/// <param name="size">Size of the region</param>
/// <returns>True if overlapping, false otherwise</returns>
public bool OverlapsWith(int offset, int size)
{
return Offset < offset + size && offset < Offset + Size;
}
/// <summary>
/// Dispose this texture group handle, removing all its dependencies and disposing its memory tracking handles.
/// </summary>
public void Dispose()
{
foreach (CpuRegionHandle handle in Handles)
{
handle.Dispose();
}
foreach (TextureDependency dependency in Dependencies.ToArray())
{
dependency.Other.Handle.RemoveDependency(dependency.Other);
}
}
}
}

View file

@ -0,0 +1,381 @@
using Ryujinx.Common;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Texture;
using System;
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
/// Texture information.
/// </summary>
readonly struct TextureInfo
{
/// <summary>
/// Address of the texture in GPU mapped memory.
/// </summary>
public ulong GpuAddress { get; }
/// <summary>
/// The width of the texture.
/// </summary>
public int Width { get; }
/// <summary>
/// The height of the texture, or layers count for 1D array textures.
/// </summary>
public int Height { get; }
/// <summary>
/// The depth of the texture (for 3D textures), or layers count for array textures.
/// </summary>
public int DepthOrLayers { get; }
/// <summary>
/// The number of mipmap levels of the texture.
/// </summary>
public int Levels { get; }
/// <summary>
/// The number of samples in the X direction for multisampled textures.
/// </summary>
public int SamplesInX { get; }
/// <summary>
/// The number of samples in the Y direction for multisampled textures.
/// </summary>
public int SamplesInY { get; }
/// <summary>
/// The number of bytes per line for linear textures.
/// </summary>
public int Stride { get; }
/// <summary>
/// Indicates whenever or not the texture is a linear texture.
/// </summary>
public bool IsLinear { get; }
/// <summary>
/// GOB blocks in the Y direction, for block linear textures.
/// </summary>
public int GobBlocksInY { get; }
/// <summary>
/// GOB blocks in the Z direction, for block linear textures.
/// </summary>
public int GobBlocksInZ { get; }
/// <summary>
/// Number of GOB blocks per tile in the X direction, for block linear textures.
/// </summary>
public int GobBlocksInTileX { get; }
/// <summary>
/// Total number of samples for multisampled textures.
/// </summary>
public int Samples => SamplesInX * SamplesInY;
/// <summary>
/// Texture target type.
/// </summary>
public Target Target { get; }
/// <summary>
/// Texture format information.
/// </summary>
public FormatInfo FormatInfo { get; }
/// <summary>
/// Depth-stencil mode of the texture. This defines whenever the depth or stencil value is read from shaders,
/// for depth-stencil texture formats.
/// </summary>
public DepthStencilMode DepthStencilMode { get; }
/// <summary>
/// Texture swizzle for the red color channel.
/// </summary>
public SwizzleComponent SwizzleR { get; }
/// <summary>
/// Texture swizzle for the green color channel.
/// </summary>
public SwizzleComponent SwizzleG { get; }
/// <summary>
/// Texture swizzle for the blue color channel.
/// </summary>
public SwizzleComponent SwizzleB { get; }
/// <summary>
/// Texture swizzle for the alpha color channel.
/// </summary>
public SwizzleComponent SwizzleA { get; }
/// <summary>
/// Constructs the texture information structure.
/// </summary>
/// <param name="gpuAddress">The GPU address of the texture</param>
/// <param name="width">The width of the texture</param>
/// <param name="height">The height or the texture</param>
/// <param name="depthOrLayers">The depth or layers count of the texture</param>
/// <param name="levels">The amount of mipmap levels of the texture</param>
/// <param name="samplesInX">The number of samples in the X direction for multisample textures, should be 1 otherwise</param>
/// <param name="samplesInY">The number of samples in the Y direction for multisample textures, should be 1 otherwise</param>
/// <param name="stride">The stride for linear textures</param>
/// <param name="isLinear">Whenever the texture is linear or block linear</param>
/// <param name="gobBlocksInY">Number of GOB blocks in the Y direction</param>
/// <param name="gobBlocksInZ">Number of GOB blocks in the Z direction</param>
/// <param name="gobBlocksInTileX">Number of GOB blocks per tile in the X direction</param>
/// <param name="target">Texture target type</param>
/// <param name="formatInfo">Texture format information</param>
/// <param name="depthStencilMode">Depth-stencil mode</param>
/// <param name="swizzleR">Swizzle for the red color channel</param>
/// <param name="swizzleG">Swizzle for the green color channel</param>
/// <param name="swizzleB">Swizzle for the blue color channel</param>
/// <param name="swizzleA">Swizzle for the alpha color channel</param>
public TextureInfo(
ulong gpuAddress,
int width,
int height,
int depthOrLayers,
int levels,
int samplesInX,
int samplesInY,
int stride,
bool isLinear,
int gobBlocksInY,
int gobBlocksInZ,
int gobBlocksInTileX,
Target target,
FormatInfo formatInfo,
DepthStencilMode depthStencilMode = DepthStencilMode.Depth,
SwizzleComponent swizzleR = SwizzleComponent.Red,
SwizzleComponent swizzleG = SwizzleComponent.Green,
SwizzleComponent swizzleB = SwizzleComponent.Blue,
SwizzleComponent swizzleA = SwizzleComponent.Alpha)
{
GpuAddress = gpuAddress;
Width = width;
Height = height;
DepthOrLayers = depthOrLayers;
Levels = levels;
SamplesInX = samplesInX;
SamplesInY = samplesInY;
Stride = stride;
IsLinear = isLinear;
GobBlocksInY = gobBlocksInY;
GobBlocksInZ = gobBlocksInZ;
GobBlocksInTileX = gobBlocksInTileX;
Target = target;
FormatInfo = formatInfo;
DepthStencilMode = depthStencilMode;
SwizzleR = swizzleR;
SwizzleG = swizzleG;
SwizzleB = swizzleB;
SwizzleA = swizzleA;
}
/// <summary>
/// Gets the real texture depth.
/// Returns 1 for any target other than 3D textures.
/// </summary>
/// <returns>Texture depth</returns>
public int GetDepth()
{
return GetDepth(Target, DepthOrLayers);
}
/// <summary>
/// Gets the real texture depth.
/// Returns 1 for any target other than 3D textures.
/// </summary>
/// <param name="target">Texture target</param>
/// <param name="depthOrLayers">Texture depth if the texture is 3D, otherwise ignored</param>
/// <returns>Texture depth</returns>
public static int GetDepth(Target target, int depthOrLayers)
{
return target == Target.Texture3D ? depthOrLayers : 1;
}
/// <summary>
/// Gets the number of layers of the texture.
/// Returns 1 for non-array textures, 6 for cubemap textures, and layer faces for cubemap array textures.
/// </summary>
/// <returns>The number of texture layers</returns>
public int GetLayers()
{
return GetLayers(Target, DepthOrLayers);
}
/// <summary>
/// Gets the number of layers of the texture.
/// Returns 1 for non-array textures, 6 for cubemap textures, and layer faces for cubemap array textures.
/// </summary>
/// <param name="target">Texture target</param>
/// <param name="depthOrLayers">Texture layers if the is a array texture, ignored otherwise</param>
/// <returns>The number of texture layers</returns>
public static int GetLayers(Target target, int depthOrLayers)
{
if (target == Target.Texture2DArray || target == Target.Texture2DMultisampleArray)
{
return depthOrLayers;
}
else if (target == Target.CubemapArray)
{
return depthOrLayers * 6;
}
else if (target == Target.Cubemap)
{
return 6;
}
else
{
return 1;
}
}
/// <summary>
/// Gets the number of 2D slices of the texture.
/// Returns 6 for cubemap textures, layer faces for cubemap array textures, and DepthOrLayers for everything else.
/// </summary>
/// <returns>The number of texture slices</returns>
public int GetSlices()
{
if (Target == Target.Texture3D || Target == Target.Texture2DArray || Target == Target.Texture2DMultisampleArray)
{
return DepthOrLayers;
}
else if (Target == Target.CubemapArray)
{
return DepthOrLayers * 6;
}
else if (Target == Target.Cubemap)
{
return 6;
}
else
{
return 1;
}
}
/// <summary>
/// Calculates the size information from the texture information.
/// </summary>
/// <param name="layerSize">Optional size of each texture layer in bytes</param>
/// <returns>Texture size information</returns>
public SizeInfo CalculateSizeInfo(int layerSize = 0)
{
if (Target == Target.TextureBuffer)
{
return new SizeInfo(Width * FormatInfo.BytesPerPixel);
}
else if (IsLinear)
{
return SizeCalculator.GetLinearTextureSize(
Stride,
Height,
FormatInfo.BlockHeight);
}
else
{
return SizeCalculator.GetBlockLinearTextureSize(
Width,
Height,
GetDepth(),
Levels,
GetLayers(),
FormatInfo.BlockWidth,
FormatInfo.BlockHeight,
FormatInfo.BytesPerPixel,
GobBlocksInY,
GobBlocksInZ,
GobBlocksInTileX,
layerSize);
}
}
/// <summary>
/// Creates texture information for a given mipmap level of the specified parent texture and this information.
/// </summary>
/// <param name="parent">The parent texture</param>
/// <param name="firstLevel">The first level of the texture view</param>
/// <returns>The adjusted texture information with the new size</returns>
public TextureInfo CreateInfoForLevelView(Texture parent, int firstLevel)
{
// When the texture is used as view of another texture, we must
// ensure that the sizes are valid, otherwise data uploads would fail
// (and the size wouldn't match the real size used on the host API).
// Given a parent texture from where the view is created, we have the
// following rules:
// - The view size must be equal to the parent size, divided by (2 ^ l),
// where l is the first mipmap level of the view. The division result must
// be rounded down, and the result must be clamped to 1.
// - If the parent format is compressed, and the view format isn't, the
// view size is calculated as above, but the width and height of the
// view must be also divided by the compressed format block width and height.
// - If the parent format is not compressed, and the view is, the view
// size is calculated as described on the first point, but the width and height
// of the view must be also multiplied by the block width and height.
int width = Math.Max(1, parent.Info.Width >> firstLevel);
int height = Math.Max(1, parent.Info.Height >> firstLevel);
if (parent.Info.FormatInfo.IsCompressed && !FormatInfo.IsCompressed)
{
width = BitUtils.DivRoundUp(width, parent.Info.FormatInfo.BlockWidth);
height = BitUtils.DivRoundUp(height, parent.Info.FormatInfo.BlockHeight);
}
else if (!parent.Info.FormatInfo.IsCompressed && FormatInfo.IsCompressed)
{
width *= FormatInfo.BlockWidth;
height *= FormatInfo.BlockHeight;
}
int depthOrLayers;
if (Target == Target.Texture3D)
{
depthOrLayers = Math.Max(1, parent.Info.DepthOrLayers >> firstLevel);
}
else
{
depthOrLayers = DepthOrLayers;
}
// 2D and 2D multisample textures are not considered compatible.
// This specific case is required for copies, where the source texture might be multisample.
// In this case, we inherit the parent texture multisample state.
Target target = Target;
int samplesInX = SamplesInX;
int samplesInY = SamplesInY;
if (target == Target.Texture2D && parent.Target == Target.Texture2DMultisample)
{
target = Target.Texture2DMultisample;
samplesInX = parent.Info.SamplesInX;
samplesInY = parent.Info.SamplesInY;
}
return new TextureInfo(
GpuAddress,
width,
height,
depthOrLayers,
Levels,
samplesInX,
samplesInY,
Stride,
IsLinear,
GobBlocksInY,
GobBlocksInZ,
GobBlocksInTileX,
target,
FormatInfo,
DepthStencilMode,
SwizzleR,
SwizzleG,
SwizzleB,
SwizzleA);
}
}
}

View file

@ -0,0 +1,498 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Types;
using Ryujinx.Graphics.Gpu.Shader;
using System;
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
/// Texture manager.
/// </summary>
class TextureManager : IDisposable
{
private readonly GpuContext _context;
private readonly GpuChannel _channel;
private readonly TextureBindingsManager _cpBindingsManager;
private readonly TextureBindingsManager _gpBindingsManager;
private readonly TexturePoolCache _texturePoolCache;
private readonly SamplerPoolCache _samplerPoolCache;
private readonly Texture[] _rtColors;
private readonly ITexture[] _rtHostColors;
private Texture _rtDepthStencil;
private ITexture _rtHostDs;
public int ClipRegionWidth { get; private set; }
public int ClipRegionHeight { get; private set; }
/// <summary>
/// The scaling factor applied to all currently bound render targets.
/// </summary>
public float RenderTargetScale { get; private set; } = 1f;
/// <summary>
/// Creates a new instance of the texture manager.
/// </summary>
/// <param name="context">GPU context that the texture manager belongs to</param>
/// <param name="channel">GPU channel that the texture manager belongs to</param>
public TextureManager(GpuContext context, GpuChannel channel)
{
_context = context;
_channel = channel;
TexturePoolCache texturePoolCache = new TexturePoolCache(context);
SamplerPoolCache samplerPoolCache = new SamplerPoolCache(context);
float[] scales = new float[64];
new Span<float>(scales).Fill(1f);
_cpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, samplerPoolCache, scales, isCompute: true);
_gpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, samplerPoolCache, scales, isCompute: false);
_texturePoolCache = texturePoolCache;
_samplerPoolCache = samplerPoolCache;
_rtColors = new Texture[Constants.TotalRenderTargets];
_rtHostColors = new ITexture[Constants.TotalRenderTargets];
}
/// <summary>
/// Sets the texture and image bindings for the compute pipeline.
/// </summary>
/// <param name="bindings">Bindings for the active shader</param>
public void SetComputeBindings(CachedShaderBindings bindings)
{
_cpBindingsManager.SetBindings(bindings);
}
/// <summary>
/// Sets the texture and image bindings for the graphics pipeline.
/// </summary>
/// <param name="bindings">Bindings for the active shader</param>
public void SetGraphicsBindings(CachedShaderBindings bindings)
{
_gpBindingsManager.SetBindings(bindings);
}
/// <summary>
/// Sets the texture constant buffer index on the compute pipeline.
/// </summary>
/// <param name="index">The texture constant buffer index</param>
public void SetComputeTextureBufferIndex(int index)
{
_cpBindingsManager.SetTextureBufferIndex(index);
}
/// <summary>
/// Sets the texture constant buffer index on the graphics pipeline.
/// </summary>
/// <param name="index">The texture constant buffer index</param>
public void SetGraphicsTextureBufferIndex(int index)
{
_gpBindingsManager.SetTextureBufferIndex(index);
}
/// <summary>
/// Sets the current sampler pool on the compute pipeline.
/// </summary>
/// <param name="gpuVa">The start GPU virtual address of the sampler pool</param>
/// <param name="maximumId">The maximum ID of the sampler pool</param>
/// <param name="samplerIndex">The indexing type of the sampler pool</param>
public void SetComputeSamplerPool(ulong gpuVa, int maximumId, SamplerIndex samplerIndex)
{
_cpBindingsManager.SetSamplerPool(gpuVa, maximumId, samplerIndex);
}
/// <summary>
/// Sets the current sampler pool on the graphics pipeline.
/// </summary>
/// <param name="gpuVa">The start GPU virtual address of the sampler pool</param>
/// <param name="maximumId">The maximum ID of the sampler pool</param>
/// <param name="samplerIndex">The indexing type of the sampler pool</param>
public void SetGraphicsSamplerPool(ulong gpuVa, int maximumId, SamplerIndex samplerIndex)
{
_gpBindingsManager.SetSamplerPool(gpuVa, maximumId, samplerIndex);
}
/// <summary>
/// Sets the current texture pool on the compute pipeline.
/// </summary>
/// <param name="gpuVa">The start GPU virtual address of the texture pool</param>
/// <param name="maximumId">The maximum ID of the texture pool</param>
public void SetComputeTexturePool(ulong gpuVa, int maximumId)
{
_cpBindingsManager.SetTexturePool(gpuVa, maximumId);
}
/// <summary>
/// Sets the current texture pool on the graphics pipeline.
/// </summary>
/// <param name="gpuVa">The start GPU virtual address of the texture pool</param>
/// <param name="maximumId">The maximum ID of the texture pool</param>
public void SetGraphicsTexturePool(ulong gpuVa, int maximumId)
{
_gpBindingsManager.SetTexturePool(gpuVa, maximumId);
}
/// <summary>
/// Check if a texture's scale must be updated to match the configured resolution scale.
/// </summary>
/// <param name="texture">The texture to check</param>
/// <returns>True if the scale needs updating, false if the scale is up to date</returns>
private bool ScaleNeedsUpdated(Texture texture)
{
return texture != null && !(texture.ScaleMode == TextureScaleMode.Blacklisted || texture.ScaleMode == TextureScaleMode.Undesired) && texture.ScaleFactor != GraphicsConfig.ResScale;
}
/// <summary>
/// Sets the render target color buffer.
/// </summary>
/// <param name="index">The index of the color buffer to set (up to 8)</param>
/// <param name="color">The color buffer texture</param>
/// <returns>True if render target scale must be updated.</returns>
public bool SetRenderTargetColor(int index, Texture color)
{
bool hasValue = color != null;
bool changesScale = (hasValue != (_rtColors[index] != null)) || (hasValue && RenderTargetScale != color.ScaleFactor);
if (_rtColors[index] != color)
{
_rtColors[index]?.SignalModifying(false);
if (color != null)
{
color.SynchronizeMemory();
color.SignalModifying(true);
}
_rtColors[index] = color;
}
return changesScale || ScaleNeedsUpdated(color);
}
/// <summary>
/// Sets the render target depth-stencil buffer.
/// </summary>
/// <param name="depthStencil">The depth-stencil buffer texture</param>
/// <returns>True if render target scale must be updated.</returns>
public bool SetRenderTargetDepthStencil(Texture depthStencil)
{
bool hasValue = depthStencil != null;
bool changesScale = (hasValue != (_rtDepthStencil != null)) || (hasValue && RenderTargetScale != depthStencil.ScaleFactor);
if (_rtDepthStencil != depthStencil)
{
_rtDepthStencil?.SignalModifying(false);
if (depthStencil != null)
{
depthStencil.SynchronizeMemory();
depthStencil.SignalModifying(true);
}
_rtDepthStencil = depthStencil;
}
return changesScale || ScaleNeedsUpdated(depthStencil);
}
/// <summary>
/// Sets the host clip region, which should be the intersection of all render target texture sizes.
/// </summary>
/// <param name="width">Width of the clip region, defined as the minimum width across all bound textures</param>
/// <param name="height">Height of the clip region, defined as the minimum height across all bound textures</param>
public void SetClipRegion(int width, int height)
{
ClipRegionWidth = width;
ClipRegionHeight = height;
}
/// <summary>
/// Gets the first available bound colour target, or the depth stencil target if not present.
/// </summary>
/// <returns>The first bound colour target, otherwise the depth stencil target</returns>
public Texture GetAnyRenderTarget()
{
return _rtColors[0] ?? _rtDepthStencil;
}
/// <summary>
/// Updates the Render Target scale, given the currently bound render targets.
/// This will update scale to match the configured scale, scale textures that are eligible but not scaled,
/// and propagate blacklisted status from one texture to the ones bound with it.
/// </summary>
/// <param name="singleUse">If this is not -1, it indicates that only the given indexed target will be used.</param>
public void UpdateRenderTargetScale(int singleUse)
{
// Make sure all scales for render targets are at the highest they should be. Blacklisted targets should propagate their scale to the other targets.
bool mismatch = false;
bool blacklisted = false;
bool hasUpscaled = false;
bool hasUndesired = false;
float targetScale = GraphicsConfig.ResScale;
void ConsiderTarget(Texture target)
{
if (target == null) return;
float scale = target.ScaleFactor;
switch (target.ScaleMode)
{
case TextureScaleMode.Blacklisted:
mismatch |= scale != 1f;
blacklisted = true;
break;
case TextureScaleMode.Eligible:
mismatch = true; // We must make a decision.
break;
case TextureScaleMode.Undesired:
hasUndesired = true;
mismatch |= scale != 1f || hasUpscaled; // If another target is upscaled, scale this one up too.
break;
case TextureScaleMode.Scaled:
hasUpscaled = true;
mismatch |= hasUndesired || scale != targetScale; // If the target scale has changed, reset the scale for all targets.
break;
}
}
if (singleUse != -1)
{
// If only one target is in use (by a clear, for example) the others do not need to be checked for mismatching scale.
ConsiderTarget(_rtColors[singleUse]);
}
else
{
foreach (Texture color in _rtColors)
{
ConsiderTarget(color);
}
}
ConsiderTarget(_rtDepthStencil);
mismatch |= blacklisted && hasUpscaled;
if (blacklisted || (hasUndesired && !hasUpscaled))
{
targetScale = 1f;
}
if (mismatch)
{
if (blacklisted)
{
// Propagate the blacklisted state to the other textures.
foreach (Texture color in _rtColors)
{
color?.BlacklistScale();
}
_rtDepthStencil?.BlacklistScale();
}
else
{
// Set the scale of the other textures.
foreach (Texture color in _rtColors)
{
color?.SetScale(targetScale);
}
_rtDepthStencil?.SetScale(targetScale);
}
}
RenderTargetScale = targetScale;
}
/// <summary>
/// Gets a texture and a sampler from their respective pools from a texture ID and a sampler ID.
/// </summary>
/// <param name="textureId">ID of the texture</param>
/// <param name="samplerId">ID of the sampler</param>
public (Texture, Sampler) GetGraphicsTextureAndSampler(int textureId, int samplerId)
{
return _gpBindingsManager.GetTextureAndSampler(textureId, samplerId);
}
/// <summary>
/// Commits bindings on the compute pipeline.
/// </summary>
/// <param name="specState">Specialization state for the bound shader</param>
/// <returns>True if all bound textures match the current shader specialization state, false otherwise</returns>
public bool CommitComputeBindings(ShaderSpecializationState specState)
{
// Every time we switch between graphics and compute work,
// we must rebind everything.
// Since compute work happens less often, we always do that
// before and after the compute dispatch.
_texturePoolCache.Tick();
_samplerPoolCache.Tick();
_cpBindingsManager.Rebind();
bool result = _cpBindingsManager.CommitBindings(specState);
_gpBindingsManager.Rebind();
return result;
}
/// <summary>
/// Commits bindings on the graphics pipeline.
/// </summary>
/// <param name="specState">Specialization state for the bound shader</param>
/// <returns>True if all bound textures match the current shader specialization state, false otherwise</returns>
public bool CommitGraphicsBindings(ShaderSpecializationState specState)
{
_texturePoolCache.Tick();
_samplerPoolCache.Tick();
bool result = _gpBindingsManager.CommitBindings(specState);
UpdateRenderTargets();
return result;
}
/// <summary>
/// Returns a texture pool from the cache, with the given address and maximum id.
/// </summary>
/// <param name="poolGpuVa">GPU virtual address of the texture pool</param>
/// <param name="maximumId">Maximum ID of the texture pool</param>
/// <returns>The texture pool</returns>
public TexturePool GetTexturePool(ulong poolGpuVa, int maximumId)
{
ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa);
TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId);
return texturePool;
}
/// <summary>
/// Gets a texture descriptor used on the compute pipeline.
/// </summary>
/// <param name="poolGpuVa">GPU virtual address of the texture pool</param>
/// <param name="bufferIndex">Index of the constant buffer with texture handles</param>
/// <param name="maximumId">Maximum ID of the texture pool</param>
/// <param name="handle">Shader "fake" handle of the texture</param>
/// <param name="cbufSlot">Shader constant buffer slot of the texture</param>
/// <returns>The texture descriptor</returns>
public TextureDescriptor GetComputeTextureDescriptor(ulong poolGpuVa, int bufferIndex, int maximumId, int handle, int cbufSlot)
{
return _cpBindingsManager.GetTextureDescriptor(poolGpuVa, bufferIndex, maximumId, 0, handle, cbufSlot);
}
/// <summary>
/// Gets a texture descriptor used on the graphics pipeline.
/// </summary>
/// <param name="poolGpuVa">GPU virtual address of the texture pool</param>
/// <param name="bufferIndex">Index of the constant buffer with texture handles</param>
/// <param name="maximumId">Maximum ID of the texture pool</param>
/// <param name="stageIndex">Index of the shader stage where the texture is bound</param>
/// <param name="handle">Shader "fake" handle of the texture</param>
/// <param name="cbufSlot">Shader constant buffer slot of the texture</param>
/// <returns>The texture descriptor</returns>
public TextureDescriptor GetGraphicsTextureDescriptor(
ulong poolGpuVa,
int bufferIndex,
int maximumId,
int stageIndex,
int handle,
int cbufSlot)
{
return _gpBindingsManager.GetTextureDescriptor(poolGpuVa, bufferIndex, maximumId, stageIndex, handle, cbufSlot);
}
/// <summary>
/// Update host framebuffer attachments based on currently bound render target buffers.
/// </summary>
public void UpdateRenderTargets()
{
bool anyChanged = false;
if (_rtHostDs != _rtDepthStencil?.HostTexture)
{
_rtHostDs = _rtDepthStencil?.HostTexture;
anyChanged = true;
}
for (int index = 0; index < _rtColors.Length; index++)
{
ITexture hostTexture = _rtColors[index]?.HostTexture;
if (_rtHostColors[index] != hostTexture)
{
_rtHostColors[index] = hostTexture;
anyChanged = true;
}
}
if (anyChanged)
{
_context.Renderer.Pipeline.SetRenderTargets(_rtHostColors, _rtHostDs);
}
}
/// <summary>
/// Update host framebuffer attachments based on currently bound render target buffers.
/// </summary>
/// <remarks>
/// All color attachments will be unbound.
/// </remarks>
public void UpdateRenderTargetDepthStencil()
{
new Span<ITexture>(_rtHostColors).Fill(null);
_rtHostDs = _rtDepthStencil?.HostTexture;
_context.Renderer.Pipeline.SetRenderTargets(_rtHostColors, _rtHostDs);
}
/// <summary>
/// Forces the texture and sampler pools to be re-loaded from the cache on next use.
/// </summary>
public void ReloadPools()
{
_cpBindingsManager.ReloadPools();
_gpBindingsManager.ReloadPools();
}
/// <summary>
/// Forces all textures, samplers, images and render targets to be rebound the next time
/// CommitGraphicsBindings is called.
/// </summary>
public void Rebind()
{
_gpBindingsManager.Rebind();
for (int index = 0; index < _rtHostColors.Length; index++)
{
_rtHostColors[index] = null;
}
_rtHostDs = null;
}
/// <summary>
/// Disposes the texture manager.
/// It's an error to use the texture manager after disposal.
/// </summary>
public void Dispose()
{
// Textures are owned by the texture cache, so we shouldn't dispose the texture pool cache.
_samplerPoolCache.Dispose();
for (int i = 0; i < _rtColors.Length; i++)
{
_rtColors[i]?.DecrementReferenceCount();
_rtColors[i] = null;
}
_rtDepthStencil?.DecrementReferenceCount();
_rtDepthStencil = null;
}
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.Graphics.Gpu.Image
{
enum TextureMatchQuality
{
NoMatch,
FormatAlias,
Perfect
}
}

View file

@ -0,0 +1,68 @@
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
/// Multisampled texture samples count.
/// </summary>
enum TextureMsaaMode
{
Ms1x1 = 0,
Ms2x2 = 2,
Ms4x2 = 4,
Ms2x1 = 5,
Ms4x4 = 6
}
static class TextureMsaaModeConverter
{
/// <summary>
/// Returns the total number of samples from the MSAA mode.
/// </summary>
/// <param name="msaaMode">The MSAA mode</param>
/// <returns>The total number of samples</returns>
public static int SamplesCount(this TextureMsaaMode msaaMode)
{
return msaaMode switch
{
TextureMsaaMode.Ms2x1 => 2,
TextureMsaaMode.Ms2x2 => 4,
TextureMsaaMode.Ms4x2 => 8,
TextureMsaaMode.Ms4x4 => 16,
_ => 1
};
}
/// <summary>
/// Returns the number of samples in the X direction from the MSAA mode.
/// </summary>
/// <param name="msaaMode">The MSAA mode</param>
/// <returns>The number of samples in the X direction</returns>
public static int SamplesInX(this TextureMsaaMode msaaMode)
{
return msaaMode switch
{
TextureMsaaMode.Ms2x1 => 2,
TextureMsaaMode.Ms2x2 => 2,
TextureMsaaMode.Ms4x2 => 4,
TextureMsaaMode.Ms4x4 => 4,
_ => 1
};
}
/// <summary>
/// Returns the number of samples in the Y direction from the MSAA mode.
/// </summary>
/// <param name="msaaMode">The MSAA mode</param>
/// <returns>The number of samples in the Y direction</returns>
public static int SamplesInY(this TextureMsaaMode msaaMode)
{
return msaaMode switch
{
TextureMsaaMode.Ms2x1 => 1,
TextureMsaaMode.Ms2x2 => 2,
TextureMsaaMode.Ms4x2 => 2,
TextureMsaaMode.Ms4x4 => 4,
_ => 1
};
}
}
}

View file

@ -0,0 +1,603 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Texture;
using Ryujinx.Memory.Range;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
/// Texture pool.
/// </summary>
class TexturePool : Pool<Texture, TextureDescriptor>, IPool<TexturePool>
{
/// <summary>
/// A request to dereference a texture from a pool.
/// </summary>
private struct DereferenceRequest
{
/// <summary>
/// Whether the dereference is due to a mapping change or not.
/// </summary>
public readonly bool IsRemapped;
/// <summary>
/// The texture being dereferenced.
/// </summary>
public readonly Texture Texture;
/// <summary>
/// The ID of the pool entry this reference belonged to.
/// </summary>
public readonly int ID;
/// <summary>
/// Create a dereference request for a texture with a specific pool ID, and remapped flag.
/// </summary>
/// <param name="isRemapped">Whether the dereference is due to a mapping change or not</param>
/// <param name="texture">The texture being dereferenced</param>
/// <param name="id">The ID of the pool entry, used to restore remapped textures</param>
private DereferenceRequest(bool isRemapped, Texture texture, int id)
{
IsRemapped = isRemapped;
Texture = texture;
ID = id;
}
/// <summary>
/// Create a dereference request for a texture removal.
/// </summary>
/// <param name="texture">The texture being removed</param>
/// <returns>A texture removal dereference request</returns>
public static DereferenceRequest Remove(Texture texture)
{
return new DereferenceRequest(false, texture, 0);
}
/// <summary>
/// Create a dereference request for a texture remapping with a specific pool ID.
/// </summary>
/// <param name="texture">The texture being remapped</param>
/// <param name="id">The ID of the pool entry, used to restore remapped textures</param>
/// <returns>A remap dereference request</returns>
public static DereferenceRequest Remap(Texture texture, int id)
{
return new DereferenceRequest(true, texture, id);
}
}
private readonly GpuChannel _channel;
private readonly ConcurrentQueue<DereferenceRequest> _dereferenceQueue = new ConcurrentQueue<DereferenceRequest>();
private TextureDescriptor _defaultDescriptor;
/// <summary>
/// Linked list node used on the texture pool cache.
/// </summary>
public LinkedListNode<TexturePool> CacheNode { get; set; }
/// <summary>
/// Timestamp used by the texture pool cache, updated on every use of this texture pool.
/// </summary>
public ulong CacheTimestamp { get; set; }
/// <summary>
/// Creates a new instance of the texture pool.
/// </summary>
/// <param name="context">GPU context that the texture pool belongs to</param>
/// <param name="channel">GPU channel that the texture pool belongs to</param>
/// <param name="address">Address of the texture pool in guest memory</param>
/// <param name="maximumId">Maximum texture ID of the texture pool (equal to maximum textures minus one)</param>
public TexturePool(GpuContext context, GpuChannel channel, ulong address, int maximumId) : base(context, channel.MemoryManager.Physical, address, maximumId)
{
_channel = channel;
}
/// <summary>
/// Gets the texture descripor and texture with the given ID with no bounds check or synchronization.
/// </summary>
/// <param name="id">ID of the texture. This is effectively a zero-based index</param>
/// <param name="texture">The texture with the given ID</param>
/// <returns>The texture descriptor with the given ID</returns>
private ref readonly TextureDescriptor GetInternal(int id, out Texture texture)
{
texture = Items[id];
ref readonly TextureDescriptor descriptor = ref GetDescriptorRef(id);
if (texture == null)
{
texture = PhysicalMemory.TextureCache.FindShortCache(descriptor);
if (texture == null)
{
TextureInfo info = GetInfo(descriptor, out int layerSize);
// The dereference queue can put our texture back on the cache.
if ((texture = ProcessDereferenceQueue(id)) != null)
{
return ref descriptor;
}
texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize);
// If this happens, then the texture address is invalid, we can't add it to the cache.
if (texture == null)
{
return ref descriptor;
}
}
else
{
texture.SynchronizeMemory();
}
Items[id] = texture;
texture.IncrementReferenceCount(this, id, descriptor.UnpackAddress());
DescriptorCache[id] = descriptor;
}
else
{
// On the path above (texture not yet in the pool), memory is automatically synchronized on texture creation.
texture.SynchronizeMemory();
}
return ref descriptor;
}
/// <summary>
/// Gets the texture with the given ID.
/// </summary>
/// <param name="id">ID of the texture. This is effectively a zero-based index</param>
/// <returns>The texture with the given ID</returns>
public override Texture Get(int id)
{
if ((uint)id >= Items.Length)
{
return null;
}
if (SequenceNumber != Context.SequenceNumber)
{
SequenceNumber = Context.SequenceNumber;
SynchronizeMemory();
}
GetInternal(id, out Texture texture);
return texture;
}
/// <summary>
/// Gets the texture descriptor and texture with the given ID.
/// </summary>
/// <remarks>
/// This method assumes that the pool has been manually synchronized before doing binding.
/// </remarks>
/// <param name="id">ID of the texture. This is effectively a zero-based index</param>
/// <param name="texture">The texture with the given ID</param>
/// <returns>The texture descriptor with the given ID</returns>
public ref readonly TextureDescriptor GetForBinding(int id, out Texture texture)
{
if ((uint)id >= Items.Length)
{
texture = null;
return ref _defaultDescriptor;
}
// When getting for binding, assume the pool has already been synchronized.
return ref GetInternal(id, out texture);
}
/// <summary>
/// Checks if the pool was modified, and returns the last sequence number where a modification was detected.
/// </summary>
/// <returns>A number that increments each time a modification is detected</returns>
public int CheckModified()
{
if (SequenceNumber != Context.SequenceNumber)
{
SequenceNumber = Context.SequenceNumber;
SynchronizeMemory();
}
return ModifiedSequenceNumber;
}
/// <summary>
/// Forcibly remove a texture from this pool's items.
/// If deferred, the dereference will be queued to occur on the render thread.
/// </summary>
/// <param name="texture">The texture being removed</param>
/// <param name="id">The ID of the texture in this pool</param>
/// <param name="deferred">If true, queue the dereference to happen on the render thread, otherwise dereference immediately</param>
public void ForceRemove(Texture texture, int id, bool deferred)
{
var previous = Interlocked.Exchange(ref Items[id], null);
if (deferred)
{
if (previous != null)
{
_dereferenceQueue.Enqueue(DereferenceRequest.Remove(texture));
}
}
else
{
texture.DecrementReferenceCount();
}
}
/// <summary>
/// Queues a request to update a texture's mapping.
/// Mapping is updated later to avoid deleting the texture if it is still sparsely mapped.
/// </summary>
/// <param name="texture">Texture with potential mapping change</param>
/// <param name="id">ID in cache of texture with potential mapping change</param>
public void QueueUpdateMapping(Texture texture, int id)
{
if (Interlocked.Exchange(ref Items[id], null) == texture)
{
_dereferenceQueue.Enqueue(DereferenceRequest.Remap(texture, id));
}
}
/// <summary>
/// Process the dereference queue, decrementing the reference count for each texture in it.
/// This is used to ensure that texture disposal happens on the render thread.
/// </summary>
/// <param name="id">The ID of the entry that triggered this method</param>
/// <returns>Texture that matches the entry ID if it has been readded to the cache.</returns>
private Texture ProcessDereferenceQueue(int id = -1)
{
while (_dereferenceQueue.TryDequeue(out DereferenceRequest request))
{
Texture texture = request.Texture;
// Unmapped storage textures can swap their ranges. The texture must be storage with no views or dependencies.
// TODO: Would need to update ranges on views, or guarantee that ones where the range changes can be instantly deleted.
if (request.IsRemapped && texture.Group.Storage == texture && !texture.HasViews && !texture.Group.HasCopyDependencies)
{
// Has the mapping for this texture changed?
ref readonly TextureDescriptor descriptor = ref GetDescriptorRef(request.ID);
ulong address = descriptor.UnpackAddress();
MultiRange range = _channel.MemoryManager.GetPhysicalRegions(address, texture.Size);
// If the texture is not mapped at all, delete its reference.
if (range.Count == 1 && range.GetSubRange(0).Address == MemoryManager.PteUnmapped)
{
texture.DecrementReferenceCount();
continue;
}
Items[request.ID] = texture;
// Create a new pool reference, as the last one was removed on unmap.
texture.IncrementReferenceCount(this, request.ID, address);
texture.DecrementReferenceCount();
// Refetch the range. Changes since the last check could have been lost
// as the cache entry was not restored (required to queue mapping change).
range = _channel.MemoryManager.GetPhysicalRegions(address, texture.Size);
if (!range.Equals(texture.Range))
{
// Part of the texture was mapped or unmapped. Replace the range and regenerate tracking handles.
if (!_channel.MemoryManager.Physical.TextureCache.UpdateMapping(texture, range))
{
// Texture could not be remapped due to a collision, just delete it.
if (Interlocked.Exchange(ref Items[request.ID], null) != null)
{
// If this is null, a request was already queued to decrement reference.
texture.DecrementReferenceCount(this, request.ID);
}
continue;
}
}
if (request.ID == id)
{
return texture;
}
}
else
{
texture.DecrementReferenceCount();
}
}
return null;
}
/// <summary>
/// Implementation of the texture pool range invalidation.
/// </summary>
/// <param name="address">Start address of the range of the texture pool</param>
/// <param name="size">Size of the range being invalidated</param>
protected override void InvalidateRangeImpl(ulong address, ulong size)
{
ProcessDereferenceQueue();
ulong endAddress = address + size;
for (; address < endAddress; address += DescriptorSize)
{
int id = (int)((address - Address) / DescriptorSize);
Texture texture = Items[id];
if (texture != null)
{
ref TextureDescriptor cachedDescriptor = ref DescriptorCache[id];
ref readonly TextureDescriptor descriptor = ref GetDescriptorRefAddress(address);
// If the descriptors are the same, the texture is the same,
// we don't need to remove as it was not modified. Just continue.
if (descriptor.Equals(ref cachedDescriptor))
{
continue;
}
if (texture.HasOneReference())
{
_channel.MemoryManager.Physical.TextureCache.AddShortCache(texture, ref cachedDescriptor);
}
if (Interlocked.Exchange(ref Items[id], null) != null)
{
texture.DecrementReferenceCount(this, id);
}
}
}
}
/// <summary>
/// Gets texture information from a texture descriptor.
/// </summary>
/// <param name="descriptor">The texture descriptor</param>
/// <param name="layerSize">Layer size for textures using a sub-range of mipmap levels, otherwise 0</param>
/// <returns>The texture information</returns>
private TextureInfo GetInfo(in TextureDescriptor descriptor, out int layerSize)
{
int depthOrLayers = descriptor.UnpackDepth();
int levels = descriptor.UnpackLevels();
TextureMsaaMode msaaMode = descriptor.UnpackTextureMsaaMode();
int samplesInX = msaaMode.SamplesInX();
int samplesInY = msaaMode.SamplesInY();
int stride = descriptor.UnpackStride();
TextureDescriptorType descriptorType = descriptor.UnpackTextureDescriptorType();
bool isLinear = descriptorType == TextureDescriptorType.Linear;
Target target = descriptor.UnpackTextureTarget().Convert((samplesInX | samplesInY) != 1);
int width = target == Target.TextureBuffer ? descriptor.UnpackBufferTextureWidth() : descriptor.UnpackWidth();
int height = descriptor.UnpackHeight();
if (target == Target.Texture2DMultisample || target == Target.Texture2DMultisampleArray)
{
// This is divided back before the backend texture is created.
width *= samplesInX;
height *= samplesInY;
}
// We use 2D targets for 1D textures as that makes texture cache
// management easier. We don't know the target for render target
// and copies, so those would normally use 2D targets, which are
// not compatible with 1D targets. By doing that we also allow those
// to match when looking for compatible textures on the cache.
if (target == Target.Texture1D)
{
target = Target.Texture2D;
height = 1;
}
else if (target == Target.Texture1DArray)
{
target = Target.Texture2DArray;
height = 1;
}
uint format = descriptor.UnpackFormat();
bool srgb = descriptor.UnpackSrgb();
ulong gpuVa = descriptor.UnpackAddress();
if (!FormatTable.TryGetTextureFormat(format, srgb, out FormatInfo formatInfo))
{
if (gpuVa != 0 && (int)format > 0)
{
Logger.Error?.Print(LogClass.Gpu, $"Invalid texture format 0x{format:X} (sRGB: {srgb}).");
}
formatInfo = FormatInfo.Default;
}
int gobBlocksInY = descriptor.UnpackGobBlocksInY();
int gobBlocksInZ = descriptor.UnpackGobBlocksInZ();
int gobBlocksInTileX = descriptor.UnpackGobBlocksInTileX();
layerSize = 0;
int minLod = descriptor.UnpackBaseLevel();
int maxLod = descriptor.UnpackMaxLevelInclusive();
// Linear textures don't support mipmaps, so we don't handle this case here.
if ((minLod != 0 || maxLod + 1 != levels) && target != Target.TextureBuffer && !isLinear)
{
int depth = TextureInfo.GetDepth(target, depthOrLayers);
int layers = TextureInfo.GetLayers(target, depthOrLayers);
SizeInfo sizeInfo = SizeCalculator.GetBlockLinearTextureSize(
width,
height,
depth,
levels,
layers,
formatInfo.BlockWidth,
formatInfo.BlockHeight,
formatInfo.BytesPerPixel,
gobBlocksInY,
gobBlocksInZ,
gobBlocksInTileX);
layerSize = sizeInfo.LayerSize;
if (minLod != 0 && minLod < levels)
{
// If the base level is not zero, we additionally add the mip level offset
// to the address, this allows the texture manager to find the base level from the
// address if there is a overlapping texture on the cache that can contain the new texture.
gpuVa += (ulong)sizeInfo.GetMipOffset(minLod);
width = Math.Max(1, width >> minLod);
height = Math.Max(1, height >> minLod);
if (target == Target.Texture3D)
{
depthOrLayers = Math.Max(1, depthOrLayers >> minLod);
}
(gobBlocksInY, gobBlocksInZ) = SizeCalculator.GetMipGobBlockSizes(height, depth, formatInfo.BlockHeight, gobBlocksInY, gobBlocksInZ);
}
levels = (maxLod - minLod) + 1;
}
SwizzleComponent swizzleR = descriptor.UnpackSwizzleR().Convert();
SwizzleComponent swizzleG = descriptor.UnpackSwizzleG().Convert();
SwizzleComponent swizzleB = descriptor.UnpackSwizzleB().Convert();
SwizzleComponent swizzleA = descriptor.UnpackSwizzleA().Convert();
DepthStencilMode depthStencilMode = GetDepthStencilMode(
formatInfo.Format,
swizzleR,
swizzleG,
swizzleB,
swizzleA);
if (formatInfo.Format.IsDepthOrStencil())
{
swizzleR = SwizzleComponent.Red;
swizzleG = SwizzleComponent.Red;
swizzleB = SwizzleComponent.Red;
if (depthStencilMode == DepthStencilMode.Depth)
{
swizzleA = SwizzleComponent.One;
}
else
{
swizzleA = SwizzleComponent.Red;
}
}
return new TextureInfo(
gpuVa,
width,
height,
depthOrLayers,
levels,
samplesInX,
samplesInY,
stride,
isLinear,
gobBlocksInY,
gobBlocksInZ,
gobBlocksInTileX,
target,
formatInfo,
depthStencilMode,
swizzleR,
swizzleG,
swizzleB,
swizzleA);
}
/// <summary>
/// Gets the texture depth-stencil mode, based on the swizzle components of each color channel.
/// The depth-stencil mode is determined based on how the driver sets those parameters.
/// </summary>
/// <param name="format">The format of the texture</param>
/// <param name="components">The texture swizzle components</param>
/// <returns>The depth-stencil mode</returns>
private static DepthStencilMode GetDepthStencilMode(Format format, params SwizzleComponent[] components)
{
// R = Depth, G = Stencil.
// On 24-bits depth formats, this is inverted (Stencil is R etc).
// NVN setup:
// For depth, A is set to 1.0f, the other components are set to Depth.
// For stencil, all components are set to Stencil.
SwizzleComponent component = components[0];
for (int index = 1; index < 4 && !IsRG(component); index++)
{
component = components[index];
}
if (!IsRG(component))
{
return DepthStencilMode.Depth;
}
if (format == Format.D24UnormS8Uint)
{
return component == SwizzleComponent.Red
? DepthStencilMode.Stencil
: DepthStencilMode.Depth;
}
else
{
return component == SwizzleComponent.Red
? DepthStencilMode.Depth
: DepthStencilMode.Stencil;
}
}
/// <summary>
/// Checks if the swizzle component is equal to the red or green channels.
/// </summary>
/// <param name="component">The swizzle component to check</param>
/// <returns>True if the swizzle component is equal to the red or green, false otherwise</returns>
private static bool IsRG(SwizzleComponent component)
{
return component == SwizzleComponent.Red ||
component == SwizzleComponent.Green;
}
/// <summary>
/// Decrements the reference count of the texture.
/// This indicates that the texture pool is not using it anymore.
/// </summary>
/// <param name="item">The texture to be deleted</param>
protected override void Delete(Texture item)
{
item?.DecrementReferenceCount(this);
}
public override void Dispose()
{
ProcessDereferenceQueue();
base.Dispose();
}
}
}

View file

@ -0,0 +1,30 @@
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
/// Texture pool cache.
/// This can keep multiple texture pools, and return the current one as needed.
/// It is useful for applications that uses multiple texture pools.
/// </summary>
class TexturePoolCache : PoolCache<TexturePool>
{
/// <summary>
/// Constructs a new instance of the texture pool.
/// </summary>
/// <param name="context">GPU context that the texture pool belongs to</param>
public TexturePoolCache(GpuContext context) : base(context)
{
}
/// <summary>
/// Creates a new instance of the texture pool.
/// </summary>
/// <param name="context">GPU context that the texture pool belongs to</param>
/// <param name="channel">GPU channel that the texture pool belongs to</param>
/// <param name="address">Address of the texture pool in guest memory</param>
/// <param name="maximumId">Maximum texture ID of the texture pool (equal to maximum textures minus one)</param>
protected override TexturePool CreatePool(GpuContext context, GpuChannel channel, ulong address, int maximumId)
{
return new TexturePool(context, channel, address, maximumId);
}
}
}

View file

@ -0,0 +1,16 @@
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
/// The scale mode for a given texture.
/// Blacklisted textures cannot be scaled, Eligible textures have not been scaled yet,
/// and Scaled textures have been scaled already.
/// Undesired textures will stay at 1x until a situation where they must match a scaled texture.
/// </summary>
enum TextureScaleMode
{
Eligible = 0,
Scaled = 1,
Blacklisted = 2,
Undesired = 3
}
}

View file

@ -0,0 +1,17 @@
using System;
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
/// Texture search flags, defines texture information comparison rules.
/// </summary>
[Flags]
enum TextureSearchFlags
{
None = 0,
ForSampler = 1 << 1,
ForCopy = 1 << 2,
WithUpscale = 1 << 3,
NoCreate = 1 << 4
}
}

View file

@ -0,0 +1,81 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
/// Texture target.
/// </summary>
enum TextureTarget : byte
{
Texture1D,
Texture2D,
Texture3D,
Cubemap,
Texture1DArray,
Texture2DArray,
TextureBuffer,
Texture2DRect,
CubemapArray
}
static class TextureTargetConverter
{
/// <summary>
/// Converts the texture target enum to a host compatible, Graphics Abstraction Layer enum.
/// </summary>
/// <param name="target">The target enum to convert</param>
/// <param name="isMultisample">True if the texture is a multisampled texture</param>
/// <returns>The host compatible texture target</returns>
public static Target Convert(this TextureTarget target, bool isMultisample)
{
if (isMultisample)
{
switch (target)
{
case TextureTarget.Texture2D: return Target.Texture2DMultisample;
case TextureTarget.Texture2DArray: return Target.Texture2DMultisampleArray;
}
}
else
{
switch (target)
{
case TextureTarget.Texture1D: return Target.Texture1D;
case TextureTarget.Texture2D: return Target.Texture2D;
case TextureTarget.Texture2DRect: return Target.Texture2D;
case TextureTarget.Texture3D: return Target.Texture3D;
case TextureTarget.Texture1DArray: return Target.Texture1DArray;
case TextureTarget.Texture2DArray: return Target.Texture2DArray;
case TextureTarget.Cubemap: return Target.Cubemap;
case TextureTarget.CubemapArray: return Target.CubemapArray;
case TextureTarget.TextureBuffer: return Target.TextureBuffer;
}
}
return Target.Texture1D;
}
/// <summary>
/// Converts the texture target enum to a shader sampler type.
/// </summary>
/// <param name="target">The target enum to convert</param>
/// <returns>The shader sampler type</returns>
public static SamplerType ConvertSamplerType(this TextureTarget target)
{
return target switch
{
TextureTarget.Texture1D => SamplerType.Texture1D,
TextureTarget.Texture2D => SamplerType.Texture2D,
TextureTarget.Texture3D => SamplerType.Texture3D,
TextureTarget.Cubemap => SamplerType.TextureCube,
TextureTarget.Texture1DArray => SamplerType.Texture1D | SamplerType.Array,
TextureTarget.Texture2DArray => SamplerType.Texture2D | SamplerType.Array,
TextureTarget.TextureBuffer => SamplerType.TextureBuffer,
TextureTarget.Texture2DRect => SamplerType.Texture2D,
TextureTarget.CubemapArray => SamplerType.TextureCube | SamplerType.Array,
_ => SamplerType.Texture2D
};
}
}
}

View file

@ -0,0 +1,14 @@
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
/// The level of view compatibility one texture has to another.
/// Values are increasing in compatibility from 0 (incompatible).
/// </summary>
enum TextureViewCompatibility
{
Incompatible = 0,
LayoutIncompatible,
CopyOnly,
Full
}
}