aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Graphics.Gpu/Image
diff options
context:
space:
mode:
Diffstat (limited to 'Ryujinx.Graphics.Gpu/Image')
-rw-r--r--Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs62
-rw-r--r--Ryujinx.Graphics.Gpu/Image/FormatInfo.cs31
-rw-r--r--Ryujinx.Graphics.Gpu/Image/FormatTable.cs201
-rw-r--r--Ryujinx.Graphics.Gpu/Image/Pool.cs99
-rw-r--r--Ryujinx.Graphics.Gpu/Image/ReductionFilter.cs9
-rw-r--r--Ryujinx.Graphics.Gpu/Image/Sampler.cs52
-rw-r--r--Ryujinx.Graphics.Gpu/Image/SamplerDescriptor.cs132
-rw-r--r--Ryujinx.Graphics.Gpu/Image/SamplerPool.cs61
-rw-r--r--Ryujinx.Graphics.Gpu/Image/Texture.cs719
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs17
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs95
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureComponent.cs35
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs119
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureDescriptorType.cs11
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureInfo.cs101
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureManager.cs669
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureMsaaMode.cs53
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TexturePool.cs219
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs73
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureSearchFlags.cs13
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureTarget.cs49
21 files changed, 2820 insertions, 0 deletions
diff --git a/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs b/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs
new file mode 100644
index 00000000..33ed7881
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs
@@ -0,0 +1,62 @@
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ class AutoDeleteCache : IEnumerable<Texture>
+ {
+ private const int MaxCapacity = 2048;
+
+ private LinkedList<Texture> _textures;
+
+ public AutoDeleteCache()
+ {
+ _textures = new LinkedList<Texture>();
+ }
+
+ public void Add(Texture texture)
+ {
+ texture.IncrementReferenceCount();
+
+ texture.CacheNode = _textures.AddLast(texture);
+
+ if (_textures.Count > MaxCapacity)
+ {
+ Texture oldestTexture = _textures.First.Value;
+
+ _textures.RemoveFirst();
+
+ oldestTexture.DecrementReferenceCount();
+
+ oldestTexture.CacheNode = null;
+ }
+ }
+
+ public void Lift(Texture texture)
+ {
+ if (texture.CacheNode != null)
+ {
+ if (texture.CacheNode != _textures.Last)
+ {
+ _textures.Remove(texture.CacheNode);
+
+ texture.CacheNode = _textures.AddLast(texture);
+ }
+ }
+ else
+ {
+ Add(texture);
+ }
+ }
+
+ public IEnumerator<Texture> GetEnumerator()
+ {
+ return _textures.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return _textures.GetEnumerator();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/FormatInfo.cs b/Ryujinx.Graphics.Gpu/Image/FormatInfo.cs
new file mode 100644
index 00000000..a728c66e
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/FormatInfo.cs
@@ -0,0 +1,31 @@
+using Ryujinx.Graphics.GAL;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ struct FormatInfo
+ {
+ private static FormatInfo _rgba8 = new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4);
+
+ public static FormatInfo Default => _rgba8;
+
+ public Format Format { get; }
+
+ public int BlockWidth { get; }
+ public int BlockHeight { get; }
+ public int BytesPerPixel { get; }
+
+ public bool IsCompressed => (BlockWidth | BlockHeight) != 1;
+
+ public FormatInfo(
+ Format format,
+ int blockWidth,
+ int blockHeight,
+ int bytesPerPixel)
+ {
+ Format = format;
+ BlockWidth = blockWidth;
+ BlockHeight = blockHeight;
+ BytesPerPixel = bytesPerPixel;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/FormatTable.cs b/Ryujinx.Graphics.Gpu/Image/FormatTable.cs
new file mode 100644
index 00000000..9f5b26d4
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/FormatTable.cs
@@ -0,0 +1,201 @@
+using Ryujinx.Graphics.GAL;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ static class FormatTable
+ {
+ private static Dictionary<uint, FormatInfo> _textureFormats = new Dictionary<uint, FormatInfo>()
+ {
+ { 0x2491d, new FormatInfo(Format.R8Unorm, 1, 1, 1) },
+ { 0x1249d, new FormatInfo(Format.R8Snorm, 1, 1, 1) },
+ { 0x4921d, new FormatInfo(Format.R8Uint, 1, 1, 1) },
+ { 0x36d9d, new FormatInfo(Format.R8Sint, 1, 1, 1) },
+ { 0x7ff9b, new FormatInfo(Format.R16Float, 1, 1, 2) },
+ { 0x2491b, new FormatInfo(Format.R16Unorm, 1, 1, 2) },
+ { 0x1249b, new FormatInfo(Format.R16Snorm, 1, 1, 2) },
+ { 0x4921b, new FormatInfo(Format.R16Uint, 1, 1, 2) },
+ { 0x36d9b, new FormatInfo(Format.R16Sint, 1, 1, 2) },
+ { 0x7ff8f, new FormatInfo(Format.R32Float, 1, 1, 4) },
+ { 0x4920f, new FormatInfo(Format.R32Uint, 1, 1, 4) },
+ { 0x36d8f, new FormatInfo(Format.R32Sint, 1, 1, 4) },
+ { 0x24918, new FormatInfo(Format.R8G8Unorm, 1, 1, 2) },
+ { 0x12498, new FormatInfo(Format.R8G8Snorm, 1, 1, 2) },
+ { 0x49218, new FormatInfo(Format.R8G8Uint, 1, 1, 2) },
+ { 0x36d98, new FormatInfo(Format.R8G8Sint, 1, 1, 2) },
+ { 0x7ff8c, new FormatInfo(Format.R16G16Float, 1, 1, 4) },
+ { 0x2490c, new FormatInfo(Format.R16G16Unorm, 1, 1, 4) },
+ { 0x1248c, new FormatInfo(Format.R16G16Snorm, 1, 1, 4) },
+ { 0x4920c, new FormatInfo(Format.R16G16Uint, 1, 1, 4) },
+ { 0x36d8c, new FormatInfo(Format.R16G16Sint, 1, 1, 4) },
+ { 0x7ff84, new FormatInfo(Format.R32G32Float, 1, 1, 8) },
+ { 0x49204, new FormatInfo(Format.R32G32Uint, 1, 1, 8) },
+ { 0x36d84, new FormatInfo(Format.R32G32Sint, 1, 1, 8) },
+ { 0x7ff82, new FormatInfo(Format.R32G32B32Float, 1, 1, 12) },
+ { 0x49202, new FormatInfo(Format.R32G32B32Uint, 1, 1, 12) },
+ { 0x36d82, new FormatInfo(Format.R32G32B32Sint, 1, 1, 12) },
+ { 0x24908, new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4) },
+ { 0x12488, new FormatInfo(Format.R8G8B8A8Snorm, 1, 1, 4) },
+ { 0x49208, new FormatInfo(Format.R8G8B8A8Uint, 1, 1, 4) },
+ { 0x36d88, new FormatInfo(Format.R8G8B8A8Sint, 1, 1, 4) },
+ { 0x7ff83, new FormatInfo(Format.R16G16B16A16Float, 1, 1, 8) },
+ { 0x24903, new FormatInfo(Format.R16G16B16A16Unorm, 1, 1, 8) },
+ { 0x12483, new FormatInfo(Format.R16G16B16A16Snorm, 1, 1, 8) },
+ { 0x49203, new FormatInfo(Format.R16G16B16A16Uint, 1, 1, 8) },
+ { 0x36d83, new FormatInfo(Format.R16G16B16A16Sint, 1, 1, 8) },
+ { 0x7ff81, new FormatInfo(Format.R32G32B32A32Float, 1, 1, 16) },
+ { 0x49201, new FormatInfo(Format.R32G32B32A32Uint, 1, 1, 16) },
+ { 0x36d81, new FormatInfo(Format.R32G32B32A32Sint, 1, 1, 16) },
+ { 0x2493a, new FormatInfo(Format.D16Unorm, 1, 1, 2) },
+ { 0x7ffaf, new FormatInfo(Format.D32Float, 1, 1, 4) },
+ { 0x24a29, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4) },
+ { 0x253b0, new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8) },
+ { 0xa4908, new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4) },
+ { 0x24912, new FormatInfo(Format.R4G4B4A4Unorm, 1, 1, 2) },
+ { 0x24914, new FormatInfo(Format.R5G5B5A1Unorm, 1, 1, 2) },
+ { 0x24915, new FormatInfo(Format.R5G6B5Unorm, 1, 1, 2) },
+ { 0x24909, new FormatInfo(Format.R10G10B10A2Unorm, 1, 1, 4) },
+ { 0x49209, new FormatInfo(Format.R10G10B10A2Uint, 1, 1, 4) },
+ { 0x7ffa1, new FormatInfo(Format.R11G11B10Float, 1, 1, 4) },
+ { 0x7ffa0, new FormatInfo(Format.R9G9B9E5Float, 1, 1, 4) },
+ { 0x24924, new FormatInfo(Format.Bc1RgbaUnorm, 4, 4, 8) },
+ { 0x24925, new FormatInfo(Format.Bc2Unorm, 4, 4, 16) },
+ { 0x24926, new FormatInfo(Format.Bc3Unorm, 4, 4, 16) },
+ { 0xa4924, new FormatInfo(Format.Bc1RgbaSrgb, 4, 4, 8) },
+ { 0xa4925, new FormatInfo(Format.Bc2Srgb, 4, 4, 16) },
+ { 0xa4926, new FormatInfo(Format.Bc3Srgb, 4, 4, 16) },
+ { 0x24927, new FormatInfo(Format.Bc4Unorm, 4, 4, 8) },
+ { 0x124a7, new FormatInfo(Format.Bc4Snorm, 4, 4, 8) },
+ { 0x24928, new FormatInfo(Format.Bc5Unorm, 4, 4, 16) },
+ { 0x124a8, new FormatInfo(Format.Bc5Snorm, 4, 4, 16) },
+ { 0x24917, new FormatInfo(Format.Bc7Unorm, 4, 4, 16) },
+ { 0xa4917, new FormatInfo(Format.Bc7Srgb, 4, 4, 16) },
+ { 0x7ff90, new FormatInfo(Format.Bc6HUfloat, 4, 4, 16) },
+ { 0x7ff91, new FormatInfo(Format.Bc6HSfloat, 4, 4, 16) },
+ { 0x24940, new FormatInfo(Format.Astc4x4Unorm, 4, 4, 16) },
+ { 0x24950, new FormatInfo(Format.Astc5x4Unorm, 5, 4, 16) },
+ { 0x24941, new FormatInfo(Format.Astc5x5Unorm, 5, 5, 16) },
+ { 0x24951, new FormatInfo(Format.Astc6x5Unorm, 6, 5, 16) },
+ { 0x24942, new FormatInfo(Format.Astc6x6Unorm, 6, 6, 16) },
+ { 0x24955, new FormatInfo(Format.Astc8x5Unorm, 8, 5, 16) },
+ { 0x24952, new FormatInfo(Format.Astc8x6Unorm, 8, 6, 16) },
+ { 0x24944, new FormatInfo(Format.Astc8x8Unorm, 8, 8, 16) },
+ { 0x24956, new FormatInfo(Format.Astc10x5Unorm, 10, 5, 16) },
+ { 0x24957, new FormatInfo(Format.Astc10x6Unorm, 10, 6, 16) },
+ { 0x24953, new FormatInfo(Format.Astc10x8Unorm, 10, 8, 16) },
+ { 0x24945, new FormatInfo(Format.Astc10x10Unorm, 10, 10, 16) },
+ { 0x24954, new FormatInfo(Format.Astc12x10Unorm, 12, 10, 16) },
+ { 0x24946, new FormatInfo(Format.Astc12x12Unorm, 12, 12, 16) },
+ { 0xa4940, new FormatInfo(Format.Astc4x4Srgb, 4, 4, 16) },
+ { 0xa4950, new FormatInfo(Format.Astc5x4Srgb, 5, 4, 16) },
+ { 0xa4941, new FormatInfo(Format.Astc5x5Srgb, 5, 5, 16) },
+ { 0xa4951, new FormatInfo(Format.Astc6x5Srgb, 6, 5, 16) },
+ { 0xa4942, new FormatInfo(Format.Astc6x6Srgb, 6, 6, 16) },
+ { 0xa4955, new FormatInfo(Format.Astc8x5Srgb, 8, 5, 16) },
+ { 0xa4952, new FormatInfo(Format.Astc8x6Srgb, 8, 6, 16) },
+ { 0xa4944, new FormatInfo(Format.Astc8x8Srgb, 8, 8, 16) },
+ { 0xa4956, new FormatInfo(Format.Astc10x5Srgb, 10, 5, 16) },
+ { 0xa4957, new FormatInfo(Format.Astc10x6Srgb, 10, 6, 16) },
+ { 0xa4953, new FormatInfo(Format.Astc10x8Srgb, 10, 8, 16) },
+ { 0xa4945, new FormatInfo(Format.Astc10x10Srgb, 10, 10, 16) },
+ { 0xa4954, new FormatInfo(Format.Astc12x10Srgb, 12, 10, 16) },
+ { 0xa4946, new FormatInfo(Format.Astc12x12Srgb, 12, 12, 16) },
+ { 0x24913, new FormatInfo(Format.A1B5G5R5Unorm, 1, 1, 2) }
+ };
+
+ private static Dictionary<ulong, Format> _attribFormats = new Dictionary<ulong, Format>()
+ {
+ { 0x13a00000, Format.R8Unorm },
+ { 0x0ba00000, Format.R8Snorm },
+ { 0x23a00000, Format.R8Uint },
+ { 0x1ba00000, Format.R8Sint },
+ { 0x3b600000, Format.R16Float },
+ { 0x13600000, Format.R16Unorm },
+ { 0x0b600000, Format.R16Snorm },
+ { 0x23600000, Format.R16Uint },
+ { 0x1b600000, Format.R16Sint },
+ { 0x3a400000, Format.R32Float },
+ { 0x22400000, Format.R32Uint },
+ { 0x1a400000, Format.R32Sint },
+ { 0x13000000, Format.R8G8Unorm },
+ { 0x0b000000, Format.R8G8Snorm },
+ { 0x23000000, Format.R8G8Uint },
+ { 0x1b000000, Format.R8G8Sint },
+ { 0x39e00000, Format.R16G16Float },
+ { 0x11e00000, Format.R16G16Unorm },
+ { 0x09e00000, Format.R16G16Snorm },
+ { 0x21e00000, Format.R16G16Uint },
+ { 0x19e00000, Format.R16G16Sint },
+ { 0x38800000, Format.R32G32Float },
+ { 0x20800000, Format.R32G32Uint },
+ { 0x18800000, Format.R32G32Sint },
+ { 0x12600000, Format.R8G8B8Unorm },
+ { 0x0a600000, Format.R8G8B8Snorm },
+ { 0x22600000, Format.R8G8B8Uint },
+ { 0x1a600000, Format.R8G8B8Sint },
+ { 0x38a00000, Format.R16G16B16Float },
+ { 0x10a00000, Format.R16G16B16Unorm },
+ { 0x08a00000, Format.R16G16B16Snorm },
+ { 0x20a00000, Format.R16G16B16Uint },
+ { 0x18a00000, Format.R16G16B16Sint },
+ { 0x38400000, Format.R32G32B32Float },
+ { 0x20400000, Format.R32G32B32Uint },
+ { 0x18400000, Format.R32G32B32Sint },
+ { 0x11400000, Format.R8G8B8A8Unorm },
+ { 0x09400000, Format.R8G8B8A8Snorm },
+ { 0x21400000, Format.R8G8B8A8Uint },
+ { 0x19400000, Format.R8G8B8A8Sint },
+ { 0x38600000, Format.R16G16B16A16Float },
+ { 0x10600000, Format.R16G16B16A16Unorm },
+ { 0x08600000, Format.R16G16B16A16Snorm },
+ { 0x20600000, Format.R16G16B16A16Uint },
+ { 0x18600000, Format.R16G16B16A16Sint },
+ { 0x38200000, Format.R32G32B32A32Float },
+ { 0x20200000, Format.R32G32B32A32Uint },
+ { 0x18200000, Format.R32G32B32A32Sint },
+ { 0x16000000, Format.R10G10B10A2Unorm },
+ { 0x26000000, Format.R10G10B10A2Uint },
+ { 0x3e200000, Format.R11G11B10Float },
+ { 0x2ba00000, Format.R8Uscaled },
+ { 0x33a00000, Format.R8Sscaled },
+ { 0x2b600000, Format.R16Uscaled },
+ { 0x33600000, Format.R16Sscaled },
+ { 0x2a400000, Format.R32Uscaled },
+ { 0x32400000, Format.R32Sscaled },
+ { 0x2b000000, Format.R8G8Uscaled },
+ { 0x33000000, Format.R8G8Sscaled },
+ { 0x29e00000, Format.R16G16Uscaled },
+ { 0x31e00000, Format.R16G16Sscaled },
+ { 0x28800000, Format.R32G32Uscaled },
+ { 0x30800000, Format.R32G32Sscaled },
+ { 0x2a600000, Format.R8G8B8Uscaled },
+ { 0x32600000, Format.R8G8B8Sscaled },
+ { 0x28a00000, Format.R16G16B16Uscaled },
+ { 0x30a00000, Format.R16G16B16Sscaled },
+ { 0x28400000, Format.R32G32B32Uscaled },
+ { 0x30400000, Format.R32G32B32Sscaled },
+ { 0x29400000, Format.R8G8B8A8Uscaled },
+ { 0x31400000, Format.R8G8B8A8Sscaled },
+ { 0x28600000, Format.R16G16B16A16Uscaled },
+ { 0x30600000, Format.R16G16B16A16Sscaled },
+ { 0x28200000, Format.R32G32B32A32Uscaled },
+ { 0x30200000, Format.R32G32B32A32Sscaled },
+ { 0x0e000000, Format.R10G10B10A2Snorm },
+ { 0x1e000000, Format.R10G10B10A2Sint },
+ { 0x2e000000, Format.R10G10B10A2Uscaled },
+ { 0x36000000, Format.R10G10B10A2Sscaled }
+ };
+
+ public static bool TryGetTextureFormat(uint encoded, bool isSrgb, out FormatInfo format)
+ {
+ encoded |= (isSrgb ? 1u << 19 : 0u);
+
+ return _textureFormats.TryGetValue(encoded, out format);
+ }
+
+ public static bool TryGetAttribFormat(uint encoded, out Format format)
+ {
+ return _attribFormats.TryGetValue(encoded, out format);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/Pool.cs b/Ryujinx.Graphics.Gpu/Image/Pool.cs
new file mode 100644
index 00000000..196fc137
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/Pool.cs
@@ -0,0 +1,99 @@
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ abstract class Pool<T> : IDisposable
+ {
+ protected const int DescriptorSize = 0x20;
+
+ protected GpuContext Context;
+
+ protected T[] Items;
+
+ public ulong Address { get; }
+ public ulong Size { get; }
+
+ public Pool(GpuContext context, ulong address, int maximumId)
+ {
+ Context = context;
+
+ int count = maximumId + 1;
+
+ ulong size = (ulong)(uint)count * DescriptorSize;;
+
+ Items = new T[count];
+
+ Address = address;
+ Size = size;
+ }
+
+ public abstract T Get(int id);
+
+ public void SynchronizeMemory()
+ {
+ (ulong, ulong)[] modifiedRanges = Context.PhysicalMemory.GetModifiedRanges(Address, Size);
+
+ for (int index = 0; index < modifiedRanges.Length; index++)
+ {
+ (ulong mAddress, ulong mSize) = modifiedRanges[index];
+
+ if (mAddress < Address)
+ {
+ mAddress = Address;
+ }
+
+ ulong maxSize = Address + Size - mAddress;
+
+ if (mSize > maxSize)
+ {
+ mSize = maxSize;
+ }
+
+ InvalidateRangeImpl(mAddress, mSize);
+ }
+ }
+
+ public void InvalidateRange(ulong address, ulong size)
+ {
+ ulong endAddress = address + size;
+
+ ulong texturePoolEndAddress = Address + Size;
+
+ // If the range being invalidated is not overlapping the texture pool range,
+ // then we don't have anything to do, exit early.
+ if (address >= texturePoolEndAddress || endAddress <= Address)
+ {
+ return;
+ }
+
+ if (address < Address)
+ {
+ address = Address;
+ }
+
+ if (endAddress > texturePoolEndAddress)
+ {
+ endAddress = texturePoolEndAddress;
+ }
+
+ InvalidateRangeImpl(address, size);
+ }
+
+ protected abstract void InvalidateRangeImpl(ulong address, ulong size);
+
+ protected abstract void Delete(T item);
+
+ public void Dispose()
+ {
+ if (Items != null)
+ {
+ for (int index = 0; index < Items.Length; index++)
+ {
+ Delete(Items[index]);
+ }
+
+ Items = null;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/ReductionFilter.cs b/Ryujinx.Graphics.Gpu/Image/ReductionFilter.cs
new file mode 100644
index 00000000..f14a17f2
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/ReductionFilter.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ enum ReductionFilter
+ {
+ Average,
+ Minimum,
+ Maximum
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Image/Sampler.cs b/Ryujinx.Graphics.Gpu/Image/Sampler.cs
new file mode 100644
index 00000000..06fedd8a
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/Sampler.cs
@@ -0,0 +1,52 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.GAL.Color;
+using Ryujinx.Graphics.GAL.Sampler;
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ class Sampler : IDisposable
+ {
+ public ISampler HostSampler { get; }
+
+ public Sampler(GpuContext context, SamplerDescriptor descriptor)
+ {
+ MinFilter minFilter = descriptor.UnpackMinFilter();
+ MagFilter magFilter = descriptor.UnpackMagFilter();
+
+ AddressMode addressU = descriptor.UnpackAddressU();
+ AddressMode addressV = descriptor.UnpackAddressV();
+ AddressMode addressP = descriptor.UnpackAddressP();
+
+ CompareMode compareMode = descriptor.UnpackCompareMode();
+ CompareOp compareOp = descriptor.UnpackCompareOp();
+
+ ColorF color = new ColorF(0, 0, 0, 0);
+
+ float minLod = descriptor.UnpackMinLod();
+ float maxLod = descriptor.UnpackMaxLod();
+ float mipLodBias = descriptor.UnpackMipLodBias();
+
+ float maxAnisotropy = descriptor.UnpackMaxAnisotropy();
+
+ HostSampler = context.Renderer.CreateSampler(new SamplerCreateInfo(
+ minFilter,
+ magFilter,
+ addressU,
+ addressV,
+ addressP,
+ compareMode,
+ compareOp,
+ color,
+ minLod,
+ maxLod,
+ mipLodBias,
+ maxAnisotropy));
+ }
+
+ public void Dispose()
+ {
+ HostSampler.Dispose();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/SamplerDescriptor.cs b/Ryujinx.Graphics.Gpu/Image/SamplerDescriptor.cs
new file mode 100644
index 00000000..0a43dd1b
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/SamplerDescriptor.cs
@@ -0,0 +1,132 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.GAL.Sampler;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ 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;
+
+ public uint Word0;
+ public uint Word1;
+ public uint Word2;
+ public uint Word3;
+ public uint BorderColorR;
+ public uint BorderColorG;
+ public uint BorderColorB;
+ public uint BorderColorA;
+
+ public AddressMode UnpackAddressU()
+ {
+ return (AddressMode)(Word0 & 7);
+ }
+
+ public AddressMode UnpackAddressV()
+ {
+ return (AddressMode)((Word0 >> 3) & 7);
+ }
+
+ public AddressMode UnpackAddressP()
+ {
+ return (AddressMode)((Word0 >> 6) & 7);
+ }
+
+ public CompareMode UnpackCompareMode()
+ {
+ return (CompareMode)((Word0 >> 9) & 1);
+ }
+
+ public CompareOp UnpackCompareOp()
+ {
+ return (CompareOp)(((Word0 >> 10) & 7) + 1);
+ }
+
+ public float UnpackMaxAnisotropy()
+ {
+ return _maxAnisotropyLut[(Word0 >> 20) & 7];
+ }
+
+ public MagFilter UnpackMagFilter()
+ {
+ return (MagFilter)(Word1 & 3);
+ }
+
+ public MinFilter UnpackMinFilter()
+ {
+ int minFilter = (int)(Word1 >> 4) & 3;
+ int mipFilter = (int)(Word1 >> 6) & 3;
+
+ return (MinFilter)(minFilter + (mipFilter - 1) * 2);
+ }
+
+ public ReductionFilter UnpackReductionFilter()
+ {
+ return (ReductionFilter)((Word1 >> 10) & 3);
+ }
+
+ public float UnpackMipLodBias()
+ {
+ int fixedValue = (int)(Word1 >> 12) & 0x1fff;
+
+ fixedValue = (fixedValue << 19) >> 19;
+
+ return fixedValue * Frac8ToF32;
+ }
+
+ public float UnpackLodSnap()
+ {
+ return _f5ToF32ConversionLut[(Word1 >> 26) & 0x1f];
+ }
+
+ public float UnpackMinLod()
+ {
+ return (Word2 & 0xfff) * Frac8ToF32;
+ }
+
+ public float UnpackMaxLod()
+ {
+ return ((Word2 >> 12) & 0xfff) * Frac8ToF32;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs b/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs
new file mode 100644
index 00000000..970a0983
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ class SamplerPool : Pool<Sampler>
+ {
+ public SamplerPool(GpuContext context, ulong address, int maximumId) : base(context, address, maximumId) { }
+
+ public override Sampler Get(int id)
+ {
+ if ((uint)id >= Items.Length)
+ {
+ return null;
+ }
+
+ SynchronizeMemory();
+
+ Sampler sampler = Items[id];
+
+ if (sampler == null)
+ {
+ ulong address = Address + (ulong)(uint)id * DescriptorSize;
+
+ Span<byte> data = Context.PhysicalMemory.Read(address, DescriptorSize);
+
+ SamplerDescriptor descriptor = MemoryMarshal.Cast<byte, SamplerDescriptor>(data)[0];
+
+ sampler = new Sampler(Context, descriptor);
+
+ Items[id] = sampler;
+ }
+
+ return sampler;
+ }
+
+ 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)
+ {
+ sampler.Dispose();
+
+ Items[id] = null;
+ }
+ }
+ }
+
+ protected override void Delete(Sampler item)
+ {
+ item?.Dispose();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs
new file mode 100644
index 00000000..32db8688
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs
@@ -0,0 +1,719 @@
+using Ryujinx.Common;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.GAL.Texture;
+using Ryujinx.Graphics.Gpu.Memory;
+using Ryujinx.Graphics.Texture;
+using Ryujinx.Graphics.Texture.Astc;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ class Texture : IRange<Texture>
+ {
+ private GpuContext _context;
+
+ private TextureInfo _info;
+
+ private SizeInfo _sizeInfo;
+
+ public Format Format => _info.FormatInfo.Format;
+
+ public TextureInfo Info => _info;
+
+ private int _depth;
+ private int _layers;
+ private int _firstLayer;
+ private int _firstLevel;
+
+ private bool _hasData;
+
+ private ITexture _arrayViewTexture;
+ private Target _arrayViewTarget;
+
+ private Texture _viewStorage;
+
+ private List<Texture> _views;
+
+ public ITexture HostTexture { get; private set; }
+
+ public LinkedListNode<Texture> CacheNode { get; set; }
+
+ public bool Modified { get; set; }
+
+ public ulong Address => _info.Address;
+ public ulong EndAddress => _info.Address + Size;
+
+ public ulong Size => (ulong)_sizeInfo.TotalSize;
+
+ private int _referenceCount;
+
+ private int _sequenceNumber;
+
+ private Texture(
+ GpuContext context,
+ TextureInfo info,
+ SizeInfo sizeInfo,
+ int firstLayer,
+ int firstLevel)
+ {
+ InitializeTexture(context, info, sizeInfo);
+
+ _firstLayer = firstLayer;
+ _firstLevel = firstLevel;
+
+ _hasData = true;
+ }
+
+ public Texture(GpuContext context, TextureInfo info, SizeInfo sizeInfo)
+ {
+ InitializeTexture(context, info, sizeInfo);
+
+ TextureCreateInfo createInfo = TextureManager.GetCreateInfo(info, context.Capabilities);
+
+ HostTexture = _context.Renderer.CreateTexture(createInfo);
+ }
+
+ private void InitializeTexture(GpuContext context, TextureInfo info, SizeInfo sizeInfo)
+ {
+ _context = context;
+ _sizeInfo = sizeInfo;
+
+ SetInfo(info);
+
+ _viewStorage = this;
+
+ _views = new List<Texture>();
+ }
+
+ public Texture CreateView(TextureInfo info, SizeInfo sizeInfo, int firstLayer, int firstLevel)
+ {
+ Texture texture = new Texture(
+ _context,
+ info,
+ sizeInfo,
+ _firstLayer + firstLayer,
+ _firstLevel + firstLevel);
+
+ TextureCreateInfo createInfo = TextureManager.GetCreateInfo(info, _context.Capabilities);
+
+ texture.HostTexture = HostTexture.CreateView(createInfo, firstLayer, firstLevel);
+
+ _viewStorage.AddView(texture);
+
+ return texture;
+ }
+
+ private void AddView(Texture texture)
+ {
+ _views.Add(texture);
+
+ texture._viewStorage = this;
+ }
+
+ private void RemoveView(Texture texture)
+ {
+ _views.Remove(texture);
+
+ texture._viewStorage = null;
+ }
+
+ public void ChangeSize(int width, int height, int depthOrLayers)
+ {
+ width <<= _firstLevel;
+ height <<= _firstLevel;
+
+ if (_info.Target == Target.Texture3D)
+ {
+ depthOrLayers <<= _firstLevel;
+ }
+ else
+ {
+ depthOrLayers = _viewStorage._info.DepthOrLayers;
+ }
+
+ _viewStorage.RecreateStorageOrView(width, height, depthOrLayers);
+
+ foreach (Texture view in _viewStorage._views)
+ {
+ int viewWidth = Math.Max(1, width >> view._firstLevel);
+ int viewHeight = Math.Max(1, height >> view._firstLevel);
+
+ int viewDepthOrLayers;
+
+ if (view._info.Target == Target.Texture3D)
+ {
+ viewDepthOrLayers = Math.Max(1, depthOrLayers >> view._firstLevel);
+ }
+ else
+ {
+ viewDepthOrLayers = view._info.DepthOrLayers;
+ }
+
+ view.RecreateStorageOrView(viewWidth, viewHeight, viewDepthOrLayers);
+ }
+ }
+
+ private void RecreateStorageOrView(int width, int height, int depthOrLayers)
+ {
+ SetInfo(new TextureInfo(
+ _info.Address,
+ width,
+ height,
+ depthOrLayers,
+ _info.Levels,
+ _info.SamplesInX,
+ _info.SamplesInY,
+ _info.Stride,
+ _info.IsLinear,
+ _info.GobBlocksInY,
+ _info.GobBlocksInZ,
+ _info.GobBlocksInTileX,
+ _info.Target,
+ _info.FormatInfo,
+ _info.DepthStencilMode,
+ _info.SwizzleR,
+ _info.SwizzleG,
+ _info.SwizzleB,
+ _info.SwizzleA));
+
+ TextureCreateInfo createInfo = TextureManager.GetCreateInfo(_info, _context.Capabilities);
+
+ if (_viewStorage != this)
+ {
+ ReplaceStorage(_viewStorage.HostTexture.CreateView(createInfo, _firstLayer, _firstLevel));
+ }
+ else
+ {
+ ITexture newStorage = _context.Renderer.CreateTexture(createInfo);
+
+ HostTexture.CopyTo(newStorage);
+
+ ReplaceStorage(newStorage);
+ }
+ }
+
+ public void SynchronizeMemory()
+ {
+ if (_sequenceNumber == _context.SequenceNumber && _hasData)
+ {
+ return;
+ }
+
+ _sequenceNumber = _context.SequenceNumber;
+
+ bool modified = _context.PhysicalMemory.GetModifiedRanges(Address, Size).Length != 0;
+
+ if (!modified && _hasData)
+ {
+ return;
+ }
+
+ ulong pageSize = (uint)_context.PhysicalMemory.GetPageSize();
+
+ ulong pageMask = pageSize - 1;
+
+ ulong rangeAddress = Address & ~pageMask;
+
+ ulong rangeSize = (EndAddress - Address + pageMask) & ~pageMask;
+
+ _context.Methods.InvalidateRange(rangeAddress, rangeSize);
+
+ Span<byte> data = _context.PhysicalMemory.Read(Address, Size);
+
+ if (_info.IsLinear)
+ {
+ data = LayoutConverter.ConvertLinearStridedToLinear(
+ _info.Width,
+ _info.Height,
+ _info.FormatInfo.BlockWidth,
+ _info.FormatInfo.BlockHeight,
+ _info.Stride,
+ _info.FormatInfo.BytesPerPixel,
+ data);
+ }
+ else
+ {
+ data = LayoutConverter.ConvertBlockLinearToLinear(
+ _info.Width,
+ _info.Height,
+ _depth,
+ _info.Levels,
+ _layers,
+ _info.FormatInfo.BlockWidth,
+ _info.FormatInfo.BlockHeight,
+ _info.FormatInfo.BytesPerPixel,
+ _info.GobBlocksInY,
+ _info.GobBlocksInZ,
+ _info.GobBlocksInTileX,
+ _sizeInfo,
+ data);
+ }
+
+ if (!_context.Capabilities.SupportsAstcCompression && _info.FormatInfo.Format.IsAstc())
+ {
+ int blockWidth = _info.FormatInfo.BlockWidth;
+ int blockHeight = _info.FormatInfo.BlockHeight;
+
+ data = AstcDecoder.DecodeToRgba8(
+ data,
+ blockWidth,
+ blockHeight,
+ 1,
+ _info.Width,
+ _info.Height,
+ _depth);
+ }
+
+ HostTexture.SetData(data);
+
+ _hasData = true;
+ }
+
+ public void Flush()
+ {
+ byte[] data = HostTexture.GetData(0);
+
+ _context.PhysicalMemory.Write(Address, data);
+ }
+
+ public bool IsPerfectMatch(TextureInfo info, TextureSearchFlags flags)
+ {
+ if (!FormatMatches(info, (flags & TextureSearchFlags.Strict) != 0))
+ {
+ return false;
+ }
+
+ if (!LayoutMatches(info))
+ {
+ return false;
+ }
+
+ if (!SizeMatches(info, (flags & TextureSearchFlags.Strict) == 0))
+ {
+ return false;
+ }
+
+ if ((flags & TextureSearchFlags.Sampler) != 0)
+ {
+ if (!SamplerParamsMatches(info))
+ {
+ return false;
+ }
+ }
+
+ if ((flags & TextureSearchFlags.IgnoreMs) != 0)
+ {
+ bool msTargetCompatible = _info.Target == Target.Texture2DMultisample &&
+ info.Target == Target.Texture2D;
+
+ if (!msTargetCompatible && !TargetAndSamplesCompatible(info))
+ {
+ return false;
+ }
+ }
+ else if (!TargetAndSamplesCompatible(info))
+ {
+ return false;
+ }
+
+ return _info.Address == info.Address && _info.Levels == info.Levels;
+ }
+
+ private bool FormatMatches(TextureInfo info, bool strict)
+ {
+ // D32F and R32F texture have the same representation internally,
+ // however the R32F format is used to sample from depth textures.
+ if (_info.FormatInfo.Format == Format.D32Float &&
+ info.FormatInfo.Format == Format.R32Float && !strict)
+ {
+ return true;
+ }
+
+ if (_info.FormatInfo.Format == Format.R8G8B8A8Srgb &&
+ info.FormatInfo.Format == Format.R8G8B8A8Unorm && !strict)
+ {
+ return true;
+ }
+
+ if (_info.FormatInfo.Format == Format.R8G8B8A8Unorm &&
+ info.FormatInfo.Format == Format.R8G8B8A8Srgb && !strict)
+ {
+ return true;
+ }
+
+ return _info.FormatInfo.Format == info.FormatInfo.Format;
+ }
+
+ private bool LayoutMatches(TextureInfo info)
+ {
+ if (_info.IsLinear != info.IsLinear)
+ {
+ return false;
+ }
+
+ // For linear textures, gob block sizes are ignored.
+ // For block linear textures, the stride is ignored.
+ if (info.IsLinear)
+ {
+ return _info.Stride == info.Stride;
+ }
+ else
+ {
+ return _info.GobBlocksInY == info.GobBlocksInY &&
+ _info.GobBlocksInZ == info.GobBlocksInZ;
+ }
+ }
+
+ public bool SizeMatches(TextureInfo info)
+ {
+ return SizeMatches(info, alignSizes: false);
+ }
+
+ public bool SizeMatches(TextureInfo info, int level)
+ {
+ return Math.Max(1, _info.Width >> level) == info.Width &&
+ Math.Max(1, _info.Height >> level) == info.Height &&
+ Math.Max(1, _info.GetDepth() >> level) == info.GetDepth();
+ }
+
+ private bool SizeMatches(TextureInfo info, bool alignSizes)
+ {
+ if (_info.GetLayers() != info.GetLayers())
+ {
+ return false;
+ }
+
+ if (alignSizes)
+ {
+ Size size0 = GetAlignedSize(_info);
+ Size size1 = GetAlignedSize(info);
+
+ return size0.Width == size1.Width &&
+ size0.Height == size1.Height &&
+ size0.Depth == size1.Depth;
+ }
+ else
+ {
+ return _info.Width == info.Width &&
+ _info.Height == info.Height &&
+ _info.GetDepth() == info.GetDepth();
+ }
+ }
+
+ private bool SamplerParamsMatches(TextureInfo info)
+ {
+ return _info.DepthStencilMode == info.DepthStencilMode &&
+ _info.SwizzleR == info.SwizzleR &&
+ _info.SwizzleG == info.SwizzleG &&
+ _info.SwizzleB == info.SwizzleB &&
+ _info.SwizzleA == info.SwizzleA;
+ }
+
+ private bool TargetAndSamplesCompatible(TextureInfo info)
+ {
+ return _info.Target == info.Target &&
+ _info.SamplesInX == info.SamplesInX &&
+ _info.SamplesInY == info.SamplesInY;
+ }
+
+ public bool IsViewCompatible(TextureInfo info, ulong size, out int firstLayer, out int firstLevel)
+ {
+ // Out of range.
+ if (info.Address < Address || info.Address + size > EndAddress)
+ {
+ firstLayer = 0;
+ firstLevel = 0;
+
+ return false;
+ }
+
+ int offset = (int)(info.Address - Address);
+
+ if (!_sizeInfo.FindView(offset, (int)size, out firstLayer, out firstLevel))
+ {
+ return false;
+ }
+
+ if (!ViewLayoutCompatible(info, firstLevel))
+ {
+ return false;
+ }
+
+ if (!ViewFormatCompatible(info))
+ {
+ return false;
+ }
+
+ if (!ViewSizeMatches(info, firstLevel))
+ {
+ return false;
+ }
+
+ if (!ViewTargetCompatible(info))
+ {
+ return false;
+ }
+
+ return _info.SamplesInX == info.SamplesInX &&
+ _info.SamplesInY == info.SamplesInY;
+ }
+
+ private bool ViewLayoutCompatible(TextureInfo info, int level)
+ {
+ if (_info.IsLinear != info.IsLinear)
+ {
+ return false;
+ }
+
+ // For linear textures, gob block sizes are ignored.
+ // For block linear textures, the stride is ignored.
+ if (info.IsLinear)
+ {
+ int width = Math.Max(1, _info.Width >> level);
+
+ int stride = width * _info.FormatInfo.BytesPerPixel;
+
+ stride = BitUtils.AlignUp(stride, 32);
+
+ return stride == info.Stride;
+ }
+ else
+ {
+ int height = Math.Max(1, _info.Height >> level);
+ int depth = Math.Max(1, _info.GetDepth() >> level);
+
+ (int gobBlocksInY, int gobBlocksInZ) = SizeCalculator.GetMipGobBlockSizes(
+ height,
+ depth,
+ _info.FormatInfo.BlockHeight,
+ _info.GobBlocksInY,
+ _info.GobBlocksInZ);
+
+ return gobBlocksInY == info.GobBlocksInY &&
+ gobBlocksInZ == info.GobBlocksInZ;
+ }
+ }
+
+ private bool ViewFormatCompatible(TextureInfo info)
+ {
+ return TextureCompatibility.FormatCompatible(_info.FormatInfo, info.FormatInfo);
+ }
+
+ private bool ViewSizeMatches(TextureInfo info, int level)
+ {
+ Size size = GetAlignedSize(_info, level);
+
+ Size otherSize = GetAlignedSize(info);
+
+ return size.Width == otherSize.Width &&
+ size.Height == otherSize.Height &&
+ size.Depth == otherSize.Depth;
+ }
+
+ private bool ViewTargetCompatible(TextureInfo info)
+ {
+ switch (_info.Target)
+ {
+ case Target.Texture1D:
+ case Target.Texture1DArray:
+ return info.Target == Target.Texture1D ||
+ info.Target == Target.Texture1DArray;
+
+ case Target.Texture2D:
+ return info.Target == Target.Texture2D ||
+ info.Target == Target.Texture2DArray;
+
+ case Target.Texture2DArray:
+ case Target.Cubemap:
+ case Target.CubemapArray:
+ return info.Target == Target.Texture2D ||
+ info.Target == Target.Texture2DArray ||
+ info.Target == Target.Cubemap ||
+ info.Target == Target.CubemapArray;
+
+ case Target.Texture2DMultisample:
+ case Target.Texture2DMultisampleArray:
+ return info.Target == Target.Texture2DMultisample ||
+ info.Target == Target.Texture2DMultisampleArray;
+
+ case Target.Texture3D:
+ return info.Target == Target.Texture3D;
+ }
+
+ return false;
+ }
+
+ private static Size GetAlignedSize(TextureInfo info, int level = 0)
+ {
+ int width = Math.Max(1, info.Width >> level);
+ int height = Math.Max(1, info.Height >> level);
+
+ if (info.IsLinear)
+ {
+ return SizeCalculator.GetLinearAlignedSize(
+ width,
+ height,
+ info.FormatInfo.BlockWidth,
+ info.FormatInfo.BlockHeight,
+ info.FormatInfo.BytesPerPixel);
+ }
+ else
+ {
+ int depth = Math.Max(1, info.GetDepth() >> level);
+
+ (int gobBlocksInY, int gobBlocksInZ) = SizeCalculator.GetMipGobBlockSizes(
+ height,
+ depth,
+ info.FormatInfo.BlockHeight,
+ info.GobBlocksInY,
+ info.GobBlocksInZ);
+
+ return SizeCalculator.GetBlockLinearAlignedSize(
+ width,
+ height,
+ depth,
+ info.FormatInfo.BlockWidth,
+ info.FormatInfo.BlockHeight,
+ info.FormatInfo.BytesPerPixel,
+ gobBlocksInY,
+ gobBlocksInZ,
+ info.GobBlocksInTileX);
+ }
+ }
+
+ public ITexture GetTargetTexture(Target target)
+ {
+ if (target == _info.Target)
+ {
+ return HostTexture;
+ }
+
+ if (_arrayViewTexture == null && IsSameDimensionsTarget(target))
+ {
+ TextureCreateInfo createInfo = new TextureCreateInfo(
+ _info.Width,
+ _info.Height,
+ target == Target.CubemapArray ? 6 : 1,
+ _info.Levels,
+ _info.Samples,
+ _info.FormatInfo.BlockWidth,
+ _info.FormatInfo.BlockHeight,
+ _info.FormatInfo.BytesPerPixel,
+ _info.FormatInfo.Format,
+ _info.DepthStencilMode,
+ target,
+ _info.SwizzleR,
+ _info.SwizzleG,
+ _info.SwizzleB,
+ _info.SwizzleA);
+
+ ITexture viewTexture = HostTexture.CreateView(createInfo, 0, 0);
+
+ _arrayViewTexture = viewTexture;
+ _arrayViewTarget = target;
+
+ return viewTexture;
+ }
+ else if (_arrayViewTarget == target)
+ {
+ return _arrayViewTexture;
+ }
+
+ return null;
+ }
+
+ private bool IsSameDimensionsTarget(Target target)
+ {
+ switch (_info.Target)
+ {
+ case Target.Texture1D:
+ case Target.Texture1DArray:
+ return target == Target.Texture1D ||
+ target == Target.Texture1DArray;
+
+ case Target.Texture2D:
+ case Target.Texture2DArray:
+ return target == Target.Texture2D ||
+ target == Target.Texture2DArray;
+
+ case Target.Cubemap:
+ case Target.CubemapArray:
+ return target == Target.Cubemap ||
+ target == Target.CubemapArray;
+
+ case Target.Texture2DMultisample:
+ case Target.Texture2DMultisampleArray:
+ return target == Target.Texture2DMultisample ||
+ target == Target.Texture2DMultisampleArray;
+
+ case Target.Texture3D:
+ return target == Target.Texture3D;
+ }
+
+ return false;
+ }
+
+ public void ReplaceView(Texture parent, TextureInfo info, ITexture hostTexture)
+ {
+ ReplaceStorage(hostTexture);
+
+ parent._viewStorage.AddView(this);
+
+ SetInfo(info);
+ }
+
+ private void SetInfo(TextureInfo info)
+ {
+ _info = info;
+
+ _depth = info.GetDepth();
+ _layers = info.GetLayers();
+ }
+
+ private void ReplaceStorage(ITexture hostTexture)
+ {
+ DisposeTextures();
+
+ HostTexture = hostTexture;
+ }
+
+ public bool OverlapsWith(ulong address, ulong size)
+ {
+ return Address < address + size && address < EndAddress;
+ }
+
+ public void Invalidate()
+ {
+ // _hasData = false;
+ }
+
+ public void IncrementReferenceCount()
+ {
+ _referenceCount++;
+ }
+
+ public void DecrementReferenceCount()
+ {
+ if (--_referenceCount == 0)
+ {
+ if (_viewStorage != this)
+ {
+ _viewStorage.RemoveView(this);
+ }
+
+ _context.Methods.TextureManager.RemoveTextureFromCache(this);
+
+ DisposeTextures();
+ }
+ }
+
+ private void DisposeTextures()
+ {
+ HostTexture.Dispose();
+
+ _arrayViewTexture?.Dispose();
+ _arrayViewTexture = null;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs b/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs
new file mode 100644
index 00000000..19090ab3
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs
@@ -0,0 +1,17 @@
+using Ryujinx.Graphics.GAL.Texture;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ struct TextureBindingInfo
+ {
+ public Target Target { get; }
+
+ public int Handle { get; }
+
+ public TextureBindingInfo(Target target, int handle)
+ {
+ Target = target;
+ Handle = handle;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs b/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
new file mode 100644
index 00000000..52472164
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
@@ -0,0 +1,95 @@
+using Ryujinx.Graphics.GAL;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ static class TextureCompatibility
+ {
+ private enum FormatClass
+ {
+ Unclassified,
+ BCn64,
+ BCn128,
+ Bc1Rgb,
+ Bc1Rgba,
+ Bc2,
+ Bc3,
+ Bc4,
+ Bc5,
+ Bc6,
+ Bc7
+ }
+
+ public static bool FormatCompatible(FormatInfo lhs, FormatInfo rhs)
+ {
+ if (IsDsFormat(lhs.Format) || IsDsFormat(rhs.Format))
+ {
+ return lhs.Format == rhs.Format;
+ }
+
+ if (lhs.Format.IsAstc() || rhs.Format.IsAstc())
+ {
+ return lhs.Format == rhs.Format;
+ }
+
+ if (lhs.IsCompressed && rhs.IsCompressed)
+ {
+ FormatClass lhsClass = GetFormatClass(lhs.Format);
+ FormatClass rhsClass = GetFormatClass(rhs.Format);
+
+ return lhsClass == rhsClass;
+ }
+ else
+ {
+ return lhs.BytesPerPixel == rhs.BytesPerPixel;
+ }
+ }
+
+ private static FormatClass GetFormatClass(Format format)
+ {
+ switch (format)
+ {
+ case Format.Bc1RgbSrgb:
+ case Format.Bc1RgbUnorm:
+ return FormatClass.Bc1Rgb;
+ 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;
+ }
+
+ return FormatClass.Unclassified;
+ }
+
+ private static bool IsDsFormat(Format format)
+ {
+ switch (format)
+ {
+ case Format.D16Unorm:
+ case Format.D24X8Unorm:
+ case Format.D24UnormS8Uint:
+ case Format.D32Float:
+ case Format.D32FloatS8Uint:
+ return true;
+ }
+
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureComponent.cs b/Ryujinx.Graphics.Gpu/Image/TextureComponent.cs
new file mode 100644
index 00000000..37b3f65e
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureComponent.cs
@@ -0,0 +1,35 @@
+using Ryujinx.Graphics.GAL.Texture;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ enum TextureComponent
+ {
+ Zero = 0,
+ Red = 2,
+ Green = 3,
+ Blue = 4,
+ Alpha = 5,
+ OneSI = 6,
+ OneF = 7
+ }
+
+ static class TextureComponentConverter
+ {
+ 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;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs b/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs
new file mode 100644
index 00000000..79e4f55e
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs
@@ -0,0 +1,119 @@
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ struct TextureDescriptor
+ {
+ public uint Word0;
+ public uint Word1;
+ public uint Word2;
+ public uint Word3;
+ public uint Word4;
+ public uint Word5;
+ public uint Word6;
+ public uint Word7;
+
+ public uint UnpackFormat()
+ {
+ return Word0 & 0x8007ffff;
+ }
+
+ public TextureComponent UnpackSwizzleR()
+ {
+ return(TextureComponent)((Word0 >> 19) & 7);
+ }
+
+ public TextureComponent UnpackSwizzleG()
+ {
+ return(TextureComponent)((Word0 >> 22) & 7);
+ }
+
+ public TextureComponent UnpackSwizzleB()
+ {
+ return(TextureComponent)((Word0 >> 25) & 7);
+ }
+
+ public TextureComponent UnpackSwizzleA()
+ {
+ return(TextureComponent)((Word0 >> 28) & 7);
+ }
+
+ public ulong UnpackAddress()
+ {
+ return Word1 | ((ulong)(Word2 & 0xffff) << 32);
+ }
+
+ public TextureDescriptorType UnpackTextureDescriptorType()
+ {
+ return (TextureDescriptorType)((Word2 >> 21) & 7);
+ }
+
+ public int UnpackStride()
+ {
+ return (int)(Word3 & 0xffff) << 5;
+ }
+
+ public int UnpackGobBlocksInX()
+ {
+ return 1 << (int)(Word3 & 7);
+ }
+
+ public int UnpackGobBlocksInY()
+ {
+ return 1 << (int)((Word3 >> 3) & 7);
+ }
+
+ public int UnpackGobBlocksInZ()
+ {
+ return 1 << (int)((Word3 >> 6) & 7);
+ }
+
+ public int UnpackGobBlocksInTileX()
+ {
+ return 1 << (int)((Word3 >> 10) & 7);
+ }
+
+ public int UnpackLevels()
+ {
+ return (int)(Word3 >> 28) + 1;
+ }
+
+ public int UnpackWidth()
+ {
+ return (int)(Word4 & 0xffff) + 1;
+ }
+
+ public bool UnpackSrgb()
+ {
+ return (Word4 & (1 << 22)) != 0;
+ }
+
+ public TextureTarget UnpackTextureTarget()
+ {
+ return (TextureTarget)((Word4 >> 23) & 0xf);
+ }
+
+ public int UnpackHeight()
+ {
+ return (int)(Word5 & 0xffff) + 1;
+ }
+
+ public int UnpackDepth()
+ {
+ return (int)((Word5 >> 16) & 0x3fff) + 1;
+ }
+
+ public int UnpackBaseLevel()
+ {
+ return (int)(Word7 & 0xf);
+ }
+
+ public int UnpackMaxLevelInclusive()
+ {
+ return (int)((Word7 >> 4) & 0xf);
+ }
+
+ public TextureMsaaMode UnpackTextureMsaaMode()
+ {
+ return (TextureMsaaMode)((Word7 >> 8) & 0xf);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureDescriptorType.cs b/Ryujinx.Graphics.Gpu/Image/TextureDescriptorType.cs
new file mode 100644
index 00000000..6f6048a6
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureDescriptorType.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ enum TextureDescriptorType
+ {
+ Buffer,
+ LinearColorKey,
+ Linear,
+ BlockLinear,
+ BlockLinearColorKey
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs b/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs
new file mode 100644
index 00000000..639abdd8
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs
@@ -0,0 +1,101 @@
+using Ryujinx.Graphics.GAL.Texture;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ struct TextureInfo
+ {
+ public ulong Address { get; }
+
+ public int Width { get; }
+ public int Height { get; }
+ public int DepthOrLayers { get; }
+ public int Levels { get; }
+ public int SamplesInX { get; }
+ public int SamplesInY { get; }
+ public int Stride { get; }
+ public bool IsLinear { get; }
+ public int GobBlocksInY { get; }
+ public int GobBlocksInZ { get; }
+ public int GobBlocksInTileX { get; }
+
+ public int Samples => SamplesInX * SamplesInY;
+
+ public Target Target { get; }
+
+ public FormatInfo FormatInfo { get; }
+
+ public DepthStencilMode DepthStencilMode { get; }
+
+ public SwizzleComponent SwizzleR { get; }
+ public SwizzleComponent SwizzleG { get; }
+ public SwizzleComponent SwizzleB { get; }
+ public SwizzleComponent SwizzleA { get; }
+
+ public TextureInfo(
+ ulong address,
+ 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)
+ {
+ Address = address;
+ 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;
+ }
+
+ public int GetDepth()
+ {
+ return Target == Target.Texture3D ? DepthOrLayers : 1;
+ }
+
+ public int GetLayers()
+ {
+ 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;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
new file mode 100644
index 00000000..56dff9ad
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
@@ -0,0 +1,669 @@
+using Ryujinx.Common;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.GAL.Texture;
+using Ryujinx.Graphics.Gpu.Image;
+using Ryujinx.Graphics.Gpu.Memory;
+using Ryujinx.Graphics.Gpu.State;
+using Ryujinx.Graphics.Shader;
+using Ryujinx.Graphics.Texture;
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ class TextureManager
+ {
+ private GpuContext _context;
+ private BufferManager _bufferManager;
+
+ private SamplerPool _samplerPool;
+
+ private ulong _texturePoolAddress;
+ private int _texturePoolMaximumId;
+
+ private TexturePoolCache _texturePoolCache;
+
+ private Texture[] _rtColors;
+ private Texture _rtColor3D;
+
+ private Texture _rtDepthStencil;
+
+ private ITexture[] _rtHostColors;
+
+ private ITexture _rtHostDs;
+
+ private RangeList<Texture> _textures;
+
+ private AutoDeleteCache _cache;
+
+ private TextureBindingInfo[][] _bindings;
+
+ private struct TextureStatePerStage
+ {
+ public ITexture Texture;
+ public ISampler Sampler;
+ }
+
+ private TextureStatePerStage[][] _textureState;
+
+ private int _textureBufferIndex;
+
+ public TextureManager(GpuContext context, BufferManager bufferManager)
+ {
+ _context = context;
+ _bufferManager = bufferManager;
+
+ _texturePoolCache = new TexturePoolCache(context, this);
+
+ _rtColors = new Texture[Constants.TotalRenderTargets];
+
+ _rtHostColors = new ITexture[Constants.TotalRenderTargets];
+
+ _textures = new RangeList<Texture>();
+
+ _cache = new AutoDeleteCache();
+
+ _bindings = new TextureBindingInfo[Constants.TotalShaderStages][];
+
+ _textureState = new TextureStatePerStage[Constants.TotalShaderStages][];
+ }
+
+ public void BindTextures(int stage, TextureBindingInfo[] bindings)
+ {
+ _bindings[stage] = bindings;
+
+ _textureState[stage] = new TextureStatePerStage[bindings.Length];
+ }
+
+ public void SetTextureBufferIndex(int index)
+ {
+ _textureBufferIndex = index;
+ }
+
+ public void SetSamplerPool(ulong gpuVa, int maximumId)
+ {
+ ulong address = _context.MemoryManager.Translate(gpuVa);
+
+ if (_samplerPool != null)
+ {
+ if (_samplerPool.Address == address)
+ {
+ return;
+ }
+
+ _samplerPool.Dispose();
+ }
+
+ _samplerPool = new SamplerPool(_context, address, maximumId);
+ }
+
+ public void SetTexturePool(ulong gpuVa, int maximumId)
+ {
+ ulong address = _context.MemoryManager.Translate(gpuVa);
+
+ _texturePoolAddress = address;
+ _texturePoolMaximumId = maximumId;
+ }
+
+ public void SetRenderTargetColor(int index, Texture color)
+ {
+ _rtColors[index] = color;
+
+ _rtColor3D = null;
+ }
+
+ public void SetRenderTargetColor3D(Texture color)
+ {
+ _rtColor3D = color;
+ }
+
+ public void SetRenderTargetDepthStencil(Texture depthStencil)
+ {
+ _rtDepthStencil = depthStencil;
+ }
+
+ public void CommitBindings()
+ {
+ UpdateTextures();
+ UpdateRenderTargets();
+ }
+
+ private void UpdateTextures()
+ {
+ TexturePool texturePool = _texturePoolCache.FindOrCreate(
+ _texturePoolAddress,
+ _texturePoolMaximumId);
+
+ for (ShaderStage stage = ShaderStage.Vertex; stage <= ShaderStage.Fragment; stage++)
+ {
+ int stageIndex = (int)stage - 1;
+
+ if (_bindings[stageIndex] == null)
+ {
+ continue;
+ }
+
+ for (int index = 0; index < _bindings[stageIndex].Length; index++)
+ {
+ TextureBindingInfo binding = _bindings[stageIndex][index];
+
+ int packedId = ReadPackedId(stageIndex, binding.Handle);
+
+ int textureId = (packedId >> 0) & 0xfffff;
+ int samplerId = (packedId >> 20) & 0xfff;
+
+ Texture texture = texturePool.Get(textureId);
+
+ ITexture hostTexture = texture?.GetTargetTexture(binding.Target);
+
+ if (_textureState[stageIndex][index].Texture != hostTexture)
+ {
+ _textureState[stageIndex][index].Texture = hostTexture;
+
+ _context.Renderer.GraphicsPipeline.BindTexture(index, stage, hostTexture);
+ }
+
+ Sampler sampler = _samplerPool.Get(samplerId);
+
+ ISampler hostSampler = sampler?.HostSampler;
+
+ if (_textureState[stageIndex][index].Sampler != hostSampler)
+ {
+ _textureState[stageIndex][index].Sampler = hostSampler;
+
+ _context.Renderer.GraphicsPipeline.BindSampler(index, stage, hostSampler);
+ }
+ }
+ }
+ }
+
+ private void UpdateRenderTargets()
+ {
+ bool anyChanged = false;
+
+ if (_rtHostDs != _rtDepthStencil?.HostTexture)
+ {
+ _rtHostDs = _rtDepthStencil?.HostTexture;
+
+ anyChanged = true;
+ }
+
+ if (_rtColor3D == null)
+ {
+ 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.GraphicsPipeline.SetRenderTargets(_rtHostColors, _rtHostDs);
+ }
+ }
+ else
+ {
+ if (_rtHostColors[0] != _rtColor3D.HostTexture)
+ {
+ _rtHostColors[0] = _rtColor3D.HostTexture;
+
+ anyChanged = true;
+ }
+
+ if (anyChanged)
+ {
+ _context.Renderer.GraphicsPipeline.SetRenderTargets(_rtColor3D.HostTexture, _rtHostDs);
+ }
+ }
+ }
+
+ private int ReadPackedId(int stage, int wordOffset)
+ {
+ ulong address = _bufferManager.GetGraphicsUniformBufferAddress(stage, _textureBufferIndex);
+
+ address += (uint)wordOffset * 4;
+
+ return BitConverter.ToInt32(_context.PhysicalMemory.Read(address, 4));
+ }
+
+ public Texture FindOrCreateTexture(CopyTexture copyTexture)
+ {
+ ulong address = _context.MemoryManager.Translate(copyTexture.Address.Pack());
+
+ if (address == MemoryManager.BadAddress)
+ {
+ return null;
+ }
+
+ int gobBlocksInY = copyTexture.MemoryLayout.UnpackGobBlocksInY();
+ int gobBlocksInZ = copyTexture.MemoryLayout.UnpackGobBlocksInZ();
+
+ FormatInfo formatInfo = copyTexture.Format.Convert();
+
+ TextureInfo info = new TextureInfo(
+ address,
+ copyTexture.Width,
+ copyTexture.Height,
+ copyTexture.Depth,
+ 1,
+ 1,
+ 1,
+ copyTexture.Stride,
+ copyTexture.LinearLayout,
+ gobBlocksInY,
+ gobBlocksInZ,
+ 1,
+ Target.Texture2D,
+ formatInfo);
+
+ Texture texture = FindOrCreateTexture(info, TextureSearchFlags.IgnoreMs);
+
+ texture.SynchronizeMemory();
+
+ return texture;
+ }
+
+ public Texture FindOrCreateTexture(RtColorState colorState, int samplesInX, int samplesInY)
+ {
+ ulong address = _context.MemoryManager.Translate(colorState.Address.Pack());
+
+ if (address == MemoryManager.BadAddress)
+ {
+ return null;
+ }
+
+ bool isLinear = colorState.MemoryLayout.UnpackIsLinear();
+
+ int gobBlocksInY = colorState.MemoryLayout.UnpackGobBlocksInY();
+ int gobBlocksInZ = colorState.MemoryLayout.UnpackGobBlocksInZ();
+
+ Target target;
+
+ if (colorState.MemoryLayout.UnpackIsTarget3D())
+ {
+ target = Target.Texture3D;
+ }
+ else if ((samplesInX | samplesInY) != 1)
+ {
+ target = colorState.Depth > 1
+ ? Target.Texture2DMultisampleArray
+ : Target.Texture2DMultisample;
+ }
+ else
+ {
+ target = colorState.Depth > 1
+ ? Target.Texture2DArray
+ : Target.Texture2D;
+ }
+
+ FormatInfo formatInfo = colorState.Format.Convert();
+
+ int width, stride;
+
+ // For linear textures, the width value is actually the stride.
+ // We can easily get the width by dividing the stride by the bpp,
+ // since the stride is the total number of bytes occupied by a
+ // line. The stride should also meet alignment constraints however,
+ // so the width we get here is the aligned width.
+ if (isLinear)
+ {
+ width = colorState.WidthOrStride / formatInfo.BytesPerPixel;
+ stride = colorState.WidthOrStride;
+ }
+ else
+ {
+ width = colorState.WidthOrStride;
+ stride = 0;
+ }
+
+ TextureInfo info = new TextureInfo(
+ address,
+ width,
+ colorState.Height,
+ colorState.Depth,
+ 1,
+ samplesInX,
+ samplesInY,
+ stride,
+ isLinear,
+ gobBlocksInY,
+ gobBlocksInZ,
+ 1,
+ target,
+ formatInfo);
+
+ Texture texture = FindOrCreateTexture(info);
+
+ texture.SynchronizeMemory();
+
+ return texture;
+ }
+
+ public Texture FindOrCreateTexture(RtDepthStencilState dsState, Size3D size, int samplesInX, int samplesInY)
+ {
+ ulong address = _context.MemoryManager.Translate(dsState.Address.Pack());
+
+ if (address == MemoryManager.BadAddress)
+ {
+ return null;
+ }
+
+ int gobBlocksInY = dsState.MemoryLayout.UnpackGobBlocksInY();
+ int gobBlocksInZ = dsState.MemoryLayout.UnpackGobBlocksInZ();
+
+ Target target = (samplesInX | samplesInY) != 1
+ ? Target.Texture2DMultisample
+ : Target.Texture2D;
+
+ FormatInfo formatInfo = dsState.Format.Convert();
+
+ TextureInfo info = new TextureInfo(
+ address,
+ size.Width,
+ size.Height,
+ size.Depth,
+ 1,
+ samplesInX,
+ samplesInY,
+ 0,
+ false,
+ gobBlocksInY,
+ gobBlocksInZ,
+ 1,
+ target,
+ formatInfo);
+
+ Texture texture = FindOrCreateTexture(info);
+
+ texture.SynchronizeMemory();
+
+ return texture;
+ }
+
+ public Texture FindOrCreateTexture(TextureInfo info, TextureSearchFlags flags = TextureSearchFlags.None)
+ {
+ bool isSamplerTexture = (flags & TextureSearchFlags.Sampler) != 0;
+
+ // Try to find a perfect texture match, with the same address and parameters.
+ Texture[] sameAddressOverlaps = _textures.FindOverlaps(info.Address);
+
+ foreach (Texture overlap in sameAddressOverlaps)
+ {
+ if (overlap.IsPerfectMatch(info, flags))
+ {
+ if (!isSamplerTexture)
+ {
+ // If not a sampler texture, it is managed by the auto delete
+ // cache, ensure that it is on the "top" of the list to avoid
+ // deletion.
+ _cache.Lift(overlap);
+ }
+ else if (!overlap.SizeMatches(info))
+ {
+ // If this is used for sampling, the size must match,
+ // otherwise the shader would sample garbage data.
+ // To fix that, we create a new texture with the correct
+ // size, and copy the data from the old one to the new one.
+ overlap.ChangeSize(info.Width, info.Height, info.DepthOrLayers);
+ }
+
+ return overlap;
+ }
+ }
+
+ // Calculate texture sizes, used to find all overlapping textures.
+ SizeInfo sizeInfo;
+
+ if (info.IsLinear)
+ {
+ sizeInfo = SizeCalculator.GetLinearTextureSize(
+ info.Stride,
+ info.Height,
+ info.FormatInfo.BlockHeight);
+ }
+ else
+ {
+ sizeInfo = SizeCalculator.GetBlockLinearTextureSize(
+ info.Width,
+ info.Height,
+ info.GetDepth(),
+ info.Levels,
+ info.GetLayers(),
+ info.FormatInfo.BlockWidth,
+ info.FormatInfo.BlockHeight,
+ info.FormatInfo.BytesPerPixel,
+ info.GobBlocksInY,
+ info.GobBlocksInZ,
+ info.GobBlocksInTileX);
+ }
+
+ // Find view compatible matches.
+ ulong size = (ulong)sizeInfo.TotalSize;
+
+ Texture[] overlaps = _textures.FindOverlaps(info.Address, size);
+
+ Texture texture = null;
+
+ foreach (Texture overlap in overlaps)
+ {
+ if (overlap.IsViewCompatible(info, size, out int firstLayer, out int firstLevel))
+ {
+ if (!isSamplerTexture)
+ {
+ info = AdjustSizes(overlap, info, firstLevel);
+ }
+
+ texture = overlap.CreateView(info, sizeInfo, firstLayer, firstLevel);
+
+ // The size only matters (and is only really reliable) when the
+ // texture is used on a sampler, because otherwise the size will be
+ // aligned.
+ if (!overlap.SizeMatches(info, firstLevel) && isSamplerTexture)
+ {
+ texture.ChangeSize(info.Width, info.Height, info.DepthOrLayers);
+ }
+
+ break;
+ }
+ }
+
+ // No match, create a new texture.
+ if (texture == null)
+ {
+ texture = new Texture(_context, info, sizeInfo);
+
+ // We need to synchronize before copying the old view data to the texture,
+ // otherwise the copied data would be overwritten by a future synchronization.
+ texture.SynchronizeMemory();
+
+ foreach (Texture overlap in overlaps)
+ {
+ if (texture.IsViewCompatible(overlap.Info, overlap.Size, out int firstLayer, out int firstLevel))
+ {
+ TextureInfo overlapInfo = AdjustSizes(texture, overlap.Info, firstLevel);
+
+ TextureCreateInfo createInfo = GetCreateInfo(overlapInfo, _context.Capabilities);
+
+ ITexture newView = texture.HostTexture.CreateView(createInfo, firstLayer, firstLevel);
+
+ overlap.HostTexture.CopyTo(newView);
+
+ overlap.ReplaceView(texture, overlapInfo, newView);
+ }
+ }
+ }
+
+ // Sampler textures are managed by the texture pool, all other textures
+ // are managed by the auto delete cache.
+ if (!isSamplerTexture)
+ {
+ _cache.Add(texture);
+ }
+
+ _textures.Add(texture);
+
+ return texture;
+ }
+
+ private static TextureInfo AdjustSizes(Texture parent, TextureInfo info, 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 && !info.FormatInfo.IsCompressed)
+ {
+ width = BitUtils.DivRoundUp(width, parent.Info.FormatInfo.BlockWidth);
+ height = BitUtils.DivRoundUp(height, parent.Info.FormatInfo.BlockHeight);
+ }
+ else if (!parent.Info.FormatInfo.IsCompressed && info.FormatInfo.IsCompressed)
+ {
+ width *= info.FormatInfo.BlockWidth;
+ height *= info.FormatInfo.BlockHeight;
+ }
+
+ int depthOrLayers;
+
+ if (info.Target == Target.Texture3D)
+ {
+ depthOrLayers = Math.Max(1, parent.Info.DepthOrLayers >> firstLevel);
+ }
+ else
+ {
+ depthOrLayers = info.DepthOrLayers;
+ }
+
+ return new TextureInfo(
+ info.Address,
+ width,
+ height,
+ depthOrLayers,
+ info.Levels,
+ info.SamplesInX,
+ info.SamplesInY,
+ info.Stride,
+ info.IsLinear,
+ info.GobBlocksInY,
+ info.GobBlocksInZ,
+ info.GobBlocksInTileX,
+ info.Target,
+ info.FormatInfo,
+ info.DepthStencilMode,
+ info.SwizzleR,
+ info.SwizzleG,
+ info.SwizzleB,
+ info.SwizzleA);
+ }
+
+ public static TextureCreateInfo GetCreateInfo(TextureInfo info, Capabilities caps)
+ {
+ FormatInfo formatInfo = info.FormatInfo;
+
+ if (!caps.SupportsAstcCompression)
+ {
+ if (formatInfo.Format.IsAstcUnorm())
+ {
+ formatInfo = new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4);
+ }
+ else if (formatInfo.Format.IsAstcSrgb())
+ {
+ formatInfo = new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4);
+ }
+ }
+
+ int width = info.Width / info.SamplesInX;
+ int height = info.Height / info.SamplesInY;
+
+ int depth = info.GetDepth() * info.GetLayers();
+
+ return new TextureCreateInfo(
+ width,
+ height,
+ depth,
+ info.Levels,
+ info.Samples,
+ formatInfo.BlockWidth,
+ formatInfo.BlockHeight,
+ formatInfo.BytesPerPixel,
+ formatInfo.Format,
+ info.DepthStencilMode,
+ info.Target,
+ info.SwizzleR,
+ info.SwizzleG,
+ info.SwizzleB,
+ info.SwizzleA);
+ }
+
+ public Texture Find2(ulong address)
+ {
+ Texture[] ts = _textures.FindOverlaps(address, 1);
+
+ if (ts.Length == 2)
+ {
+ return ts[1];
+ }
+
+ if (ts.Length == 0)
+ {
+ ts = _textures.FindOverlaps(address - 1, 2);
+ }
+
+ if (ts.Length == 0)
+ {
+ return null;
+ }
+
+ return ts[0];
+ }
+
+ public void InvalidateRange(ulong address, ulong size)
+ {
+ Texture[] overlaps = _textures.FindOverlaps(address, size);
+
+ foreach (Texture overlap in overlaps)
+ {
+ overlap.Invalidate();
+ }
+
+ _samplerPool?.InvalidateRange(address, size);
+
+ _texturePoolCache.InvalidateRange(address, size);
+ }
+
+ public void Flush()
+ {
+ foreach (Texture texture in _cache)
+ {
+ if (texture.Info.IsLinear && texture.Modified)
+ {
+ texture.Flush();
+
+ texture.Modified = false;
+ }
+ }
+ }
+
+ public void RemoveTextureFromCache(Texture texture)
+ {
+ _textures.Remove(texture);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureMsaaMode.cs b/Ryujinx.Graphics.Gpu/Image/TextureMsaaMode.cs
new file mode 100644
index 00000000..13421067
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureMsaaMode.cs
@@ -0,0 +1,53 @@
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ enum TextureMsaaMode
+ {
+ Ms1x1 = 0,
+ Ms2x2 = 2,
+ Ms4x2 = 4,
+ Ms2x1 = 5,
+ Ms4x4 = 6
+ }
+
+ static class TextureMsaaModeConverter
+ {
+ public static int SamplesCount(this TextureMsaaMode msaaMode)
+ {
+ switch (msaaMode)
+ {
+ case TextureMsaaMode.Ms2x1: return 2;
+ case TextureMsaaMode.Ms2x2: return 4;
+ case TextureMsaaMode.Ms4x2: return 8;
+ case TextureMsaaMode.Ms4x4: return 16;
+ }
+
+ return 1;
+ }
+
+ public static int SamplesInX(this TextureMsaaMode msaaMode)
+ {
+ switch (msaaMode)
+ {
+ case TextureMsaaMode.Ms2x1: return 2;
+ case TextureMsaaMode.Ms2x2: return 2;
+ case TextureMsaaMode.Ms4x2: return 4;
+ case TextureMsaaMode.Ms4x4: return 4;
+ }
+
+ return 1;
+ }
+
+ public static int SamplesInY(this TextureMsaaMode msaaMode)
+ {
+ switch (msaaMode)
+ {
+ case TextureMsaaMode.Ms2x1: return 1;
+ case TextureMsaaMode.Ms2x2: return 2;
+ case TextureMsaaMode.Ms4x2: return 2;
+ case TextureMsaaMode.Ms4x4: return 4;
+ }
+
+ return 1;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
new file mode 100644
index 00000000..558f4def
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
@@ -0,0 +1,219 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.GAL.Texture;
+using Ryujinx.Graphics.Gpu.Memory;
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ class TexturePool : Pool<Texture>
+ {
+ private TextureManager _textureManager;
+
+ public LinkedListNode<TexturePool> CacheNode { get; set; }
+
+ private struct TextureContainer
+ {
+ public Texture Texture0 { get; set; }
+ public Texture Texture1 { get; set; }
+ }
+
+ public TexturePool(
+ GpuContext context,
+ TextureManager textureManager,
+ ulong address,
+ int maximumId) : base(context, address, maximumId)
+ {
+ _textureManager = textureManager;
+ }
+
+ public override Texture Get(int id)
+ {
+ if ((uint)id >= Items.Length)
+ {
+ return null;
+ }
+
+ SynchronizeMemory();
+
+ Texture texture = Items[id];
+
+ if (texture == null)
+ {
+ ulong address = Address + (ulong)(uint)id * DescriptorSize;
+
+ Span<byte> data = Context.PhysicalMemory.Read(address, DescriptorSize);
+
+ TextureDescriptor descriptor = MemoryMarshal.Cast<byte, TextureDescriptor>(data)[0];
+
+ TextureInfo info = GetInfo(descriptor);
+
+ // Bad address. We can't add a texture with a invalid address
+ // to the cache.
+ if (info.Address == MemoryManager.BadAddress)
+ {
+ return null;
+ }
+
+ texture = _textureManager.FindOrCreateTexture(info, TextureSearchFlags.Sampler);
+
+ texture.IncrementReferenceCount();
+
+ Items[id] = texture;
+ }
+ else
+ {
+ // Memory is automatically synchronized on texture creation.
+ texture.SynchronizeMemory();
+ }
+
+ return texture;
+ }
+
+ protected override void InvalidateRangeImpl(ulong address, ulong size)
+ {
+ ulong endAddress = address + size;
+
+ for (; address < endAddress; address += DescriptorSize)
+ {
+ int id = (int)((address - Address) / DescriptorSize);
+
+ Texture texture = Items[id];
+
+ if (texture != null)
+ {
+ Span<byte> data = Context.PhysicalMemory.Read(address, DescriptorSize);
+
+ TextureDescriptor descriptor = MemoryMarshal.Cast<byte, TextureDescriptor>(data)[0];
+
+ // 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 (texture.IsPerfectMatch(GetInfo(descriptor), TextureSearchFlags.Strict))
+ {
+ continue;
+ }
+
+ texture.DecrementReferenceCount();
+
+ Items[id] = null;
+ }
+ }
+ }
+
+ private TextureInfo GetInfo(TextureDescriptor descriptor)
+ {
+ ulong address = Context.MemoryManager.Translate(descriptor.UnpackAddress());
+
+ int width = descriptor.UnpackWidth();
+ int height = descriptor.UnpackHeight();
+ 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);
+
+ uint format = descriptor.UnpackFormat();
+ bool srgb = descriptor.UnpackSrgb();
+
+ if (!FormatTable.TryGetTextureFormat(format, srgb, out FormatInfo formatInfo))
+ {
+ // TODO: Warning.
+
+ formatInfo = FormatInfo.Default;
+ }
+
+ int gobBlocksInY = descriptor.UnpackGobBlocksInY();
+ int gobBlocksInZ = descriptor.UnpackGobBlocksInZ();
+
+ int gobBlocksInTileX = descriptor.UnpackGobBlocksInTileX();
+
+ 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);
+
+ return new TextureInfo(
+ address,
+ width,
+ height,
+ depthOrLayers,
+ levels,
+ samplesInX,
+ samplesInY,
+ stride,
+ isLinear,
+ gobBlocksInY,
+ gobBlocksInZ,
+ gobBlocksInTileX,
+ target,
+ formatInfo,
+ depthStencilMode,
+ swizzleR,
+ swizzleG,
+ swizzleB,
+ swizzleA);
+ }
+
+ 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.D24X8Unorm || format == Format.D24UnormS8Uint)
+ {
+ return component == SwizzleComponent.Red
+ ? DepthStencilMode.Stencil
+ : DepthStencilMode.Depth;
+ }
+ else
+ {
+ return component == SwizzleComponent.Red
+ ? DepthStencilMode.Depth
+ : DepthStencilMode.Stencil;
+ }
+ }
+
+ private static bool IsRG(SwizzleComponent component)
+ {
+ return component == SwizzleComponent.Red ||
+ component == SwizzleComponent.Green;
+ }
+
+ protected override void Delete(Texture item)
+ {
+ item?.DecrementReferenceCount();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs b/Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs
new file mode 100644
index 00000000..8e8313ae
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs
@@ -0,0 +1,73 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ class TexturePoolCache
+ {
+ private const int MaxCapacity = 4;
+
+ private GpuContext _context;
+ private TextureManager _textureManager;
+
+ private LinkedList<TexturePool> _pools;
+
+ public TexturePoolCache(GpuContext context, TextureManager textureManager)
+ {
+ _context = context;
+ _textureManager = textureManager;
+
+ _pools = new LinkedList<TexturePool>();
+ }
+
+ public TexturePool FindOrCreate(ulong address, int maximumId)
+ {
+ TexturePool pool;
+
+ // First we try to find the pool.
+ for (LinkedListNode<TexturePool> 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);
+ }
+
+ return pool;
+ }
+ }
+
+ // If not found, create a new one.
+ pool = new TexturePool(_context, _textureManager, address, maximumId);
+
+ pool.CacheNode = _pools.AddLast(pool);
+
+ if (_pools.Count > MaxCapacity)
+ {
+ TexturePool oldestPool = _pools.First.Value;
+
+ _pools.RemoveFirst();
+
+ oldestPool.Dispose();
+
+ oldestPool.CacheNode = null;
+ }
+
+ return pool;
+ }
+
+ public void InvalidateRange(ulong address, ulong size)
+ {
+ for (LinkedListNode<TexturePool> node = _pools.First; node != null; node = node.Next)
+ {
+ TexturePool pool = node.Value;
+
+ pool.InvalidateRange(address, size);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureSearchFlags.cs b/Ryujinx.Graphics.Gpu/Image/TextureSearchFlags.cs
new file mode 100644
index 00000000..a5c951b5
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureSearchFlags.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ [Flags]
+ enum TextureSearchFlags
+ {
+ None = 0,
+ IgnoreMs = 1 << 0,
+ Strict = 1 << 1 | Sampler,
+ Sampler = 1 << 2
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureTarget.cs b/Ryujinx.Graphics.Gpu/Image/TextureTarget.cs
new file mode 100644
index 00000000..8f513903
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureTarget.cs
@@ -0,0 +1,49 @@
+using Ryujinx.Graphics.GAL.Texture;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ enum TextureTarget
+ {
+ Texture1D,
+ Texture2D,
+ Texture3D,
+ Cubemap,
+ Texture1DArray,
+ Texture2DArray,
+ TextureBuffer,
+ Texture2DLinear,
+ CubemapArray
+ }
+
+ static class TextureTargetConverter
+ {
+ 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.Texture2DLinear: 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;
+ }
+ }
+} \ No newline at end of file