using Ryujinx.Common;
using Ryujinx.Graphics.Gpu.State;
using Ryujinx.Graphics.Texture;
using System;
using System.Runtime.Intrinsics;
namespace Ryujinx.Graphics.Gpu.Engine
{
partial class Methods
{
enum CopyFlags
{
SrcLinear = 1 << 7,
DstLinear = 1 << 8,
MultiLineEnable = 1 << 9,
RemapEnable = 1 << 10
}
///
/// Determine if a buffer-to-texture region covers the entirety of a texture.
///
/// Copy command parameters
/// Texture to compare
/// True if the texture is linear, false if block linear
/// Texture bytes per pixel
/// Texture stride
///
private bool IsTextureCopyComplete(CopyBufferParams cbp, CopyBufferTexture tex, bool linear, int bpp, int stride)
{
if (linear)
{
int alignWidth = Constants.StrideAlignment / bpp;
return tex.RegionX == 0 &&
tex.RegionY == 0 &&
stride / bpp == BitUtils.AlignUp(cbp.XCount, alignWidth);
}
else
{
int alignWidth = Constants.GobAlignment / bpp;
return tex.RegionX == 0 &&
tex.RegionY == 0 &&
tex.Width == BitUtils.AlignUp(cbp.XCount, alignWidth) &&
tex.Height == cbp.YCount;
}
}
///
/// Performs a buffer to buffer, or buffer to texture copy.
///
/// Current GPU state
/// Method call argument
private void CopyBuffer(GpuState state, int argument)
{
var cbp = state.Get(MethodOffset.CopyBufferParams);
var swizzle = state.Get(MethodOffset.CopyBufferSwizzle);
CopyFlags copyFlags = (CopyFlags)argument;
bool srcLinear = copyFlags.HasFlag(CopyFlags.SrcLinear);
bool dstLinear = copyFlags.HasFlag(CopyFlags.DstLinear);
bool copy2D = copyFlags.HasFlag(CopyFlags.MultiLineEnable);
bool remap = copyFlags.HasFlag(CopyFlags.RemapEnable);
int size = cbp.XCount;
if (size == 0)
{
return;
}
FlushUboDirty(state.Channel.MemoryManager);
if (copy2D)
{
// Buffer to texture copy.
int srcBpp = remap ? swizzle.UnpackSrcComponentsCount() * swizzle.UnpackComponentSize() : 1;
int dstBpp = remap ? swizzle.UnpackDstComponentsCount() * swizzle.UnpackComponentSize() : 1;
var dst = state.Get(MethodOffset.CopyBufferDstTexture);
var src = state.Get(MethodOffset.CopyBufferSrcTexture);
var srcCalculator = new OffsetCalculator(
src.Width,
src.Height,
cbp.SrcStride,
srcLinear,
src.MemoryLayout.UnpackGobBlocksInY(),
src.MemoryLayout.UnpackGobBlocksInZ(),
srcBpp);
var dstCalculator = new OffsetCalculator(
dst.Width,
dst.Height,
cbp.DstStride,
dstLinear,
dst.MemoryLayout.UnpackGobBlocksInY(),
dst.MemoryLayout.UnpackGobBlocksInZ(),
dstBpp);
ulong srcBaseAddress = state.Channel.MemoryManager.Translate(cbp.SrcAddress.Pack());
ulong dstBaseAddress = state.Channel.MemoryManager.Translate(cbp.DstAddress.Pack());
(int srcBaseOffset, int srcSize) = srcCalculator.GetRectangleRange(src.RegionX, src.RegionY, cbp.XCount, cbp.YCount);
(int dstBaseOffset, int dstSize) = dstCalculator.GetRectangleRange(dst.RegionX, dst.RegionY, cbp.XCount, cbp.YCount);
ReadOnlySpan srcSpan = state.Channel.MemoryManager.Physical.GetSpan(srcBaseAddress + (ulong)srcBaseOffset, srcSize, true);
Span dstSpan = state.Channel.MemoryManager.Physical.GetSpan(dstBaseAddress + (ulong)dstBaseOffset, dstSize).ToArray();
bool completeSource = IsTextureCopyComplete(cbp, src, srcLinear, srcBpp, cbp.SrcStride);
bool completeDest = IsTextureCopyComplete(cbp, dst, dstLinear, dstBpp, cbp.DstStride);
if (completeSource && completeDest)
{
Image.Texture target = state.Channel.MemoryManager.Physical.TextureCache.FindTexture(
state.Channel.MemoryManager,
dst,
cbp,
swizzle,
dstLinear);
if (target != null)
{
ReadOnlySpan data;
if (srcLinear)
{
data = LayoutConverter.ConvertLinearStridedToLinear(
target.Info.Width,
target.Info.Height,
1,
1,
cbp.SrcStride,
target.Info.FormatInfo.BytesPerPixel,
srcSpan);
}
else
{
data = LayoutConverter.ConvertBlockLinearToLinear(
src.Width,
src.Height,
1,
target.Info.Levels,
1,
1,
1,
srcBpp,
src.MemoryLayout.UnpackGobBlocksInY(),
src.MemoryLayout.UnpackGobBlocksInZ(),
1,
new SizeInfo((int)target.Size),
srcSpan);
}
target.SetData(data);
target.SignalModified();
return;
}
else if (srcCalculator.LayoutMatches(dstCalculator))
{
srcSpan.CopyTo(dstSpan); // No layout conversion has to be performed, just copy the data entirely.
state.Channel.MemoryManager.Physical.Write(dstBaseAddress + (ulong)dstBaseOffset, dstSpan);
return;
}
}
unsafe bool Convert(Span dstSpan, ReadOnlySpan srcSpan) where T : unmanaged
{
fixed (byte* dstPtr = dstSpan, srcPtr = srcSpan)
{
byte* dstBase = dstPtr - dstBaseOffset; // Layout offset is relative to the base, so we need to subtract the span's offset.
byte* srcBase = srcPtr - srcBaseOffset;
for (int y = 0; y < cbp.YCount; y++)
{
srcCalculator.SetY(src.RegionY + y);
dstCalculator.SetY(dst.RegionY + y);
for (int x = 0; x < cbp.XCount; x++)
{
int srcOffset = srcCalculator.GetOffset(src.RegionX + x);
int dstOffset = dstCalculator.GetOffset(dst.RegionX + x);
*(T*)(dstBase + dstOffset) = *(T*)(srcBase + srcOffset);
}
}
}
return true;
}
bool _ = srcBpp switch
{
1 => Convert(dstSpan, srcSpan),
2 => Convert(dstSpan, srcSpan),
4 => Convert(dstSpan, srcSpan),
8 => Convert(dstSpan, srcSpan),
12 => Convert(dstSpan, srcSpan),
16 => Convert>(dstSpan, srcSpan),
_ => throw new NotSupportedException($"Unable to copy ${srcBpp} bpp pixel format.")
};
state.Channel.MemoryManager.Physical.Write(dstBaseAddress + (ulong)dstBaseOffset, dstSpan);
}
else
{
if (remap &&
swizzle.UnpackDstX() == BufferSwizzleComponent.ConstA &&
swizzle.UnpackDstY() == BufferSwizzleComponent.ConstA &&
swizzle.UnpackDstZ() == BufferSwizzleComponent.ConstA &&
swizzle.UnpackDstW() == BufferSwizzleComponent.ConstA &&
swizzle.UnpackSrcComponentsCount() == 1 &&
swizzle.UnpackDstComponentsCount() == 1 &&
swizzle.UnpackComponentSize() == 4)
{
// Fast path for clears when remap is enabled.
state.Channel.MemoryManager.Physical.BufferCache.ClearBuffer(
state.Channel.MemoryManager,
cbp.DstAddress,
(uint)size * 4,
state.Get(MethodOffset.CopyBufferConstA));
}
else
{
// TODO: Implement remap functionality.
// Buffer to buffer copy.
state.Channel.MemoryManager.Physical.BufferCache.CopyBuffer(
state.Channel.MemoryManager,
cbp.SrcAddress,
cbp.DstAddress,
(uint)size);
}
}
}
}
}