diff options
| author | TSR Berry <20988865+TSRBerry@users.noreply.github.com> | 2023-04-08 01:22:00 +0200 |
|---|---|---|
| committer | Mary <thog@protonmail.com> | 2023-04-27 23:51:14 +0200 |
| commit | cee712105850ac3385cd0091a923438167433f9f (patch) | |
| tree | 4a5274b21d8b7f938c0d0ce18736d3f2993b11b1 /src/Ryujinx.Graphics.Nvdec/Image | |
| parent | cd124bda587ef09668a971fa1cac1c3f0cfc9f21 (diff) | |
Move solution and projects to src
Diffstat (limited to 'src/Ryujinx.Graphics.Nvdec/Image')
| -rw-r--r-- | src/Ryujinx.Graphics.Nvdec/Image/SurfaceCache.cs | 174 | ||||
| -rw-r--r-- | src/Ryujinx.Graphics.Nvdec/Image/SurfaceCommon.cs | 26 | ||||
| -rw-r--r-- | src/Ryujinx.Graphics.Nvdec/Image/SurfaceReader.cs | 133 | ||||
| -rw-r--r-- | src/Ryujinx.Graphics.Nvdec/Image/SurfaceWriter.cs | 175 |
4 files changed, 508 insertions, 0 deletions
diff --git a/src/Ryujinx.Graphics.Nvdec/Image/SurfaceCache.cs b/src/Ryujinx.Graphics.Nvdec/Image/SurfaceCache.cs new file mode 100644 index 00000000..dc119673 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec/Image/SurfaceCache.cs @@ -0,0 +1,174 @@ +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Graphics.Video; +using System; +using System.Diagnostics; + +namespace Ryujinx.Graphics.Nvdec.Image +{ + class SurfaceCache + { + // Must be equal to at least the maximum number of surfaces + // that can be in use simultaneously (which is 17, since H264 + // can have up to 16 reference frames, and we need another one + // for the current frame). + // Realistically, most codecs won't ever use more than 4 simultaneously. + private const int MaxItems = 17; + + private struct CacheItem + { + public int ReferenceCount; + public uint LumaOffset; + public uint ChromaOffset; + public int Width; + public int Height; + public IDecoder Owner; + public ISurface Surface; + } + + private readonly CacheItem[] _pool = new CacheItem[MaxItems]; + + private readonly MemoryManager _gmm; + + public SurfaceCache(MemoryManager gmm) + { + _gmm = gmm; + } + + public ISurface Get(IDecoder decoder, uint lumaOffset, uint chromaOffset, int width, int height) + { + lock (_pool) + { + ISurface surface = null; + + // Try to find a compatible surface with same parameters, and same offsets. + for (int i = 0; i < MaxItems; i++) + { + ref CacheItem item = ref _pool[i]; + + if (item.LumaOffset == lumaOffset && + item.ChromaOffset == chromaOffset && + item.Owner == decoder && + item.Width == width && + item.Height == height) + { + item.ReferenceCount++; + surface = item.Surface; + MoveToFront(i); + break; + } + } + + // If we failed to find a perfect match, now ignore the offsets. + // Search backwards to replace the oldest compatible surface, + // this avoids thrashing frequently used surfaces. + // Now we need to ensure that the surface is not in use, as we'll change the data. + if (surface == null) + { + for (int i = MaxItems - 1; i >= 0; i--) + { + ref CacheItem item = ref _pool[i]; + + if (item.ReferenceCount == 0 && item.Owner == decoder && item.Width == width && item.Height == height) + { + item.ReferenceCount = 1; + item.LumaOffset = lumaOffset; + item.ChromaOffset = chromaOffset; + surface = item.Surface; + + if ((lumaOffset | chromaOffset) != 0) + { + SurfaceReader.Read(_gmm, surface, lumaOffset, chromaOffset); + } + + MoveToFront(i); + break; + } + } + } + + // If everything else failed, we try to create a new surface, + // and insert it on the pool. We replace the oldest item on the + // pool to avoid thrashing frequently used surfaces. + // If even the oldest item is in use, that means that the entire pool + // is in use, in that case we throw as there's no place to insert + // the new surface. + if (surface == null) + { + if (_pool[MaxItems - 1].ReferenceCount == 0) + { + surface = decoder.CreateSurface(width, height); + + if ((lumaOffset | chromaOffset) != 0) + { + SurfaceReader.Read(_gmm, surface, lumaOffset, chromaOffset); + } + + MoveToFront(MaxItems - 1); + ref CacheItem item = ref _pool[0]; + item.Surface?.Dispose(); + item.ReferenceCount = 1; + item.LumaOffset = lumaOffset; + item.ChromaOffset = chromaOffset; + item.Width = width; + item.Height = height; + item.Owner = decoder; + item.Surface = surface; + } + else + { + throw new InvalidOperationException("No free slot on the surface pool."); + } + } + + return surface; + } + } + + public void Put(ISurface surface) + { + lock (_pool) + { + for (int i = 0; i < MaxItems; i++) + { + ref CacheItem item = ref _pool[i]; + + if (item.Surface == surface) + { + item.ReferenceCount--; + Debug.Assert(item.ReferenceCount >= 0); + break; + } + } + } + } + + private void MoveToFront(int index) + { + // If index is 0 we don't need to do anything, + // as it's already on the front. + if (index != 0) + { + CacheItem temp = _pool[index]; + Array.Copy(_pool, 0, _pool, 1, index); + _pool[0] = temp; + } + } + + public void Trim() + { + lock (_pool) + { + for (int i = 0; i < MaxItems; i++) + { + ref CacheItem item = ref _pool[i]; + + if (item.ReferenceCount == 0) + { + item.Surface?.Dispose(); + item = default; + } + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec/Image/SurfaceCommon.cs b/src/Ryujinx.Graphics.Nvdec/Image/SurfaceCommon.cs new file mode 100644 index 00000000..6087f5b1 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec/Image/SurfaceCommon.cs @@ -0,0 +1,26 @@ +using Ryujinx.Graphics.Texture; +using Ryujinx.Graphics.Video; +using System; + +namespace Ryujinx.Graphics.Nvdec.Image +{ + static class SurfaceCommon + { + public static int GetBlockLinearSize(int width, int height, int bytesPerPixel) + { + return SizeCalculator.GetBlockLinearTextureSize(width, height, 1, 1, 1, 1, 1, bytesPerPixel, 2, 1, 1).TotalSize; + } + + public static void Copy(ISurface src, ISurface dst) + { + src.YPlane.AsSpan().CopyTo(dst.YPlane.AsSpan()); + src.UPlane.AsSpan().CopyTo(dst.UPlane.AsSpan()); + src.VPlane.AsSpan().CopyTo(dst.VPlane.AsSpan()); + } + + public unsafe static Span<byte> AsSpan(this Plane plane) + { + return new Span<byte>((void*)plane.Pointer, plane.Length); + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec/Image/SurfaceReader.cs b/src/Ryujinx.Graphics.Nvdec/Image/SurfaceReader.cs new file mode 100644 index 00000000..039a2583 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec/Image/SurfaceReader.cs @@ -0,0 +1,133 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Graphics.Texture; +using Ryujinx.Graphics.Video; +using System; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +using static Ryujinx.Graphics.Nvdec.Image.SurfaceCommon; + +namespace Ryujinx.Graphics.Nvdec.Image +{ + static class SurfaceReader + { + public static void Read(MemoryManager gmm, ISurface surface, uint lumaOffset, uint chromaOffset) + { + int width = surface.Width; + int height = surface.Height; + int stride = surface.Stride; + + ReadOnlySpan<byte> luma = gmm.DeviceGetSpan(lumaOffset, GetBlockLinearSize(width, height, 1)); + + ReadLuma(surface.YPlane.AsSpan(), luma, stride, width, height); + + int uvWidth = surface.UvWidth; + int uvHeight = surface.UvHeight; + int uvStride = surface.UvStride; + + ReadOnlySpan<byte> chroma = gmm.DeviceGetSpan(chromaOffset, GetBlockLinearSize(uvWidth, uvHeight, 2)); + + ReadChroma(surface.UPlane.AsSpan(), surface.VPlane.AsSpan(), chroma, uvStride, uvWidth, uvHeight); + } + + private static void ReadLuma(Span<byte> dst, ReadOnlySpan<byte> src, int dstStride, int width, int height) + { + LayoutConverter.ConvertBlockLinearToLinear(dst, width, height, dstStride, 1, 2, src); + } + + private unsafe static void ReadChroma( + Span<byte> dstU, + Span<byte> dstV, + ReadOnlySpan<byte> src, + int dstStride, + int width, + int height) + { + OffsetCalculator calc = new OffsetCalculator(width, height, 0, false, 2, 2); + + if (Sse2.IsSupported) + { + int strideTrunc64 = BitUtils.AlignDown(width * 2, 64); + + int outStrideGap = dstStride - width; + + fixed (byte* dstUPtr = dstU, dstVPtr = dstV, dataPtr = src) + { + byte* uPtr = dstUPtr; + byte* vPtr = dstVPtr; + + for (int y = 0; y < height; y++) + { + calc.SetY(y); + + for (int x = 0; x < strideTrunc64; x += 64, uPtr += 32, vPtr += 32) + { + byte* offset = dataPtr + calc.GetOffsetWithLineOffset64(x); + byte* offset2 = offset + 0x20; + byte* offset3 = offset + 0x100; + byte* offset4 = offset + 0x120; + + Vector128<byte> value = *(Vector128<byte>*)offset; + Vector128<byte> value2 = *(Vector128<byte>*)offset2; + Vector128<byte> value3 = *(Vector128<byte>*)offset3; + Vector128<byte> value4 = *(Vector128<byte>*)offset4; + + Vector128<byte> u00 = Sse2.UnpackLow(value, value2); + Vector128<byte> v00 = Sse2.UnpackHigh(value, value2); + Vector128<byte> u01 = Sse2.UnpackLow(value3, value4); + Vector128<byte> v01 = Sse2.UnpackHigh(value3, value4); + + Vector128<byte> u10 = Sse2.UnpackLow(u00, v00); + Vector128<byte> v10 = Sse2.UnpackHigh(u00, v00); + Vector128<byte> u11 = Sse2.UnpackLow(u01, v01); + Vector128<byte> v11 = Sse2.UnpackHigh(u01, v01); + + Vector128<byte> u20 = Sse2.UnpackLow(u10, v10); + Vector128<byte> v20 = Sse2.UnpackHigh(u10, v10); + Vector128<byte> u21 = Sse2.UnpackLow(u11, v11); + Vector128<byte> v21 = Sse2.UnpackHigh(u11, v11); + + Vector128<byte> u30 = Sse2.UnpackLow(u20, v20); + Vector128<byte> v30 = Sse2.UnpackHigh(u20, v20); + Vector128<byte> u31 = Sse2.UnpackLow(u21, v21); + Vector128<byte> v31 = Sse2.UnpackHigh(u21, v21); + + *(Vector128<byte>*)uPtr = u30; + *(Vector128<byte>*)(uPtr + 16) = u31; + *(Vector128<byte>*)vPtr = v30; + *(Vector128<byte>*)(vPtr + 16) = v31; + } + + for (int x = strideTrunc64 / 2; x < width; x++, uPtr++, vPtr++) + { + byte* offset = dataPtr + calc.GetOffset(x); + + *uPtr = *offset; + *vPtr = *(offset + 1); + } + + uPtr += outStrideGap; + vPtr += outStrideGap; + } + } + } + else + { + for (int y = 0; y < height; y++) + { + int dstBaseOffset = y * dstStride; + + calc.SetY(y); + + for (int x = 0; x < width; x++) + { + int srcOffset = calc.GetOffset(x); + + dstU[dstBaseOffset + x] = src[srcOffset]; + dstV[dstBaseOffset + x] = src[srcOffset + 1]; + } + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec/Image/SurfaceWriter.cs b/src/Ryujinx.Graphics.Nvdec/Image/SurfaceWriter.cs new file mode 100644 index 00000000..cc5c251b --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec/Image/SurfaceWriter.cs @@ -0,0 +1,175 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Graphics.Texture; +using Ryujinx.Graphics.Video; +using System; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +using static Ryujinx.Graphics.Nvdec.Image.SurfaceCommon; +using static Ryujinx.Graphics.Nvdec.MemoryExtensions; + +namespace Ryujinx.Graphics.Nvdec.Image +{ + static class SurfaceWriter + { + public static void Write(MemoryManager gmm, ISurface surface, uint lumaOffset, uint chromaOffset) + { + int lumaSize = GetBlockLinearSize(surface.Width, surface.Height, 1); + + using var luma = gmm.GetWritableRegion(ExtendOffset(lumaOffset), lumaSize); + + WriteLuma( + luma.Memory.Span, + surface.YPlane.AsSpan(), + surface.Stride, + surface.Width, + surface.Height); + + int chromaSize = GetBlockLinearSize(surface.UvWidth, surface.UvHeight, 2); + + using var chroma = gmm.GetWritableRegion(ExtendOffset(chromaOffset), chromaSize); + + WriteChroma( + chroma.Memory.Span, + surface.UPlane.AsSpan(), + surface.VPlane.AsSpan(), + surface.UvStride, + surface.UvWidth, + surface.UvHeight); + } + + public static void WriteInterlaced( + MemoryManager gmm, + ISurface surface, + uint lumaTopOffset, + uint chromaTopOffset, + uint lumaBottomOffset, + uint chromaBottomOffset) + { + int lumaSize = GetBlockLinearSize(surface.Width, surface.Height / 2, 1); + + using var lumaTop = gmm.GetWritableRegion(ExtendOffset(lumaTopOffset), lumaSize); + using var lumaBottom = gmm.GetWritableRegion(ExtendOffset(lumaBottomOffset), lumaSize); + + WriteLuma( + lumaTop.Memory.Span, + surface.YPlane.AsSpan(), + surface.Stride * 2, + surface.Width, + surface.Height / 2); + + WriteLuma( + lumaBottom.Memory.Span, + surface.YPlane.AsSpan().Slice(surface.Stride), + surface.Stride * 2, + surface.Width, + surface.Height / 2); + + int chromaSize = GetBlockLinearSize(surface.UvWidth, surface.UvHeight / 2, 2); + + using var chromaTop = gmm.GetWritableRegion(ExtendOffset(chromaTopOffset), chromaSize); + using var chromaBottom = gmm.GetWritableRegion(ExtendOffset(chromaBottomOffset), chromaSize); + + WriteChroma( + chromaTop.Memory.Span, + surface.UPlane.AsSpan(), + surface.VPlane.AsSpan(), + surface.UvStride * 2, + surface.UvWidth, + surface.UvHeight / 2); + + WriteChroma( + chromaBottom.Memory.Span, + surface.UPlane.AsSpan().Slice(surface.UvStride), + surface.VPlane.AsSpan().Slice(surface.UvStride), + surface.UvStride * 2, + surface.UvWidth, + surface.UvHeight / 2); + } + + private static void WriteLuma(Span<byte> dst, ReadOnlySpan<byte> src, int srcStride, int width, int height) + { + LayoutConverter.ConvertLinearToBlockLinear(dst, width, height, srcStride, 1, 2, src); + } + + private unsafe static void WriteChroma( + Span<byte> dst, + ReadOnlySpan<byte> srcU, + ReadOnlySpan<byte> srcV, + int srcStride, + int width, + int height) + { + OffsetCalculator calc = new OffsetCalculator(width, height, 0, false, 2, 2); + + if (Sse2.IsSupported) + { + int strideTrunc64 = BitUtils.AlignDown(width * 2, 64); + + int inStrideGap = srcStride - width; + + fixed (byte* outputPtr = dst, srcUPtr = srcU, srcVPtr = srcV) + { + byte* inUPtr = srcUPtr; + byte* inVPtr = srcVPtr; + + for (int y = 0; y < height; y++) + { + calc.SetY(y); + + for (int x = 0; x < strideTrunc64; x += 64, inUPtr += 32, inVPtr += 32) + { + byte* offset = outputPtr + calc.GetOffsetWithLineOffset64(x); + byte* offset2 = offset + 0x20; + byte* offset3 = offset + 0x100; + byte* offset4 = offset + 0x120; + + Vector128<byte> value = *(Vector128<byte>*)inUPtr; + Vector128<byte> value2 = *(Vector128<byte>*)inVPtr; + Vector128<byte> value3 = *(Vector128<byte>*)(inUPtr + 16); + Vector128<byte> value4 = *(Vector128<byte>*)(inVPtr + 16); + + Vector128<byte> uv0 = Sse2.UnpackLow(value, value2); + Vector128<byte> uv1 = Sse2.UnpackHigh(value, value2); + Vector128<byte> uv2 = Sse2.UnpackLow(value3, value4); + Vector128<byte> uv3 = Sse2.UnpackHigh(value3, value4); + + *(Vector128<byte>*)offset = uv0; + *(Vector128<byte>*)offset2 = uv1; + *(Vector128<byte>*)offset3 = uv2; + *(Vector128<byte>*)offset4 = uv3; + } + + for (int x = strideTrunc64 / 2; x < width; x++, inUPtr++, inVPtr++) + { + byte* offset = outputPtr + calc.GetOffset(x); + + *offset = *inUPtr; + *(offset + 1) = *inVPtr; + } + + inUPtr += inStrideGap; + inVPtr += inStrideGap; + } + } + } + else + { + for (int y = 0; y < height; y++) + { + int srcBaseOffset = y * srcStride; + + calc.SetY(y); + + for (int x = 0; x < width; x++) + { + int dstOffset = calc.GetOffset(x); + + dst[dstOffset + 0] = srcU[srcBaseOffset + x]; + dst[dstOffset + 1] = srcV[srcBaseOffset + x]; + } + } + } + } + } +} |
