2022-02-22 15:21:29 +01:00
|
|
|
|
using Ryujinx.Common;
|
|
|
|
|
using Ryujinx.Graphics.Device;
|
2019-10-13 08:02:07 +02:00
|
|
|
|
using Ryujinx.Graphics.GAL;
|
2021-07-11 22:20:40 +02:00
|
|
|
|
using Ryujinx.Graphics.Gpu.Engine.Types;
|
2020-09-01 02:06:27 +02:00
|
|
|
|
using Ryujinx.Graphics.Gpu.Image;
|
2020-11-10 01:41:13 +01:00
|
|
|
|
using Ryujinx.Graphics.Texture;
|
2022-02-22 15:21:29 +01:00
|
|
|
|
using Ryujinx.Memory;
|
2020-07-07 04:41:07 +02:00
|
|
|
|
using System;
|
2021-07-08 01:56:06 +02:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Runtime.CompilerServices;
|
2022-02-22 15:21:29 +01:00
|
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
|
using System.Runtime.Intrinsics;
|
2019-10-13 08:02:07 +02:00
|
|
|
|
|
2021-07-08 01:56:06 +02:00
|
|
|
|
namespace Ryujinx.Graphics.Gpu.Engine.Twod
|
2019-10-13 08:02:07 +02:00
|
|
|
|
{
|
2021-07-08 01:56:06 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Represents a 2D engine class.
|
|
|
|
|
/// </summary>
|
|
|
|
|
class TwodClass : IDeviceState
|
2019-10-13 08:02:07 +02:00
|
|
|
|
{
|
2021-07-08 01:56:06 +02:00
|
|
|
|
private readonly GpuChannel _channel;
|
|
|
|
|
private readonly DeviceState<TwodClassState> _state;
|
|
|
|
|
|
2019-12-31 20:19:44 +01:00
|
|
|
|
/// <summary>
|
2021-07-08 01:56:06 +02:00
|
|
|
|
/// Creates a new instance of the 2D engine class.
|
2019-12-31 20:19:44 +01:00
|
|
|
|
/// </summary>
|
2021-07-08 01:56:06 +02:00
|
|
|
|
/// <param name="channel">The channel that will make use of the engine</param>
|
|
|
|
|
public TwodClass(GpuChannel channel)
|
2019-10-13 08:02:07 +02:00
|
|
|
|
{
|
2021-07-08 01:56:06 +02:00
|
|
|
|
_channel = channel;
|
|
|
|
|
_state = new DeviceState<TwodClassState>(new Dictionary<string, RwCallback>
|
|
|
|
|
{
|
|
|
|
|
{ nameof(TwodClassState.PixelsFromMemorySrcY0Int), new RwCallback(PixelsFromMemorySrcY0Int, null) }
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Reads data from the class registers.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="offset">Register byte offset</param>
|
|
|
|
|
/// <returns>Data at the specified offset</returns>
|
|
|
|
|
public int Read(int offset) => _state.Read(offset);
|
2021-06-29 19:32:02 +02:00
|
|
|
|
|
2021-07-08 01:56:06 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Writes data to the class registers.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="offset">Register byte offset</param>
|
|
|
|
|
/// <param name="data">Data to be written</param>
|
|
|
|
|
public void Write(int offset, int data) => _state.Write(offset, data);
|
|
|
|
|
|
2022-02-22 15:21:29 +01:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Determines if data is compatible between the source and destination texture.
|
|
|
|
|
/// The two textures must have the same size, layout, and bytes per pixel.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="lhs">Info for the first texture</param>
|
|
|
|
|
/// <param name="rhs">Info for the second texture</param>
|
|
|
|
|
/// <param name="lhsFormat">Format of the first texture</param>
|
|
|
|
|
/// <param name="rhsFormat">Format of the second texture</param>
|
|
|
|
|
/// <returns>True if the data is compatible, false otherwise</returns>
|
|
|
|
|
private bool IsDataCompatible(TwodTexture lhs, TwodTexture rhs, FormatInfo lhsFormat, FormatInfo rhsFormat)
|
|
|
|
|
{
|
|
|
|
|
if (lhsFormat.BytesPerPixel != rhsFormat.BytesPerPixel ||
|
|
|
|
|
lhs.Height != rhs.Height ||
|
|
|
|
|
lhs.Depth != rhs.Depth ||
|
|
|
|
|
lhs.LinearLayout != rhs.LinearLayout ||
|
|
|
|
|
lhs.MemoryLayout.Packed != rhs.MemoryLayout.Packed)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (lhs.LinearLayout)
|
|
|
|
|
{
|
|
|
|
|
return lhs.Stride == rhs.Stride;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return lhs.Width == rhs.Width;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Determine if the given region covers the full texture, also considering width alignment.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="texture">The texture to check</param>
|
|
|
|
|
/// <param name="formatInfo"></param>
|
|
|
|
|
/// <param name="x1">Region start x</param>
|
|
|
|
|
/// <param name="y1">Region start y</param>
|
|
|
|
|
/// <param name="x2">Region end x</param>
|
|
|
|
|
/// <param name="y2">Region end y</param>
|
|
|
|
|
/// <returns>True if the region covers the full texture, false otherwise</returns>
|
|
|
|
|
private bool IsCopyRegionComplete(TwodTexture texture, FormatInfo formatInfo, int x1, int y1, int x2, int y2)
|
|
|
|
|
{
|
|
|
|
|
if (x1 != 0 || y1 != 0 || y2 != texture.Height)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int width;
|
|
|
|
|
int widthAlignment;
|
|
|
|
|
|
|
|
|
|
if (texture.LinearLayout)
|
|
|
|
|
{
|
|
|
|
|
widthAlignment = 1;
|
|
|
|
|
width = texture.Stride / formatInfo.BytesPerPixel;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
widthAlignment = Constants.GobAlignment / formatInfo.BytesPerPixel;
|
|
|
|
|
width = texture.Width;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return width == BitUtils.AlignUp(x2, widthAlignment);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Performs a full data copy between two textures, reading and writing guest memory directly.
|
|
|
|
|
/// The textures must have a matching layout, size, and bytes per pixel.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="src">The source texture</param>
|
|
|
|
|
/// <param name="dst">The destination texture</param>
|
|
|
|
|
/// <param name="w">Copy width</param>
|
|
|
|
|
/// <param name="h">Copy height</param>
|
|
|
|
|
/// <param name="bpp">Bytes per pixel</param>
|
|
|
|
|
private void UnscaledFullCopy(TwodTexture src, TwodTexture dst, int w, int h, int bpp)
|
|
|
|
|
{
|
|
|
|
|
var srcCalculator = new OffsetCalculator(
|
|
|
|
|
w,
|
|
|
|
|
h,
|
|
|
|
|
src.Stride,
|
|
|
|
|
src.LinearLayout,
|
|
|
|
|
src.MemoryLayout.UnpackGobBlocksInY(),
|
|
|
|
|
src.MemoryLayout.UnpackGobBlocksInZ(),
|
|
|
|
|
bpp);
|
|
|
|
|
|
|
|
|
|
(int _, int srcSize) = srcCalculator.GetRectangleRange(0, 0, w, h);
|
|
|
|
|
|
|
|
|
|
var memoryManager = _channel.MemoryManager;
|
|
|
|
|
|
|
|
|
|
ulong srcGpuVa = src.Address.Pack();
|
|
|
|
|
ulong dstGpuVa = dst.Address.Pack();
|
|
|
|
|
|
|
|
|
|
ReadOnlySpan<byte> srcSpan = memoryManager.GetSpan(srcGpuVa, srcSize, true);
|
|
|
|
|
|
|
|
|
|
int width;
|
|
|
|
|
int height = src.Height;
|
|
|
|
|
if (src.LinearLayout)
|
|
|
|
|
{
|
|
|
|
|
width = src.Stride / bpp;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
width = src.Width;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If the copy is not equal to the width and height of the texture, we will need to copy partially.
|
|
|
|
|
// It's worth noting that it has already been established that the src and dst are the same size.
|
|
|
|
|
|
|
|
|
|
if (w == width && h == height)
|
|
|
|
|
{
|
|
|
|
|
memoryManager.Write(dstGpuVa, srcSpan);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
using WritableRegion dstRegion = memoryManager.GetWritableRegion(dstGpuVa, srcSize, true);
|
|
|
|
|
Span<byte> dstSpan = dstRegion.Memory.Span;
|
|
|
|
|
|
|
|
|
|
if (src.LinearLayout)
|
|
|
|
|
{
|
|
|
|
|
int stride = src.Stride;
|
|
|
|
|
int offset = 0;
|
|
|
|
|
int lineSize = width * bpp;
|
|
|
|
|
|
|
|
|
|
for (int y = 0; y < height; y++)
|
|
|
|
|
{
|
|
|
|
|
srcSpan.Slice(offset, lineSize).CopyTo(dstSpan.Slice(offset));
|
|
|
|
|
|
|
|
|
|
offset += stride;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Copy with the block linear layout in mind.
|
|
|
|
|
// Recreate the offset calculate with bpp 1 for copy.
|
|
|
|
|
|
|
|
|
|
int stride = w * bpp;
|
|
|
|
|
|
|
|
|
|
srcCalculator = new OffsetCalculator(
|
|
|
|
|
stride,
|
|
|
|
|
h,
|
|
|
|
|
0,
|
|
|
|
|
false,
|
|
|
|
|
src.MemoryLayout.UnpackGobBlocksInY(),
|
|
|
|
|
src.MemoryLayout.UnpackGobBlocksInZ(),
|
|
|
|
|
1);
|
|
|
|
|
|
|
|
|
|
int strideTrunc = BitUtils.AlignDown(stride, 16);
|
|
|
|
|
|
|
|
|
|
ReadOnlySpan<Vector128<byte>> srcVec = MemoryMarshal.Cast<byte, Vector128<byte>>(srcSpan);
|
|
|
|
|
Span<Vector128<byte>> dstVec = MemoryMarshal.Cast<byte, Vector128<byte>>(dstSpan);
|
|
|
|
|
|
|
|
|
|
for (int y = 0; y < h; y++)
|
|
|
|
|
{
|
|
|
|
|
int x = 0;
|
|
|
|
|
|
|
|
|
|
srcCalculator.SetY(y);
|
|
|
|
|
|
|
|
|
|
for (; x < strideTrunc; x += 16)
|
|
|
|
|
{
|
|
|
|
|
int offset = srcCalculator.GetOffset(x) >> 4;
|
|
|
|
|
|
|
|
|
|
dstVec[offset] = srcVec[offset];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (; x < stride; x++)
|
|
|
|
|
{
|
|
|
|
|
int offset = srcCalculator.GetOffset(x);
|
|
|
|
|
|
|
|
|
|
dstSpan[offset] = srcSpan[offset];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-08 01:56:06 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Performs the blit operation, triggered by the register write.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="argument">Method call argument</param>
|
|
|
|
|
private void PixelsFromMemorySrcY0Int(int argument)
|
|
|
|
|
{
|
|
|
|
|
var memoryManager = _channel.MemoryManager;
|
2019-10-13 08:02:07 +02:00
|
|
|
|
|
2021-07-11 22:20:40 +02:00
|
|
|
|
var dstCopyTexture = Unsafe.As<uint, TwodTexture>(ref _state.State.SetDstFormat);
|
|
|
|
|
var srcCopyTexture = Unsafe.As<uint, TwodTexture>(ref _state.State.SetSrcFormat);
|
2020-11-10 01:41:13 +01:00
|
|
|
|
|
2021-07-08 01:56:06 +02:00
|
|
|
|
long srcX = ((long)_state.State.SetPixelsFromMemorySrcX0Int << 32) | (long)(ulong)_state.State.SetPixelsFromMemorySrcX0Frac;
|
|
|
|
|
long srcY = ((long)_state.State.PixelsFromMemorySrcY0Int << 32) | (long)(ulong)_state.State.SetPixelsFromMemorySrcY0Frac;
|
2020-11-10 01:41:13 +01:00
|
|
|
|
|
2021-07-08 01:56:06 +02:00
|
|
|
|
long duDx = ((long)_state.State.SetPixelsFromMemoryDuDxInt << 32) | (long)(ulong)_state.State.SetPixelsFromMemoryDuDxFrac;
|
|
|
|
|
long dvDy = ((long)_state.State.SetPixelsFromMemoryDvDyInt << 32) | (long)(ulong)_state.State.SetPixelsFromMemoryDvDyFrac;
|
2020-11-10 01:41:13 +01:00
|
|
|
|
|
2021-07-08 01:56:06 +02:00
|
|
|
|
bool originCorner = _state.State.SetPixelsFromMemorySampleModeOrigin == SetPixelsFromMemorySampleModeOrigin.Corner;
|
2021-06-03 01:30:48 +02:00
|
|
|
|
|
|
|
|
|
if (originCorner)
|
|
|
|
|
{
|
|
|
|
|
// If the origin is corner, it is assumed that the guest API
|
|
|
|
|
// is manually centering the origin by adding a offset to the
|
|
|
|
|
// source region X/Y coordinates.
|
|
|
|
|
// Here we attempt to remove such offset to ensure we have the correct region.
|
|
|
|
|
// The offset is calculated as FactorXY / 2.0, where FactorXY = SrcXY / DstXY,
|
|
|
|
|
// so we do the same here by dividing the fixed point value by 2, while
|
|
|
|
|
// throwing away the fractional part to avoid rounding errors.
|
2021-07-08 01:56:06 +02:00
|
|
|
|
srcX -= (duDx >> 33) << 32;
|
|
|
|
|
srcY -= (dvDy >> 33) << 32;
|
2021-06-03 01:30:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int srcX1 = (int)(srcX >> 32);
|
|
|
|
|
int srcY1 = (int)(srcY >> 32);
|
|
|
|
|
|
2021-07-08 01:56:06 +02:00
|
|
|
|
int srcX2 = srcX1 + (int)((duDx * _state.State.SetPixelsFromMemoryDstWidth + uint.MaxValue) >> 32);
|
|
|
|
|
int srcY2 = srcY1 + (int)((dvDy * _state.State.SetPixelsFromMemoryDstHeight + uint.MaxValue) >> 32);
|
2020-11-10 01:41:13 +01:00
|
|
|
|
|
2021-07-08 01:56:06 +02:00
|
|
|
|
int dstX1 = (int)_state.State.SetPixelsFromMemoryDstX0;
|
|
|
|
|
int dstY1 = (int)_state.State.SetPixelsFromMemoryDstY0;
|
2020-11-10 01:41:13 +01:00
|
|
|
|
|
2021-07-08 01:56:06 +02:00
|
|
|
|
int dstX2 = dstX1 + (int)_state.State.SetPixelsFromMemoryDstWidth;
|
|
|
|
|
int dstY2 = dstY1 + (int)_state.State.SetPixelsFromMemoryDstHeight;
|
2020-11-10 01:41:13 +01:00
|
|
|
|
|
|
|
|
|
// The source and destination textures should at least be as big as the region being requested.
|
|
|
|
|
// The hints will only resize within alignment constraints, so out of bound copies won't resize in most cases.
|
|
|
|
|
var srcHint = new Size(srcX2, srcY2, 1);
|
|
|
|
|
var dstHint = new Size(dstX2, dstY2, 1);
|
|
|
|
|
|
2020-11-05 23:50:34 +01:00
|
|
|
|
var srcCopyTextureFormat = srcCopyTexture.Format.Convert();
|
|
|
|
|
|
2021-05-22 01:26:49 +02:00
|
|
|
|
int srcWidthAligned = srcCopyTexture.Stride / srcCopyTextureFormat.BytesPerPixel;
|
|
|
|
|
|
|
|
|
|
ulong offset = 0;
|
|
|
|
|
|
|
|
|
|
// For an out of bounds copy, we must ensure that the copy wraps to the next line,
|
|
|
|
|
// so for a copy from a 64x64 texture, in the region [32, 96[, there are 32 pixels that are
|
|
|
|
|
// outside the bounds of the texture. We fill the destination with the first 32 pixels
|
|
|
|
|
// of the next line on the source texture.
|
|
|
|
|
// This can be done by simply adding an offset to the texture address, so that the initial
|
|
|
|
|
// gap is skipped and the copy is inside bounds again.
|
|
|
|
|
// This is required by the proprietary guest OpenGL driver.
|
|
|
|
|
if (srcCopyTexture.LinearLayout && srcCopyTexture.Width == srcX2 && srcX2 > srcWidthAligned && srcX1 > 0)
|
|
|
|
|
{
|
|
|
|
|
offset = (ulong)(srcX1 * srcCopyTextureFormat.BytesPerPixel);
|
|
|
|
|
srcCopyTexture.Width -= srcX1;
|
|
|
|
|
srcX2 -= srcX1;
|
|
|
|
|
srcX1 = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-22 15:21:29 +01:00
|
|
|
|
FormatInfo dstCopyTextureFormat = dstCopyTexture.Format.Convert();
|
|
|
|
|
|
|
|
|
|
bool canDirectCopy = GraphicsConfig.Fast2DCopy &&
|
|
|
|
|
srcX2 == dstX2 && srcY2 == dstY2 &&
|
|
|
|
|
IsDataCompatible(srcCopyTexture, dstCopyTexture, srcCopyTextureFormat, dstCopyTextureFormat) &&
|
|
|
|
|
IsCopyRegionComplete(srcCopyTexture, srcCopyTextureFormat, srcX1, srcY1, srcX2, srcY2) &&
|
|
|
|
|
IsCopyRegionComplete(dstCopyTexture, dstCopyTextureFormat, dstX1, dstY1, dstX2, dstY2);
|
|
|
|
|
|
2021-07-08 01:56:06 +02:00
|
|
|
|
var srcTexture = memoryManager.Physical.TextureCache.FindOrCreateTexture(
|
2021-06-29 19:32:02 +02:00
|
|
|
|
memoryManager,
|
|
|
|
|
srcCopyTexture,
|
|
|
|
|
offset,
|
|
|
|
|
srcCopyTextureFormat,
|
2022-02-22 15:21:29 +01:00
|
|
|
|
!canDirectCopy,
|
2021-10-12 23:12:17 +02:00
|
|
|
|
false,
|
2021-06-29 19:32:02 +02:00
|
|
|
|
srcHint);
|
2019-10-13 08:02:07 +02:00
|
|
|
|
|
|
|
|
|
if (srcTexture == null)
|
|
|
|
|
{
|
2022-02-22 15:21:29 +01:00
|
|
|
|
if (canDirectCopy)
|
|
|
|
|
{
|
|
|
|
|
// Directly copy the data on CPU.
|
|
|
|
|
UnscaledFullCopy(srcCopyTexture, dstCopyTexture, srcX2, srcY2, srcCopyTextureFormat.BytesPerPixel);
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-13 08:02:07 +02:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
Lift textures in the AutoDeleteCache for all modifications. (#2615)
* Lift textures in the AutoDeleteCache for all modifications.
Before, this would only apply to render targets and texture blit. Now it applies to image stores, the fast dma copy path and any other type of modification.
Image store always at least has one reference in the texture pool, so the function of the AutoDeleteCache keeping textures _alive_ is not useful, but a very important function for a while has been its use to flush textures in order of modification when they are dereferenced, so that their data is not lost.
Before, textures populated using image stores were being dereferenced and reloaded as garbage. Now, when these textures are dereferenced, their data will be put back into memory, and everything stays intact.
Fixes lighting breaking when switching levels in THPS1+2, and potentially some more UE4 games. I've tested a bunch more games for regressions and performance impact, but they all seem fine.
* Lift copy srcTexture so that it doesn't remain referenceless
* Perform lift before reference count change on unbind.
It's important to lift on unbind as that is the moment the texture was truly last modified, but definitely not after releasing every single reference.
2021-09-11 21:52:54 +02:00
|
|
|
|
memoryManager.Physical.TextureCache.Lift(srcTexture);
|
|
|
|
|
|
2019-10-13 08:02:07 +02:00
|
|
|
|
// When the source texture that was found has a depth format,
|
|
|
|
|
// we must enforce the target texture also has a depth format,
|
|
|
|
|
// as copies between depth and color formats are not allowed.
|
2020-11-05 23:50:34 +01:00
|
|
|
|
|
|
|
|
|
if (srcTexture.Format.IsDepthOrStencil())
|
|
|
|
|
{
|
2020-11-16 09:37:16 +01:00
|
|
|
|
dstCopyTextureFormat = srcTexture.Info.FormatInfo;
|
2020-11-05 23:50:34 +01:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
dstCopyTextureFormat = dstCopyTexture.Format.Convert();
|
|
|
|
|
}
|
2019-10-13 08:02:07 +02:00
|
|
|
|
|
2021-07-08 01:56:06 +02:00
|
|
|
|
var dstTexture = memoryManager.Physical.TextureCache.FindOrCreateTexture(
|
2021-06-29 19:32:02 +02:00
|
|
|
|
memoryManager,
|
|
|
|
|
dstCopyTexture,
|
|
|
|
|
0,
|
|
|
|
|
dstCopyTextureFormat,
|
2022-02-22 15:21:29 +01:00
|
|
|
|
true,
|
2021-06-29 19:32:02 +02:00
|
|
|
|
srcTexture.ScaleMode == TextureScaleMode.Scaled,
|
|
|
|
|
dstHint);
|
2019-10-13 08:02:07 +02:00
|
|
|
|
|
|
|
|
|
if (dstTexture == null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-20 21:14:45 +01:00
|
|
|
|
float scale = srcTexture.ScaleFactor;
|
|
|
|
|
float dstScale = dstTexture.ScaleFactor;
|
2020-07-07 04:41:07 +02:00
|
|
|
|
|
2019-10-13 08:02:07 +02:00
|
|
|
|
Extents2D srcRegion = new Extents2D(
|
2020-07-07 04:41:07 +02:00
|
|
|
|
(int)Math.Ceiling(scale * (srcX1 / srcTexture.Info.SamplesInX)),
|
|
|
|
|
(int)Math.Ceiling(scale * (srcY1 / srcTexture.Info.SamplesInY)),
|
|
|
|
|
(int)Math.Ceiling(scale * (srcX2 / srcTexture.Info.SamplesInX)),
|
|
|
|
|
(int)Math.Ceiling(scale * (srcY2 / srcTexture.Info.SamplesInY)));
|
2019-10-13 08:02:07 +02:00
|
|
|
|
|
|
|
|
|
Extents2D dstRegion = new Extents2D(
|
2020-11-20 21:14:45 +01:00
|
|
|
|
(int)Math.Ceiling(dstScale * (dstX1 / dstTexture.Info.SamplesInX)),
|
|
|
|
|
(int)Math.Ceiling(dstScale * (dstY1 / dstTexture.Info.SamplesInY)),
|
|
|
|
|
(int)Math.Ceiling(dstScale * (dstX2 / dstTexture.Info.SamplesInX)),
|
|
|
|
|
(int)Math.Ceiling(dstScale * (dstY2 / dstTexture.Info.SamplesInY)));
|
2019-10-13 08:02:07 +02:00
|
|
|
|
|
2021-07-08 01:56:06 +02:00
|
|
|
|
bool linearFilter = _state.State.SetPixelsFromMemorySampleModeFilter == SetPixelsFromMemorySampleModeFilter.Bilinear;
|
2019-10-13 08:02:07 +02:00
|
|
|
|
|
|
|
|
|
srcTexture.HostTexture.CopyTo(dstTexture.HostTexture, srcRegion, dstRegion, linearFilter);
|
|
|
|
|
|
2020-02-06 22:49:26 +01:00
|
|
|
|
dstTexture.SignalModified();
|
2019-10-13 08:02:07 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2021-07-08 01:56:06 +02:00
|
|
|
|
}
|