Compare commits
7 Commits
Canary-1.2
...
193704a1e5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
193704a1e5 | ||
|
|
0282b216fd | ||
|
|
f2ade5f1f8 | ||
|
|
965fb9dd5f | ||
|
|
e2a5e69f4c | ||
|
|
7f27b791f8 | ||
|
|
3ca8618f5f |
@@ -1436,7 +1436,7 @@
|
||||
010083A018262000,"Hitman: Blood Money — Reprisal",deadlock,ingame,2024-09-28 16:28:50
|
||||
01004B100A5CC000,"Hob: The Definitive Edition",,playable,2021-01-13 09:39:19
|
||||
0100F7300ED2C000,"Hoggy2",,playable,2022-10-10 13:53:35
|
||||
0100F7E00C70E000,"Hogwarts Legacy",UE4;slow,ingame,2024-09-03 19:53:58
|
||||
0100F7E00C70E000,"Hogwarts Legacy",slow,ingame,2024-09-03 19:53:58
|
||||
0100633007D48000,"Hollow Knight",nvdec,playable,2023-01-16 15:44:56
|
||||
0100F2100061E800,"Hollow0",UE4;gpu,ingame,2021-03-03 23:42:56
|
||||
0100342009E16000,"Holy Potatoes! What The Hell?!",,playable,2020-07-03 10:48:56
|
||||
|
||||
|
@@ -1,9 +0,0 @@
|
||||
namespace Ryujinx.Common
|
||||
{
|
||||
public static class SharedConstants
|
||||
{
|
||||
public const string DefaultLanPlayHost = "ryuldn.vudjun.com";
|
||||
public const short LanPlayPort = 30456;
|
||||
public const string DefaultLanPlayWebHost = "ryuldnweb.vudjun.com";
|
||||
}
|
||||
}
|
||||
@@ -98,7 +98,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
|
||||
|
||||
// Make sure all pending uniform buffer data is written to memory.
|
||||
_3dEngine.FlushUboDirty();
|
||||
|
||||
|
||||
uint qmdAddress = _state.State.SendPcasA;
|
||||
|
||||
ComputeQmd qmd = _channel.MemoryManager.Read<ComputeQmd>((ulong)qmdAddress << 8);
|
||||
@@ -106,6 +106,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
|
||||
ulong shaderGpuVa = ((ulong)_state.State.SetProgramRegionAAddressUpper << 32) | _state.State.SetProgramRegionB;
|
||||
|
||||
shaderGpuVa += (uint)qmd.ProgramOffset;
|
||||
|
||||
ShaderCache shaderCache = memoryManager.GetBackingMemory(shaderGpuVa).ShaderCache;
|
||||
|
||||
int localMemorySize = qmd.ShaderLocalMemoryLowSize + qmd.ShaderLocalMemoryHighSize;
|
||||
|
||||
@@ -142,7 +144,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
|
||||
sharedMemorySize,
|
||||
_channel.BufferManager.HasUnalignedStorageBuffers);
|
||||
|
||||
CachedShaderProgram cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, samplerPoolMaximumId, poolState, computeState, shaderGpuVa);
|
||||
CachedShaderProgram cs = shaderCache.GetComputeShader(_channel, samplerPoolMaximumId, poolState, computeState, shaderGpuVa);
|
||||
|
||||
_context.Renderer.Pipeline.SetProgram(cs.HostProgram);
|
||||
|
||||
@@ -156,10 +158,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
|
||||
{
|
||||
BufferDescriptor sb = info.SBuffers[index];
|
||||
|
||||
ulong sbDescAddress = _channel.BufferManager.GetComputeUniformBufferAddress(sb.SbCbSlot);
|
||||
(PhysicalMemory physical, ulong sbDescAddress) = _channel.BufferManager.GetComputeUniformBufferAddress(sb.SbCbSlot);
|
||||
sbDescAddress += (ulong)sb.SbCbOffset * 4;
|
||||
|
||||
SbDescriptor sbDescriptor = _channel.MemoryManager.Physical.Read<SbDescriptor>(sbDescAddress);
|
||||
SbDescriptor sbDescriptor = physical.Read<SbDescriptor>(sbDescAddress);
|
||||
|
||||
uint size;
|
||||
if (sb.SbCbSlot == Constants.DriverReservedUniformBuffer)
|
||||
@@ -187,7 +189,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
|
||||
sharedMemorySize,
|
||||
_channel.BufferManager.HasUnalignedStorageBuffers);
|
||||
|
||||
cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, samplerPoolMaximumId, poolState, computeState, shaderGpuVa);
|
||||
cs = shaderCache.GetComputeShader(_channel, samplerPoolMaximumId, poolState, computeState, shaderGpuVa);
|
||||
|
||||
_context.Renderer.Pipeline.SetProgram(cs.HostProgram);
|
||||
}
|
||||
|
||||
@@ -215,7 +215,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
||||
_channel.TextureManager.RefreshModifiedTextures();
|
||||
_3dEngine.CreatePendingSyncs();
|
||||
_3dEngine.FlushUboDirty();
|
||||
|
||||
|
||||
PhysicalMemory srcPhysical = memoryManager.GetBackingMemory(srcGpuVa);
|
||||
PhysicalMemory dstPhysical = memoryManager.GetBackingMemory(dstGpuVa);
|
||||
|
||||
if (copy2D)
|
||||
{
|
||||
// Buffer to texture copy.
|
||||
@@ -293,7 +296,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
||||
|
||||
if (completeSource && completeDest && !srcLinear && isIdentityRemap)
|
||||
{
|
||||
Image.Texture source = memoryManager.Physical.TextureCache.FindTexture(
|
||||
Image.Texture source = srcPhysical.TextureCache.FindTexture(
|
||||
memoryManager,
|
||||
srcGpuVa,
|
||||
srcBpp,
|
||||
@@ -309,7 +312,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
||||
{
|
||||
source.SynchronizeMemory();
|
||||
|
||||
Image.Texture target = memoryManager.Physical.TextureCache.FindOrCreateTexture(
|
||||
Image.Texture target = dstPhysical.TextureCache.FindOrCreateTexture(
|
||||
memoryManager,
|
||||
source.Info.FormatInfo,
|
||||
dstGpuVa,
|
||||
@@ -339,7 +342,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
||||
|
||||
if (completeSource && completeDest && !(dstLinear && !srcLinear) && isIdentityRemap)
|
||||
{
|
||||
Image.Texture target = memoryManager.Physical.TextureCache.FindTexture(
|
||||
Image.Texture target = dstPhysical.TextureCache.FindTexture(
|
||||
memoryManager,
|
||||
dstGpuVa,
|
||||
dstBpp,
|
||||
@@ -462,6 +465,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
||||
}
|
||||
else
|
||||
{
|
||||
BufferCache bufferCache = dstPhysical.BufferCache;
|
||||
if (remap &&
|
||||
_state.State.SetRemapComponentsDstX == SetRemapComponentsDst.ConstA &&
|
||||
_state.State.SetRemapComponentsDstY == SetRemapComponentsDst.ConstA &&
|
||||
@@ -472,7 +476,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
||||
_state.State.SetRemapComponentsComponentSize == SetRemapComponentsComponentSize.Four)
|
||||
{
|
||||
// Fast path for clears when remap is enabled.
|
||||
memoryManager.Physical.BufferCache.ClearBuffer(memoryManager, dstGpuVa, size * 4, _state.State.SetRemapConstA);
|
||||
bufferCache.ClearBuffer(memoryManager, dstGpuVa, size * 4, _state.State.SetRemapConstA);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -492,7 +496,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
||||
}
|
||||
else
|
||||
{
|
||||
memoryManager.Physical.BufferCache.CopyBuffer(memoryManager, srcGpuVa, dstGpuVa, size);
|
||||
BufferCache.CopyBuffer(_context,memoryManager, srcGpuVa, dstGpuVa, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.InlineToMemory
|
||||
// Right now the copy code at the bottom assumes that it is used on both which might be incorrect.
|
||||
if (!_isLinear)
|
||||
{
|
||||
Image.Texture target = memoryManager.Physical.TextureCache.FindTexture(
|
||||
Image.Texture target = memoryManager.GetBackingMemory(_dstGpuVa).TextureCache.FindTexture(
|
||||
memoryManager,
|
||||
_dstGpuVa,
|
||||
1,
|
||||
|
||||
@@ -384,7 +384,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
|
||||
|
||||
ulong indirectBufferGpuVa = count.GpuVa;
|
||||
|
||||
BufferCache bufferCache = _processor.MemoryManager.Physical.BufferCache;
|
||||
BufferCache bufferCache = _processor.MemoryManager.GetBackingMemory(indirectBufferGpuVa).BufferCache;
|
||||
|
||||
bool useBuffer = bufferCache.CheckModified(_processor.MemoryManager, indirectBufferGpuVa, IndirectIndexedDataEntrySize, out ulong indirectBufferAddress);
|
||||
|
||||
@@ -394,6 +394,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
|
||||
|
||||
_processor.ThreedClass.DrawIndirect(
|
||||
topology,
|
||||
bufferCache,
|
||||
null,
|
||||
new MultiRange(indirectBufferAddress, IndirectIndexedDataEntrySize),
|
||||
default,
|
||||
1,
|
||||
@@ -491,22 +493,24 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BufferCache bufferCache = _processor.MemoryManager.Physical.BufferCache;
|
||||
BufferCache indirectBufferCache = _processor.MemoryManager.GetBackingMemory(indirectBufferGpuVa).BufferCache;
|
||||
BufferCache parameterBufferCache = _processor.MemoryManager.GetBackingMemory(parameterBufferGpuVa).BufferCache;
|
||||
|
||||
ulong indirectBufferSize = (ulong)maxDrawCount * (ulong)stride;
|
||||
|
||||
MultiRange indirectBufferRange = bufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, indirectBufferGpuVa, indirectBufferSize, BufferStage.Indirect);
|
||||
MultiRange parameterBufferRange = bufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, parameterBufferGpuVa, 4, BufferStage.Indirect);
|
||||
MultiRange indirectBufferRange = indirectBufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, indirectBufferGpuVa, indirectBufferSize, BufferStage.Indirect);
|
||||
MultiRange parameterBufferRange = parameterBufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, parameterBufferGpuVa, 4, BufferStage.Indirect);
|
||||
|
||||
_processor.ThreedClass.DrawIndirect(
|
||||
topology,
|
||||
indirectBufferCache,
|
||||
parameterBufferCache,
|
||||
indirectBufferRange,
|
||||
parameterBufferRange,
|
||||
maxDrawCount,
|
||||
stride,
|
||||
indexCount,
|
||||
Threed.IndirectDrawType.DrawIndexedIndirectCount);
|
||||
IndirectDrawType.DrawIndexedIndirectCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -11,6 +11,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||
/// </summary>
|
||||
class VtgAsComputeContext : IDisposable
|
||||
{
|
||||
private const int DummyBufferSize = 16;
|
||||
|
||||
private readonly GpuContext _context;
|
||||
|
||||
/// <summary>
|
||||
@@ -46,7 +48,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
format.GetBytesPerElement(),
|
||||
1,
|
||||
format,
|
||||
DepthStencilMode.Depth,
|
||||
Target.TextureBuffer,
|
||||
@@ -519,6 +521,21 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||
return new BufferRange(_geometryIndexDataBuffer.Handle, offset, size, write);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the range for a dummy 16 bytes buffer, filled with zeros.
|
||||
/// </summary>
|
||||
/// <returns>Dummy buffer range</returns>
|
||||
public BufferRange GetDummyBufferRange()
|
||||
{
|
||||
if (_dummyBuffer == BufferHandle.Null)
|
||||
{
|
||||
_dummyBuffer = _context.Renderer.CreateBuffer(DummyBufferSize, BufferAccess.DeviceMemory);
|
||||
_context.Renderer.Pipeline.ClearBuffer(_dummyBuffer, 0, DummyBufferSize, 0);
|
||||
}
|
||||
|
||||
return new BufferRange(_dummyBuffer, 0, DummyBufferSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the range for a sequential index buffer, with ever incrementing index values.
|
||||
/// </summary>
|
||||
|
||||
@@ -147,6 +147,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||
{
|
||||
_vacContext.VertexInfoBufferUpdater.SetVertexStride(index, 0, componentsCount);
|
||||
_vacContext.VertexInfoBufferUpdater.SetVertexOffset(index, 0, 0);
|
||||
SetDummyBufferTexture(_vertexAsCompute.Reservations, index, format);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -162,12 +163,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||
{
|
||||
_vacContext.VertexInfoBufferUpdater.SetVertexStride(index, 0, componentsCount);
|
||||
_vacContext.VertexInfoBufferUpdater.SetVertexOffset(index, 0, 0);
|
||||
SetDummyBufferTexture(_vertexAsCompute.Reservations, index, format);
|
||||
continue;
|
||||
}
|
||||
|
||||
int vbStride = vertexBuffer.UnpackStride();
|
||||
ulong vbSize = GetVertexBufferSize(address, endAddress.Pack(), vbStride, _indexed, instanced, _firstVertex, _count);
|
||||
|
||||
ulong oldVbSize = vbSize;
|
||||
|
||||
ulong attributeOffset = (ulong)vertexAttrib.UnpackOffset();
|
||||
int componentSize = format.GetScalarSize();
|
||||
|
||||
@@ -196,11 +200,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||
|
||||
int vertexInfoBinding = _vertexAsCompute.Reservations.VertexInfoConstantBufferBinding;
|
||||
BufferRange vertexInfoRange = new(_vacContext.VertexInfoBufferUpdater.Handle, 0, VertexInfoBuffer.RequiredSize);
|
||||
_context.Renderer.Pipeline.SetUniformBuffers([new BufferAssignment(vertexInfoBinding, vertexInfoRange)]);
|
||||
_context.Renderer.Pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(vertexInfoBinding, vertexInfoRange) });
|
||||
|
||||
int vertexDataBinding = _vertexAsCompute.Reservations.VertexOutputStorageBufferBinding;
|
||||
BufferRange vertexDataRange = _vacContext.GetVertexDataBufferRange(_vertexDataOffset, _vertexDataSize, write: true);
|
||||
_context.Renderer.Pipeline.SetStorageBuffers([new BufferAssignment(vertexDataBinding, vertexDataRange)]);
|
||||
_context.Renderer.Pipeline.SetStorageBuffers(stackalloc[] { new BufferAssignment(vertexDataBinding, vertexDataRange) });
|
||||
|
||||
_vacContext.VertexInfoBufferUpdater.Commit();
|
||||
|
||||
@@ -228,7 +232,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||
|
||||
int vertexInfoBinding = _vertexAsCompute.Reservations.VertexInfoConstantBufferBinding;
|
||||
BufferRange vertexInfoRange = new(_vacContext.VertexInfoBufferUpdater.Handle, 0, VertexInfoBuffer.RequiredSize);
|
||||
_context.Renderer.Pipeline.SetUniformBuffers([new BufferAssignment(vertexInfoBinding, vertexInfoRange)]);
|
||||
_context.Renderer.Pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(vertexInfoBinding, vertexInfoRange) });
|
||||
|
||||
int vertexDataBinding = _vertexAsCompute.Reservations.VertexOutputStorageBufferBinding;
|
||||
|
||||
@@ -246,11 +250,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||
BufferRange vertexBuffer = _vacContext.GetGeometryVertexDataBufferRange(_geometryVertexDataOffset, _geometryVertexDataSize, write: true);
|
||||
BufferRange indexBuffer = _vacContext.GetGeometryIndexDataBufferRange(_geometryIndexDataOffset, _geometryIndexDataSize, write: true);
|
||||
|
||||
_context.Renderer.Pipeline.SetStorageBuffers([
|
||||
_context.Renderer.Pipeline.SetStorageBuffers(stackalloc[]
|
||||
{
|
||||
new BufferAssignment(vertexDataBinding, vertexDataRange),
|
||||
new BufferAssignment(geometryVbBinding, vertexBuffer),
|
||||
new BufferAssignment(geometryIbBinding, indexBuffer)
|
||||
]);
|
||||
new BufferAssignment(geometryIbBinding, indexBuffer),
|
||||
});
|
||||
|
||||
_context.Renderer.Pipeline.DispatchCompute(
|
||||
BitUtils.DivRoundUp(primitivesCount, ComputeLocalSize),
|
||||
@@ -294,7 +299,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||
|
||||
_context.Renderer.Pipeline.SetProgram(_vertexPassthroughProgram);
|
||||
_context.Renderer.Pipeline.SetIndexBuffer(indexBuffer, IndexType.UInt);
|
||||
_context.Renderer.Pipeline.SetStorageBuffers([new BufferAssignment(vertexDataBinding, vertexBuffer)]);
|
||||
_context.Renderer.Pipeline.SetStorageBuffers(stackalloc[] { new BufferAssignment(vertexDataBinding, vertexBuffer) });
|
||||
|
||||
_context.Renderer.Pipeline.SetPrimitiveRestart(true, -1);
|
||||
_context.Renderer.Pipeline.SetPrimitiveTopology(GetGeometryOutputTopology(_geometryAsCompute.Info.GeometryVerticesPerPrimitive));
|
||||
@@ -309,7 +314,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||
BufferRange vertexDataRange = _vacContext.GetVertexDataBufferRange(_vertexDataOffset, _vertexDataSize, write: false);
|
||||
|
||||
_context.Renderer.Pipeline.SetProgram(_vertexPassthroughProgram);
|
||||
_context.Renderer.Pipeline.SetStorageBuffers([new BufferAssignment(vertexDataBinding, vertexDataRange)]);
|
||||
_context.Renderer.Pipeline.SetStorageBuffers(stackalloc[] { new BufferAssignment(vertexDataBinding, vertexDataRange) });
|
||||
_context.Renderer.Pipeline.Draw(_count, _instanceCount, 0, 0);
|
||||
}
|
||||
}
|
||||
@@ -340,6 +345,20 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||
return maxOutputVertices / verticesPerPrimitive;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds a dummy buffer as vertex buffer into a buffer texture.
|
||||
/// </summary>
|
||||
/// <param name="reservations">Shader resource binding reservations</param>
|
||||
/// <param name="index">Buffer texture index</param>
|
||||
/// <param name="format">Buffer texture format</param>
|
||||
private readonly void SetDummyBufferTexture(ResourceReservations reservations, int index, Format format)
|
||||
{
|
||||
ITexture bufferTexture = _vacContext.EnsureBufferTexture(index + 2, format);
|
||||
bufferTexture.SetStorage(_vacContext.GetDummyBufferRange());
|
||||
|
||||
_context.Renderer.Pipeline.SetTextureAndSampler(ShaderStage.Compute, reservations.GetVertexBufferTextureBinding(index), bufferTexture, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds a vertex buffer into a buffer texture.
|
||||
/// </summary>
|
||||
@@ -352,7 +371,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||
{
|
||||
MemoryManager memoryManager = _channel.MemoryManager;
|
||||
|
||||
BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange(memoryManager.GetPhysicalRegions(address, size), BufferStage.VertexBuffer);
|
||||
BufferRange range = memoryManager.GetBackingMemory(address).BufferCache.GetBufferRange(memoryManager.GetPhysicalRegions(address, size), BufferStage.VertexBuffer);
|
||||
|
||||
ITexture bufferTexture = _vacContext.EnsureBufferTexture(index + 2, format);
|
||||
bufferTexture.SetStorage(range);
|
||||
@@ -394,7 +413,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||
MemoryManager memoryManager = _channel.MemoryManager;
|
||||
|
||||
ulong misalign = address & ((ulong)_context.Capabilities.TextureBufferOffsetAlignment - 1);
|
||||
BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange(
|
||||
BufferRange range = memoryManager.GetBackingMemory(address).BufferCache.GetBufferRange(
|
||||
memoryManager.GetPhysicalRegions(address + indexOffset - misalign, size + misalign),
|
||||
BufferStage.IndexBuffer);
|
||||
misalignedOffset = (int)misalign >> shift;
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
// State associated with direct uniform buffer updates.
|
||||
// This state is used to attempt to batch together consecutive updates.
|
||||
private ulong _ubBeginGpuAddress = 0;
|
||||
private ulong _ubBeginCpuAddress = 0;
|
||||
private ulong _ubFollowUpAddress = 0;
|
||||
private ulong _ubByteCount = 0;
|
||||
@@ -113,12 +114,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
if (_ubFollowUpAddress != 0)
|
||||
{
|
||||
MemoryManager memoryManager = _channel.MemoryManager;
|
||||
PhysicalMemory physicalMemory = memoryManager.GetBackingMemory(_ubBeginGpuAddress);
|
||||
|
||||
Span<byte> data = MemoryMarshal.Cast<int, byte>(_ubData.AsSpan(0, (int)(_ubByteCount / 4)));
|
||||
|
||||
if (memoryManager.Physical.WriteWithRedundancyCheck(_ubBeginCpuAddress, data))
|
||||
if (physicalMemory.WriteWithRedundancyCheck(_ubBeginCpuAddress, data))
|
||||
{
|
||||
memoryManager.Physical.BufferCache.ForceDirty(memoryManager, _ubFollowUpAddress - _ubByteCount, _ubByteCount);
|
||||
physicalMemory.BufferCache.ForceDirty(memoryManager, _ubFollowUpAddress - _ubByteCount, _ubByteCount);
|
||||
}
|
||||
|
||||
_ubFollowUpAddress = 0;
|
||||
|
||||
@@ -641,6 +641,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
public void DrawIndirect(
|
||||
ThreedClass engine,
|
||||
PrimitiveTopology topology,
|
||||
BufferCache indirectBufferCache,
|
||||
BufferCache parameterBufferCache,
|
||||
MultiRange indirectBufferRange,
|
||||
MultiRange parameterBufferRange,
|
||||
int maxDrawCount,
|
||||
@@ -662,8 +664,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
return;
|
||||
}
|
||||
|
||||
PhysicalMemory memory = _channel.MemoryManager.Physical;
|
||||
|
||||
bool hasCount = (drawType & IndirectDrawType.Count) != 0;
|
||||
bool indexed = (drawType & IndirectDrawType.Indexed) != 0;
|
||||
|
||||
@@ -684,8 +684,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
if (hasCount)
|
||||
{
|
||||
BufferRange indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferRange, BufferStage.Indirect);
|
||||
BufferRange parameterBuffer = memory.BufferCache.GetBufferRange(parameterBufferRange, BufferStage.Indirect);
|
||||
BufferRange indirectBuffer = indirectBufferCache.GetBufferRange(indirectBufferRange, BufferStage.Indirect);
|
||||
BufferRange parameterBuffer = parameterBufferCache.GetBufferRange(parameterBufferRange, BufferStage.Indirect);
|
||||
|
||||
if (indexed)
|
||||
{
|
||||
@@ -698,7 +698,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
}
|
||||
else
|
||||
{
|
||||
BufferRange indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferRange, BufferStage.Indirect);
|
||||
BufferRange indirectBuffer = indirectBufferCache.GetBufferRange(indirectBufferRange, BufferStage.Indirect);
|
||||
|
||||
if (indexed)
|
||||
{
|
||||
|
||||
@@ -381,10 +381,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
BufferDescriptor sb = info.SBuffers[index];
|
||||
|
||||
ulong sbDescAddress = _channel.BufferManager.GetGraphicsUniformBufferAddress(stage, sb.SbCbSlot);
|
||||
(PhysicalMemory physical, ulong sbDescAddress) = _channel.BufferManager.GetGraphicsUniformBufferAddress(stage, sb.SbCbSlot);
|
||||
sbDescAddress += (ulong)sb.SbCbOffset * 4;
|
||||
|
||||
SbDescriptor sbDescriptor = _channel.MemoryManager.Physical.Read<SbDescriptor>(sbDescAddress);
|
||||
SbDescriptor sbDescriptor = physical.Read<SbDescriptor>(sbDescAddress);
|
||||
|
||||
uint size;
|
||||
if (sb.SbCbSlot == Constants.DriverReservedUniformBuffer)
|
||||
@@ -505,7 +505,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
rtNoAlphaMask |= 1u << index;
|
||||
}
|
||||
|
||||
Image.Texture color = memoryManager.Physical.TextureCache.FindOrCreateTexture(
|
||||
TextureCache colorTextureCache = memoryManager.GetBackingMemory(colorState.Address.Pack()).TextureCache;
|
||||
|
||||
Image.Texture color = colorTextureCache.FindOrCreateTexture(
|
||||
memoryManager,
|
||||
colorState,
|
||||
_vtgWritesRtLayer || layered,
|
||||
@@ -513,7 +515,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
samplesInX,
|
||||
samplesInY,
|
||||
sizeHint);
|
||||
|
||||
|
||||
changedScale |= _channel.TextureManager.SetRenderTargetColor(index, color);
|
||||
|
||||
if (color != null)
|
||||
@@ -543,8 +545,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
RtDepthStencilState dsState = _state.State.RtDepthStencilState;
|
||||
Size3D dsSize = _state.State.RtDepthStencilSize;
|
||||
|
||||
depthStencil = memoryManager.Physical.TextureCache.FindOrCreateTexture(
|
||||
TextureCache dsTextureCache = memoryManager.GetBackingMemory(dsState.Address.Pack()).TextureCache;
|
||||
|
||||
depthStencil = dsTextureCache.FindOrCreateTexture(
|
||||
memoryManager,
|
||||
dsState,
|
||||
dsSize,
|
||||
@@ -1409,8 +1412,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// </summary>
|
||||
private void UpdateShaderState()
|
||||
{
|
||||
ShaderCache shaderCache = _channel.MemoryManager.Physical.ShaderCache;
|
||||
|
||||
_vtgWritesRtLayer = false;
|
||||
|
||||
ShaderAddresses addresses = new();
|
||||
@@ -1433,6 +1434,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
? _state.State.TexturePoolState.MaximumId
|
||||
: _state.State.SamplerPoolState.MaximumId;
|
||||
|
||||
// Shader stages on different address spaces are not supported right now,
|
||||
// but it should never happen in practice anyway.
|
||||
ShaderCache shaderCache = _channel.MemoryManager.GetBackingMemory(addresses.VertexB).ShaderCache;
|
||||
CachedShaderProgram gs = shaderCache.GetGraphicsShader(
|
||||
ref _state.State,
|
||||
ref _pipeline,
|
||||
|
||||
@@ -5,6 +5,7 @@ using Ryujinx.Graphics.Gpu.Engine.GPFifo;
|
||||
using Ryujinx.Graphics.Gpu.Engine.InlineToMemory;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Threed.Blender;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Gpu.Synchronization;
|
||||
using Ryujinx.Memory.Range;
|
||||
using System;
|
||||
@@ -804,6 +805,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// Performs a indirect draw, with parameters from a GPU buffer.
|
||||
/// </summary>
|
||||
/// <param name="topology">Primitive topology</param>
|
||||
/// <param name="indirectBufferCache">Buffer cache owning the buffer with the draw parameters</param>
|
||||
/// <param name="parameterBufferCache">Buffer cache owning the buffer with the draw count</param>
|
||||
/// <param name="indirectBufferRange">Memory range of the buffer with the draw parameters, such as count, first index, etc</param>
|
||||
/// <param name="parameterBufferRange">Memory range of the buffer with the draw count</param>
|
||||
/// <param name="maxDrawCount">Maximum number of draws that can be made</param>
|
||||
@@ -812,6 +815,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// <param name="drawType">Type of the indirect draw, which can be indexed or non-indexed, with or without a draw count</param>
|
||||
public void DrawIndirect(
|
||||
PrimitiveTopology topology,
|
||||
BufferCache indirectBufferCache,
|
||||
BufferCache parameterBufferCache,
|
||||
MultiRange indirectBufferRange,
|
||||
MultiRange parameterBufferRange,
|
||||
int maxDrawCount,
|
||||
@@ -819,7 +824,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
int indexCount,
|
||||
IndirectDrawType drawType)
|
||||
{
|
||||
_drawManager.DrawIndirect(this, topology, indirectBufferRange, parameterBufferRange, maxDrawCount, stride, indexCount, drawType);
|
||||
_drawManager.DrawIndirect(this, topology, indirectBufferCache, parameterBufferCache, indirectBufferRange, parameterBufferRange, maxDrawCount, stride, indexCount, drawType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -233,6 +233,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod
|
||||
|
||||
TwodTexture dstCopyTexture = Unsafe.As<uint, TwodTexture>(ref _state.State.SetDstFormat);
|
||||
TwodTexture srcCopyTexture = Unsafe.As<uint, TwodTexture>(ref _state.State.SetSrcFormat);
|
||||
|
||||
TextureCache srcTextureCache = memoryManager.GetBackingMemory(srcCopyTexture.Address.Pack()).TextureCache;
|
||||
TextureCache dstTextureCache = memoryManager.GetBackingMemory(dstCopyTexture.Address.Pack()).TextureCache;
|
||||
|
||||
long srcX = ((long)_state.State.SetPixelsFromMemorySrcX0Int << 32) | (long)(ulong)_state.State.SetPixelsFromMemorySrcX0Frac;
|
||||
long srcY = ((long)_state.State.PixelsFromMemorySrcY0Int << 32) | (long)(ulong)_state.State.SetPixelsFromMemorySrcY0Frac;
|
||||
@@ -305,7 +308,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod
|
||||
// are the same, as we can't blit between different depth formats.
|
||||
bool srcDepthAlias = srcCopyTexture.Format == dstCopyTexture.Format;
|
||||
|
||||
Image.Texture srcTexture = memoryManager.Physical.TextureCache.FindOrCreateTexture(
|
||||
Image.Texture srcTexture = srcTextureCache.FindOrCreateTexture(
|
||||
memoryManager,
|
||||
srcCopyTexture,
|
||||
offset,
|
||||
@@ -326,7 +329,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod
|
||||
return;
|
||||
}
|
||||
|
||||
memoryManager.Physical.TextureCache.Lift(srcTexture);
|
||||
srcTextureCache.Lift(srcTexture);
|
||||
|
||||
// When the source texture that was found has a depth format,
|
||||
// we must enforce the target texture also has a depth format,
|
||||
@@ -342,7 +345,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod
|
||||
dstCopyTextureFormat = dstCopyTexture.Format.Convert();
|
||||
}
|
||||
|
||||
Image.Texture dstTexture = memoryManager.Physical.TextureCache.FindOrCreateTexture(
|
||||
Image.Texture dstTexture = dstTextureCache.FindOrCreateTexture(
|
||||
memoryManager,
|
||||
dstCopyTexture,
|
||||
0,
|
||||
|
||||
@@ -58,22 +58,24 @@ namespace Ryujinx.Graphics.Gpu
|
||||
public void BindMemory(MemoryManager memoryManager)
|
||||
{
|
||||
MemoryManager oldMemoryManager = Interlocked.Exchange(ref _memoryManager, memoryManager ?? throw new ArgumentNullException(nameof(memoryManager)));
|
||||
if (oldMemoryManager == memoryManager)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
memoryManager.Physical.IncrementReferenceCount();
|
||||
memoryManager.AttachToChannel(BufferManager.Rebind);
|
||||
|
||||
if (oldMemoryManager != null)
|
||||
{
|
||||
oldMemoryManager.Physical.BufferCache.NotifyBuffersModified -= BufferManager.Rebind;
|
||||
oldMemoryManager.Physical.DecrementReferenceCount();
|
||||
oldMemoryManager.DetachFromChannel(BufferManager.Rebind);
|
||||
oldMemoryManager.MemoryUnmapped -= MemoryUnmappedHandler;
|
||||
}
|
||||
|
||||
memoryManager.Physical.BufferCache.NotifyBuffersModified += BufferManager.Rebind;
|
||||
memoryManager.MemoryUnmapped += MemoryUnmappedHandler;
|
||||
|
||||
// Since the memory manager changed, make sure we will get pools from addresses of the new memory manager.
|
||||
TextureManager.ReloadPools();
|
||||
memoryManager.Physical.BufferCache.QueuePrune();
|
||||
memoryManager.QueuePrune();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -86,7 +88,7 @@ namespace Ryujinx.Graphics.Gpu
|
||||
TextureManager.ReloadPools();
|
||||
|
||||
MemoryManager memoryManager = Volatile.Read(ref _memoryManager);
|
||||
memoryManager?.Physical.BufferCache.QueuePrune();
|
||||
memoryManager?.QueuePrune();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -141,8 +143,7 @@ namespace Ryujinx.Graphics.Gpu
|
||||
MemoryManager oldMemoryManager = Interlocked.Exchange(ref _memoryManager, null);
|
||||
if (oldMemoryManager != null)
|
||||
{
|
||||
oldMemoryManager.Physical.BufferCache.NotifyBuffersModified -= BufferManager.Rebind;
|
||||
oldMemoryManager.Physical.DecrementReferenceCount();
|
||||
oldMemoryManager.DetachFromChannel(BufferManager.Rebind);
|
||||
oldMemoryManager.MemoryUnmapped -= MemoryUnmappedHandler;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using Ryujinx.Graphics.Gpu.Engine.GPFifo;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Gpu.Shader;
|
||||
using Ryujinx.Graphics.Gpu.Synchronization;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
@@ -172,7 +173,7 @@ namespace Ryujinx.Graphics.Gpu
|
||||
throw new ArgumentException("The PID is invalid or the process was not registered", nameof(pid));
|
||||
}
|
||||
|
||||
return new MemoryManager(physicalMemory, cpuMemorySize);
|
||||
return new MemoryManager(this, physicalMemory, cpuMemorySize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -197,7 +198,7 @@ namespace Ryujinx.Graphics.Gpu
|
||||
/// <param name="pid">ID of the process that owns <paramref name="cpuMemory"/></param>
|
||||
/// <param name="cpuMemory">Virtual memory owned by the process</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="pid"/> was already registered</exception>
|
||||
public void RegisterProcess(ulong pid, Cpu.IVirtualMemoryManagerTracked cpuMemory)
|
||||
public void RegisterProcess(ulong pid, IVirtualMemoryManagerTracked cpuMemory)
|
||||
{
|
||||
PhysicalMemory physicalMemory = new(this, cpuMemory);
|
||||
if (!PhysicalMemoryRegistry.TryAdd(pid, physicalMemory))
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
@@ -64,7 +65,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <param name="maximumId">Maximum ID of the texture pool</param>
|
||||
/// <param name="bindingsArrayCache">Cache of texture array bindings</param>
|
||||
/// <returns>The found or newly created texture pool</returns>
|
||||
public T FindOrCreate(GpuChannel channel, ulong address, int maximumId, TextureBindingsArrayCache bindingsArrayCache)
|
||||
public T FindOrCreate(GpuChannel channel, PhysicalMemory physicalMemory, ulong address, int maximumId, TextureBindingsArrayCache bindingsArrayCache)
|
||||
{
|
||||
// Remove old entries from the cache, if possible.
|
||||
while (_pools.Count > MaxCapacity && (_currentTimestamp - _pools.First.Value.CacheTimestamp) >= MinDeltaForRemoval)
|
||||
@@ -99,7 +100,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
|
||||
// If not found, create a new one.
|
||||
pool = CreatePool(_context, channel, address, maximumId);
|
||||
pool = CreatePool(_context, channel, physicalMemory, address, maximumId);
|
||||
|
||||
pool.CacheNode = _pools.AddLast(pool);
|
||||
pool.CacheTimestamp = _currentTimestamp;
|
||||
@@ -112,9 +113,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context that the pool belongs to</param>
|
||||
/// <param name="channel">GPU channel that the pool belongs to</param>
|
||||
/// <param name="physicalMemory">GPU backing memory of the pool</param>
|
||||
/// <param name="address">Address of the pool in guest memory</param>
|
||||
/// <param name="maximumId">Maximum ID of the pool (equal to maximum minus one)</param>
|
||||
protected abstract T CreatePool(GpuContext context, GpuChannel channel, ulong address, int maximumId);
|
||||
protected abstract T CreatePool(GpuContext context, GpuChannel channel, PhysicalMemory physicalMemory, ulong address, int maximumId);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
@@ -20,11 +22,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context that the sampler pool belongs to</param>
|
||||
/// <param name="channel">GPU channel that the texture pool belongs to</param>
|
||||
/// <param name="physicalMemory">GPU backing memory of the pool</param>
|
||||
/// <param name="address">Address of the sampler pool in guest memory</param>
|
||||
/// <param name="maximumId">Maximum sampler ID of the sampler pool (equal to maximum samplers minus one)</param>
|
||||
protected override SamplerPool CreatePool(GpuContext context, GpuChannel channel, ulong address, int maximumId)
|
||||
protected override SamplerPool CreatePool(GpuContext context, GpuChannel channel, PhysicalMemory physicalMemory, ulong address, int maximumId)
|
||||
{
|
||||
return new SamplerPool(context, channel.MemoryManager.Physical, address, maximumId);
|
||||
return new SamplerPool(context, physicalMemory, address, maximumId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -660,6 +660,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
ISampler[] samplers = isImage ? null : new ISampler[bindingInfo.ArrayLength];
|
||||
ITexture[] textures = new ITexture[bindingInfo.ArrayLength];
|
||||
BufferCache bufferCache = null;
|
||||
|
||||
for (int index = 0; index < length; index++)
|
||||
{
|
||||
@@ -673,7 +674,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
else
|
||||
{
|
||||
ref readonly TextureDescriptor descriptor = ref texturePool.GetForBinding(index, bindingInfo.FormatInfo, out texture);
|
||||
|
||||
bufferCache = _channel.MemoryManager.GetBackingMemory(descriptor.UnpackAddress()).BufferCache;
|
||||
if (texture != null)
|
||||
{
|
||||
entry.Textures[texture] = texture.InvalidatedSequence;
|
||||
@@ -702,11 +703,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
// to ensure we're not using a old buffer that was already deleted.
|
||||
if (isImage)
|
||||
{
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, entry.ImageArray, hostTexture, texture.Range, bindingInfo, index);
|
||||
}
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, entry.ImageArray, hostTexture, bufferCache, texture.Range, bindingInfo, index); }
|
||||
else
|
||||
{
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, texture.Range, bindingInfo, index);
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, bufferCache, texture.Range, bindingInfo, index);
|
||||
}
|
||||
}
|
||||
else if (isImage)
|
||||
@@ -797,11 +797,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return;
|
||||
}
|
||||
|
||||
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(textureBufferBounds.Range));
|
||||
|
||||
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(textureBufferBounds.Physical.GetSpan(textureBufferBounds.Range));
|
||||
|
||||
if (separateSamplerBuffer)
|
||||
{
|
||||
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(samplerBufferBounds.Range));
|
||||
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(samplerBufferBounds.Physical.GetSpan(samplerBufferBounds.Range));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -828,11 +828,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
else
|
||||
{
|
||||
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(textureBufferBounds.Range));
|
||||
|
||||
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(textureBufferBounds.Physical.GetSpan(textureBufferBounds.Range));
|
||||
if (separateSamplerBuffer)
|
||||
{
|
||||
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(samplerBufferBounds.Range));
|
||||
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(samplerBufferBounds.Physical.GetSpan(samplerBufferBounds.Range));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -901,16 +900,18 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
if (hostTexture != null && texture.Target == Target.TextureBuffer)
|
||||
{
|
||||
BufferCache bufferCache = textureBufferBounds.BufferCache;
|
||||
|
||||
// Ensure that the buffer texture is using the correct buffer as storage.
|
||||
// Buffers are frequently re-created to accommodate larger data, so we need to re-bind
|
||||
// to ensure we're not using a old buffer that was already deleted.
|
||||
if (isImage)
|
||||
{
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, entry.ImageArray, hostTexture, texture.Range, bindingInfo, index);
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, entry.ImageArray, hostTexture, bufferCache, texture.Range, bindingInfo, index);
|
||||
}
|
||||
else
|
||||
{
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, texture.Range, bindingInfo, index);
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, bufferCache, texture.Range, bindingInfo, index);
|
||||
}
|
||||
}
|
||||
else if (isImage)
|
||||
|
||||
@@ -396,7 +396,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
ref BufferBounds bounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, textureBufferIndex);
|
||||
|
||||
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(bounds.Range));
|
||||
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(bounds.Physical.GetSpan(bounds.Range));
|
||||
cachedTextureBufferIndex = textureBufferIndex;
|
||||
|
||||
if (samplerBufferIndex == textureBufferIndex)
|
||||
@@ -410,7 +410,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
ref BufferBounds bounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, samplerBufferIndex);
|
||||
|
||||
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(bounds.Range));
|
||||
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(bounds.Physical.GetSpan(bounds.Range));
|
||||
cachedSamplerBufferIndex = samplerBufferIndex;
|
||||
}
|
||||
}
|
||||
@@ -524,7 +524,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
// Ensure that the buffer texture is using the correct buffer as storage.
|
||||
// Buffers are frequently re-created to accommodate larger data, so we need to re-bind
|
||||
// to ensure we're not using a old buffer that was already deleted.
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range, bindingInfo, false);
|
||||
BufferCache bufferCache = _channel.MemoryManager.GetBackingMemory(descriptor.UnpackAddress()).BufferCache;
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, bufferCache, texture.Range, bindingInfo, false);
|
||||
|
||||
// Cache is not used for buffer texture, it must always rebind.
|
||||
state.CachedTexture = null;
|
||||
@@ -659,7 +660,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
// Buffers are frequently re-created to accommodate larger data, so we need to re-bind
|
||||
// to ensure we're not using a old buffer that was already deleted.
|
||||
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range, bindingInfo, true);
|
||||
BufferCache bufferCache = _channel.MemoryManager.GetBackingMemory(descriptor.UnpackAddress()).BufferCache;
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, bufferCache, texture.Range, bindingInfo, true);
|
||||
|
||||
// Cache is not used for buffer texture, it must always rebind.
|
||||
state.CachedTexture = null;
|
||||
@@ -715,9 +717,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
int packedId = ReadPackedId(stageIndex, handle, textureBufferIndex, samplerBufferIndex);
|
||||
int textureId = TextureHandle.UnpackTextureId(packedId);
|
||||
|
||||
PhysicalMemory physical = _channel.MemoryManager.GetBackingMemory(poolGpuVa);
|
||||
ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa);
|
||||
|
||||
TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId, _bindingsArrayCache);
|
||||
TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, physical, poolAddress, maximumId, _bindingsArrayCache);
|
||||
|
||||
TextureDescriptor descriptor;
|
||||
|
||||
@@ -751,12 +754,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
(int textureWordOffset, int samplerWordOffset, TextureHandleType handleType) = TextureHandle.UnpackOffsets(wordOffset);
|
||||
|
||||
ulong textureBufferAddress = _isCompute
|
||||
(PhysicalMemory texturePhysicalMemory, ulong textureBufferAddress) = _isCompute
|
||||
? _channel.BufferManager.GetComputeUniformBufferAddress(textureBufferIndex)
|
||||
: _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, textureBufferIndex);
|
||||
|
||||
int handle = textureBufferAddress != MemoryManager.PteUnmapped
|
||||
? _channel.MemoryManager.Physical.Read<int>(textureBufferAddress + (uint)textureWordOffset * 4)
|
||||
? texturePhysicalMemory.Read<int>(textureBufferAddress + (uint)textureWordOffset * 4)
|
||||
: 0;
|
||||
|
||||
// The "wordOffset" (which is really the immediate value used on texture instructions on the shader)
|
||||
@@ -771,12 +774,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
if (handleType != TextureHandleType.SeparateConstantSamplerHandle)
|
||||
{
|
||||
ulong samplerBufferAddress = _isCompute
|
||||
(PhysicalMemory samplerPhysicalMemory, ulong samplerBufferAddress) = _isCompute
|
||||
? _channel.BufferManager.GetComputeUniformBufferAddress(samplerBufferIndex)
|
||||
: _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, samplerBufferIndex);
|
||||
|
||||
samplerHandle = samplerBufferAddress != MemoryManager.PteUnmapped
|
||||
? _channel.MemoryManager.Physical.Read<int>(samplerBufferAddress + (uint)samplerWordOffset * 4)
|
||||
? samplerPhysicalMemory.Read<int>(samplerBufferAddress + (uint)samplerWordOffset * 4)
|
||||
: 0;
|
||||
}
|
||||
else
|
||||
@@ -813,7 +816,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
if (poolAddress != MemoryManager.PteUnmapped)
|
||||
{
|
||||
texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, _texturePoolMaximumId, _bindingsArrayCache);
|
||||
PhysicalMemory physical = _channel.MemoryManager.GetBackingMemory(_texturePoolGpuVa);
|
||||
texturePool = _texturePoolCache.FindOrCreate(_channel, physical, poolAddress, _texturePoolMaximumId, _bindingsArrayCache);
|
||||
_texturePool = texturePool;
|
||||
}
|
||||
}
|
||||
@@ -824,7 +828,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
if (poolAddress != MemoryManager.PteUnmapped)
|
||||
{
|
||||
samplerPool = _samplerPoolCache.FindOrCreate(_channel, poolAddress, _samplerPoolMaximumId, _bindingsArrayCache);
|
||||
PhysicalMemory physical = _channel.MemoryManager.GetBackingMemory(_samplerPoolGpuVa);
|
||||
samplerPool = _samplerPoolCache.FindOrCreate(_channel, physical, poolAddress, _samplerPoolMaximumId, _bindingsArrayCache);
|
||||
_samplerPool = samplerPool;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Gpu.Shader;
|
||||
using System;
|
||||
|
||||
@@ -385,8 +386,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
public TexturePool GetTexturePool(ulong poolGpuVa, int maximumId)
|
||||
{
|
||||
ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa);
|
||||
PhysicalMemory physical = _channel.MemoryManager.GetBackingMemory(poolAddress);
|
||||
|
||||
TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId, _bindingsArrayCache);
|
||||
TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, physical, poolAddress, maximumId, _bindingsArrayCache);
|
||||
|
||||
return texturePool;
|
||||
}
|
||||
|
||||
@@ -160,9 +160,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context that the texture pool belongs to</param>
|
||||
/// <param name="channel">GPU channel that the texture pool belongs to</param>
|
||||
/// <param name="physicalMemory">Backing memory of the pool</param>
|
||||
/// <param name="address">Address of the texture pool in guest memory</param>
|
||||
/// <param name="maximumId">Maximum texture ID of the texture pool (equal to maximum textures minus one)</param>
|
||||
public TexturePool(GpuContext context, GpuChannel channel, ulong address, int maximumId) : base(context, channel.MemoryManager.Physical, address, maximumId)
|
||||
public TexturePool(GpuContext context, GpuChannel channel, PhysicalMemory physicalMemory, ulong address, int maximumId) : base(context, physicalMemory, address, maximumId)
|
||||
{
|
||||
_channel = channel;
|
||||
_aliasLists = new Dictionary<Texture, TextureAliasList>();
|
||||
@@ -193,7 +194,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
|
||||
TextureInfo info = GetInfo(descriptor, out int layerSize);
|
||||
texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize);
|
||||
MemoryManager memoryManager = _channel.MemoryManager;
|
||||
TextureCache textureCache = memoryManager.GetBackingMemory(descriptor.UnpackAddress()).TextureCache;
|
||||
texture = textureCache.FindOrCreateTexture(memoryManager, TextureSearchFlags.ForSampler, info, layerSize);
|
||||
|
||||
// If this happens, then the texture address is invalid, we can't add it to the cache.
|
||||
if (texture == null)
|
||||
@@ -421,7 +424,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
continue;
|
||||
}
|
||||
|
||||
MultiRange range = _channel.MemoryManager.Physical.TextureCache.UpdatePartiallyMapped(_channel.MemoryManager, address, texture);
|
||||
TextureCache textureCache = _channel.MemoryManager.GetBackingMemory(address).TextureCache;
|
||||
MultiRange range = textureCache.UpdatePartiallyMapped(_channel.MemoryManager, address, texture);
|
||||
|
||||
// If the texture is not mapped at all, delete its reference.
|
||||
|
||||
@@ -446,7 +450,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
if (!range.Equals(texture.Range))
|
||||
{
|
||||
// Part of the texture was mapped or unmapped. Replace the range and regenerate tracking handles.
|
||||
if (!_channel.MemoryManager.Physical.TextureCache.UpdateMapping(texture, range))
|
||||
if (!textureCache.UpdateMapping(texture, range))
|
||||
{
|
||||
// Texture could not be remapped due to a collision, just delete it.
|
||||
if (Interlocked.Exchange(ref Items[request.ID], null) != null)
|
||||
@@ -481,6 +485,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <param name="size">Size of the range being invalidated</param>
|
||||
protected override void InvalidateRangeImpl(ulong address, ulong size)
|
||||
{
|
||||
MemoryManager memoryManager = _channel.MemoryManager;
|
||||
ProcessDereferenceQueue();
|
||||
|
||||
ulong endAddress = address + size;
|
||||
@@ -505,7 +510,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
if (texture.HasOneReference())
|
||||
{
|
||||
_channel.MemoryManager.Physical.TextureCache.AddShortCache(texture, ref cachedDescriptor);
|
||||
TextureCache textureCache = memoryManager.GetBackingMemory(descriptor.UnpackAddress()).TextureCache;
|
||||
textureCache.AddShortCache(texture, ref cachedDescriptor);
|
||||
}
|
||||
|
||||
if (Interlocked.Exchange(ref Items[id], null) != null)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
@@ -20,11 +22,17 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context that the texture pool belongs to</param>
|
||||
/// <param name="channel">GPU channel that the texture pool belongs to</param>
|
||||
/// <param name="physicalMemory">Backing memory of the pool</param>
|
||||
/// <param name="address">Address of the texture pool in guest memory</param>
|
||||
/// <param name="maximumId">Maximum texture ID of the texture pool (equal to maximum textures minus one)</param>
|
||||
protected override TexturePool CreatePool(GpuContext context, GpuChannel channel, ulong address, int maximumId)
|
||||
protected override TexturePool CreatePool(
|
||||
GpuContext context,
|
||||
GpuChannel channel,
|
||||
PhysicalMemory physicalMemory,
|
||||
ulong address,
|
||||
int maximumId)
|
||||
{
|
||||
return new TexturePool(context, channel, address, maximumId);
|
||||
return new TexturePool(context, channel, physicalMemory, address, maximumId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </summary>
|
||||
readonly struct BufferBounds : IEquatable<BufferBounds>
|
||||
{
|
||||
/// <summary>
|
||||
/// Physical memory backing the buffer.
|
||||
/// </summary>
|
||||
public PhysicalMemory Physical { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Buffer cache that owns the buffer.
|
||||
/// </summary>
|
||||
public BufferCache BufferCache => Physical.BufferCache;
|
||||
|
||||
/// <summary>
|
||||
/// Physical memory ranges where the buffer is mapped.
|
||||
/// </summary>
|
||||
@@ -29,8 +39,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </summary>
|
||||
/// <param name="range">Physical memory ranges where the buffer is mapped</param>
|
||||
/// <param name="flags">Buffer usage flags</param>
|
||||
public BufferBounds(MultiRange range, BufferUsageFlags flags = BufferUsageFlags.None)
|
||||
public BufferBounds(PhysicalMemory physical, MultiRange range, BufferUsageFlags flags = BufferUsageFlags.None)
|
||||
{
|
||||
Physical = physical;
|
||||
Range = range;
|
||||
Flags = flags;
|
||||
}
|
||||
|
||||
@@ -735,18 +735,22 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <remarks>
|
||||
/// This does a GPU side copy.
|
||||
/// </remarks>
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <param name="memoryManager">GPU memory manager where the buffer is mapped</param>
|
||||
/// <param name="srcVa">GPU virtual address of the copy source</param>
|
||||
/// <param name="dstVa">GPU virtual address of the copy destination</param>
|
||||
/// <param name="size">Size in bytes of the copy</param>
|
||||
public void CopyBuffer(MemoryManager memoryManager, ulong srcVa, ulong dstVa, ulong size)
|
||||
public static void CopyBuffer(GpuContext context, MemoryManager memoryManager, ulong srcVa, ulong dstVa, ulong size)
|
||||
{
|
||||
MultiRange srcRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, srcVa, size, BufferStage.Copy);
|
||||
MultiRange dstRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, dstVa, size, BufferStage.Copy);
|
||||
PhysicalMemory srcPhysical = memoryManager.GetBackingMemory(srcVa);
|
||||
PhysicalMemory dstPhysical = memoryManager.GetBackingMemory(dstVa);
|
||||
|
||||
MultiRange srcRange = srcPhysical.BufferCache.TranslateAndCreateBuffer(memoryManager, srcVa, size, BufferStage.Copy);
|
||||
MultiRange dstRange = dstPhysical.BufferCache.TranslateAndCreateBuffer(memoryManager, dstVa, size, BufferStage.Copy);
|
||||
|
||||
if (srcRange.Count == 1 && dstRange.Count == 1)
|
||||
{
|
||||
CopyBufferSingleRange(memoryManager, srcRange.GetSubRange(0).Address, dstRange.GetSubRange(0).Address, size);
|
||||
CopyBufferSingleRange(context, srcPhysical, dstPhysical, srcRange.GetSubRange(0).Address, dstRange.GetSubRange(0).Address, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -777,7 +781,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
ulong dstSize = dstSubRange.Size - dstOffset;
|
||||
ulong copySize = Math.Min(srcSize, dstSize);
|
||||
|
||||
CopyBufferSingleRange(memoryManager, srcSubRange.Address + srcOffset, dstSubRange.Address + dstOffset, copySize);
|
||||
CopyBufferSingleRange(context, srcPhysical, dstPhysical, srcSubRange.Address + srcOffset, dstSubRange.Address + dstOffset, copySize);
|
||||
|
||||
srcOffset += copySize;
|
||||
dstOffset += copySize;
|
||||
@@ -793,18 +797,26 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// This does a GPU side copy.
|
||||
/// </remarks>
|
||||
/// <param name="memoryManager">GPU memory manager where the buffer is mapped</param>
|
||||
/// <param name="srcPhysical">Physical memory backing the source buffer.</param>
|
||||
/// <param name="dstPhysical">Physical memory backing the destination buffer.</param>
|
||||
/// <param name="srcAddress">Physical address of the copy source</param>
|
||||
/// <param name="dstAddress">Physical address of the copy destination</param>
|
||||
/// <param name="size">Size in bytes of the copy</param>
|
||||
private void CopyBufferSingleRange(MemoryManager memoryManager, ulong srcAddress, ulong dstAddress, ulong size)
|
||||
private static void CopyBufferSingleRange(
|
||||
GpuContext context,
|
||||
PhysicalMemory srcPhysical,
|
||||
PhysicalMemory dstPhysical,
|
||||
ulong srcAddress,
|
||||
ulong dstAddress,
|
||||
ulong size)
|
||||
{
|
||||
Buffer srcBuffer = GetBuffer(srcAddress, size, BufferStage.Copy);
|
||||
Buffer dstBuffer = GetBuffer(dstAddress, size, BufferStage.Copy);
|
||||
Buffer srcBuffer = srcPhysical.BufferCache.GetBuffer(srcAddress, size, BufferStage.Copy);
|
||||
Buffer dstBuffer = dstPhysical.BufferCache.GetBuffer(dstAddress, size, BufferStage.Copy);
|
||||
|
||||
int srcOffset = (int)(srcAddress - srcBuffer.Address);
|
||||
int dstOffset = (int)(dstAddress - dstBuffer.Address);
|
||||
|
||||
_context.Renderer.Pipeline.CopyBuffer(
|
||||
context.Renderer.Pipeline.CopyBuffer(
|
||||
srcBuffer.Handle,
|
||||
dstBuffer.Handle,
|
||||
srcOffset,
|
||||
@@ -820,7 +832,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
// Optimization: If the data being copied is already in memory, then copy it directly instead of flushing from GPU.
|
||||
|
||||
dstBuffer.ClearModified(dstAddress, size);
|
||||
memoryManager.Physical.WriteTrackedResource(dstAddress, memoryManager.Physical.GetSpan(srcAddress, (int)size), ResourceKind.Buffer);
|
||||
dstPhysical.WriteTrackedResource(dstAddress, srcPhysical.GetSpan(srcAddress, (int)size), ResourceKind.Buffer);
|
||||
}
|
||||
|
||||
dstBuffer.CopyToDependantVirtualBuffers(dstAddress, size);
|
||||
@@ -849,7 +861,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
_context.Renderer.Pipeline.ClearBuffer(buffer.Handle, offset, (int)subRange.Size, value);
|
||||
|
||||
memoryManager.Physical.FillTrackedResource(subRange.Address, subRange.Size, value, ResourceKind.Buffer);
|
||||
memoryManager.GetBackingMemory(gpuVa).FillTrackedResource(subRange.Address, subRange.Size, value, ResourceKind.Buffer);
|
||||
|
||||
buffer.CopyToDependantVirtualBuffers(subRange.Address, subRange.Size);
|
||||
}
|
||||
|
||||
@@ -66,18 +66,19 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
Buffers = new BufferBounds[count];
|
||||
Unaligned = new bool[count];
|
||||
|
||||
Buffers.AsSpan().Fill(new BufferBounds(new MultiRange(MemoryManager.PteUnmapped, 0UL)));
|
||||
Buffers.AsSpan().Fill(new BufferBounds(null, new MultiRange(MemoryManager.PteUnmapped, 0UL)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the region of a buffer at a given slot.
|
||||
/// </summary>
|
||||
/// <param name="index">Buffer slot</param>
|
||||
/// <param name="physical">Physical memory backing the buffer</param>
|
||||
/// <param name="range">Physical memory regions where the buffer is mapped</param>
|
||||
/// <param name="flags">Buffer usage flags</param>
|
||||
public void SetBounds(int index, MultiRange range, BufferUsageFlags flags = BufferUsageFlags.None)
|
||||
public void SetBounds(int index, PhysicalMemory physical, MultiRange range, BufferUsageFlags flags = BufferUsageFlags.None)
|
||||
{
|
||||
Buffers[index] = new BufferBounds(range, flags);
|
||||
Buffers[index] = new BufferBounds(physical, range, flags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -156,8 +157,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="type">Type of each index buffer element</param>
|
||||
public void SetIndexBuffer(ulong gpuVa, ulong size, IndexType type)
|
||||
{
|
||||
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.IndexBuffer);
|
||||
BufferCache bufferCache = _channel.MemoryManager.GetBackingMemory(gpuVa).BufferCache;
|
||||
MultiRange range = bufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.IndexBuffer);
|
||||
|
||||
_indexBuffer.BufferCache = bufferCache;
|
||||
_indexBuffer.Range = range;
|
||||
_indexBuffer.Type = type;
|
||||
|
||||
@@ -186,11 +189,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="divisor">Vertex divisor of the buffer, for instanced draws</param>
|
||||
public void SetVertexBuffer(int index, ulong gpuVa, ulong size, int stride, int divisor)
|
||||
{
|
||||
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.VertexBuffer);
|
||||
BufferCache bufferCache = _channel.MemoryManager.GetBackingMemory(gpuVa).BufferCache;
|
||||
MultiRange range = bufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.VertexBuffer);
|
||||
|
||||
_vertexBuffers[index].Range = range;
|
||||
_vertexBuffers[index].Stride = stride;
|
||||
_vertexBuffers[index].Divisor = divisor;
|
||||
ref VertexBuffer vb = ref _vertexBuffers[index];
|
||||
|
||||
vb.BufferCache = bufferCache;
|
||||
vb.Range = range;
|
||||
vb.Stride = stride;
|
||||
vb.Divisor = divisor;
|
||||
|
||||
_vertexBuffersDirty = true;
|
||||
|
||||
@@ -213,9 +220,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="size">Size in bytes of the transform feedback buffer</param>
|
||||
public void SetTransformFeedbackBuffer(int index, ulong gpuVa, ulong size)
|
||||
{
|
||||
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStage.TransformFeedback);
|
||||
PhysicalMemory physical = _channel.MemoryManager.GetBackingMemory(gpuVa);
|
||||
MultiRange range = physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStage.TransformFeedback);
|
||||
|
||||
_transformFeedbackBuffers[index] = new BufferBounds(range);
|
||||
_transformFeedbackBuffers[index] = new BufferBounds(physical, range);
|
||||
_transformFeedbackBuffersDirty = true;
|
||||
}
|
||||
|
||||
@@ -258,11 +266,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
RecordStorageAlignment(_cpStorageBuffers, index, gpuVa);
|
||||
|
||||
gpuVa = BitUtils.AlignDown<ulong>(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment);
|
||||
gpuVa = BitUtils.AlignDown(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment);
|
||||
|
||||
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStageUtils.ComputeStorage(flags));
|
||||
PhysicalMemory physical = _channel.MemoryManager.GetBackingMemory(gpuVa);
|
||||
MultiRange range = physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStageUtils.ComputeStorage(flags));
|
||||
|
||||
_cpStorageBuffers.SetBounds(index, range, flags);
|
||||
_cpStorageBuffers.SetBounds(index, physical, range, flags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -282,16 +291,17 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
RecordStorageAlignment(buffers, index, gpuVa);
|
||||
|
||||
gpuVa = BitUtils.AlignDown<ulong>(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment);
|
||||
gpuVa = BitUtils.AlignDown(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment);
|
||||
|
||||
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStageUtils.GraphicsStorage(stage, flags));
|
||||
PhysicalMemory physical = _channel.MemoryManager.GetBackingMemory(gpuVa);
|
||||
MultiRange range = physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStageUtils.GraphicsStorage(stage, flags));
|
||||
|
||||
if (!buffers.Buffers[index].Range.Equals(range))
|
||||
{
|
||||
_gpStorageBuffersDirty = true;
|
||||
}
|
||||
|
||||
buffers.SetBounds(index, range, flags);
|
||||
buffers.SetBounds(index, physical, range, flags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -303,9 +313,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="size">Size in bytes of the storage buffer</param>
|
||||
public void SetComputeUniformBuffer(int index, ulong gpuVa, ulong size)
|
||||
{
|
||||
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.Compute);
|
||||
PhysicalMemory physical = _channel.MemoryManager.GetBackingMemory(gpuVa);
|
||||
MultiRange range = physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.Compute);
|
||||
|
||||
_cpUniformBuffers.SetBounds(index, range);
|
||||
_cpUniformBuffers.SetBounds(index, physical, range);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -318,9 +329,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="size">Size in bytes of the storage buffer</param>
|
||||
public void SetGraphicsUniformBuffer(int stage, int index, ulong gpuVa, ulong size)
|
||||
{
|
||||
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStageUtils.FromShaderStage(stage));
|
||||
PhysicalMemory physical = _channel.MemoryManager.GetBackingMemory(gpuVa);
|
||||
MultiRange range = _channel.MemoryManager.GetBackingMemory(gpuVa).BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStageUtils.FromShaderStage(stage));
|
||||
|
||||
_gpUniformBuffers[stage].SetBounds(index, range);
|
||||
_gpUniformBuffers[stage].SetBounds(index, physical, range);
|
||||
_gpUniformBuffersDirty = true;
|
||||
}
|
||||
|
||||
@@ -416,9 +428,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </summary>
|
||||
/// <param name="index">Index of the uniform buffer binding</param>
|
||||
/// <returns>The uniform buffer address, or an undefined value if the buffer is not currently bound</returns>
|
||||
public ulong GetComputeUniformBufferAddress(int index)
|
||||
public (PhysicalMemory, ulong) GetComputeUniformBufferAddress(int index)
|
||||
{
|
||||
return _cpUniformBuffers.Buffers[index].Range.GetSubRange(0).Address;
|
||||
ref BufferBounds buffer = ref _cpUniformBuffers.Buffers[index];
|
||||
return (buffer.Physical, buffer.Range.GetSubRange(0).Address);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -437,9 +450,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="stage">Index of the shader stage</param>
|
||||
/// <param name="index">Index of the uniform buffer binding</param>
|
||||
/// <returns>The uniform buffer address, or an undefined value if the buffer is not currently bound</returns>
|
||||
public ulong GetGraphicsUniformBufferAddress(int stage, int index)
|
||||
public (PhysicalMemory, ulong) GetGraphicsUniformBufferAddress(int stage, int index)
|
||||
{
|
||||
return _gpUniformBuffers[stage].Buffers[index].Range.GetSubRange(0).Address;
|
||||
ref BufferBounds buffer = ref _gpUniformBuffers[stage].Buffers[index];
|
||||
return (buffer.Physical, buffer.Range.GetSubRange(0).Address);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -478,12 +492,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </summary>
|
||||
public void CommitComputeBindings()
|
||||
{
|
||||
BufferCache bufferCache = _channel.MemoryManager.Physical.BufferCache;
|
||||
BindBuffers(_cpStorageBuffers, isStorage: true);
|
||||
BindBuffers(_cpUniformBuffers, isStorage: false);
|
||||
|
||||
BindBuffers(bufferCache, _cpStorageBuffers, isStorage: true);
|
||||
BindBuffers(bufferCache, _cpUniformBuffers, isStorage: false);
|
||||
|
||||
CommitBufferTextureBindings(bufferCache);
|
||||
CommitBufferTextureBindings();
|
||||
|
||||
// Force rebind after doing compute work.
|
||||
Rebind();
|
||||
@@ -495,14 +507,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// Commit any queued buffer texture bindings.
|
||||
/// </summary>
|
||||
/// <param name="bufferCache">Buffer cache</param>
|
||||
private void CommitBufferTextureBindings(BufferCache bufferCache)
|
||||
private void CommitBufferTextureBindings()
|
||||
{
|
||||
if (_bufferTextures.Count > 0)
|
||||
{
|
||||
foreach (BufferTextureBinding binding in _bufferTextures)
|
||||
{
|
||||
bool isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
|
||||
BufferRange range = bufferCache.GetBufferRange(binding.Range, BufferStageUtils.TextureBuffer(binding.Stage, binding.BindingInfo.Flags), isStore);
|
||||
BufferRange range = binding.BufferCache.GetBufferRange(binding.Range, BufferStageUtils.TextureBuffer(binding.Stage, binding.BindingInfo.Flags), isStore);
|
||||
binding.Texture.SetStorage(range);
|
||||
|
||||
// The texture must be rebound to use the new storage if it was updated.
|
||||
@@ -526,7 +538,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
foreach (BufferTextureArrayBinding<ITextureArray> binding in _bufferTextureArrays)
|
||||
{
|
||||
BufferRange range = bufferCache.GetBufferRange(binding.Range, BufferStage.None);
|
||||
BufferRange range = binding.BufferCache.GetBufferRange(binding.Range, BufferStage.None);
|
||||
binding.Texture.SetStorage(range);
|
||||
|
||||
textureArray[0] = binding.Texture;
|
||||
@@ -536,7 +548,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
foreach (BufferTextureArrayBinding<IImageArray> binding in _bufferImageArrays)
|
||||
{
|
||||
bool isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
|
||||
BufferRange range = bufferCache.GetBufferRange(binding.Range, BufferStage.None, isStore);
|
||||
BufferRange range = binding.BufferCache.GetBufferRange(binding.Range, BufferStage.None, isStore);
|
||||
binding.Texture.SetStorage(range);
|
||||
|
||||
textureArray[0] = binding.Texture;
|
||||
@@ -555,8 +567,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="indexed">True if the index buffer is in use</param>
|
||||
public void CommitGraphicsBindings(bool indexed)
|
||||
{
|
||||
BufferCache bufferCache = _channel.MemoryManager.Physical.BufferCache;
|
||||
|
||||
if (indexed)
|
||||
{
|
||||
if (_indexBufferDirty || _rebind)
|
||||
@@ -565,14 +575,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
if (!_indexBuffer.Range.IsUnmapped)
|
||||
{
|
||||
BufferRange buffer = bufferCache.GetBufferRange(_indexBuffer.Range, BufferStage.IndexBuffer);
|
||||
BufferRange buffer = _indexBuffer.BufferCache.GetBufferRange(_indexBuffer.Range, BufferStage.IndexBuffer);
|
||||
|
||||
_context.Renderer.Pipeline.SetIndexBuffer(buffer, _indexBuffer.Type);
|
||||
}
|
||||
}
|
||||
else if (!_indexBuffer.Range.IsUnmapped)
|
||||
{
|
||||
bufferCache.SynchronizeBufferRange(_indexBuffer.Range);
|
||||
_indexBuffer.BufferCache.SynchronizeBufferRange(_indexBuffer.Range);
|
||||
}
|
||||
}
|
||||
else if (_rebind)
|
||||
@@ -597,7 +607,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
continue;
|
||||
}
|
||||
|
||||
BufferRange buffer = bufferCache.GetBufferRange(vb.Range, BufferStage.VertexBuffer);
|
||||
BufferRange buffer = vb.BufferCache.GetBufferRange(vb.Range, BufferStage.VertexBuffer);
|
||||
|
||||
vertexBuffers[index] = new VertexBufferDescriptor(buffer, vb.Stride, vb.Divisor);
|
||||
}
|
||||
@@ -615,7 +625,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
continue;
|
||||
}
|
||||
|
||||
bufferCache.SynchronizeBufferRange(vb.Range);
|
||||
vb.BufferCache.SynchronizeBufferRange(vb.Range);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -637,7 +647,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
continue;
|
||||
}
|
||||
|
||||
tfbs[index] = bufferCache.GetBufferRange(tfb.Range, BufferStage.TransformFeedback, write: true);
|
||||
tfbs[index] = tfb.BufferCache.GetBufferRange(tfb.Range, BufferStage.TransformFeedback, write: true);
|
||||
}
|
||||
|
||||
_context.Renderer.Pipeline.SetTransformFeedbackBuffers(tfbs);
|
||||
@@ -684,7 +694,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
_context.SupportBufferUpdater.SetTfeOffset(index, tfeOffset);
|
||||
|
||||
buffers[index] = new BufferAssignment(index, bufferCache.GetBufferRange(range, BufferStage.TransformFeedback, write: true));
|
||||
buffers[index] = new BufferAssignment(index, tfb.BufferCache.GetBufferRange(range, BufferStage.TransformFeedback, write: true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -702,7 +712,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
continue;
|
||||
}
|
||||
|
||||
bufferCache.SynchronizeBufferRange(tfb.Range);
|
||||
tfb.BufferCache.SynchronizeBufferRange(tfb.Range);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -710,7 +720,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
_gpStorageBuffersDirty = false;
|
||||
|
||||
BindBuffers(bufferCache, _gpStorageBuffers, isStorage: true);
|
||||
BindBuffers(_gpStorageBuffers, isStorage: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -721,14 +731,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
_gpUniformBuffersDirty = false;
|
||||
|
||||
BindBuffers(bufferCache, _gpUniformBuffers, isStorage: false);
|
||||
BindBuffers(_gpUniformBuffers, isStorage: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateBuffers(_gpUniformBuffers);
|
||||
}
|
||||
|
||||
CommitBufferTextureBindings(bufferCache);
|
||||
CommitBufferTextureBindings();
|
||||
|
||||
_rebind = false;
|
||||
|
||||
@@ -742,7 +752,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="bindings">Buffer memory ranges to bind</param>
|
||||
/// <param name="isStorage">True to bind as storage buffer, false to bind as uniform buffer</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void BindBuffers(BufferCache bufferCache, BuffersPerStage[] bindings, bool isStorage)
|
||||
private void BindBuffers(BuffersPerStage[] bindings, bool isStorage)
|
||||
{
|
||||
int rangesCount = 0;
|
||||
|
||||
@@ -763,8 +773,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
bool isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write);
|
||||
BufferRange range = isStorage
|
||||
? bufferCache.GetBufferRangeAligned(bounds.Range, bufferStage | BufferStageUtils.FromUsage(bounds.Flags), isWrite)
|
||||
: bufferCache.GetBufferRange(bounds.Range, bufferStage);
|
||||
? bounds.BufferCache.GetBufferRangeAligned(bounds.Range, bufferStage | BufferStageUtils.FromUsage(bounds.Flags), isWrite)
|
||||
: bounds.BufferCache.GetBufferRange(bounds.Range, bufferStage);
|
||||
|
||||
ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range);
|
||||
}
|
||||
@@ -780,11 +790,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <summary>
|
||||
/// Bind respective buffer bindings on the host API.
|
||||
/// </summary>
|
||||
/// <param name="bufferCache">Buffer cache holding the buffers for the specified ranges</param>
|
||||
/// <param name="buffers">Buffer memory ranges to bind</param>
|
||||
/// <param name="isStorage">True to bind as storage buffer, false to bind as uniform buffer</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void BindBuffers(BufferCache bufferCache, BuffersPerStage buffers, bool isStorage)
|
||||
private void BindBuffers(BuffersPerStage buffers, bool isStorage)
|
||||
{
|
||||
int rangesCount = 0;
|
||||
|
||||
@@ -800,8 +809,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
bool isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write);
|
||||
BufferRange range = isStorage
|
||||
? bufferCache.GetBufferRangeAligned(bounds.Range, BufferStageUtils.ComputeStorage(bounds.Flags), isWrite)
|
||||
: bufferCache.GetBufferRange(bounds.Range, BufferStage.Compute);
|
||||
? bounds.BufferCache.GetBufferRangeAligned(bounds.Range, BufferStageUtils.ComputeStorage(bounds.Flags), isWrite)
|
||||
: bounds.BufferCache.GetBufferRange(bounds.Range, BufferStage.Compute);
|
||||
|
||||
ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range);
|
||||
}
|
||||
@@ -854,7 +863,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
continue;
|
||||
}
|
||||
|
||||
_channel.MemoryManager.Physical.BufferCache.SynchronizeBufferRange(bounds.Range);
|
||||
bounds.BufferCache.SynchronizeBufferRange(bounds.Range);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -871,13 +880,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
public void SetBufferTextureStorage(
|
||||
ShaderStage stage,
|
||||
ITexture texture,
|
||||
BufferCache bufferCache,
|
||||
MultiRange range,
|
||||
TextureBindingInfo bindingInfo,
|
||||
bool isImage)
|
||||
{
|
||||
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
|
||||
bufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
|
||||
|
||||
_bufferTextures.Add(new BufferTextureBinding(stage, texture, range, bindingInfo, isImage));
|
||||
_bufferTextures.Add(new BufferTextureBinding(stage, texture, bufferCache, range, bindingInfo, isImage));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -894,13 +904,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
ShaderStage stage,
|
||||
ITextureArray array,
|
||||
ITexture texture,
|
||||
BufferCache bufferCache,
|
||||
MultiRange range,
|
||||
TextureBindingInfo bindingInfo,
|
||||
int index)
|
||||
{
|
||||
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
|
||||
bufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
|
||||
|
||||
_bufferTextureArrays.Add(new BufferTextureArrayBinding<ITextureArray>(array, texture, range, bindingInfo, index));
|
||||
_bufferTextureArrays.Add(new BufferTextureArrayBinding<ITextureArray>(array, texture, bufferCache, range, bindingInfo, index));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -917,13 +928,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
ShaderStage stage,
|
||||
IImageArray array,
|
||||
ITexture texture,
|
||||
BufferCache bufferCache,
|
||||
MultiRange range,
|
||||
TextureBindingInfo bindingInfo,
|
||||
int index)
|
||||
{
|
||||
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
|
||||
bufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
|
||||
|
||||
_bufferImageArrays.Add(new BufferTextureArrayBinding<IImageArray>(array, texture, range, bindingInfo, index));
|
||||
_bufferImageArrays.Add(new BufferTextureArrayBinding<IImageArray>(array, texture, bufferCache, range, bindingInfo, index));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -19,6 +19,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </summary>
|
||||
public ITexture Texture { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Buffer cache that owns the buffer.
|
||||
/// </summary>
|
||||
public BufferCache BufferCache { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Physical ranges of memory where the buffer texture data is located.
|
||||
/// </summary>
|
||||
@@ -39,18 +44,21 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </summary>
|
||||
/// <param name="array">Array</param>
|
||||
/// <param name="texture">Buffer texture</param>
|
||||
/// <param name="bufferCache">Buffer cache that owns the buffer</param>
|
||||
/// <param name="range">Physical ranges of memory where the buffer texture data is located</param>
|
||||
/// <param name="bindingInfo">Binding info</param>
|
||||
/// <param name="index">Index of the binding on the array</param>
|
||||
public BufferTextureArrayBinding(
|
||||
T array,
|
||||
ITexture texture,
|
||||
BufferCache bufferCache,
|
||||
MultiRange range,
|
||||
TextureBindingInfo bindingInfo,
|
||||
int index)
|
||||
{
|
||||
Array = array;
|
||||
Texture = texture;
|
||||
BufferCache = bufferCache;
|
||||
Range = range;
|
||||
BindingInfo = bindingInfo;
|
||||
Index = index;
|
||||
|
||||
@@ -20,6 +20,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </summary>
|
||||
public ITexture Texture { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Buffer cache that owns the buffer.
|
||||
/// </summary>
|
||||
public BufferCache BufferCache { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Physical ranges of memory where the buffer texture data is located.
|
||||
/// </summary>
|
||||
@@ -40,18 +45,21 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </summary>
|
||||
/// <param name="stage">Shader stage accessing the texture</param>
|
||||
/// <param name="texture">Buffer texture</param>
|
||||
/// <param name="bufferCache">Buffer cache that owns the buffer</param>
|
||||
/// <param name="range">Physical ranges of memory where the buffer texture data is located</param>
|
||||
/// <param name="bindingInfo">Binding info</param>
|
||||
/// <param name="isImage">Whether the binding is for an image or a sampler</param>
|
||||
public BufferTextureBinding(
|
||||
ShaderStage stage,
|
||||
ITexture texture,
|
||||
BufferCache bufferCache,
|
||||
MultiRange range,
|
||||
TextureBindingInfo bindingInfo,
|
||||
bool isImage)
|
||||
{
|
||||
Stage = stage;
|
||||
Texture = texture;
|
||||
BufferCache = bufferCache;
|
||||
Range = range;
|
||||
BindingInfo = bindingInfo;
|
||||
IsImage = isImage;
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </summary>
|
||||
struct IndexBuffer
|
||||
{
|
||||
public BufferCache BufferCache;
|
||||
public MultiRange Range;
|
||||
public IndexType Type;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Memory;
|
||||
using Ryujinx.Memory.Range;
|
||||
using System;
|
||||
@@ -35,10 +36,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
public event EventHandler<UnmapEventArgs> MemoryUnmapped;
|
||||
|
||||
/// <summary>
|
||||
/// Physical memory where the virtual memory is mapped into.
|
||||
/// </summary>
|
||||
internal PhysicalMemory Physical { get; }
|
||||
private readonly GpuContext _context;
|
||||
private readonly List<PhysicalMemory> _physicalMemoryList;
|
||||
private readonly Dictionary<PhysicalMemory, byte> _physicalMemoryMap;
|
||||
|
||||
/// <summary>
|
||||
/// Virtual range cache.
|
||||
@@ -53,19 +53,65 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <summary>
|
||||
/// Creates a new instance of the GPU memory manager.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <param name="physicalMemory">Physical memory that this memory manager will map into</param>
|
||||
/// <param name="cpuMemorySize">The amount of physical CPU Memory Avaiable on the device.</param>
|
||||
internal MemoryManager(PhysicalMemory physicalMemory, ulong cpuMemorySize)
|
||||
|
||||
internal MemoryManager(GpuContext context, PhysicalMemory physicalMemory, ulong cpuMemorySize)
|
||||
{
|
||||
Physical = physicalMemory;
|
||||
_context = context;
|
||||
|
||||
_physicalMemoryList = new List<PhysicalMemory>()
|
||||
{
|
||||
physicalMemory
|
||||
};
|
||||
|
||||
_physicalMemoryMap = new Dictionary<PhysicalMemory, byte>
|
||||
{
|
||||
{ physicalMemory, 0 }
|
||||
};
|
||||
|
||||
VirtualRangeCache = new VirtualRangeCache(this);
|
||||
CounterCache = new CounterCache();
|
||||
_pageTable = new ulong[PtLvl0Size][];
|
||||
MemoryUnmapped += Physical.TextureCache.MemoryUnmappedHandler;
|
||||
MemoryUnmapped += Physical.BufferCache.MemoryUnmappedHandler;
|
||||
MemoryUnmapped += physicalMemory.TextureCache.MemoryUnmappedHandler;
|
||||
MemoryUnmapped += physicalMemory.BufferCache.MemoryUnmappedHandler;
|
||||
MemoryUnmapped += VirtualRangeCache.MemoryUnmappedHandler;
|
||||
MemoryUnmapped += CounterCache.MemoryUnmappedHandler;
|
||||
Physical.TextureCache.Initialize(cpuMemorySize);
|
||||
physicalMemory.TextureCache.Initialize(cpuMemorySize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attaches the memory manager to a new GPU channel.
|
||||
/// </summary>
|
||||
/// <param name="rebind">Action to be performed when the buffer cache changes</param>
|
||||
internal void AttachToChannel(Action rebind)
|
||||
{
|
||||
PhysicalMemory physicalMemory = GetOwnPhysicalMemory();
|
||||
|
||||
physicalMemory.IncrementReferenceCount();
|
||||
physicalMemory.BufferCache.NotifyBuffersModified += rebind;
|
||||
physicalMemory.BufferCache.QueuePrune();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attaches the memory manager to a new GPU channel.
|
||||
/// </summary>
|
||||
/// <param name="rebind">Action that was performed when the buffer cache changed</param>
|
||||
internal void DetachFromChannel(Action rebind)
|
||||
{
|
||||
PhysicalMemory physicalMemory = GetOwnPhysicalMemory();
|
||||
|
||||
physicalMemory.BufferCache.NotifyBuffersModified -= rebind;
|
||||
physicalMemory.DecrementReferenceCount();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queues a prune of invalid entries on the buffer cache.
|
||||
/// </summary>
|
||||
internal void QueuePrune()
|
||||
{
|
||||
GetOwnPhysicalMemory().BufferCache.QueuePrune();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -81,15 +127,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
if (IsContiguous(va, size))
|
||||
{
|
||||
ulong address = Translate(va);
|
||||
(PhysicalMemory physicalMemory, ulong address) = TranslateWithPhysicalMemory(va);
|
||||
|
||||
if (tracked)
|
||||
{
|
||||
return Physical.ReadTracked<T>(address);
|
||||
return physicalMemory.ReadTracked<T>(address);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Physical.Read<T>(address);
|
||||
return physicalMemory.Read<T>(address);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -113,7 +159,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
if (IsContiguous(va, size))
|
||||
{
|
||||
return Physical.GetSpan(Translate(va), size, tracked);
|
||||
(PhysicalMemory physicalMemory, ulong address) = TranslateWithPhysicalMemory(va);
|
||||
|
||||
return physicalMemory.GetSpan(address, size, tracked);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -138,7 +186,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
bool isContiguous = true;
|
||||
int mappedSize;
|
||||
|
||||
if (ValidateAddress(va) && GetPte(va) != PteUnmapped && Physical.IsMapped(Translate(va)))
|
||||
if (ValidateAddress(va) && IsMappedOnGpuAndPhysical(va))
|
||||
{
|
||||
ulong endVa = va + (ulong)size;
|
||||
ulong endVaAligned = (endVa + PageMask) & ~PageMask;
|
||||
@@ -151,7 +199,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
ulong nextVa = currentVa + PageSize;
|
||||
ulong nextPa = Translate(nextVa);
|
||||
|
||||
if (!ValidateAddress(nextVa) || GetPte(nextVa) == PteUnmapped || !Physical.IsMapped(nextPa))
|
||||
if (!ValidateAddress(nextVa) || !IsMappedOnGpuAndPhysical(nextVa))
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -180,7 +228,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
if (isContiguous)
|
||||
{
|
||||
return Physical.GetSpan(Translate(va), mappedSize, tracked);
|
||||
(PhysicalMemory physicalMemory, ulong address) = TranslateWithPhysicalMemory(va);
|
||||
|
||||
return physicalMemory.GetSpan(address, mappedSize, tracked);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -192,6 +242,23 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a page of memory is mapped on the GPU and its backing memory.
|
||||
/// </summary>
|
||||
/// <param name="va">GPU virtual address of the page</param>
|
||||
/// <returns>True if mapped, false otherwise</returns>
|
||||
private bool IsMappedOnGpuAndPhysical(ulong va)
|
||||
{
|
||||
(PhysicalMemory physicalMemory, ulong address) = TranslateWithPhysicalMemory(va);
|
||||
|
||||
if (address == PteUnmapped)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return physicalMemory.IsMapped(address);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads data from a possibly non-contiguous region of GPU mapped memory.
|
||||
/// </summary>
|
||||
@@ -209,22 +276,22 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
if ((va & PageMask) != 0)
|
||||
{
|
||||
ulong pa = Translate(va);
|
||||
(PhysicalMemory physicalMemory, ulong pa) = TranslateWithPhysicalMemory(va);
|
||||
|
||||
size = Math.Min(data.Length, (int)PageSize - (int)(va & PageMask));
|
||||
|
||||
Physical.GetSpan(pa, size, tracked).CopyTo(data[..size]);
|
||||
physicalMemory.GetSpan(pa, size, tracked).CopyTo(data[..size]);
|
||||
|
||||
offset += size;
|
||||
}
|
||||
|
||||
for (; offset < data.Length; offset += size)
|
||||
{
|
||||
ulong pa = Translate(va + (ulong)offset);
|
||||
(PhysicalMemory physicalMemory, ulong pa) = TranslateWithPhysicalMemory(va + (ulong)offset);
|
||||
|
||||
size = Math.Min(data.Length - offset, (int)PageSize);
|
||||
|
||||
Physical.GetSpan(pa, size, tracked).CopyTo(data.Slice(offset, size));
|
||||
physicalMemory.GetSpan(pa, size, tracked).CopyTo(data.Slice(offset, size));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,15 +306,17 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
if (IsContiguous(va, size))
|
||||
{
|
||||
return Physical.GetWritableRegion(Translate(va), size, tracked);
|
||||
(PhysicalMemory physicalMemory, ulong address) = TranslateWithPhysicalMemory(va);
|
||||
|
||||
return physicalMemory.GetWritableRegion(address, size, tracked);
|
||||
}
|
||||
else
|
||||
{
|
||||
MemoryOwner<byte> memoryOwner = MemoryOwner<byte>.Rent(size);
|
||||
Memory<byte> memory = new byte[size];
|
||||
|
||||
ReadImpl(va, memoryOwner.Span, tracked);
|
||||
GetSpan(va, size).CopyTo(memory.Span);
|
||||
|
||||
return new WritableRegion(this, va, memoryOwner, tracked);
|
||||
return new WritableRegion(this, va, memory, tracked);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,7 +338,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="data">The data to be written</param>
|
||||
public void Write(ulong va, ReadOnlySpan<byte> data)
|
||||
{
|
||||
WriteImpl(va, data, Physical.Write);
|
||||
WriteImpl(va, data, (physical, va, data) => physical.Write(va, data));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -279,7 +348,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="data">The data to be written</param>
|
||||
public void WriteTrackedResource(ulong va, ReadOnlySpan<byte> data)
|
||||
{
|
||||
WriteImpl(va, data, Physical.WriteTrackedResource);
|
||||
WriteImpl(va, data, (physical, va, data) => physical.WriteTrackedResource(va, data));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -289,10 +358,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="data">The data to be written</param>
|
||||
public void WriteUntracked(ulong va, ReadOnlySpan<byte> data)
|
||||
{
|
||||
WriteImpl(va, data, Physical.WriteUntracked);
|
||||
WriteImpl(va, data, (physical, va, data) => physical.WriteUntracked(va, data));
|
||||
}
|
||||
|
||||
private delegate void WriteCallback(ulong address, ReadOnlySpan<byte> data);
|
||||
private delegate void WriteCallback(PhysicalMemory physicalMemory, ulong address, ReadOnlySpan<byte> data);
|
||||
|
||||
/// <summary>
|
||||
/// Writes data to possibly non-contiguous GPU mapped memory.
|
||||
@@ -304,7 +373,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
if (IsContiguous(va, data.Length))
|
||||
{
|
||||
writeCallback(Translate(va), data);
|
||||
(PhysicalMemory physicalMemory, ulong address) = TranslateWithPhysicalMemory(va);
|
||||
|
||||
writeCallback(physicalMemory, address, data);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -312,22 +383,67 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
if ((va & PageMask) != 0)
|
||||
{
|
||||
ulong pa = Translate(va);
|
||||
(PhysicalMemory physicalMemory, ulong pa) = TranslateWithPhysicalMemory(va);
|
||||
|
||||
size = Math.Min(data.Length, (int)PageSize - (int)(va & PageMask));
|
||||
|
||||
writeCallback(pa, data[..size]);
|
||||
writeCallback(physicalMemory, pa, data[..size]);
|
||||
|
||||
offset += size;
|
||||
}
|
||||
|
||||
for (; offset < data.Length; offset += size)
|
||||
{
|
||||
ulong pa = Translate(va + (ulong)offset);
|
||||
(PhysicalMemory physicalMemory, ulong pa) = TranslateWithPhysicalMemory(va + (ulong)offset);
|
||||
|
||||
size = Math.Min(data.Length - offset, (int)PageSize);
|
||||
|
||||
writeCallback(pa, data.Slice(offset, size));
|
||||
writeCallback(physicalMemory, pa, data.Slice(offset, size));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data to GPU mapped memory, stopping at the first unmapped page at the memory region, if any.
|
||||
/// </summary>
|
||||
/// <param name="va">GPU virtual address to write the data into</param>
|
||||
/// <param name="data">The data to be written</param>
|
||||
public void WriteMapped(ulong va, ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (IsContiguous(va, data.Length))
|
||||
{
|
||||
(PhysicalMemory physicalMemory, ulong address) = TranslateWithPhysicalMemory(va);
|
||||
|
||||
physicalMemory.Write(address, data);
|
||||
}
|
||||
else
|
||||
{
|
||||
int offset = 0, size;
|
||||
|
||||
if ((va & PageMask) != 0)
|
||||
{
|
||||
(PhysicalMemory physicalMemory, ulong pa) = TranslateWithPhysicalMemory(va);
|
||||
|
||||
size = Math.Min(data.Length, (int)PageSize - (int)(va & PageMask));
|
||||
|
||||
if (pa != PteUnmapped && physicalMemory.IsMapped(pa))
|
||||
{
|
||||
physicalMemory.Write(pa, data[..size]);
|
||||
}
|
||||
|
||||
offset += size;
|
||||
}
|
||||
|
||||
for (; offset < data.Length; offset += size)
|
||||
{
|
||||
(PhysicalMemory physicalMemory, ulong pa) = TranslateWithPhysicalMemory(va + (ulong)offset);
|
||||
|
||||
size = Math.Min(data.Length - offset, (int)PageSize);
|
||||
|
||||
if (pa != PteUnmapped && physicalMemory.IsMapped(pa))
|
||||
{
|
||||
physicalMemory.Write(pa, data.Slice(offset, size));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -359,15 +475,51 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="size">Size in bytes of the mapping</param>
|
||||
/// <param name="kind">Kind of the resource located at the mapping</param>
|
||||
public void Map(ulong pa, ulong va, ulong size, PteKind kind)
|
||||
{
|
||||
MapImpl(pa, va, size, kind);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps a given range of pages to the specified CPU virtual address from a different process.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// All addresses and sizes must be page aligned.
|
||||
/// </remarks>
|
||||
/// <param name="pa">CPU virtual address to map into</param>
|
||||
/// <param name="va">GPU virtual address to be mapped</param>
|
||||
/// <param name="size">Size in bytes of the mapping</param>
|
||||
/// <param name="kind">Kind of the resource located at the mapping</param>
|
||||
/// <param name="ownedPid">PID of the process that owns the mapping</param>
|
||||
public void MapForeign(ulong pa, ulong va, ulong size, PteKind kind, ulong ownedPid)
|
||||
{
|
||||
if (_context.PhysicalMemoryRegistry.TryGetValue(ownedPid, out PhysicalMemory physicalMemory))
|
||||
{
|
||||
MapImpl(pa, va, size, kind, physicalMemory);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps a given range of pages to the specified CPU virtual address.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// All addresses and sizes must be page aligned.
|
||||
/// </remarks>
|
||||
/// <param name="pa">CPU virtual address to map into</param>
|
||||
/// <param name="va">GPU virtual address to be mapped</param>
|
||||
/// <param name="size">Size in bytes of the mapping</param>
|
||||
/// <param name="kind">Kind of the resource located at the mapping</param>
|
||||
/// <param name="physicalMemory">Optional physical memory to import for the mapping</param>
|
||||
private void MapImpl(ulong pa, ulong va, ulong size, PteKind kind, PhysicalMemory physicalMemory = null)
|
||||
{
|
||||
lock (_pageTable)
|
||||
{
|
||||
UnmapEventArgs e = new(va, size);
|
||||
MemoryUnmapped?.Invoke(this, e);
|
||||
byte pIndex = physicalMemory != null ? GetOrAddPhysicalMemory(physicalMemory) : (byte)0;
|
||||
|
||||
for (ulong offset = 0; offset < size; offset += PageSize)
|
||||
{
|
||||
SetPte(va + offset, PackPte(pa + offset, kind));
|
||||
SetPte(va + offset, PackPte(pa + offset, pIndex, kind));
|
||||
}
|
||||
|
||||
RunRemapActions(e);
|
||||
@@ -418,12 +570,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
for (int page = 0; page < pages - 1; page++)
|
||||
{
|
||||
if (!ValidateAddress(va + PageSize) || GetPte(va + PageSize) == PteUnmapped)
|
||||
ulong nextPte = GetPte(va + PageSize);
|
||||
|
||||
if (!ValidateAddress(va + PageSize) || nextPte == PteUnmapped)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Translate(va) + PageSize != Translate(va + PageSize))
|
||||
if (GetPte(va) + PageSize != nextPte)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -457,7 +611,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
int pages = (int)((endVaRounded - va) / PageSize);
|
||||
|
||||
List<MemoryRange> regions = [];
|
||||
List<MemoryRange> regions = new();
|
||||
|
||||
for (int page = 0; page < pages - 1; page++)
|
||||
{
|
||||
@@ -535,6 +689,49 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the backing memory for a given GPU virtual address.
|
||||
/// </summary>
|
||||
/// <param name="va">GPU virtual address to get the backing memory from</param>
|
||||
/// <returns>The backing memory for the specified GPU virtual address</returns>
|
||||
internal PhysicalMemory GetBackingMemory(ulong va)
|
||||
{
|
||||
ulong pte = GetPte(va);
|
||||
|
||||
if (pte == PteUnmapped)
|
||||
{
|
||||
return GetOwnPhysicalMemory();
|
||||
}
|
||||
|
||||
return _physicalMemoryList[UnpackPIndexFromPte(pte)];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the backing memory that is owned by this GPU memory manager.
|
||||
/// </summary>
|
||||
/// <returns>The backing memory owned by this memory manager</returns>
|
||||
private PhysicalMemory GetOwnPhysicalMemory()
|
||||
{
|
||||
return _physicalMemoryList[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index for a given physical memory on the list, adding it to the list if needed.
|
||||
/// </summary>
|
||||
/// <param name="physicalMemory">Physical memory to get the index from</param>
|
||||
/// <returns>The index of the physical memory on the list</returns>
|
||||
private byte GetOrAddPhysicalMemory(PhysicalMemory physicalMemory)
|
||||
{
|
||||
if (!_physicalMemoryMap.TryGetValue(physicalMemory, out byte pIndex))
|
||||
{
|
||||
pIndex = checked((byte)_physicalMemoryList.Count);
|
||||
_physicalMemoryList.Add(physicalMemory);
|
||||
_physicalMemoryMap.Add(physicalMemory, pIndex);
|
||||
}
|
||||
|
||||
return pIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates a GPU virtual address.
|
||||
/// </summary>
|
||||
@@ -636,6 +833,28 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
return Math.Min(maxSize, va - startVa);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Translates a GPU virtual address to a CPU virtual address and the associated physical memory.
|
||||
/// </summary>
|
||||
/// <param name="va">GPU virtual address to be translated</param>
|
||||
/// <returns>CPU virtual address with the physical memory, or <see cref="PteUnmapped"/> if unmapped</returns>
|
||||
private (PhysicalMemory, ulong) TranslateWithPhysicalMemory(ulong va)
|
||||
{
|
||||
if (!ValidateAddress(va))
|
||||
{
|
||||
return (GetOwnPhysicalMemory(), PteUnmapped);
|
||||
}
|
||||
|
||||
ulong pte = GetPte(va);
|
||||
|
||||
if (pte == PteUnmapped)
|
||||
{
|
||||
return (GetOwnPhysicalMemory(), PteUnmapped);
|
||||
}
|
||||
|
||||
return (_physicalMemoryList[UnpackPIndexFromPte(pte)], UnpackPaFromPte(pte) + (va & PageMask));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the kind of a given memory page.
|
||||
/// This might indicate the type of resource that can be allocated on the page, and also texture tiling.
|
||||
@@ -659,6 +878,18 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
return UnpackKindFromPte(pte);
|
||||
}
|
||||
|
||||
public bool IsForeignMapping(ulong va)
|
||||
{
|
||||
ulong pte = GetPte(va);
|
||||
|
||||
if (pte == PteUnmapped)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return UnpackPIndexFromPte(pte) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Page Table entry for a given GPU virtual address.
|
||||
/// </summary>
|
||||
@@ -704,11 +935,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// Creates a page table entry from a physical address and kind.
|
||||
/// </summary>
|
||||
/// <param name="pa">Physical address</param>
|
||||
/// <param name="pIndex">Index of the physical memory on the list</param>
|
||||
/// <param name="kind">Kind</param>
|
||||
/// <returns>Page table entry</returns>
|
||||
private static ulong PackPte(ulong pa, PteKind kind)
|
||||
private static ulong PackPte(ulong pa, byte pIndex, PteKind kind)
|
||||
{
|
||||
return pa | ((ulong)kind << 56);
|
||||
return pa | ((ulong)pIndex << 48) | ((ulong)kind << 56);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -721,6 +953,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
return (PteKind)(pte >> 56);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the physical memory index in the list from a page table entry.
|
||||
/// </summary>
|
||||
/// <param name="pte">Page table entry</param>
|
||||
/// <returns>Physical memory index</returns>
|
||||
private static byte UnpackPIndexFromPte(ulong pte)
|
||||
{
|
||||
return (byte)(pte >> 48);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks physical address from a page table entry.
|
||||
/// </summary>
|
||||
@@ -728,7 +970,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <returns>Physical address</returns>
|
||||
private static ulong UnpackPaFromPte(ulong pte)
|
||||
{
|
||||
return pte & 0xffffffffffffffUL;
|
||||
return pte & 0xffffffffffffUL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </summary>
|
||||
struct VertexBuffer
|
||||
{
|
||||
public BufferCache BufferCache;
|
||||
public MultiRange Range;
|
||||
public int Stride;
|
||||
public int Divisor;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using System;
|
||||
@@ -66,11 +67,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// <inheritdoc/>
|
||||
public uint ConstantBuffer1Read(int offset)
|
||||
{
|
||||
ulong baseAddress = _compute
|
||||
(PhysicalMemory physical, ulong baseAddress) = _compute
|
||||
? _channel.BufferManager.GetComputeUniformBufferAddress(1)
|
||||
: _channel.BufferManager.GetGraphicsUniformBufferAddress(_stageIndex, 1);
|
||||
|
||||
return _channel.MemoryManager.Physical.Read<uint>(baseAddress + (ulong)offset);
|
||||
return physical.Read<uint>(baseAddress + (ulong)offset);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -733,15 +733,15 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
byte[] codeB,
|
||||
bool asCompute)
|
||||
{
|
||||
ulong cb1DataAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(0, 1);
|
||||
|
||||
(PhysicalMemory physical, ulong cb1DataAddress) = channel.BufferManager.GetGraphicsUniformBufferAddress(0, 1);
|
||||
|
||||
MemoryManager memoryManager = channel.MemoryManager;
|
||||
|
||||
codeA ??= memoryManager.GetSpan(vertexA.Address, vertexA.Size).ToArray();
|
||||
codeB ??= memoryManager.GetSpan(currentStage.Address, currentStage.Size).ToArray();
|
||||
byte[] cb1DataA = ReadArray(memoryManager, cb1DataAddress, vertexA.Cb1DataSize);
|
||||
byte[] cb1DataB = ReadArray(memoryManager, cb1DataAddress, currentStage.Cb1DataSize);
|
||||
|
||||
byte[] cb1DataA = ReadArray(physical, cb1DataAddress, vertexA.Cb1DataSize);
|
||||
byte[] cb1DataB = ReadArray(physical, cb1DataAddress, currentStage.Cb1DataSize);
|
||||
|
||||
ShaderDumpPaths pathsA = default;
|
||||
ShaderDumpPaths pathsB = default;
|
||||
|
||||
@@ -775,11 +775,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
MemoryManager memoryManager = channel.MemoryManager;
|
||||
|
||||
ulong cb1DataAddress = context.Stage == ShaderStage.Compute
|
||||
(PhysicalMemory physical, ulong cb1DataAddress) = context.Stage == ShaderStage.Compute
|
||||
? channel.BufferManager.GetComputeUniformBufferAddress(1)
|
||||
: channel.BufferManager.GetGraphicsUniformBufferAddress(StageToStageIndex(context.Stage), 1);
|
||||
|
||||
byte[] cb1Data = ReadArray(memoryManager, cb1DataAddress, context.Cb1DataSize);
|
||||
byte[] cb1Data = ReadArray(physical, cb1DataAddress, context.Cb1DataSize);
|
||||
code ??= memoryManager.GetSpan(context.Address, context.Size).ToArray();
|
||||
|
||||
ShaderDumpPaths paths = dumper?.Dump(code, context.Stage == ShaderStage.Compute) ?? default;
|
||||
@@ -793,18 +793,18 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// <summary>
|
||||
/// Reads data from physical memory, returns an empty array if the memory is unmapped or size is 0.
|
||||
/// </summary>
|
||||
/// <param name="memoryManager">Memory manager with the physical memory to read from</param>
|
||||
/// <param name="physicalMemory">Physical memory to read the data from, might be null</param>
|
||||
/// <param name="address">Physical address of the region to read</param>
|
||||
/// <param name="size">Size in bytes of the data</param>
|
||||
/// <returns>An array with the data at the specified memory location</returns>
|
||||
private static byte[] ReadArray(MemoryManager memoryManager, ulong address, int size)
|
||||
private static byte[] ReadArray(PhysicalMemory physicalMemory, ulong address, int size)
|
||||
{
|
||||
if (address == MemoryManager.PteUnmapped || size == 0)
|
||||
if (address == MemoryManager.PteUnmapped || size == 0 || physicalMemory == null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return memoryManager.Physical.GetSpan(address, size).ToArray();
|
||||
return physicalMemory.GetSpan(address, size).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -696,7 +696,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
ref BufferBounds bounds = ref channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, textureBufferIndex);
|
||||
|
||||
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(channel.MemoryManager.Physical.GetSpan(bounds.Range));
|
||||
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(bounds.Physical.GetSpan(bounds.Range));
|
||||
cachedTextureBufferIndex = textureBufferIndex;
|
||||
|
||||
if (samplerBufferIndex == textureBufferIndex)
|
||||
@@ -710,7 +710,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
ref BufferBounds bounds = ref channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, samplerBufferIndex);
|
||||
|
||||
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(channel.MemoryManager.Physical.GetSpan(bounds.Range));
|
||||
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(bounds.Physical.GetSpan(bounds.Range));
|
||||
cachedSamplerBufferIndex = samplerBufferIndex;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,11 +9,18 @@ using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu
|
||||
{
|
||||
using Texture = Image.Texture;
|
||||
|
||||
public record TextureData(int Width, int Height, byte[] Data);
|
||||
|
||||
/// <summary>
|
||||
/// GPU image presentation window.
|
||||
/// </summary>
|
||||
public class Window
|
||||
{
|
||||
private const int CaptureTextureWidth = 1280;
|
||||
private const int CaptureTextureHeight = 720;
|
||||
|
||||
private readonly GpuContext _context;
|
||||
|
||||
/// <summary>
|
||||
@@ -85,7 +92,21 @@ namespace Ryujinx.Graphics.Gpu
|
||||
}
|
||||
}
|
||||
|
||||
private class PresentedTexture
|
||||
{
|
||||
public readonly Texture Texture;
|
||||
public readonly ImageCrop Crop;
|
||||
|
||||
public PresentedTexture(Texture texture, ImageCrop crop)
|
||||
{
|
||||
Texture = texture;
|
||||
Crop = crop;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ConcurrentQueue<PresentationTexture> _frameQueue;
|
||||
private PresentedTexture _lastPresentedTexture;
|
||||
private ITexture _captureTexture;
|
||||
|
||||
private int _framesAvailable;
|
||||
|
||||
@@ -188,6 +209,51 @@ namespace Ryujinx.Graphics.Gpu
|
||||
return true;
|
||||
}
|
||||
|
||||
public TextureData GetLastPresentedData()
|
||||
{
|
||||
PresentedTexture pt = Volatile.Read(ref _lastPresentedTexture);
|
||||
|
||||
if (pt != null)
|
||||
{
|
||||
byte[] inputData = CaptureLastFrame(pt.Texture.HostTexture, pt.Crop);
|
||||
|
||||
int size = SizeCalculator.GetBlockLinearTextureSize(
|
||||
CaptureTextureWidth,
|
||||
CaptureTextureHeight,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
4,
|
||||
16,
|
||||
1,
|
||||
1).TotalSize;
|
||||
|
||||
byte[] data = new byte[size];
|
||||
|
||||
LayoutConverter.ConvertLinearToBlockLinear(data, CaptureTextureWidth, CaptureTextureHeight, CaptureTextureWidth * 4, 4, 16, inputData);
|
||||
|
||||
return new TextureData(CaptureTextureWidth, CaptureTextureHeight, data);
|
||||
}
|
||||
|
||||
return new TextureData(0, 0, Array.Empty<byte>());
|
||||
}
|
||||
|
||||
public TextureData GetLastPresentedDataLinear()
|
||||
{
|
||||
PresentedTexture pt = Volatile.Read(ref _lastPresentedTexture);
|
||||
|
||||
if (pt != null)
|
||||
{
|
||||
byte[] inputData = CaptureLastFrame(pt.Texture.HostTexture, new ImageCrop());
|
||||
|
||||
return new TextureData(pt.Texture.Info.Width, pt.Texture.Info.Height, inputData);
|
||||
}
|
||||
|
||||
return new TextureData(0, 0, Array.Empty<byte>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Presents a texture on the queue.
|
||||
/// If the queue is empty, then no texture is presented.
|
||||
@@ -205,6 +271,10 @@ namespace Ryujinx.Graphics.Gpu
|
||||
|
||||
pt.Cache.Tick();
|
||||
|
||||
EnsureCaptureTexture();
|
||||
|
||||
Volatile.Write(ref _lastPresentedTexture, new PresentedTexture(texture, pt.Crop));
|
||||
|
||||
texture.SynchronizeMemory();
|
||||
|
||||
ImageCrop crop = new(
|
||||
@@ -244,6 +314,96 @@ namespace Ryujinx.Graphics.Gpu
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureCaptureTexture()
|
||||
{
|
||||
if (_captureTexture == null)
|
||||
{
|
||||
_captureTexture = _context.Renderer.CreateTexture(new TextureCreateInfo(
|
||||
1280,
|
||||
720,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
4,
|
||||
Format.R8G8B8A8Unorm,
|
||||
DepthStencilMode.Depth,
|
||||
Target.Texture2D,
|
||||
SwizzleComponent.Red,
|
||||
SwizzleComponent.Green,
|
||||
SwizzleComponent.Blue,
|
||||
SwizzleComponent.Alpha));
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] CaptureLastFrame(ITexture lastFrame, ImageCrop crop)
|
||||
{
|
||||
int cropLeft, cropRight, cropTop, cropBottom;
|
||||
|
||||
if (crop.Left == 0 && crop.Right == 0)
|
||||
{
|
||||
cropLeft = 0;
|
||||
cropRight = lastFrame.Width;
|
||||
}
|
||||
else
|
||||
{
|
||||
cropLeft = crop.Left;
|
||||
cropRight = crop.Right;
|
||||
}
|
||||
|
||||
if (crop.Top == 0 && crop.Bottom == 0)
|
||||
{
|
||||
cropTop = 0;
|
||||
cropBottom = lastFrame.Height;
|
||||
}
|
||||
else
|
||||
{
|
||||
cropTop = crop.Top;
|
||||
cropBottom = crop.Bottom;
|
||||
}
|
||||
|
||||
int x1, y1, x2, y2;
|
||||
|
||||
if (crop.FlipX)
|
||||
{
|
||||
x1 = cropRight;
|
||||
x2 = cropLeft;
|
||||
}
|
||||
else
|
||||
{
|
||||
x1 = cropLeft;
|
||||
x2 = cropRight;
|
||||
}
|
||||
|
||||
if (crop.FlipY)
|
||||
{
|
||||
y1 = cropBottom;
|
||||
y2 = cropTop;
|
||||
}
|
||||
else
|
||||
{
|
||||
y1 = cropTop;
|
||||
y2 = cropBottom;
|
||||
}
|
||||
|
||||
Extents2D srcRegion = new(x1, y1, x2, y2);
|
||||
Extents2D dstRegion = new(0, 0, CaptureTextureWidth, CaptureTextureHeight);
|
||||
|
||||
byte[] outputData = null;
|
||||
|
||||
_context.Renderer.BackgroundContextAction(() =>
|
||||
{
|
||||
lastFrame.CopyTo(_captureTexture, srcRegion, dstRegion, true);
|
||||
|
||||
using var data = _captureTexture.GetData();
|
||||
|
||||
outputData = data.Get().ToArray();
|
||||
});
|
||||
|
||||
return outputData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicate that a frame on the queue is ready to be acquired.
|
||||
/// </summary>
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Ryujinx.HLE.HOS
|
||||
private readonly ICpuContext _cpuContext;
|
||||
private T _memoryManager;
|
||||
|
||||
public IVirtualMemoryManager AddressSpace => _memoryManager;
|
||||
public IVirtualMemoryManagerTracked AddressSpace => _memoryManager;
|
||||
|
||||
public ulong AddressSpaceSize { get; }
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace Ryujinx.HLE.HOS
|
||||
internal ServerBase TimeServer { get; private set; }
|
||||
internal ServerBase ViServer { get; private set; }
|
||||
internal ServerBase ViServerM { get; private set; }
|
||||
internal ServerBase ViServerS { get; private set; }
|
||||
internal ViServer ViServerS { get; private set; }
|
||||
internal ServerBase LdnServer { get; private set; }
|
||||
|
||||
internal KSharedMemory HidSharedMem { get; private set; }
|
||||
@@ -254,7 +254,7 @@ namespace Ryujinx.HLE.HOS
|
||||
TimeServer = new ServerBase(KernelContext, "TimeServer");
|
||||
ViServer = new ServerBase(KernelContext, "ViServerU");
|
||||
ViServerM = new ServerBase(KernelContext, "ViServerM");
|
||||
ViServerS = new ServerBase(KernelContext, "ViServerS");
|
||||
ViServerS = new ViServer(KernelContext, "ViServerS");
|
||||
LdnServer = new ServerBase(KernelContext, "LdnServer");
|
||||
|
||||
StartNewServices();
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
{
|
||||
interface IProcessContext : IDisposable
|
||||
{
|
||||
IVirtualMemoryManager AddressSpace { get; }
|
||||
IVirtualMemoryManagerTracked AddressSpace { get; }
|
||||
|
||||
ulong AddressSpaceSize { get; }
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
|
||||
private IProcessContextFactory _contextFactory;
|
||||
public IProcessContext Context { get; private set; }
|
||||
public IVirtualMemoryManager CpuMemory => Context.AddressSpace;
|
||||
public IVirtualMemoryManagerTracked CpuMemory => Context.AddressSpace;
|
||||
|
||||
public HleProcessDebugger Debugger { get; private set; }
|
||||
|
||||
|
||||
@@ -6,11 +6,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
{
|
||||
class ProcessContext : IProcessContext
|
||||
{
|
||||
public IVirtualMemoryManager AddressSpace { get; }
|
||||
public IVirtualMemoryManagerTracked AddressSpace { get; }
|
||||
|
||||
public ulong AddressSpaceSize { get; }
|
||||
|
||||
public ProcessContext(IVirtualMemoryManager asManager, ulong addressSpaceSize)
|
||||
public ProcessContext(IVirtualMemoryManagerTracked asManager, ulong addressSpaceSize)
|
||||
{
|
||||
AddressSpace = asManager;
|
||||
AddressSpaceSize = addressSpaceSize;
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.OverlayAppletProxy;
|
||||
using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService
|
||||
{
|
||||
class IOverlayAppletProxy : IpcService
|
||||
{
|
||||
private readonly ulong _pid;
|
||||
|
||||
public IOverlayAppletProxy(ulong pid)
|
||||
{
|
||||
_pid = pid;
|
||||
}
|
||||
|
||||
[CommandCmif(0)]
|
||||
// GetCommonStateGetter() -> object<nn::am::service::ICommonStateGetter>
|
||||
public ResultCode GetCommonStateGetter(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new ICommonStateGetter(context));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(1)]
|
||||
// GetSelfController() -> object<nn::am::service::ISelfController>
|
||||
public ResultCode GetSelfController(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new ISelfController(context, _pid));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(2)]
|
||||
// GetWindowController() -> object<nn::am::service::IWindowController>
|
||||
public ResultCode GetWindowController(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new IWindowController(_pid));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(3)]
|
||||
// GetAudioController() -> object<nn::am::service::IAudioController>
|
||||
public ResultCode GetAudioController(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new IAudioController());
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(4)]
|
||||
// GetDisplayController() -> object<nn::am::service::IDisplayController>
|
||||
public ResultCode GetDisplayController(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new IDisplayController(context));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(11)]
|
||||
// GetLibraryAppletCreator() -> object<nn::am::service::ILibraryAppletCreator>
|
||||
public ResultCode GetLibraryAppletCreator(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new ILibraryAppletCreator());
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(20)]
|
||||
// GetOverlayFunctions() -> object<nn::am::service::IOverlayFunctions>
|
||||
public ResultCode GetOverlayFunctions(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new IOverlayFunctions(context.Device.System));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(21)]
|
||||
// GetAppletCommonFunctions() -> object<nn::am::service::IAppletCommonFunctions>
|
||||
public ResultCode GetAppletCommonFunctions(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new IAppletCommonFunctions());
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(23)]
|
||||
// GetGlobalStateController() -> object<nn::am::service::IGlobalStateController>
|
||||
public ResultCode GetGlobalStateController(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new IGlobalStateController());
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(1000)]
|
||||
// GetDebugFunctions() -> object<nn::am::service::IDebugFunctions>
|
||||
public ResultCode GetDebugFunctions(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new IDebugFunctions());
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
using LibHac.Util;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Ipc;
|
||||
using Ryujinx.Horizon.Common;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.OverlayAppletProxy
|
||||
{
|
||||
class IOverlayFunctions : IpcService
|
||||
{
|
||||
|
||||
public IOverlayFunctions(Horizon system)
|
||||
{
|
||||
}
|
||||
|
||||
[CommandCmif(0)]
|
||||
// BeginToWatchShortHomeButtonMessage()
|
||||
public ResultCode BeginToWatchShortHomeButtonMessage(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(1)]
|
||||
// EndToWatchShortHomeButtonMessage()
|
||||
public ResultCode EndToWatchShortHomeButtonMessage(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(2)]
|
||||
// GetApplicationIdForLogo() -> nn::ncm::ApplicationId
|
||||
public ResultCode GetApplicationIdForLogo(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
context.ResponseData.Write(0L);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(3)]
|
||||
// SetGpuTimeSliceBoost(u64)
|
||||
public ResultCode SetGpuTimeSliceBoost(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(4)]
|
||||
// SetAutoSleepTimeAndDimmingTimeEnabled(u8)
|
||||
public ResultCode SetAutoSleepTimeAndDimmingTimeEnabled(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(5)]
|
||||
// TerminateApplicationAndSetReason(u32)
|
||||
public ResultCode TerminateApplicationAndSetReason(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(6)]
|
||||
// SetScreenShotPermissionGlobally(u8)
|
||||
public ResultCode SetScreenShotPermissionGlobally(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(10)]
|
||||
// StartShutdownSequenceForOverlay()
|
||||
public ResultCode StartShutdownSequenceForOverlay(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(11)]
|
||||
// StartRebootSequenceForOverlay()
|
||||
public ResultCode StartRebootSequenceForOverlay(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(20)]
|
||||
// SetHandlingHomeButtonShortPressedEnabled(u8)
|
||||
public ResultCode SetHandlingHomeButtonShortPressedEnabled(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(21)]
|
||||
// SetHandlingTouchScreenInputEnabled(u8)
|
||||
public ResultCode SetHandlingTouchScreenInputEnabled(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(30)]
|
||||
// SetHealthWarningShowingState(u8)
|
||||
public ResultCode SetHealthWarningShowingState(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(31)]
|
||||
// IsHealthWarningRequired() -> bool
|
||||
public ResultCode IsHealthWarningRequired(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
context.ResponseData.Write(false);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(90)]
|
||||
// SetRequiresGpuResourceUse(u8)
|
||||
public ResultCode SetRequiresGpuResourceUse(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(101)]
|
||||
// BeginToObserveHidInputForDevelop()
|
||||
public ResultCode BeginToObserveHidInputForDevelop(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,5 +102,34 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(22)]
|
||||
// AcquireLastApplicationCaptureSharedBuffer() -> (b8, u32)
|
||||
public ResultCode AcquireLastApplicationCaptureSharedBuffer(ServiceCtx context)
|
||||
{
|
||||
context.ResponseData.Write(1);
|
||||
context.ResponseData.Write(context.Device.System.ViServerS.GetApplicationLastPresentedFrameHandle(context.Device.Gpu));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(26)]
|
||||
|
||||
// AcquireCallerAppletCaptureSharedBuffer() -> (b8, u32)
|
||||
public ResultCode AcquireCallerAppletCaptureSharedBuffer(ServiceCtx context)
|
||||
{
|
||||
// TODO: How does the handling for applets differ from the one for applications?
|
||||
context.ResponseData.Write(1);
|
||||
context.ResponseData.Write(context.Device.System.ViServerS.GetApplicationLastPresentedFrameHandle(context.Device.Gpu));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(27)]
|
||||
public ResultCode ReleaseCallerAppletCaptureSharedBuffer(ServiceCtx context)
|
||||
{
|
||||
context.ResponseData.Write(2);
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,7 +225,6 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||
public ResultCode CreateManagedDisplayLayer(ServiceCtx context)
|
||||
{
|
||||
context.Device.System.SurfaceFlinger.CreateLayer(out long layerId, _pid);
|
||||
context.Device.System.SurfaceFlinger.SetRenderLayer(layerId);
|
||||
|
||||
context.ResponseData.Write(layerId);
|
||||
|
||||
@@ -236,18 +235,27 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||
// IsSystemBufferSharingEnabled()
|
||||
public ResultCode IsSystemBufferSharingEnabled(ServiceCtx context)
|
||||
{
|
||||
// NOTE: Service checks a private field and return an error if the SystemBufferSharing is disabled.
|
||||
|
||||
return ResultCode.NotImplemented;
|
||||
// TODO: Implement this once we have a way to check if we're not an AppletId.Application
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(42)] // 4.0.0+
|
||||
// GetSystemSharedLayerHandle() -> (nn::vi::fbshare::SharedBufferHandle, nn::vi::fbshare::SharedLayerHandle)
|
||||
public ResultCode GetSystemSharedLayerHandle(ServiceCtx context)
|
||||
{
|
||||
context.ResponseData.Write((ulong)context.Device.System.ViServerS.GetSharedBufferNvMapId());
|
||||
context.ResponseData.Write(context.Device.System.ViServerS.GetSharedLayerId());
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(44)] // 10.0.0+
|
||||
// CreateManagedDisplaySeparableLayer() -> (u64, u64)
|
||||
public ResultCode CreateManagedDisplaySeparableLayer(ServiceCtx context)
|
||||
{
|
||||
context.Device.System.SurfaceFlinger.CreateLayer(out long displayLayerId, _pid);
|
||||
context.Device.System.SurfaceFlinger.CreateLayer(out long recordingLayerId, _pid);
|
||||
context.Device.System.SurfaceFlinger.SetRenderLayer(displayLayerId);
|
||||
//context.Device.System.SurfaceFlinger.SetRenderLayer(displayLayerId);
|
||||
|
||||
context.ResponseData.Write(displayLayerId);
|
||||
context.ResponseData.Write(recordingLayerId);
|
||||
|
||||
@@ -26,6 +26,15 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(300)]
|
||||
// OpenOverlayAppletProxy(pid, handle<copy>) -> object<nn::am::service::IOverlayAppletProxy>
|
||||
public ResultCode OpenOverlayAppletProxy(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new IOverlayAppletProxy(context.Request.HandleDesc.PId));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(350)]
|
||||
// OpenSystemApplicationProxy(u64, pid, handle<copy>) -> object<nn::am::service::IApplicationProxy>
|
||||
|
||||
@@ -35,6 +35,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
|
||||
public Dictionary<PlayerIndex, ConcurrentQueue<(VibrationValue, VibrationValue)>> RumbleQueues = new();
|
||||
public Dictionary<PlayerIndex, (VibrationValue, VibrationValue)> LastVibrationValues = new();
|
||||
|
||||
internal PlayerIndex LastActiveNpad { get; set; }
|
||||
|
||||
public NpadDevices(Switch device, bool active = true) : base(device, active)
|
||||
{
|
||||
@@ -384,6 +386,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
return;
|
||||
}
|
||||
|
||||
LastActiveNpad = state.PlayerId;
|
||||
|
||||
ref RingLifo<NpadCommonState> lifo = ref GetCommonStateLifo(ref currentNpad);
|
||||
|
||||
NpadCommonState newState = new()
|
||||
@@ -639,5 +643,20 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
|
||||
return rumbleQueue;
|
||||
}
|
||||
|
||||
public NpadIdType GetLastActiveNpadId()
|
||||
{
|
||||
return LastActiveNpad switch {
|
||||
PlayerIndex.Player1 => NpadIdType.Player1,
|
||||
PlayerIndex.Player2 => NpadIdType.Player2,
|
||||
PlayerIndex.Player3 => NpadIdType.Player3,
|
||||
PlayerIndex.Player4 => NpadIdType.Player4,
|
||||
PlayerIndex.Player5 => NpadIdType.Player5,
|
||||
PlayerIndex.Player6 => NpadIdType.Player6,
|
||||
PlayerIndex.Player7 => NpadIdType.Player7,
|
||||
PlayerIndex.Player8 => NpadIdType.Player8,
|
||||
_ => NpadIdType.Handheld,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,15 +24,9 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
// GetLastActiveNpad(u32) -> u8, u8
|
||||
public ResultCode GetLastActiveNpad(ServiceCtx context)
|
||||
{
|
||||
// TODO: RequestData seems to have garbage data, reading an extra uint seems to fix the issue.
|
||||
context.RequestData.ReadUInt32();
|
||||
context.ResponseData.Write((byte)context.Device.Hid.Npads.GetLastActiveNpadId());
|
||||
|
||||
ResultCode resultCode = GetAppletFooterUiTypeImpl(context, out AppletFooterUiType appletFooterUiType);
|
||||
|
||||
context.ResponseData.Write((byte)appletFooterUiType);
|
||||
context.ResponseData.Write((byte)0);
|
||||
|
||||
return resultCode;
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(307)]
|
||||
|
||||
@@ -23,6 +23,9 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
{
|
||||
class IUserLocalCommunicationService : IpcService, IDisposable
|
||||
{
|
||||
public static string DefaultLanPlayHost = "ryuldn.vudjun.com";
|
||||
public static short LanPlayPort = 30456;
|
||||
|
||||
public INetworkClient NetworkClient { get; private set; }
|
||||
|
||||
private const int NifmRequestID = 90;
|
||||
@@ -1089,18 +1092,20 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
case MultiplayerMode.LdnRyu:
|
||||
try
|
||||
{
|
||||
string ldnServer = context.Device.Configuration.MultiplayerLdnServer
|
||||
?? throw new InvalidOperationException("Cannot initialize RyuLDN with a null Multiplayer server.");
|
||||
|
||||
string ldnServer = context.Device.Configuration.MultiplayerLdnServer;
|
||||
if (string.IsNullOrEmpty(ldnServer))
|
||||
{
|
||||
ldnServer = DefaultLanPlayHost;
|
||||
}
|
||||
if (!IPAddress.TryParse(ldnServer, out IPAddress ipAddress))
|
||||
{
|
||||
ipAddress = Dns.GetHostEntry(ldnServer).AddressList[0];
|
||||
}
|
||||
NetworkClient = new LdnMasterProxyClient(ipAddress.ToString(), SharedConstants.LanPlayPort, context.Device.Configuration);
|
||||
NetworkClient = new LdnMasterProxyClient(ipAddress.ToString(), LanPlayPort, context.Device.Configuration);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.ServiceLdn, "Could not locate RyuLDN server. Defaulting to stubbed wireless.");
|
||||
Logger.Error?.Print(LogClass.ServiceLdn, "Could not locate LdnRyu server. Defaulting to stubbed wireless.");
|
||||
Logger.Error?.Print(LogClass.ServiceLdn, ex.Message);
|
||||
NetworkClient = new LdnDisabledClient();
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Ipc;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
@@ -105,6 +106,26 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(6)]
|
||||
// SetRequirementPreset(u32)
|
||||
public ResultCode SetRequirementPreset(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceNifm);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(9)]
|
||||
// SetNetworkProfileId(nn::util::Uuid)
|
||||
public ResultCode SetNetworkProfileId(ServiceCtx context)
|
||||
{
|
||||
UInt128 uuid = context.RequestData.ReadStruct<UInt128>();
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceNifm, new { uuid });
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(11)]
|
||||
// SetConnectionConfirmationOption(i8)
|
||||
public ResultCode SetConnectionConfirmationOption(ServiceCtx context)
|
||||
@@ -142,5 +163,38 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(23)]
|
||||
// SetKeptInSleep(bool)
|
||||
public ResultCode SetKeptInSleep(ServiceCtx context)
|
||||
{
|
||||
bool keptInSleep = context.RequestData.ReadBoolean();
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceNifm, new { keptInSleep });
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(24)]
|
||||
// RegisterSocketDescriptor(s32)
|
||||
public ResultCode RegisterSocketDescriptor(ServiceCtx context)
|
||||
{
|
||||
int socketDescriptor = context.RequestData.ReadInt32();
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceNifm, new { socketDescriptor });
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(25)]
|
||||
// UnregisterSocketDescriptor(s32)
|
||||
public ResultCode UnregisterSocketDescriptor(ServiceCtx context)
|
||||
{
|
||||
int socketDescriptor = context.RequestData.ReadInt32();
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceNifm, new { socketDescriptor });
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,8 +245,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu
|
||||
}
|
||||
}
|
||||
|
||||
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, arguments.NvMapHandle);
|
||||
|
||||
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(arguments.NvMapHandle);
|
||||
|
||||
if (map == null)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid NvMap handle 0x{arguments.NvMapHandle:x8}!");
|
||||
@@ -282,7 +282,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu
|
||||
{
|
||||
if (_asContext.ValidateFixedBuffer(arguments.Offset, size, pageSize))
|
||||
{
|
||||
_asContext.Gmm.Map(physicalAddress, arguments.Offset, size, (PteKind)arguments.Kind);
|
||||
Map(physicalAddress, arguments.Offset, size, (PteKind)arguments.Kind, map.OwnerPid);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -301,7 +301,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu
|
||||
_memoryAllocator.AllocateRange(va, size, freeAddressStartPosition);
|
||||
}
|
||||
|
||||
_asContext.Gmm.Map(physicalAddress, va, size, (PteKind)arguments.Kind);
|
||||
Map(physicalAddress, va, size, (PteKind)arguments.Kind, map.OwnerPid);
|
||||
arguments.Offset = va;
|
||||
}
|
||||
|
||||
@@ -380,8 +380,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu
|
||||
ulong mapOffs = (ulong)argument.MapOffset << 16;
|
||||
PteKind kind = (PteKind)argument.Kind;
|
||||
|
||||
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, nvmapHandle);
|
||||
|
||||
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(nvmapHandle);
|
||||
|
||||
if (map == null)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid NvMap handle 0x{nvmapHandle:x8}!");
|
||||
@@ -389,13 +389,25 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu
|
||||
return NvInternalResult.InvalidInput;
|
||||
}
|
||||
|
||||
gmm.Map(mapOffs + map.Address, gpuVa, size, kind);
|
||||
Map(mapOffs + map.Address, gpuVa, size, kind, map.OwnerPid);
|
||||
}
|
||||
}
|
||||
|
||||
return NvInternalResult.Success;
|
||||
}
|
||||
|
||||
private void Map(ulong pa, ulong va, ulong size, PteKind kind, ulong ownerPid)
|
||||
{
|
||||
if (Owner == ownerPid)
|
||||
{
|
||||
_asContext.Gmm.Map(pa, va, size, kind);
|
||||
}
|
||||
else
|
||||
{
|
||||
_asContext.Gmm.MapForeign(pa, va, size, kind, ownerPid);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Close() { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,8 +167,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
|
||||
|
||||
foreach (CommandBuffer commandBuffer in commandBuffers)
|
||||
{
|
||||
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, commandBuffer.Mem);
|
||||
|
||||
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(commandBuffer.Mem);
|
||||
|
||||
ReadOnlySpan<byte> data = _memory.GetSpan(map.Address + commandBuffer.Offset, commandBuffer.WordsCount * 4);
|
||||
|
||||
_host1xContext.Host1x.Submit(MemoryMarshal.Cast<byte, int>(data), _contextId);
|
||||
@@ -237,8 +237,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
|
||||
|
||||
foreach (ref CommandBufferHandle commandBufferEntry in commandBufferEntries)
|
||||
{
|
||||
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, commandBufferEntry.MapHandle);
|
||||
|
||||
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(commandBufferEntry.MapHandle);
|
||||
|
||||
if (map == null)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{commandBufferEntry.MapHandle:x8}!");
|
||||
@@ -279,8 +279,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
|
||||
|
||||
foreach (ref CommandBufferHandle commandBufferEntry in commandBufferEntries)
|
||||
{
|
||||
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, commandBufferEntry.MapHandle);
|
||||
|
||||
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(commandBufferEntry.MapHandle);
|
||||
|
||||
if (map == null)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{commandBufferEntry.MapHandle:x8}!");
|
||||
|
||||
@@ -71,8 +71,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
||||
|
||||
uint size = BitUtils.AlignUp(arguments.Size, (uint)MemoryManager.PageSize);
|
||||
|
||||
arguments.Handle = CreateHandleFromMap(new NvMapHandle(size));
|
||||
|
||||
arguments.Handle = CreateHandleFromMap(new NvMapHandle(size) { OwnerPid = Owner });
|
||||
|
||||
Logger.Debug?.Print(LogClass.ServiceNv, $"Created map {arguments.Handle} with size 0x{size:x8}!");
|
||||
|
||||
return NvInternalResult.Success;
|
||||
@@ -80,8 +80,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
||||
|
||||
private NvInternalResult FromId(ref NvMapFromId arguments)
|
||||
{
|
||||
NvMapHandle map = GetMapFromHandle(Owner, arguments.Id);
|
||||
|
||||
NvMapHandle map = GetMapFromHandle(arguments.Id);
|
||||
|
||||
if (map == null)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!");
|
||||
@@ -98,8 +98,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
||||
|
||||
private NvInternalResult Alloc(ref NvMapAlloc arguments)
|
||||
{
|
||||
NvMapHandle map = GetMapFromHandle(Owner, arguments.Handle);
|
||||
|
||||
NvMapHandle map = GetMapFromHandle(arguments.Handle);
|
||||
|
||||
if (map == null)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!");
|
||||
@@ -152,8 +152,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
||||
|
||||
private NvInternalResult Free(ref NvMapFree arguments)
|
||||
{
|
||||
NvMapHandle map = GetMapFromHandle(Owner, arguments.Handle);
|
||||
|
||||
NvMapHandle map = GetMapFromHandle(arguments.Handle);
|
||||
|
||||
if (map == null)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!");
|
||||
@@ -179,8 +179,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
||||
|
||||
private NvInternalResult Param(ref NvMapParam arguments)
|
||||
{
|
||||
NvMapHandle map = GetMapFromHandle(Owner, arguments.Handle);
|
||||
|
||||
NvMapHandle map = GetMapFromHandle(arguments.Handle);
|
||||
|
||||
if (map == null)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!");
|
||||
@@ -217,8 +217,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
||||
|
||||
private NvInternalResult GetId(ref NvMapGetId arguments)
|
||||
{
|
||||
NvMapHandle map = GetMapFromHandle(Owner, arguments.Handle);
|
||||
|
||||
NvMapHandle map = GetMapFromHandle(arguments.Handle);
|
||||
|
||||
if (map == null)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!");
|
||||
@@ -237,25 +237,30 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
||||
// _maps.TryRemove(GetOwner(), out _);
|
||||
}
|
||||
|
||||
private int CreateHandleFromMap(NvMapHandle map)
|
||||
public static int CreateMap(ulong pid, ulong address, uint size)
|
||||
{
|
||||
return CreateHandleFromMap(new NvMapHandle(size) { Address = address, OwnerPid = pid });
|
||||
}
|
||||
|
||||
private static int CreateHandleFromMap(NvMapHandle map)
|
||||
{
|
||||
return _maps.Add(map);
|
||||
}
|
||||
|
||||
private static bool DeleteMapWithHandle(ulong pid, int handle)
|
||||
private static bool DeleteMapWithHandle(int handle)
|
||||
{
|
||||
return _maps.Delete(handle) != null;
|
||||
}
|
||||
|
||||
public static void IncrementMapRefCount(ulong pid, int handle)
|
||||
{
|
||||
GetMapFromHandle(pid, handle)?.IncrementRefCount();
|
||||
GetMapFromHandle(handle)?.IncrementRefCount();
|
||||
}
|
||||
|
||||
public static bool DecrementMapRefCount(ulong pid, int handle)
|
||||
{
|
||||
NvMapHandle map = GetMapFromHandle(pid, handle);
|
||||
|
||||
NvMapHandle map = GetMapFromHandle(handle);
|
||||
|
||||
if (map == null)
|
||||
{
|
||||
return false;
|
||||
@@ -263,8 +268,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
||||
|
||||
if (map.DecrementRefCount() <= 0)
|
||||
{
|
||||
DeleteMapWithHandle(pid, handle);
|
||||
|
||||
DeleteMapWithHandle(handle);
|
||||
|
||||
Logger.Debug?.Print(LogClass.ServiceNv, $"Deleted map {handle}!");
|
||||
|
||||
return true;
|
||||
@@ -275,7 +280,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
||||
}
|
||||
}
|
||||
|
||||
public static NvMapHandle GetMapFromHandle(ulong pid, int handle)
|
||||
public static NvMapHandle GetMapFromHandle(int handle)
|
||||
{
|
||||
return _maps.Get(handle);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
||||
public ulong Address;
|
||||
public bool Allocated;
|
||||
public ulong DmaMapAddress;
|
||||
|
||||
public ulong OwnerPid;
|
||||
|
||||
private long _dupes;
|
||||
|
||||
public NvMapHandle()
|
||||
|
||||
@@ -172,6 +172,15 @@ namespace Ryujinx.HLE.HOS.Services
|
||||
{
|
||||
ServerLoop();
|
||||
}
|
||||
|
||||
protected virtual ulong CalculateRequiredHeapSize()
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
protected virtual void CustomInit(KernelContext context, ulong pid, ulong heapAddress)
|
||||
{
|
||||
}
|
||||
|
||||
private void ServerLoop()
|
||||
{
|
||||
@@ -196,10 +205,14 @@ namespace Ryujinx.HLE.HOS.Services
|
||||
Result result = _selfProcess.HandleTable.GenerateHandle(_wakeEvent.ReadableEvent, out _wakeHandle);
|
||||
|
||||
InitDone.Set();
|
||||
|
||||
ulong heapSize = CalculateRequiredHeapSize() + PointerBufferSize;
|
||||
|
||||
ulong messagePtr = _selfThread.TlsAddress;
|
||||
_context.Syscall.SetHeapSize(out ulong heapAddr, 0x200000);
|
||||
|
||||
_context.Syscall.SetHeapSize(out ulong heapAddr, BitUtils.AlignUp(heapSize, 0x200000UL));
|
||||
|
||||
CustomInit(_context, _selfProcess.Pid, heapAddr + PointerBufferSize);
|
||||
|
||||
_selfProcess.CpuMemory.Write(messagePtr + 0x0, 0);
|
||||
_selfProcess.CpuMemory.Write(messagePtr + 0x4, 2 << 10);
|
||||
_selfProcess.CpuMemory.Write(messagePtr + 0x8, heapAddr | ((ulong)PointerBufferSize << 48));
|
||||
|
||||
@@ -4,6 +4,7 @@ using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.PreciseSleep;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu;
|
||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -38,8 +39,6 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
|
||||
private readonly Lock _lock = new();
|
||||
|
||||
public long RenderLayerId { get; private set; }
|
||||
|
||||
private class Layer
|
||||
{
|
||||
public int ProducerBinderId;
|
||||
@@ -60,7 +59,6 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
_device = device;
|
||||
_layers = new Dictionary<long, Layer>();
|
||||
RenderLayerId = 0;
|
||||
|
||||
_composerThread = new Thread(HandleComposition)
|
||||
{
|
||||
@@ -239,34 +237,12 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
|
||||
private void CloseLayer(long layerId, Layer layer)
|
||||
{
|
||||
// If the layer was removed and the current in use, we need to change the current layer in use.
|
||||
if (RenderLayerId == layerId)
|
||||
{
|
||||
// If no layer is availaible, reset to default value.
|
||||
if (_layers.Count == 0)
|
||||
{
|
||||
SetRenderLayer(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetRenderLayer(_layers.Last().Key);
|
||||
}
|
||||
}
|
||||
|
||||
if (layer.State == LayerState.ManagedOpened)
|
||||
{
|
||||
layer.State = LayerState.ManagedClosed;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetRenderLayer(long layerId)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
RenderLayerId = layerId;
|
||||
}
|
||||
}
|
||||
|
||||
private Layer GetLayerByIdLocked(long layerId)
|
||||
{
|
||||
foreach (KeyValuePair<long, Layer> pair in _layers)
|
||||
@@ -360,41 +336,55 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
// TODO: support multilayers (& multidisplay ?)
|
||||
if (RenderLayerId == 0)
|
||||
foreach (var (layerId, layer) in _layers)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Layer layer = GetLayerByIdLocked(RenderLayerId);
|
||||
|
||||
Status acquireStatus = layer.Consumer.AcquireBuffer(out BufferItem item, 0);
|
||||
|
||||
if (acquireStatus == Status.Success)
|
||||
{
|
||||
if (_device.VSyncMode == VSyncMode.Unbounded)
|
||||
if (layer.State == LayerState.NotInitialized || layer.State == LayerState.ManagedClosed)
|
||||
continue;
|
||||
|
||||
if (_device.System.KernelContext.Processes.TryGetValue(layer.Owner, out var process))
|
||||
{
|
||||
if (_swapInterval != 0)
|
||||
if (process.State == ProcessState.Exiting || process.State == ProcessState.Exited)
|
||||
{
|
||||
UpdateSwapInterval(0);
|
||||
_vSyncMode = _device.VSyncMode;
|
||||
HOSBinderDriverServer.UnregisterBinderObject(layer.ProducerBinderId);
|
||||
|
||||
if (_layers.Remove(layerId))
|
||||
{
|
||||
CloseLayer(layerId, layer);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (_device.VSyncMode != _vSyncMode)
|
||||
|
||||
Status acquireStatus = layer.Consumer.AcquireBuffer(out BufferItem item, 0);
|
||||
|
||||
if (acquireStatus == Status.Success)
|
||||
{
|
||||
UpdateSwapInterval(_device.VSyncMode == VSyncMode.Unbounded ? 0 : item.SwapInterval);
|
||||
_vSyncMode = _device.VSyncMode;
|
||||
}
|
||||
else if (item.SwapInterval != _swapInterval || _device.TargetVSyncInterval != _targetVSyncInterval)
|
||||
{
|
||||
UpdateSwapInterval(item.SwapInterval);
|
||||
}
|
||||
if (_device.VSyncMode == VSyncMode.Unbounded)
|
||||
{
|
||||
if (_swapInterval != 0)
|
||||
{
|
||||
UpdateSwapInterval(0);
|
||||
_vSyncMode = _device.VSyncMode;
|
||||
}
|
||||
}
|
||||
else if (_device.VSyncMode != _vSyncMode)
|
||||
{
|
||||
UpdateSwapInterval(_device.VSyncMode == VSyncMode.Unbounded ? 0 : item.SwapInterval);
|
||||
_vSyncMode = _device.VSyncMode;
|
||||
}
|
||||
else if (item.SwapInterval != _swapInterval || _device.TargetVSyncInterval != _targetVSyncInterval)
|
||||
{
|
||||
UpdateSwapInterval(item.SwapInterval);
|
||||
}
|
||||
|
||||
PostFrameBuffer(layer, item);
|
||||
}
|
||||
else if (acquireStatus != Status.NoBufferAvailaible && acquireStatus != Status.InvalidOperation)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
PostFrameBuffer(layer, item);
|
||||
}
|
||||
else if (acquireStatus != Status.NoBufferAvailaible && acquireStatus != Status.InvalidOperation)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.SurfaceFlinger, $"Failed to acquire buffer for layer {layerId} (status: {acquireStatus})");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -413,8 +403,8 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
|
||||
ulong bufferOffset = (ulong)item.GraphicBuffer.Object.Buffer.Surfaces[0].Offset;
|
||||
|
||||
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(layer.Owner, nvMapHandle);
|
||||
|
||||
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(nvMapHandle);
|
||||
|
||||
ulong frameBufferAddress = map.Address + bufferOffset;
|
||||
|
||||
Format format = ConvertColorFormat(item.GraphicBuffer.Object.Buffer.Surfaces[0].ColorFormat);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
@@ -36,6 +37,6 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
public int PlanesCount;
|
||||
|
||||
[FieldOffset(0x34)]
|
||||
public NvGraphicBufferSurfaceArray Surfaces;
|
||||
public Array3<NvGraphicBufferSurface> Surfaces;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,6 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService
|
||||
ulong pid = context.Device.System.AppletState.AppletResourceUserIds.GetData<ulong>((int)appletResourceUserId);
|
||||
|
||||
context.Device.System.SurfaceFlinger.CreateLayer(out long layerId, pid);
|
||||
context.Device.System.SurfaceFlinger.SetRenderLayer(layerId);
|
||||
|
||||
context.ResponseData.Write(layerId);
|
||||
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Ipc;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.HLE.HOS.Services.SurfaceFlinger;
|
||||
using Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService.Types.Fbshare;
|
||||
using Ryujinx.Horizon.Common;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService
|
||||
{
|
||||
@@ -7,6 +14,9 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService
|
||||
#pragma warning disable IDE0052 // Remove unread private member
|
||||
private readonly IApplicationDisplayService _applicationDisplayService;
|
||||
#pragma warning restore IDE0052
|
||||
|
||||
private KEvent _sharedFramebufferAcquirableEvent;
|
||||
private int _sharedFramebufferAcquirableEventHandle;
|
||||
|
||||
public ISystemDisplayService(IApplicationDisplayService applicationDisplayService)
|
||||
{
|
||||
@@ -57,5 +67,138 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(8225)] // 4.0.0+
|
||||
// GetSharedBufferMemoryHandleId()
|
||||
public ResultCode GetSharedBufferMemoryHandleId(ServiceCtx context)
|
||||
{
|
||||
context.ResponseData.Write((ulong)context.Device.System.ViServerS.GetSharedBufferNvMapId());
|
||||
context.ResponseData.Write(context.Device.System.ViServerS.GetSharedBufferSize());
|
||||
|
||||
(ulong mapAddress, ulong mapSize) = context.Request.GetBufferType0x22();
|
||||
|
||||
context.Memory.Write(mapAddress, context.Device.System.ViServerS.GetSharedBufferMap());
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(8250)] // 4.0.0+
|
||||
// OpenSharedLayer(nn::vi::fbshare::SharedLayerHandle, nn::applet::AppletResourceUserId, pid)
|
||||
public ResultCode OpenSharedLayer(ServiceCtx context)
|
||||
{
|
||||
long sharedLayerHandle = context.RequestData.ReadInt64();
|
||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||
|
||||
context.Device.System.ViServerS.OpenSharedLayer(sharedLayerHandle);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(8251)] // 4.0.0+
|
||||
// CloseSharedLayer(nn::vi::fbshare::SharedLayerHandle)
|
||||
public ResultCode CloseSharedLayer(ServiceCtx context)
|
||||
{
|
||||
long sharedLayerHandle = context.RequestData.ReadInt64();
|
||||
|
||||
context.Device.System.ViServerS.CloseSharedLayer(sharedLayerHandle);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(8252)] // 4.0.0+
|
||||
// ConnectSharedLayer(nn::vi::fbshare::SharedLayerHandle)
|
||||
public ResultCode ConnectSharedLayer(ServiceCtx context)
|
||||
{
|
||||
long sharedLayerHandle = context.RequestData.ReadInt64();
|
||||
|
||||
context.Device.System.ViServerS.ConnectSharedLayer(sharedLayerHandle);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(8253)] // 4.0.0+
|
||||
// DisconnectSharedLayer(nn::vi::fbshare::SharedLayerHandle)
|
||||
public ResultCode DisconnectSharedLayer(ServiceCtx context)
|
||||
{
|
||||
long sharedLayerHandle = context.RequestData.ReadInt64();
|
||||
|
||||
context.Device.System.ViServerS.DisconnectSharedLayer(sharedLayerHandle);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(8254)] // 4.0.0+
|
||||
// AcquireSharedFrameBuffer(nn::vi::fbshare::SharedLayerHandle) -> (nn::vi::native::NativeSync, nn::vi::fbshare::SharedLayerTextureIndexList, u64)
|
||||
public ResultCode AcquireSharedFrameBuffer(ServiceCtx context)
|
||||
{
|
||||
long sharedLayerHandle = context.RequestData.ReadInt64();
|
||||
|
||||
int slot = context.Device.System.ViServerS.DequeueFrameBuffer(sharedLayerHandle, out AndroidFence fence);
|
||||
|
||||
var indexList = new SharedLayerTextureIndexList();
|
||||
|
||||
for (int i = 0; i < indexList.Indices.Length; i++)
|
||||
{
|
||||
indexList.Indices[i] = context.Device.System.ViServerS.GetFrameBufferMapIndex(i);
|
||||
}
|
||||
|
||||
context.ResponseData.WriteStruct(fence);
|
||||
context.ResponseData.WriteStruct(indexList);
|
||||
context.ResponseData.Write(0); // Padding
|
||||
context.ResponseData.Write((ulong)slot);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(8255)] // 4.0.0+
|
||||
// PresentSharedFrameBuffer(nn::vi::native::NativeSync, nn::vi::CropRegion, u32, u32, nn::vi::fbshare::SharedLayerHandle, u64)
|
||||
public ResultCode PresentSharedFrameBuffer(ServiceCtx context)
|
||||
{
|
||||
AndroidFence nativeSync = context.RequestData.ReadStruct<AndroidFence>();
|
||||
Rect cropRegion = context.RequestData.ReadStruct<Rect>();
|
||||
|
||||
NativeWindowTransform transform = (NativeWindowTransform)context.RequestData.ReadUInt32();
|
||||
int swapInterval = context.RequestData.ReadInt32();
|
||||
int padding = context.RequestData.ReadInt32();
|
||||
|
||||
long sharedLayerHandle = context.RequestData.ReadInt64();
|
||||
ulong slot = context.RequestData.ReadUInt64();
|
||||
|
||||
context.Device.System.ViServerS.QueueFrameBuffer(sharedLayerHandle, (int)slot, cropRegion, transform, swapInterval, nativeSync);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(8256)] // 4.0.0+
|
||||
// GetSharedFrameBufferAcquirableEvent(nn::vi::fbshare::SharedLayerHandle) -> handle<copy>
|
||||
public ResultCode GetSharedFrameBufferAcquirableEvent(ServiceCtx context)
|
||||
{
|
||||
if (_sharedFramebufferAcquirableEventHandle == 0)
|
||||
{
|
||||
_sharedFramebufferAcquirableEvent = new KEvent(context.Device.System.KernelContext);
|
||||
_sharedFramebufferAcquirableEvent.WritableEvent.Signal();
|
||||
|
||||
if (context.Process.HandleTable.GenerateHandle(_sharedFramebufferAcquirableEvent.ReadableEvent, out _sharedFramebufferAcquirableEventHandle) != Result.Success)
|
||||
{
|
||||
throw new InvalidOperationException("Out of handles!");
|
||||
}
|
||||
}
|
||||
|
||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_sharedFramebufferAcquirableEventHandle);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(8258)] // 5.0.0+
|
||||
// CancelSharedFrameBuffer(nn::vi::fbshare::SharedLayerHandle, u64)
|
||||
public ResultCode CancelSharedFrameBuffer(ServiceCtx context)
|
||||
{
|
||||
long sharedLayerHandle = context.RequestData.ReadInt64();
|
||||
ulong slot = context.RequestData.ReadUInt64();
|
||||
|
||||
context.Device.System.ViServerS.CancelFrameBuffer(sharedLayerHandle, (int)slot);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService.Types.Fbshare
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x4)]
|
||||
struct SharedLayerTextureIndexList
|
||||
{
|
||||
public Array4<int> Indices;
|
||||
}
|
||||
}
|
||||
@@ -248,8 +248,6 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
|
||||
return result;
|
||||
}
|
||||
|
||||
context.Device.System.SurfaceFlinger.SetRenderLayer(layerId);
|
||||
|
||||
using Parcel parcel = new(0x28, 0x4);
|
||||
|
||||
parcel.WriteObject(producer, "dispdrv\0");
|
||||
@@ -285,9 +283,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
|
||||
|
||||
// TODO: support multi display.
|
||||
IBinder producer = context.Device.System.SurfaceFlinger.CreateLayer(out long layerId, 0, LayerState.Stray);
|
||||
|
||||
context.Device.System.SurfaceFlinger.SetRenderLayer(layerId);
|
||||
|
||||
|
||||
using Parcel parcel = new(0x28, 0x4);
|
||||
|
||||
parcel.WriteObject(producer, "dispdrv\0");
|
||||
|
||||
19
src/Ryujinx.HLE/HOS/Services/Vi/Types/SharedBufferMap.cs
Normal file
19
src/Ryujinx.HLE/HOS/Services/Vi/Types/SharedBufferMap.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Vi.Types
|
||||
{
|
||||
struct SharedBufferMap
|
||||
{
|
||||
public struct Entry
|
||||
{
|
||||
public ulong Offset;
|
||||
public ulong Size;
|
||||
public uint Width;
|
||||
public uint Height;
|
||||
}
|
||||
|
||||
public int Count;
|
||||
public int Padding;
|
||||
public Array16<Entry> SharedBuffers;
|
||||
}
|
||||
}
|
||||
235
src/Ryujinx.HLE/HOS/Services/Vi/ViServer.cs
Normal file
235
src/Ryujinx.HLE/HOS/Services/Vi/ViServer.cs
Normal file
@@ -0,0 +1,235 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Graphics.Gpu;
|
||||
using Ryujinx.HLE.HOS.Kernel;
|
||||
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap;
|
||||
using Ryujinx.HLE.HOS.Services.SurfaceFlinger;
|
||||
using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Vi.Types;
|
||||
using Ryujinx.Memory;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services
|
||||
{
|
||||
class ViServer : ServerBase
|
||||
{
|
||||
private const int TotalFramebuffers = 16;
|
||||
|
||||
private SurfaceFlinger.SurfaceFlinger _surfaceFlinger;
|
||||
|
||||
private readonly uint _fbWidth;
|
||||
private readonly uint _fbHeight;
|
||||
private readonly PixelFormat _fbFormat;
|
||||
private readonly int _fbUsage;
|
||||
private readonly uint _fbCount;
|
||||
private uint _fbSlotsRequested;
|
||||
|
||||
private ulong _pid;
|
||||
private ulong _fbsBaseAddress;
|
||||
private int _bufferNvMapId;
|
||||
private SharedBufferMap _bufferMap;
|
||||
private long _sharedLayerId;
|
||||
|
||||
public ViServer(KernelContext context, string name) : base(context, name)
|
||||
{
|
||||
_fbWidth = 1280;
|
||||
_fbHeight = 720;
|
||||
_fbFormat = PixelFormat.Rgba8888;
|
||||
_fbUsage = 0x100 | 0x200 | 0x800;
|
||||
_fbCount = 4;
|
||||
}
|
||||
|
||||
protected override ulong CalculateRequiredHeapSize()
|
||||
{
|
||||
return GetSharedBufferSize();
|
||||
}
|
||||
|
||||
protected override void CustomInit(KernelContext context, ulong pid, ulong heapAddress)
|
||||
{
|
||||
_pid = pid;
|
||||
_fbsBaseAddress = heapAddress;
|
||||
|
||||
context.Device.Gpu.RegisterProcess(pid, KernelStatic.GetCurrentProcess().CpuMemory);
|
||||
|
||||
ulong bufferSize = CalculateFramebufferSize();
|
||||
ulong totalSize = bufferSize * TotalFramebuffers;
|
||||
|
||||
KernelStatic.GetCurrentProcess().CpuMemory.Fill(heapAddress, totalSize, 0xff);
|
||||
|
||||
int mapId = NvMapDeviceFile.CreateMap(pid, heapAddress, (uint)totalSize);
|
||||
|
||||
_bufferNvMapId = mapId;
|
||||
_bufferMap.Count = TotalFramebuffers;
|
||||
|
||||
ulong offset = 0;
|
||||
|
||||
for (int i = 0; i < TotalFramebuffers; i++)
|
||||
{
|
||||
_bufferMap.SharedBuffers[i].Offset = offset;
|
||||
_bufferMap.SharedBuffers[i].Size = bufferSize;
|
||||
_bufferMap.SharedBuffers[i].Width = _fbWidth;
|
||||
_bufferMap.SharedBuffers[i].Height = _fbHeight;
|
||||
|
||||
offset += bufferSize;
|
||||
}
|
||||
|
||||
_surfaceFlinger = context.Device.System.SurfaceFlinger;
|
||||
_surfaceFlinger.CreateLayer(out _sharedLayerId, pid);
|
||||
}
|
||||
|
||||
public void OpenSharedLayer(long layerId)
|
||||
{
|
||||
_surfaceFlinger.OpenLayer(_pid, layerId, out _);
|
||||
}
|
||||
|
||||
public void CloseSharedLayer(long layerId)
|
||||
{
|
||||
_surfaceFlinger.CloseLayer(layerId);
|
||||
}
|
||||
|
||||
public void ConnectSharedLayer(long layerId)
|
||||
{
|
||||
IGraphicBufferProducer producer = _surfaceFlinger.GetProducerByLayerId(layerId);
|
||||
|
||||
producer.Connect(null, NativeWindowApi.NVN, false, out IGraphicBufferProducer.QueueBufferOutput output);
|
||||
|
||||
GraphicBuffer graphicBuffer = new GraphicBuffer();
|
||||
|
||||
int gobHeightLog2 = 4;
|
||||
int blockHeight = 8 * (1 << gobHeightLog2);
|
||||
uint widthAlignedBytes = BitUtils.AlignUp(_fbWidth * 4, 64u);
|
||||
uint widthAligned = widthAlignedBytes / 4;
|
||||
uint heightAligned = BitUtils.AlignUp(_fbHeight, (uint)blockHeight);
|
||||
uint totalSize = widthAlignedBytes * heightAligned;
|
||||
|
||||
graphicBuffer.Header.Magic = 0x47424652;
|
||||
graphicBuffer.Header.Width = (int)_fbWidth;
|
||||
graphicBuffer.Header.Height = (int)_fbHeight;
|
||||
graphicBuffer.Header.Stride = (int)widthAligned;
|
||||
graphicBuffer.Header.Format = _fbFormat;
|
||||
graphicBuffer.Header.Usage = _fbUsage;
|
||||
graphicBuffer.Header.IntsCount = (Unsafe.SizeOf<GraphicBuffer>() - Unsafe.SizeOf<GraphicBufferHeader>()) / sizeof(int);
|
||||
graphicBuffer.Buffer.NvMapId = _bufferNvMapId;
|
||||
graphicBuffer.Buffer.Magic = unchecked((int)0xDAFFCAFF);
|
||||
graphicBuffer.Buffer.Pid = 42;
|
||||
graphicBuffer.Buffer.Usage = _fbUsage;
|
||||
graphicBuffer.Buffer.PixelFormat = (int)_fbFormat;
|
||||
graphicBuffer.Buffer.ExternalPixelFormat = (int)_fbFormat;
|
||||
graphicBuffer.Buffer.Stride = (int)widthAligned;
|
||||
graphicBuffer.Buffer.FrameBufferSize = (int)totalSize;
|
||||
graphicBuffer.Buffer.PlanesCount = 1;
|
||||
graphicBuffer.Buffer.Surfaces[0].Width = _fbWidth;
|
||||
graphicBuffer.Buffer.Surfaces[0].Height = _fbHeight;
|
||||
graphicBuffer.Buffer.Surfaces[0].ColorFormat = ColorFormat.A8B8G8R8;
|
||||
graphicBuffer.Buffer.Surfaces[0].Layout = 3; // Block linear
|
||||
graphicBuffer.Buffer.Surfaces[0].Pitch = (int)widthAlignedBytes;
|
||||
graphicBuffer.Buffer.Surfaces[0].Kind = 0xfe; // Generic 16Bx2
|
||||
graphicBuffer.Buffer.Surfaces[0].BlockHeightLog2 = gobHeightLog2;
|
||||
graphicBuffer.Buffer.Surfaces[0].Size = (int)totalSize;
|
||||
|
||||
for (int slot = 0; slot < _fbCount; slot++)
|
||||
{
|
||||
graphicBuffer.Buffer.Surfaces[0].Offset = slot * (int)totalSize;
|
||||
|
||||
producer.SetPreallocatedBuffer(slot, new AndroidStrongPointer<GraphicBuffer>(graphicBuffer));
|
||||
}
|
||||
|
||||
_fbSlotsRequested = 0;
|
||||
}
|
||||
|
||||
public void DisconnectSharedLayer(long layerId)
|
||||
{
|
||||
IGraphicBufferProducer producer = _surfaceFlinger.GetProducerByLayerId(layerId);
|
||||
|
||||
producer.Disconnect(NativeWindowApi.NVN);
|
||||
}
|
||||
|
||||
public int DequeueFrameBuffer(long layerId, out AndroidFence fence)
|
||||
{
|
||||
IGraphicBufferProducer producer = _surfaceFlinger.GetProducerByLayerId(layerId);
|
||||
|
||||
Status status = producer.DequeueBuffer(out int slot, out fence, false, _fbWidth, _fbHeight, _fbFormat, (uint)_fbUsage);
|
||||
|
||||
if (status == Status.Success)
|
||||
{
|
||||
if ((_fbSlotsRequested & (1u << slot)) == 0)
|
||||
{
|
||||
status = producer.RequestBuffer(slot, out _);
|
||||
|
||||
if (status != Status.Success)
|
||||
{
|
||||
producer.CancelBuffer(slot, ref fence);
|
||||
}
|
||||
|
||||
_fbSlotsRequested |= 1u << slot;
|
||||
}
|
||||
}
|
||||
|
||||
return slot;
|
||||
}
|
||||
|
||||
public void QueueFrameBuffer(long layerId, int slot, Rect crop, NativeWindowTransform transform, int swapInterval, AndroidFence fence)
|
||||
{
|
||||
IGraphicBufferProducer producer = _surfaceFlinger.GetProducerByLayerId(layerId);
|
||||
|
||||
IGraphicBufferProducer.QueueBufferInput input = new();
|
||||
|
||||
input.Crop = crop;
|
||||
input.Transform = transform;
|
||||
input.SwapInterval = swapInterval;
|
||||
input.Fence = fence;
|
||||
|
||||
Status status = producer.QueueBuffer(slot, ref input, out _);
|
||||
}
|
||||
|
||||
public void CancelFrameBuffer(long layerId, int slot)
|
||||
{
|
||||
IGraphicBufferProducer producer = _surfaceFlinger.GetProducerByLayerId(layerId);
|
||||
AndroidFence fence = default;
|
||||
|
||||
producer.CancelBuffer(slot, ref fence);
|
||||
}
|
||||
|
||||
public int GetFrameBufferMapIndex(int index)
|
||||
{
|
||||
return (uint)index < _fbCount ? index : 0;
|
||||
}
|
||||
|
||||
public int GetSharedBufferNvMapId()
|
||||
{
|
||||
return _bufferNvMapId;
|
||||
}
|
||||
|
||||
public ulong GetSharedBufferSize()
|
||||
{
|
||||
return CalculateFramebufferSize() * TotalFramebuffers;
|
||||
}
|
||||
|
||||
public long GetSharedLayerId()
|
||||
{
|
||||
return _sharedLayerId;
|
||||
}
|
||||
|
||||
private ulong CalculateFramebufferSize()
|
||||
{
|
||||
// Each GOB dimension is 512 bytes x 8 lines.
|
||||
// Assume 16 GOBs, for a total of 16 x 8 = 128 lines.
|
||||
return BitUtils.AlignUp(_fbWidth * 4, 512u) * BitUtils.AlignUp(_fbHeight, 128u);
|
||||
}
|
||||
|
||||
public SharedBufferMap GetSharedBufferMap()
|
||||
{
|
||||
return _bufferMap;
|
||||
}
|
||||
|
||||
public int GetApplicationLastPresentedFrameHandle(GpuContext gpuContext)
|
||||
{
|
||||
TextureData texture = gpuContext.Window.GetLastPresentedData();
|
||||
IVirtualMemoryManagerTracked selfAs = KernelStatic.GetProcessByPid(_pid).CpuMemory;
|
||||
int fbIndex = (int)_fbCount; // Place it after all our frame buffers.
|
||||
|
||||
selfAs.Write(_fbsBaseAddress + _bufferMap.SharedBuffers[fbIndex].Offset, texture.Data);
|
||||
|
||||
return fbIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
using LibHac.Common;
|
||||
using LibHac.Ncm;
|
||||
using LibHac.Ns;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using Ryujinx.Audio.Backends.CompatLayer;
|
||||
using Ryujinx.Audio.Integration;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Graphics.Gpu;
|
||||
using Ryujinx.HLE.Exceptions;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using Ryujinx.HLE.HOS.Services.Apm;
|
||||
@@ -158,5 +161,18 @@ namespace Ryujinx.HLE
|
||||
Shared = null;
|
||||
}
|
||||
}
|
||||
|
||||
public bool LoadSystemProgramId(ulong programId)
|
||||
{
|
||||
string contentPath = System.ContentManager.GetInstalledContentPath(programId, StorageId.BuiltInSystem, NcaContentType.Program);
|
||||
string filePath = VirtualFileSystem.SwitchPathToSystemPath(contentPath);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
{
|
||||
throw new InvalidSystemResourceException("Specified title ID is not installed on the system.");
|
||||
}
|
||||
|
||||
return Processes.LoadNca(filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ namespace Ryujinx.Input.SDL2
|
||||
byte blue = packedRgb > 0 ? (byte)(packedRgb % 256) : (byte)0;
|
||||
|
||||
if (SDL_GameControllerSetLED(_gamepadHandle, red, green, blue) != 0)
|
||||
Logger.Debug?.Print(LogClass.Hid, "LED setting failed; probably in the middle of disconnecting.");
|
||||
Logger.Error?.Print(LogClass.Hid, "LED setting failed; probably in the middle of disconnecting.");
|
||||
}
|
||||
|
||||
private GamepadFeaturesFlag GetFeaturesFlag()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Ryujinx.Memory.Range;
|
||||
using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
@@ -9,7 +10,7 @@ namespace Ryujinx.Memory
|
||||
/// Represents a address space manager.
|
||||
/// Supports virtual memory region mapping, address translation and read/write access to mapped regions.
|
||||
/// </summary>
|
||||
public sealed class AddressSpaceManager : VirtualMemoryManagerBase, IVirtualMemoryManager
|
||||
public sealed class AddressSpaceManager : VirtualMemoryManagerBase, IVirtualMemoryManagerTracked
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public bool UsesPrivateAllocations => false;
|
||||
@@ -21,6 +22,8 @@ namespace Ryujinx.Memory
|
||||
|
||||
private readonly MemoryBlock _backingMemory;
|
||||
private readonly PageTable<nuint> _pageTable;
|
||||
private readonly MemoryTracking _tracking;
|
||||
private bool _writeTracked;
|
||||
|
||||
protected override ulong AddressSpaceSize { get; }
|
||||
|
||||
@@ -44,6 +47,7 @@ namespace Ryujinx.Memory
|
||||
AddressSpaceSize = asSize;
|
||||
_backingMemory = backingMemory;
|
||||
_pageTable = new PageTable<nuint>();
|
||||
_tracking = new MemoryTracking(this, 0x1000);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -227,7 +231,7 @@ namespace Ryujinx.Memory
|
||||
/// <inheritdoc/>
|
||||
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest = false)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
_writeTracked = true;
|
||||
}
|
||||
|
||||
protected unsafe override Memory<byte> GetPhysicalAddressMemory(nuint pa, int size)
|
||||
@@ -240,5 +244,29 @@ namespace Ryujinx.Memory
|
||||
|
||||
protected override nuint TranslateVirtualAddressUnchecked(ulong va)
|
||||
=> GetHostAddress(va);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null)
|
||||
{
|
||||
if (_writeTracked)
|
||||
{
|
||||
_tracking.VirtualMemoryEvent(va, size, write, precise);
|
||||
}
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None)
|
||||
{
|
||||
return _tracking.BeginTracking(address, size, id, flags);
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id, RegionFlags flags = RegionFlags.None)
|
||||
{
|
||||
return _tracking.BeginGranularTracking(address, size, handles, granularity, id, flags);
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public SmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity, int id)
|
||||
{
|
||||
return _tracking.BeginSmartGranularTracking(address, size, granularity, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Cpu
|
||||
namespace Ryujinx.Memory
|
||||
{
|
||||
public interface IVirtualMemoryManagerTracked : IVirtualMemoryManager
|
||||
{
|
||||
@@ -41,6 +41,7 @@ using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Ryujinx.HLE.HOS.SystemState;
|
||||
using Ryujinx.HLE.Loaders.Processes;
|
||||
using Ryujinx.Input;
|
||||
using Ryujinx.Input.HLE;
|
||||
using SkiaSharp;
|
||||
@@ -672,6 +673,7 @@ namespace Ryujinx.Ava
|
||||
DiscordIntegrationModule.GuestAppStartedAt = Timestamps.Now;
|
||||
|
||||
InitEmulatedSwitch();
|
||||
Device.LoadSystemProgramId(0x010000000000100C);
|
||||
MainWindow.UpdateGraphicsConfig();
|
||||
|
||||
SystemVersion firmwareVersion = ContentManager.GetCurrentFirmwareVersion();
|
||||
@@ -951,7 +953,7 @@ namespace Ryujinx.Ava
|
||||
ConfigurationState.Instance.Multiplayer.Mode,
|
||||
ConfigurationState.Instance.Multiplayer.DisableP2p,
|
||||
ConfigurationState.Instance.Multiplayer.LdnPassphrase,
|
||||
ConfigurationState.Instance.Multiplayer.GetLdnServer(),
|
||||
ConfigurationState.Instance.Multiplayer.LdnServer,
|
||||
ConfigurationState.Instance.Graphics.CustomVSyncInterval.Value,
|
||||
ConfigurationState.Instance.Hacks.ShowDirtyHacks ? ConfigurationState.Instance.Hacks.EnabledHacks : null));
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@
|
||||
"sv_SE": "Direkt musåtkomst",
|
||||
"th_TH": "เข้าถึงเมาส์ได้โดยตรง",
|
||||
"tr_TR": "Doğrudan Mouse Erişimi",
|
||||
"uk_UA": "Пряме керування мишею",
|
||||
"uk_UA": "Прямий доступ мишею",
|
||||
"zh_CN": "直通鼠标操作",
|
||||
"zh_TW": "滑鼠直接存取"
|
||||
}
|
||||
@@ -417,7 +417,7 @@
|
||||
"sv_SE": "Läs in titeluppdateringar från mapp",
|
||||
"th_TH": "โหลดไฟล์อัพเดตจากโฟลเดอร์",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Завантажити оновлення ігор з теки",
|
||||
"uk_UA": "Завантажити оновлення заголовків з теки",
|
||||
"zh_CN": "从文件夹加载游戏更新",
|
||||
"zh_TW": "從資料夾中載入遊戲更新"
|
||||
}
|
||||
@@ -467,8 +467,8 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Відкрити теку скріншотів",
|
||||
"zh_CN": "打开截图文件夹",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
@@ -617,7 +617,7 @@
|
||||
"sv_SE": "Starta spel med dolt användargränssnitt",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Запускати ігри з прихованим інтерфейсом",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "启动游戏时隐藏 UI",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -1217,7 +1217,7 @@
|
||||
"sv_SE": "_Hjälp",
|
||||
"th_TH": "_ช่วยเหลือ",
|
||||
"tr_TR": "_Yardım",
|
||||
"uk_UA": "_Довідка",
|
||||
"uk_UA": "_Допомога",
|
||||
"zh_CN": "帮助(_H)",
|
||||
"zh_TW": "說明(_H)"
|
||||
}
|
||||
@@ -1567,7 +1567,7 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Розроблено: {0}",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "由 {0} 开发",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -1867,7 +1867,7 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Сумісність:",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "兼容性:",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -1892,7 +1892,7 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "ID гри:",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "标题 ID:",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -1917,7 +1917,7 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Розміщені ігри: {0}",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "服务的游戏: {0}",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -1942,7 +1942,7 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Гравців онлайн: {0}",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "在线玩家: {0}",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -2292,7 +2292,7 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Очистити кеш PPTC",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "清理 PPTC 缓存",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -2317,7 +2317,7 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Видаляє всі файли кешу PPTC для застосунку",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "删除应用程序的所有 PPTC 缓存",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -2642,7 +2642,7 @@
|
||||
"sv_SE": "Extrahera RomFS från en vald DLC-fil",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Витягти RomFS з обраного файлу DLC",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "从选定的 DLC 文件中解压 RomFS",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -2792,7 +2792,7 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Iнформація про сумісність",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "显示兼容性项目",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -2817,7 +2817,7 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Показати цю гру в Списку Сумісності. Список сумісності також можна зайти в меню Довідки.",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "在兼容性列表中显示选定的游戏,您通常可以通过帮助菜单访问。",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -2842,7 +2842,7 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Інформація про гру",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "显示游戏信息",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -2867,7 +2867,7 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Показати статистику та деталі обраної гри.",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "显示当前选定游戏的状态与详细信息。",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -3392,7 +3392,7 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Перевірка оновлень:",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "检查更新",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -3417,7 +3417,7 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Вимкнути",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "关闭",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -3442,7 +3442,7 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Запитувати щоразу",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "提示",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -3467,7 +3467,7 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Оновлювати в фоні",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "背景",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -3492,7 +3492,7 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "При втраті фокуса емулятором:",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "当模拟器在后台时:",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -3517,7 +3517,7 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Нічого не робити",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "什么事情也不做",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -3542,7 +3542,7 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Блокувати введення",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "禁用输入",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -3567,7 +3567,7 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Вимкнути звук",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "静音",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -3592,7 +3592,7 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Блокувати введення та Вимкнути звук",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "阻止输入且静音",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -3617,7 +3617,7 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Поставити на паузу",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "暂停模拟",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -3742,7 +3742,7 @@
|
||||
"sv_SE": "Dölj markör:",
|
||||
"th_TH": "ซ่อน เคอร์เซอร์:",
|
||||
"tr_TR": "İşaretçiyi Gizle:",
|
||||
"uk_UA": "Сховати курсор:",
|
||||
"uk_UA": "Сховати вказівник:",
|
||||
"zh_CN": "隐藏鼠标指针:",
|
||||
"zh_TW": "隱藏滑鼠游標:"
|
||||
}
|
||||
@@ -5142,8 +5142,8 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Ігнорувати Аплет Контролера",
|
||||
"zh_CN": "忽略控制器小程序",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
@@ -5992,7 +5992,7 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Увімкнути журнали інтерфейсу",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "启用 UI 日志",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -6017,7 +6017,7 @@
|
||||
"sv_SE": "Aktivera loggar för filsystemsåtkomst",
|
||||
"th_TH": "เปิดใช้งานการบันทึกประวัติการเข้าถึง Fs",
|
||||
"tr_TR": "Fs Erişim Loglarını Etkinleştir",
|
||||
"uk_UA": "Увімкнути журнали доступу до файлової системи",
|
||||
"uk_UA": "Увімкнути журнали доступу Fs",
|
||||
"zh_CN": "启用文件访问日志",
|
||||
"zh_TW": "啟用檔案系統存取日誌"
|
||||
}
|
||||
@@ -6042,7 +6042,7 @@
|
||||
"sv_SE": "Loggläge för global filsystemsåtkomst:",
|
||||
"th_TH": "โหมด การเข้าถึงประวัติส่วนกลาง:",
|
||||
"tr_TR": "Fs Evrensel Erişim Log Modu:",
|
||||
"uk_UA": "Режим журналу глобального доступу файлової системи:",
|
||||
"uk_UA": "Режим журналу глобального доступу Fs:",
|
||||
"zh_CN": "文件系统全局访问日志模式:",
|
||||
"zh_TW": "檔案系統全域存取日誌模式:"
|
||||
}
|
||||
@@ -6267,7 +6267,7 @@
|
||||
"sv_SE": "Inmatning",
|
||||
"th_TH": "ป้อนข้อมูล",
|
||||
"tr_TR": "Giriş Yöntemi",
|
||||
"uk_UA": "Керування",
|
||||
"uk_UA": "Введення",
|
||||
"zh_CN": "输入",
|
||||
"zh_TW": "輸入"
|
||||
}
|
||||
@@ -6392,7 +6392,7 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Скинути налаштування",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "重置设置",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -6417,7 +6417,7 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Я хочу скинути налаштування.",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "我要重置我的设置。",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -10692,7 +10692,7 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "Rehber",
|
||||
"uk_UA": "Меню",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "主页键",
|
||||
"zh_TW": "快顯功能表鍵"
|
||||
}
|
||||
@@ -10842,7 +10842,7 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Сенсорна панель",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "触摸板",
|
||||
"zh_TW": "觸控板"
|
||||
}
|
||||
@@ -13417,7 +13417,7 @@
|
||||
"sv_SE": "Amiibo-API",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "API Amiibo",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -13667,7 +13667,7 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Ви збираєтесь видалити всі дані PPTC з:\n\n{0}\n\nБажаєте продовжити цю операцію?",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "您正要清理 PPTC 数据:\n\n{0}\n\n您确实要继续吗?",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -13967,7 +13967,7 @@
|
||||
"sv_SE": "En ogiltig nyckelfil hittades i {0}",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Виявлено неправильний файл ключів у теці {0}",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "在 {0} 发现了一个无效的密匙文件",
|
||||
"zh_TW": "找到無效的金鑰檔案 {0}"
|
||||
}
|
||||
@@ -15642,7 +15642,7 @@
|
||||
"sv_SE": "Favorit",
|
||||
"th_TH": "สิ่งที่ชื่นชอบ",
|
||||
"tr_TR": "Favori",
|
||||
"uk_UA": "Обрані",
|
||||
"uk_UA": "Вибрані",
|
||||
"zh_CN": "收藏",
|
||||
"zh_TW": "我的最愛"
|
||||
}
|
||||
@@ -16067,7 +16067,7 @@
|
||||
"sv_SE": "Stöd för direkt musåtkomst (HID). Ger spel åtkomst till din mus som pekdon.\n\nFungerar endast med spel som har inbyggt stöd för muskontroller på Switch-hårdvara, som är endast ett fåtal.\n\nViss pekskärmsfunktionalitet kanske inte fungerar när aktiverat.\n\nLämna AV om du är osäker.",
|
||||
"th_TH": "รองรับการเข้าถึงเมาส์โดยตรง (HID) ให้เกมเข้าถึงเมาส์ของคุณเป็นอุปกรณ์ชี้ตำแหน่ง\n\nใช้งานได้เฉพาะกับเกมที่รองรับการควบคุมเมาส์บนฮาร์ดแวร์ของ Switch เท่านั้น ซึ่งมีอยู่ไม่มากนัก\n\nเมื่อเปิดใช้งาน ฟังก์ชั่นหน้าจอสัมผัสอาจไม่ทำงาน\n\nหากคุณไม่แน่ใจให้ปิดใช้งานไว้",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Підтримка прямого доступу до миші (HID). Надає іграм доступ до миші, як пристрій вказування.\n\nПрацює тільки з тими іграми, що підтримують мишу на обладнанні Switch (таких небагато).\n\nФункціонал сенсорного екрану може не працювати, якщо увімкнути цю функцію.\n\nЗалиште вимкненим, якщо не впевнені.",
|
||||
"uk_UA": "Підтримка прямого доступу до миші (HID). Надає іграм доступ до миші, як пристрій вказування.\n\nПрацює тільки з іграми, які підтримують мишу на обладнанні Switch (таких небагато).\n\nФункціонал сенсорного екрану може не працювати, якщо ця функція ввімкнена.\n\nЗалиште вимкненим, якщо не впевнені.",
|
||||
"zh_CN": "直接鼠标访问(HID)支持,游戏可以直接访问鼠标作为指针输入设备。\n\n只适用于在 Switch 硬件上原生支持鼠标控制的游戏,这种游戏很少。\n\n启用后,触屏功能可能无法正常工作。\n\n如果不确定,请保持关闭状态。",
|
||||
"zh_TW": "支援滑鼠直接存取 (HID)。遊戲可將滑鼠作為指向裝置使用。\n\n僅適用於在 Switch 硬體上原生支援滑鼠控制的遊戲,這類遊戲很少。\n\n啟用後,觸控螢幕功能可能無法使用。\n\n如果不確定,請保持關閉狀態。"
|
||||
}
|
||||
@@ -16492,7 +16492,7 @@
|
||||
"sv_SE": "Ignorerar Horizon OS-tjänster som inte har implementerats. Detta kan avhjälpa krascher när vissa spel startar upp.\n\nLämna AV om du är osäker.",
|
||||
"th_TH": "ละเว้นบริการ Horizon OS ที่ยังไม่ได้ใช้งาน วิธีนี้อาจช่วยในการหลีกเลี่ยงข้อผิดพลาดเมื่อบูตเกมบางเกม\n\nปล่อยให้ปิดหากคุณไม่แน่ใจ",
|
||||
"tr_TR": "Henüz programlanmamış Horizon işletim sistemi servislerini görmezden gelir. Bu seçenek belirli oyunların açılırken çökmesinin önüne geçmeye yardımcı olabilir.\n\nEmin değilseniz devre dışı bırakın.",
|
||||
"uk_UA": "Ігнорує нереалізовані служби Horizon OS. Це може допомогти в обході збоїв під час завантаження певних ігор.\n\nЗалиште вимкненим якщо не впевнені.",
|
||||
"uk_UA": "Ігнорує нереалізовані служби Horizon OS. Це може допомогти в обході збоїв під час завантаження певних ігор.\n\nЗалиште вимкненим, якщо не впевнені.",
|
||||
"zh_CN": "开启后,游戏会忽略未实现的系统服务,从而继续运行。\n少部分新发布的游戏由于使用了新的未知系统服务,可能需要此选项来避免闪退。\n模拟器更新完善系统服务之后,则无需开启此选项。\n\n如果不确定,请保持关闭状态。",
|
||||
"zh_TW": "忽略未實現的 Horizon OS 服務。這可能有助於在啟動某些遊戲時避免崩潰。\n\n如果不確定,請保持關閉狀態。"
|
||||
}
|
||||
@@ -16517,8 +16517,8 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Діалогове вікно Аплету Контролера не з'явиться, якщо геймпад було відключено під час роботи програми.\n\nЗалиште вимкненим якщо не впевнені.",
|
||||
"zh_CN": "在应用程序运行时如果游戏手柄断开连接则不会显示控制器小程序对话框。\n\n如果不确定,请保持关闭状态。",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
@@ -16542,7 +16542,7 @@
|
||||
"sv_SE": "Kör kommandon för grafikbakände i en andra tråd.\n\nSnabbar upp shader compilation, minskar stuttering och förbättrar prestandan på GPU-drivrutiner utan stöd för egen multithreading. Något bättre prestanda på drivrutiner med multithreading.\n\nStäll in till AUTO om du är osäker.",
|
||||
"th_TH": "ดำเนินการคำสั่งแบ็กเอนด์กราฟิกบนเธรดที่สอง\n\nเร่งความเร็วการคอมไพล์ ลดการกระตุก และปรับปรุงประสิทธิภาพการทำงานของไดรเวอร์ GPU โดยไม่ต้องรองรับมัลติเธรดในตัว ประสิทธิภาพที่ดีขึ้นเล็กน้อยสำหรับไดรเวอร์ที่มีมัลติเธรด\n\nตั้งเป็น อัตโนมัติ หากคุณไม่แน่ใจ",
|
||||
"tr_TR": "Grafik arka uç komutlarını ikinci bir iş parçacığında işletir.\n\nKendi multithreading desteği olmayan sürücülerde shader derlemeyi hızlandırıp performansı artırır. Multithreading desteği olan sürücülerde çok az daha iyi performans sağlar.\n\nEmin değilseniz Otomatik seçeneğine ayarlayın.",
|
||||
"uk_UA": "Виконує команди графічного сервера в другому потоці.\n\nПрискорює компіляцію шейдерів, зменшує затримки та покращує продуктивність драйверів GPU без власної підтримки багатопоточності. Трохи краща продуктивність на драйверах з багатопотоковістю.\nВстановіть значення «Авто» якщо не впевнені",
|
||||
"uk_UA": "Виконує команди графічного сервера в другому потоці.\n\nПрискорює компіляцію шейдерів, зменшує затримки та покращує продуктивність драйверів GPU без власної підтримки багатопоточності. Трохи краща продуктивність на драйверах з багатопотоковістю.\nВстановіть значення «Авто», якщо не впевнені",
|
||||
"zh_CN": "在第二个线程上执行图形引擎指令。\n\n可以加速着色器编译,减少卡顿,提高 GPU 的性能。\n\n如果不确定,请设置为“自动”。",
|
||||
"zh_TW": "在第二個執行緒上執行圖形後端指令。\n\n在本身不支援多執行緒的 GPU 驅動程式上,可加快著色器編譯、減少卡頓並提高效能。在支援多執行緒的驅動程式上效能略有提升。\n\n如果不確定,請設定為自動。"
|
||||
}
|
||||
@@ -16567,7 +16567,7 @@
|
||||
"sv_SE": "Kör kommandon för grafikbakände i en andra tråd.\n\nSnabbar upp shader compilation, minskar stuttering och förbättrar prestandan på GPU-drivrutiner utan stöd för egen multithreading. Något bättre prestanda på drivrutiner med multithreading.\n\nStäll in till AUTO om du är osäker.",
|
||||
"th_TH": "ดำเนินการคำสั่งแบ็กเอนด์กราฟิกบนเธรดที่สอง\n\nเร่งความเร็วการคอมไพล์เชเดอร์ ลดการกระตุก และปรับปรุงประสิทธิภาพการทำงานของไดรเวอร์ GPU โดยไม่ต้องรองรับมัลติเธรดในตัว ประสิทธิภาพที่ดีขึ้นเล็กน้อยสำหรับไดรเวอร์ที่มีมัลติเธรด\n\nตั้งเป็น อัตโนมัติ หากคุณไม่แน่ใจ",
|
||||
"tr_TR": "Grafik arka uç komutlarını ikinci bir iş parçacığında işletir.\n\nKendi multithreading desteği olmayan sürücülerde shader derlemeyi hızlandırıp performansı artırır. Multithreading desteği olan sürücülerde çok az daha iyi performans sağlar.\n\nEmin değilseniz Otomatik seçeneğine ayarlayın.",
|
||||
"uk_UA": "Виконує команди графічного сервера в другому потоці.\n\nПрискорює компіляцію шейдерів, зменшує затримки та покращує продуктивність драйверів GPU без власної підтримки багатопоточності. Трохи краща продуктивність на драйверах з багатопотоковістю.\n\nВстановіть значення «Авто» якщо не впевнені.",
|
||||
"uk_UA": "Виконує команди графічного сервера в другому потоці.\n\nПрискорює компіляцію шейдерів, зменшує затримки та покращує продуктивність драйверів GPU без власної підтримки багатопоточності. Трохи краща продуктивність на драйверах з багатопотоковістю.\n\nВстановіть значення «Авто», якщо не впевнені.",
|
||||
"zh_CN": "在第二个线程上执行图形引擎指令。\n\n可以加速着色器编译,减少卡顿,提高 GPU 的性能。\n\n如果不确定,请设置为“自动”。",
|
||||
"zh_TW": "在第二個執行緒上執行圖形後端指令。\n\n在本身不支援多執行緒的 GPU 驅動程式上,可加快著色器編譯、減少卡頓並提高效能。在支援多執行緒的驅動程式上效能略有提升。\n\n如果不確定,請設定為自動。"
|
||||
}
|
||||
@@ -16592,7 +16592,7 @@
|
||||
"sv_SE": "Sparar en disk shader cache som minskar stuttering i efterföljande körningar.\n\nLämna PÅ om du är osäker.",
|
||||
"th_TH": "บันทึกแคชแสงเงาของดิสก์ซึ่งช่วยลดการกระตุกในการรันครั้งต่อๆ ไป\n\nเปิดทิ้งไว้หากคุณไม่แน่ใจ",
|
||||
"tr_TR": "Sonraki çalışmalarda takılmaları engelleyen bir gölgelendirici disk önbelleğine kaydeder.",
|
||||
"uk_UA": "Зберігає кеш дискового шейдера, що зменшує затримки під час наступних запусків.\n\nЗалиште увімкненим якщо не впевнені.",
|
||||
"uk_UA": "Зберігає кеш дискового шейдера, що зменшує затримки під час наступних запусків.\n\nЗалиште увімкненим, якщо не впевнені.",
|
||||
"zh_CN": "模拟器将已编译的着色器保存到硬盘,可以减少游戏再次渲染相同图形导致的卡顿。\n\n如果不确定,请保持开启状态。",
|
||||
"zh_TW": "儲存磁碟著色器快取,減少後續執行時的卡頓。\n\n如果不確定,請保持開啟狀態。"
|
||||
}
|
||||
@@ -16942,7 +16942,7 @@
|
||||
"sv_SE": "Aktiverar loggutdata för filsystemsåtkomst i konsollen. Möjliga lägen är 0-3",
|
||||
"th_TH": "เปิดใช้งาน เอาต์พุตประวัติการเข้าถึง FS ไปยังคอนโซล โหมดที่เป็นไปได้คือ 0-3",
|
||||
"tr_TR": "Konsola FS erişim loglarının yazılmasını etkinleştirir. Kullanılabilir modlar 0-3'tür",
|
||||
"uk_UA": "Увімкнути виведення журналу доступу (access log) до файлової системи в консоль. Можливі режими: 0-3",
|
||||
"uk_UA": "Вмикає виведення журналу доступу (access log) до FS на консоль. Можливі режими 0-3",
|
||||
"zh_CN": "在控制台中显示文件系统访问日志,可选模式为 0-3。",
|
||||
"zh_TW": "啟用檔案系統存取日誌輸出到控制台中。可能的模式為 0 到 3"
|
||||
}
|
||||
@@ -16992,7 +16992,7 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Виводити повідомлення журналу Avalonia (UI) в консоль",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "在控制台显示 Avalonia (UI) 的日志信息",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -17142,7 +17142,7 @@
|
||||
"sv_SE": "Öppna en filutforskare för att välja en eller flera mappar att läsa in alla titeluppdateringar från",
|
||||
"th_TH": "เปิดตัวสำรวจไฟล์เพื่อเลือกหนึ่งโฟลเดอร์ขึ้นไปเพื่อโหลดไฟล์อัปเดตจำนวนมาก",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Відкриває Файловий Провідник для обрання однієї або декількох тек для масового завантаження оновлень",
|
||||
"uk_UA": "Відкриває Файловий провідник для обрання однієї або декількох тек для масового завантаження оновлень",
|
||||
"zh_CN": "打开文件资源管理器以选择一个或多个文件夹来批量加载游戏更新。",
|
||||
"zh_TW": "開啟檔案總管,選擇一個或多個資料夾來大量載入遊戲更新"
|
||||
}
|
||||
@@ -17192,8 +17192,8 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Відкрити теку куди зберігаються скріншоти Ryujinx",
|
||||
"zh_CN": "打开 Ryujinx 截图文件夹",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
@@ -17217,7 +17217,7 @@
|
||||
"sv_SE": "Öppnar mappen där loggarna har skrivits till",
|
||||
"th_TH": "เปิดโฟลเดอร์ ที่เก็บไฟล์ประวัติ",
|
||||
"tr_TR": "Log dosyalarının bulunduğu klasörü açar",
|
||||
"uk_UA": "Відкриває теку, куди зберігаються журнали (logs)",
|
||||
"uk_UA": "Відкриває теку, куди записуються журнали (logs)",
|
||||
"zh_CN": "打开日志存放的目录",
|
||||
"zh_TW": "開啟日誌被寫入的資料夾"
|
||||
}
|
||||
@@ -17242,7 +17242,7 @@
|
||||
"sv_SE": "Avsluta Ryujinx",
|
||||
"th_TH": "ออกจากโปรแกรม Ryujinx",
|
||||
"tr_TR": "Ryujinx'ten çıkış yapmayı sağlar",
|
||||
"uk_UA": "Закриває Ryujinx",
|
||||
"uk_UA": "Виходить з Ryujinx",
|
||||
"zh_CN": "退出 Ryujinx 模拟器",
|
||||
"zh_TW": "結束 Ryujinx"
|
||||
}
|
||||
@@ -17942,7 +17942,7 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Доступне оновлення!",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "有可用的更新!",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -18817,7 +18817,7 @@
|
||||
"sv_SE": "Cabinet-dialog",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Сповіщення Cabinet",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "档案对话框",
|
||||
"zh_TW": "Cabinet 對話方塊"
|
||||
}
|
||||
@@ -19742,7 +19742,7 @@
|
||||
"sv_SE": "LED-inställningar",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Налаштування LED",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "LED 设置",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -20692,7 +20692,7 @@
|
||||
"sv_SE": "Markera visade",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Вибрати показані",
|
||||
"uk_UA": "Вибрати показане",
|
||||
"zh_CN": "选定显示的",
|
||||
"zh_TW": "選擇已顯示"
|
||||
}
|
||||
@@ -21542,7 +21542,7 @@
|
||||
"sv_SE": "Automatiskt",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Автоматично",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "自动",
|
||||
"zh_TW": "自動"
|
||||
}
|
||||
@@ -21567,7 +21567,7 @@
|
||||
"sv_SE": "Använder Vulkan.\nPå en ARM Mac och vid spel som körs bra på den så används Metal-bakänden.",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Використовує Vulkan.\nНа Mac з ARM-архітектурою, якщо гра добре працює з Vulkan, використовується графічний рушій Metal.",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "使用 Vulkan。\n在 ARM Mac 上,当玩在其下运行良好的游戏时,使用 Metal 后端。",
|
||||
"zh_TW": "使用Vulkan。\n在 ARM Mac 上,如果遊戲執行性能良好時,則將使用 Metal 後端。"
|
||||
}
|
||||
@@ -23542,7 +23542,7 @@
|
||||
"sv_SE": "Intervall",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Інтервал",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "间隔",
|
||||
"zh_TW": "間隔"
|
||||
}
|
||||
@@ -23642,7 +23642,7 @@
|
||||
"sv_SE": "Senast uppdaterad: {0}",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Останнє оновлення: {0}",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "最后更新于: {0}",
|
||||
"zh_TW": "上次更新時間: {0}"
|
||||
}
|
||||
@@ -23667,7 +23667,7 @@
|
||||
"sv_SE": "Denna kompatibilitetslista kan innehålla utdaterade poster.\nTesta gärna spelen som listas med \"Spelproblem\"-status.",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Цей список сумісності може містити застарілі дані.\nНе відмовляйтеся від тестування ігор що мають статус \"Запускаються\".",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "此兼容性列表可能包含过时的条目。\n不要只测试 \"进入游戏\" 状态的游戏。",
|
||||
"zh_TW": "這個相容性列表可能含有已過時的紀錄。\n敬請繼續測試「大致可遊玩 (Ingame)」狀態的遊戲並回報以更新紀錄。"
|
||||
}
|
||||
@@ -23692,7 +23692,7 @@
|
||||
"sv_SE": "Sök i kompatibilitetsposter...",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Перевірити сумісність гри...",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "正在搜索兼容性条目...",
|
||||
"zh_TW": "搜尋相容性列表紀錄..."
|
||||
}
|
||||
@@ -23717,7 +23717,7 @@
|
||||
"sv_SE": "Öppna kompatibilitetslistan",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Відкрити Список Сумісності",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "打开兼容性列表",
|
||||
"zh_TW": "開啟相容性列表"
|
||||
}
|
||||
@@ -23742,7 +23742,7 @@
|
||||
"sv_SE": "Visa endast ägda spel",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Показувати лише ігри в наявності",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "仅显示拥有的游戏",
|
||||
"zh_TW": "只顯示已擁有的遊戲"
|
||||
}
|
||||
@@ -23767,7 +23767,7 @@
|
||||
"sv_SE": "Spelbart",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Оптимально",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "可游玩",
|
||||
"zh_TW": "可暢順遊玩"
|
||||
}
|
||||
@@ -23792,7 +23792,7 @@
|
||||
"sv_SE": "Spelproblem",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "З недоліками",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "进入游戏",
|
||||
"zh_TW": "大致可遊玩"
|
||||
}
|
||||
@@ -23817,7 +23817,7 @@
|
||||
"sv_SE": "Menyer",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Меню",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "菜单",
|
||||
"zh_TW": "只開啟至遊戲開始功能表"
|
||||
}
|
||||
@@ -23842,7 +23842,7 @@
|
||||
"sv_SE": "Startar",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Запускається",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "启动",
|
||||
"zh_TW": "只能啟動"
|
||||
}
|
||||
@@ -23867,7 +23867,7 @@
|
||||
"sv_SE": "Ingenting",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Не працює",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "什么都没有",
|
||||
"zh_TW": "無法啟動"
|
||||
}
|
||||
@@ -23892,7 +23892,7 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Запускається та оптимально працює (без збоїв або графічних багів) на середньостатистичному комп'ютері.",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "启动和游戏时不会出现任何崩溃或任何类型的 GPU bug 且速度足够快可以在一般 PC 上尽情游玩。",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -23917,7 +23917,7 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Запускається, але в грі на вас чекатимуть одна або декілька наступних проблем: збої, зависання, графічні баги, спотворений звук або ж гра загалом працюватиме надто повільно. Можливо, її все ще можна пройти, але досвід буде не найкращим.",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "可以成功启动并进入游戏但可能会遇到以下一种或多种问题: 崩溃、卡死、GPU bug、令人无法接受的音频,或者只是太慢。仍然可以继续进行游戏,但是可能无法达到预期。",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -23942,7 +23942,7 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Запускається та проходить початковий екран, але пограти не вийде.",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "可以启动并通过标题画面但是无法进入到主要的游戏流程。",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -23967,7 +23967,7 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Запускається, але не відображає навіть початкового екрану.",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "可以启动但是无法通过标题画面。",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -23992,7 +23992,7 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Взагалі не запускається.",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "无法启动或显示无任何动静。",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -24017,7 +24017,7 @@
|
||||
"sv_SE": "Välj en DLC att extrahera",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Оберіть DLC які бажаєте вилучити",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "选择一个要解压的 DLC",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -24042,7 +24042,7 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Зображення картки активності Discord",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "Rich Presence 图像",
|
||||
"zh_TW": ""
|
||||
}
|
||||
@@ -24067,10 +24067,10 @@
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Динамічна картка активності Discord",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "动态 Rich Presence",
|
||||
"zh_TW": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,11 +148,8 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
{
|
||||
if ((firmwarePath.ExistsAsFile && firmwarePath.Extension is "xci" or "zip") ||
|
||||
firmwarePath.ExistsAsDirectory)
|
||||
{
|
||||
await Dispatcher.UIThread.InvokeAsync(() =>
|
||||
ViewModel.HandleFirmwareInstallation(firmwarePath));
|
||||
CommandLineState.FirmwareToInstallPathArg = null;
|
||||
}
|
||||
else
|
||||
Logger.Notice.Print(LogClass.UI, "Invalid firmware type provided. Path must be a directory, or a .zip or .xci file.");
|
||||
}
|
||||
@@ -189,12 +186,17 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
{
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
List<LdnGameData> ldnGameDataArray = e.LdnData.ToList();
|
||||
ViewModel.LdnData.Clear();
|
||||
foreach (ApplicationData application in ViewModel.Applications.Where(it => it.HasControlHolder))
|
||||
{
|
||||
ref ApplicationControlProperty controlHolder = ref application.ControlHolder.Value;
|
||||
|
||||
ViewModel.LdnData[application.IdString] = e.LdnData.Where(ref controlHolder);
|
||||
|
||||
ViewModel.LdnData[application.IdString] =
|
||||
LdnGameData.GetArrayForApp(
|
||||
ldnGameDataArray,
|
||||
ref controlHolder
|
||||
);
|
||||
|
||||
UpdateApplicationWithLdnData(application);
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
|
||||
{
|
||||
public class ApplicationLibrary
|
||||
{
|
||||
public const string DefaultLanPlayWebHost = "ryuldnweb.vudjun.com";
|
||||
public Language DesiredLanguage { get; set; }
|
||||
public event EventHandler<ApplicationCountUpdatedEventArgs> ApplicationCountUpdated;
|
||||
public event Action<LdnGameDataReceivedEventArgs> LdnGameDataReceived;
|
||||
@@ -825,6 +826,7 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
|
||||
|
||||
public async Task RefreshLdn()
|
||||
{
|
||||
|
||||
if (ConfigurationState.Instance.Multiplayer.Mode == MultiplayerMode.LdnRyu)
|
||||
{
|
||||
try
|
||||
@@ -832,22 +834,33 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
|
||||
string ldnWebHost = ConfigurationState.Instance.Multiplayer.LdnServer;
|
||||
if (string.IsNullOrEmpty(ldnWebHost))
|
||||
{
|
||||
ldnWebHost = SharedConstants.DefaultLanPlayWebHost;
|
||||
ldnWebHost = DefaultLanPlayWebHost;
|
||||
}
|
||||
|
||||
IEnumerable<LdnGameData> ldnGameDataArray = Array.Empty<LdnGameData>();
|
||||
using HttpClient httpClient = new();
|
||||
string ldnGameDataArrayString = await httpClient.GetStringAsync($"https://{ldnWebHost}/api/public_games");
|
||||
LdnGameData[] ldnGameDataArray = JsonHelper.Deserialize(ldnGameDataArrayString, _ldnDataSerializerContext.IEnumerableLdnGameData).ToArray();
|
||||
LdnGameDataReceived?.Invoke(new LdnGameDataReceivedEventArgs(ldnGameDataArray));
|
||||
return;
|
||||
ldnGameDataArray = JsonHelper.Deserialize(ldnGameDataArrayString, _ldnDataSerializerContext.IEnumerableLdnGameData);
|
||||
LdnGameDataReceived?.Invoke(new LdnGameDataReceivedEventArgs
|
||||
{
|
||||
LdnData = ldnGameDataArray
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Failed to fetch the public games JSON from the API. Player and game count in the game list will be unavailable.\n{ex.Message}");
|
||||
LdnGameDataReceived?.Invoke(new LdnGameDataReceivedEventArgs
|
||||
{
|
||||
LdnData = Array.Empty<LdnGameData>()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
LdnGameDataReceived?.Invoke(LdnGameDataReceivedEventArgs.Empty);
|
||||
else
|
||||
{
|
||||
LdnGameDataReceived?.Invoke(new LdnGameDataReceivedEventArgs
|
||||
{
|
||||
LdnData = Array.Empty<LdnGameData>()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Replace the currently stored DLC state for the game with the provided DLC state.
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
|
||||
public IEnumerable<string> Players { get; set; }
|
||||
|
||||
public static Array GetArrayForApp(
|
||||
LdnGameData[] receivedData, ref ApplicationControlProperty acp)
|
||||
IEnumerable<LdnGameData> receivedData, ref ApplicationControlProperty acp)
|
||||
{
|
||||
LibHac.Common.FixedArrays.Array8<ulong> communicationId = acp.LocalCommunicationId;
|
||||
|
||||
@@ -40,10 +40,4 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
|
||||
public int GameCount => _ldnDatas.Length;
|
||||
}
|
||||
}
|
||||
|
||||
public static class LdnGameDataHelper
|
||||
{
|
||||
public static LdnGameData.Array Where(this LdnGameData[] unfilteredDatas, ref ApplicationControlProperty acp)
|
||||
=> LdnGameData.GetArrayForApp(unfilteredDatas, ref acp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,6 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
|
||||
{
|
||||
public class LdnGameDataReceivedEventArgs : EventArgs
|
||||
{
|
||||
public static new readonly LdnGameDataReceivedEventArgs Empty = new(null);
|
||||
|
||||
public LdnGameDataReceivedEventArgs(LdnGameData[] ldnData)
|
||||
{
|
||||
LdnData = ldnData ?? [];
|
||||
}
|
||||
|
||||
|
||||
public LdnGameData[] LdnData { get; set; }
|
||||
public IEnumerable<LdnGameData> LdnData { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Ryujinx.Ava.Utilities
|
||||
public static string OverrideBackendThreading { get; private set; }
|
||||
public static string OverrideHideCursor { get; private set; }
|
||||
public static string BaseDirPathArg { get; private set; }
|
||||
public static FilePath FirmwareToInstallPathArg { get; set; }
|
||||
public static FilePath FirmwareToInstallPathArg { get; private set; }
|
||||
public static string Profile { get; private set; }
|
||||
public static string LaunchPathArg { get; private set; }
|
||||
public static string LaunchApplicationId { get; private set; }
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using ARMeilleure;
|
||||
using Gommon;
|
||||
using Ryujinx.Ava.Utilities.AppLibrary;
|
||||
using Ryujinx.Ava.Utilities.Configuration.System;
|
||||
using Ryujinx.Ava.Utilities.Configuration.UI;
|
||||
using Ryujinx.Common;
|
||||
@@ -648,14 +647,6 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
||||
/// </summary>
|
||||
public ReactiveObject<string> LdnServer { get; private set; }
|
||||
|
||||
public string GetLdnServer()
|
||||
{
|
||||
string ldnServer = LdnServer;
|
||||
return string.IsNullOrEmpty(ldnServer)
|
||||
? SharedConstants.DefaultLanPlayHost
|
||||
: ldnServer;
|
||||
}
|
||||
|
||||
public MultiplayerSection()
|
||||
{
|
||||
LanInterfaceId = new ReactiveObject<string>();
|
||||
|
||||
Reference in New Issue
Block a user