diff --git a/Ryujinx.Graphics.GAL/ITexture.cs b/Ryujinx.Graphics.GAL/ITexture.cs index 96b592937..7f46806c7 100644 --- a/Ryujinx.Graphics.GAL/ITexture.cs +++ b/Ryujinx.Graphics.GAL/ITexture.cs @@ -19,6 +19,7 @@ namespace Ryujinx.Graphics.GAL void SetData(ReadOnlySpan data); void SetData(ReadOnlySpan data, int layer, int level); + void SetData(ReadOnlySpan data, int layer, int level, Rectangle region); void SetStorage(BufferRange buffer); void Release(); } diff --git a/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs b/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs index ea4d049ff..8080ab649 100644 --- a/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs +++ b/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs @@ -113,6 +113,8 @@ namespace Ryujinx.Graphics.GAL.Multithreading TextureSetDataCommand.Run(ref GetCommand(memory), threaded, renderer); _lookup[(int)CommandType.TextureSetDataSlice] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => TextureSetDataSliceCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.TextureSetDataSliceRegion] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + TextureSetDataSliceRegionCommand.Run(ref GetCommand(memory), threaded, renderer); _lookup[(int)CommandType.TextureSetStorage] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => TextureSetStorageCommand.Run(ref GetCommand(memory), threaded, renderer); diff --git a/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs b/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs index 8c3ad8446..c25f0834e 100644 --- a/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs +++ b/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs @@ -37,6 +37,7 @@ TextureRelease, TextureSetData, TextureSetDataSlice, + TextureSetDataSliceRegion, TextureSetStorage, WindowPresent, diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceRegionCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceRegionCommand.cs new file mode 100644 index 000000000..b4285592f --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceRegionCommand.cs @@ -0,0 +1,31 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture +{ + struct TextureSetDataSliceRegionCommand : IGALCommand + { + public CommandType CommandType => CommandType.TextureSetDataSliceRegion; + private TableRef _texture; + private TableRef _data; + private int _layer; + private int _level; + private Rectangle _region; + + public void Set(TableRef texture, TableRef data, int layer, int level, Rectangle region) + { + _texture = texture; + _data = data; + _layer = layer; + _level = level; + _region = region; + } + + public static void Run(ref TextureSetDataSliceRegionCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ThreadedTexture texture = command._texture.Get(threaded); + texture.Base.SetData(new ReadOnlySpan(command._data.Get(threaded)), command._layer, command._level, command._region); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs b/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs index 64d8aa3bd..1e7d86ba3 100644 --- a/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs +++ b/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs @@ -119,6 +119,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources _renderer.QueueCommand(); } + public void SetData(ReadOnlySpan data, int layer, int level, Rectangle region) + { + _renderer.New().Set(Ref(this), Ref(data.ToArray()), layer, level, region); + _renderer.QueueCommand(); + } + public void SetStorage(BufferRange buffer) { _renderer.New().Set(Ref(this), buffer); diff --git a/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs b/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs index 5814eeb70..da25a89d8 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs @@ -224,7 +224,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma xCount, yCount, dstLinear, - dst.MemoryLayout); + dst.MemoryLayout.UnpackGobBlocksInY(), + dst.MemoryLayout.UnpackGobBlocksInZ()); if (target != null) { diff --git a/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs b/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs index c09390579..6120d2950 100644 --- a/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs +++ b/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs @@ -29,6 +29,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.InlineToMemory private int _dstHeight; private int _dstStride; private int _dstGobBlocksInY; + private int _dstGobBlocksInZ; private int _lineLengthIn; private int _lineCount; @@ -117,6 +118,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.InlineToMemory _dstHeight = (int)state.SetDstHeight; _dstStride = (int)state.PitchOut; _dstGobBlocksInY = 1 << (int)state.SetDstBlockSizeHeight; + _dstGobBlocksInZ = 1 << (int)state.SetDstBlockSizeDepth; _lineLengthIn = (int)state.LineLengthIn; _lineCount = (int)state.LineCount; @@ -176,6 +178,31 @@ namespace Ryujinx.Graphics.Gpu.Engine.InlineToMemory } else { + // TODO: Verify if the destination X/Y and width/height are taken into account + // for linear texture transfers. If not, we can use the fast path for that aswell. + // Right now the copy code at the bottom assumes that it is used on both which might be incorrect. + if (!_isLinear) + { + var target = memoryManager.Physical.TextureCache.FindTexture( + memoryManager, + _dstGpuVa, + 1, + _dstStride, + _dstHeight, + _lineLengthIn, + _lineCount, + _isLinear, + _dstGobBlocksInY, + _dstGobBlocksInZ); + + if (target != null) + { + target.SetData(data, 0, 0, new GAL.Rectangle(_dstX, _dstY, _lineLengthIn / target.Info.FormatInfo.BytesPerPixel, _lineCount)); + + return; + } + } + var dstCalculator = new OffsetCalculator( _dstWidth, _dstHeight, diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs index a598f212e..320bc0149 100644 --- a/Ryujinx.Graphics.Gpu/Image/Texture.cs +++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs @@ -761,6 +761,24 @@ namespace Ryujinx.Graphics.Gpu.Image _hasData = true; } + /// + /// Uploads new texture data to the host GPU for a specific layer/level and 2D sub-region. + /// + /// New data + /// Target layer + /// Target level + /// Target sub-region of the texture to update + public void SetData(ReadOnlySpan data, int layer, int level, Rectangle region) + { + BlacklistScale(); + + HostTexture.SetData(data, layer, level, region); + + _currentData = null; + + _hasData = true; + } + /// /// Converts texture data to a format and layout that is supported by the host GPU. /// diff --git a/Ryujinx.Graphics.Gpu/Image/TextureCache.cs b/Ryujinx.Graphics.Gpu/Image/TextureCache.cs index dcac9f643..d76879eb4 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureCache.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureCache.cs @@ -905,7 +905,8 @@ namespace Ryujinx.Graphics.Gpu.Image /// Number of pixels to be copied per line /// Number of lines to be copied /// True if the texture has a linear layout, false otherwise - /// If is false, should have the memory layout, otherwise ignored + /// If is false, the amount of GOB blocks in the Y axis + /// If is false, the amount of GOB blocks in the Z axis /// A matching texture, or null if there is no match public Texture FindTexture( MemoryManager memoryManager, @@ -916,7 +917,8 @@ namespace Ryujinx.Graphics.Gpu.Image int xCount, int yCount, bool linear, - MemoryLayout memoryLayout) + int gobBlocksInY, + int gobBlocksInZ) { ulong address = memoryManager.Translate(gpuVa); @@ -955,8 +957,8 @@ namespace Ryujinx.Graphics.Gpu.Image bool sizeMatch = xCount * bpp == texture.Info.Width * format.BytesPerPixel && height == texture.Info.Height; bool formatMatch = !texture.Info.IsLinear && - texture.Info.GobBlocksInY == memoryLayout.UnpackGobBlocksInY() && - texture.Info.GobBlocksInZ == memoryLayout.UnpackGobBlocksInZ(); + texture.Info.GobBlocksInY == gobBlocksInY && + texture.Info.GobBlocksInZ == gobBlocksInZ; match = sizeMatch && formatMatch; } diff --git a/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs b/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs index e5b39aa64..e46d5c48e 100644 --- a/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs +++ b/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs @@ -58,6 +58,11 @@ namespace Ryujinx.Graphics.OpenGL.Image throw new NotSupportedException(); } + public void SetData(ReadOnlySpan data, int layer, int level, Rectangle region) + { + throw new NotSupportedException(); + } + public void SetStorage(BufferRange buffer) { if (_buffer != BufferHandle.Null && diff --git a/Ryujinx.Graphics.OpenGL/Image/TextureView.cs b/Ryujinx.Graphics.OpenGL/Image/TextureView.cs index 8f9e2a662..243ca1b3e 100644 --- a/Ryujinx.Graphics.OpenGL/Image/TextureView.cs +++ b/Ryujinx.Graphics.OpenGL/Image/TextureView.cs @@ -1,4 +1,5 @@ using OpenTK.Graphics.OpenGL; +using Ryujinx.Common; using Ryujinx.Graphics.GAL; using System; @@ -385,7 +386,34 @@ namespace Ryujinx.Graphics.OpenGL.Image int width = Math.Max(Info.Width >> level, 1); int height = Math.Max(Info.Height >> level, 1); - ReadFrom2D((IntPtr)ptr, layer, level, width, height); + ReadFrom2D((IntPtr)ptr, layer, level, 0, 0, width, height); + } + } + } + + public void SetData(ReadOnlySpan data, int layer, int level, Rectangle region) + { + if (Format == Format.S8UintD24Unorm) + { + data = FormatConverter.ConvertS8D24ToD24S8(data); + } + + int wInBlocks = BitUtils.DivRoundUp(region.Width, Info.BlockWidth); + int hInBlocks = BitUtils.DivRoundUp(region.Height, Info.BlockHeight); + + unsafe + { + fixed (byte* ptr = data) + { + ReadFrom2D( + (IntPtr)ptr, + layer, + level, + region.X, + region.Y, + region.Width, + region.Height, + BitUtils.AlignUp(wInBlocks * Info.BytesPerPixel, 4) * hInBlocks); } } } @@ -397,15 +425,20 @@ namespace Ryujinx.Graphics.OpenGL.Image public void ReadFromPbo2D(int offset, int layer, int level, int width, int height) { - ReadFrom2D(IntPtr.Zero + offset, layer, level, width, height); + ReadFrom2D(IntPtr.Zero + offset, layer, level, 0, 0, width, height); } - private void ReadFrom2D(IntPtr data, int layer, int level, int width, int height) + private void ReadFrom2D(IntPtr data, int layer, int level, int x, int y, int width, int height) + { + int mipSize = Info.GetMipSize2D(level); + + ReadFrom2D(data, layer, level, x, y, width, height, mipSize); + } + + private void ReadFrom2D(IntPtr data, int layer, int level, int x, int y, int width, int height, int mipSize) { TextureTarget target = Target.Convert(); - int mipSize = Info.GetMipSize2D(level); - Bind(target, 0); FormatInfo format = FormatTable.GetFormatInfo(Info.Format); @@ -418,7 +451,7 @@ namespace Ryujinx.Graphics.OpenGL.Image GL.CompressedTexSubImage1D( target, level, - 0, + x, width, format.PixelFormat, mipSize, @@ -429,7 +462,7 @@ namespace Ryujinx.Graphics.OpenGL.Image GL.TexSubImage1D( target, level, - 0, + x, width, format.PixelFormat, format.PixelType, @@ -443,7 +476,7 @@ namespace Ryujinx.Graphics.OpenGL.Image GL.CompressedTexSubImage2D( target, level, - 0, + x, layer, width, 1, @@ -456,7 +489,7 @@ namespace Ryujinx.Graphics.OpenGL.Image GL.TexSubImage2D( target, level, - 0, + x, layer, width, 1, @@ -472,8 +505,8 @@ namespace Ryujinx.Graphics.OpenGL.Image GL.CompressedTexSubImage2D( target, level, - 0, - 0, + x, + y, width, height, format.PixelFormat, @@ -485,8 +518,8 @@ namespace Ryujinx.Graphics.OpenGL.Image GL.TexSubImage2D( target, level, - 0, - 0, + x, + y, width, height, format.PixelFormat, @@ -503,8 +536,8 @@ namespace Ryujinx.Graphics.OpenGL.Image GL.CompressedTexSubImage3D( target, level, - 0, - 0, + x, + y, layer, width, height, @@ -518,8 +551,8 @@ namespace Ryujinx.Graphics.OpenGL.Image GL.TexSubImage3D( target, level, - 0, - 0, + x, + y, layer, width, height, @@ -536,8 +569,8 @@ namespace Ryujinx.Graphics.OpenGL.Image GL.CompressedTexSubImage2D( TextureTarget.TextureCubeMapPositiveX + layer, level, - 0, - 0, + x, + y, width, height, format.PixelFormat, @@ -549,8 +582,8 @@ namespace Ryujinx.Graphics.OpenGL.Image GL.TexSubImage2D( TextureTarget.TextureCubeMapPositiveX + layer, level, - 0, - 0, + x, + y, width, height, format.PixelFormat, diff --git a/Ryujinx.Graphics.Vulkan/TextureBuffer.cs b/Ryujinx.Graphics.Vulkan/TextureBuffer.cs index c74956100..3e90a96f5 100644 --- a/Ryujinx.Graphics.Vulkan/TextureBuffer.cs +++ b/Ryujinx.Graphics.Vulkan/TextureBuffer.cs @@ -98,6 +98,11 @@ namespace Ryujinx.Graphics.Vulkan throw new NotSupportedException(); } + public void SetData(ReadOnlySpan data, int layer, int level, Rectangle region) + { + throw new NotSupportedException(); + } + public void SetStorage(BufferRange buffer) { if (_bufferHandle == buffer.Handle && diff --git a/Ryujinx.Graphics.Vulkan/TextureView.cs b/Ryujinx.Graphics.Vulkan/TextureView.cs index d94587bd6..2eeafae9e 100644 --- a/Ryujinx.Graphics.Vulkan/TextureView.cs +++ b/Ryujinx.Graphics.Vulkan/TextureView.cs @@ -819,7 +819,7 @@ namespace Ryujinx.Graphics.Vulkan var buffer = bufferHolder.GetBuffer(cbs.CommandBuffer).Get(cbs).Value; var image = GetImage().Get(cbs).Value; - CopyFromOrToBuffer(cbs.CommandBuffer, buffer, image, size, true, x, y, width, height); + CopyFromOrToBuffer(cbs.CommandBuffer, buffer, image, size, true, 0, 0, x, y, width, height); } bufferHolder.WaitForFences(); @@ -893,7 +893,12 @@ namespace Ryujinx.Graphics.Vulkan SetData(data, layer, level, 1, 1, singleSlice: true); } - private void SetData(ReadOnlySpan data, int layer, int level, int layers, int levels, bool singleSlice) + public void SetData(ReadOnlySpan data, int layer, int level, Rectangle region) + { + SetData(data, layer, level, 1, 1, singleSlice: true, region); + } + + private void SetData(ReadOnlySpan data, int layer, int level, int layers, int levels, bool singleSlice, Rectangle? region = null) { int bufferDataLength = GetBufferDataLength(data.Length); @@ -917,7 +922,25 @@ namespace Ryujinx.Graphics.Vulkan var buffer = bufferHolder.GetBuffer(cbs.CommandBuffer).Get(cbs).Value; var image = imageAuto.Get(cbs).Value; - CopyFromOrToBuffer(cbs.CommandBuffer, buffer, image, bufferDataLength, false, layer, level, layers, levels, singleSlice); + if (region.HasValue) + { + CopyFromOrToBuffer( + cbs.CommandBuffer, + buffer, + image, + bufferDataLength, + false, + layer, + level, + region.Value.X, + region.Value.Y, + region.Value.Width, + region.Value.Height); + } + else + { + CopyFromOrToBuffer(cbs.CommandBuffer, buffer, image, bufferDataLength, false, layer, level, layers, levels, singleSlice); + } } private int GetBufferDataLength(int length) @@ -1059,6 +1082,8 @@ namespace Ryujinx.Graphics.Vulkan Image image, int size, bool to, + int dstLayer, + int dstLevel, int x, int y, int width, @@ -1071,13 +1096,21 @@ namespace Ryujinx.Graphics.Vulkan aspectFlags = ImageAspectFlags.ImageAspectDepthBit; } - var sl = new ImageSubresourceLayers(aspectFlags, (uint)FirstLevel, (uint)FirstLayer, 1); + var sl = new ImageSubresourceLayers(aspectFlags, (uint)(FirstLevel + dstLevel), (uint)(FirstLayer + dstLayer), 1); var extent = new Extent3D((uint)width, (uint)height, 1); + int rowLengthAlignment = Info.BlockWidth; + + // We expect all data being written into the texture to have a stride aligned by 4. + if (!to && Info.BytesPerPixel < 4) + { + rowLengthAlignment = 4 / Info.BytesPerPixel; + } + var region = new BufferImageCopy( 0, - (uint)AlignUpNpot(width, Info.BlockWidth), + (uint)AlignUpNpot(width, rowLengthAlignment), (uint)AlignUpNpot(height, Info.BlockHeight), sl, new Offset3D(x, y, 0),