diff options
Diffstat (limited to 'Ryujinx.Graphics.Gpu/Image')
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 |
