Compare commits
7 Commits
965fb9dd5f
...
Canary-1.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d88771d1b | ||
|
|
4e8157688e | ||
|
|
5085af0050 | ||
|
|
2c8edaf89e | ||
|
|
aa8ba8b503 | ||
|
|
a4211fec33 | ||
|
|
54b233dd78 |
@@ -332,6 +332,7 @@
|
||||
0100E680149DC000,"Arcaea",,playable,2023-03-16 19:31:21
|
||||
01003C2010C78000,"Archaica: The Path Of Light",crash,nothing,2020-10-16 13:22:26
|
||||
01004DA012976000,"Area 86",,playable,2020-12-16 16:45:52
|
||||
01008d8006a6a000,"Arena of Valor",crash,boots,2025-02-03 22:19:34
|
||||
0100691013C46000,"ARIA CHRONICLE",,playable,2022-11-16 13:50:55
|
||||
0100D4A00B284000,"ARK: Survival Evolved",gpu;nvdec;online-broken;UE4;ldn-untested,ingame,2024-04-16 00:53:56
|
||||
0100C56012C96000,"Arkanoid vs. Space Invaders",services,ingame,2021-01-21 12:50:30
|
||||
@@ -426,6 +427,7 @@
|
||||
0100E48013A34000,"Balan Wonderworld Demo",gpu;services;UE4;demo,ingame,2023-02-16 20:05:07
|
||||
0100CD801CE5E000,"Balatro",,ingame,2024-04-21 02:01:53
|
||||
010010A00DA48000,"Baldur's Gate and Baldur's Gate II: Enhanced Editions",32-bit,playable,2022-09-12 23:52:15
|
||||
0100fd1014726000,"Baldur's Gate: Dark Alliance",ldn-untested,ingame,2025-02-03 22:21:00
|
||||
0100BC400FB64000,"Balthazar's Dream",,playable,2022-09-13 00:13:22
|
||||
01008D30128E0000,"Bamerang",,playable,2022-10-26 00:29:39
|
||||
010013C010C5C000,"Banner of the Maid",,playable,2021-06-14 15:23:37
|
||||
@@ -528,6 +530,7 @@
|
||||
01005950022EC000,"Blade Strangers",nvdec,playable,2022-07-17 19:02:43
|
||||
0100DF0011A6A000,"Bladed Fury",,playable,2022-10-26 11:36:26
|
||||
0100CFA00CC74000,"Blades of Time",deadlock;online,boots,2022-07-17 19:19:58
|
||||
01003d700dd8a000,"Blades",,boots,2025-02-03 22:22:00
|
||||
01006CC01182C000,"Blair Witch",nvdec;UE4,playable,2022-10-01 14:06:16
|
||||
010039501405E000,"Blanc",gpu;slow,ingame,2023-02-22 14:00:13
|
||||
0100698009C6E000,"Blasphemous",nvdec,playable,2021-03-01 12:15:31
|
||||
@@ -955,7 +958,7 @@
|
||||
010012800EBAE000,"Disney TSUM TSUM FESTIVAL",crash,menus,2020-07-14 14:05:28
|
||||
01009740120FE000,"DISTRAINT 2",,playable,2020-09-03 16:08:12
|
||||
010075B004DD2000,"DISTRAINT: Deluxe Edition",,playable,2020-06-15 23:42:24
|
||||
010027400CDC6000,"Divinity: Original Sin 2 - Definitive Edition",services;crash;online-broken;regression,menus,2023-08-13 17:20:03
|
||||
010027400CDC6000,"Divinity: Original Sin 2 - Definitive Edition",services;crash;online-broken;regression,ingame,2025-02-03 22:12:30
|
||||
01001770115C8000,"Dodo Peak",nvdec;UE4,playable,2022-10-04 16:13:05
|
||||
010077B0100DA000,"Dogurai",,playable,2020-10-04 02:40:16
|
||||
010048100D51A000,"Dokapon Up! Mugen no Roulette",gpu;Needs Update,menus,2022-12-08 19:39:10
|
||||
@@ -1654,7 +1657,7 @@
|
||||
0100A73006E74000,"Legendary Eleven",,playable,2021-06-08 12:09:03
|
||||
0100A7700B46C000,"Legendary Fishing",online,playable,2021-04-14 15:08:46
|
||||
0100739018020000,"LEGO® 2K Drive",gpu;ldn-works,ingame,2024-04-09 02:05:12
|
||||
01003A30012C0000,"LEGO® CITY Undercover",nvdec,playable,2024-09-30 08:44:27
|
||||
010085500130a000,"LEGO® CITY Undercover",nvdec,playable,2024-09-30 08:44:27
|
||||
010070D009FEC000,"LEGO® DC Super-Villains",,playable,2021-05-27 18:10:37
|
||||
010052A00B5D2000,"LEGO® Harry Potter™ Collection",crash,ingame,2024-01-31 10:28:07
|
||||
010073C01AF34000,"LEGO® Horizon Adventures™",vulkan-backend-bug;opengl-backend-bug;UE4,ingame,2025-01-07 04:24:56
|
||||
@@ -1913,6 +1916,7 @@
|
||||
010073E008E6E000,"Mugsters",,playable,2021-01-28 17:57:17
|
||||
0100A8400471A000,"MUJO",,playable,2020-05-08 16:31:04
|
||||
0100211005E94000,"Mulaka",,playable,2021-01-28 18:07:20
|
||||
01008e2013fb4000,"Multi Quiz",ldn-untested,ingame,2025-02-03 22:26:00
|
||||
010038B00B9AE000,"Mummy Pinball",,playable,2022-08-05 16:08:11
|
||||
01008E200C5C2000,"Muse Dash",,playable,2020-06-06 14:41:29
|
||||
010035901046C000,"Mushroom Quest",,playable,2020-05-17 13:07:08
|
||||
@@ -2028,6 +2032,7 @@
|
||||
010003C00B868000,"Ninjin: Clash of Carrots",online-broken,playable,2024-07-10 05:12:26
|
||||
0100746010E4C000,"NinNinDays",,playable,2022-11-20 15:17:29
|
||||
0100C9A00ECE6000,"Nintendo 64™ – Nintendo Switch Online",gpu;vulkan,ingame,2024-04-23 20:21:07
|
||||
0100e0601c632000,"Nintendo 64™ – Nintendo Switch Online: MATURE 17+",,ingame,2025-02-03 22:27:00
|
||||
0100D870045B6000,"Nintendo Entertainment System™ - Nintendo Switch Online",online,playable,2022-07-01 15:45:06
|
||||
0100C4B0034B2000,"Nintendo Labo Toy-Con 01 Variety Kit",gpu,ingame,2022-08-07 12:56:07
|
||||
01001E9003502000,"Nintendo Labo Toy-Con 03 Vehicle Kit",services;crash,menus,2022-08-03 17:20:11
|
||||
@@ -2532,7 +2537,7 @@
|
||||
0100C3E00B700000,"SEGA AGES Space Harrier",,playable,2021-01-11 12:57:40
|
||||
010054400D2E6000,"SEGA AGES Virtua Racing",online-broken,playable,2023-01-29 17:08:39
|
||||
01001E700AC60000,"SEGA AGES Wonder Boy: Monster Land",online,playable,2021-05-05 16:28:25
|
||||
0100B3C014BDA000,"SEGA Genesis™ – Nintendo Switch Online",crash;regression,nothing,2022-04-11 07:27:21
|
||||
0100B3C014BDA000,"SEGA Genesis™ – Nintendo Switch Online",crash;regression,ingame,2025-02-03 22:13:30
|
||||
0100F7300B24E000,"SEGA Mega Drive Classics",online,playable,2021-01-05 11:08:00
|
||||
01009840046BC000,"Semispheres",,playable,2021-01-06 23:08:31
|
||||
0100D1800D902000,"SENRAN KAGURA Peach Ball",,playable,2021-06-03 15:12:10
|
||||
@@ -2964,6 +2969,7 @@
|
||||
0100C38004DCC000,"The Flame In The Flood: Complete Edition",gpu;nvdec;UE4,ingame,2022-08-22 16:23:49
|
||||
010007700D4AC000,"The Forbidden Arts",,playable,2021-01-26 16:26:24
|
||||
010030700CBBC000,"The friends of Ringo Ishikawa",,playable,2022-08-22 16:33:17
|
||||
0100b620139d8000,"The Game of Life 2",ldn-untested,ingame,2025-02-03 22:30:00
|
||||
01006350148DA000,"The Gardener and the Wild Vines",gpu,ingame,2024-04-29 16:32:10
|
||||
0100B13007A6A000,"The Gardens Between",,playable,2021-01-29 16:16:53
|
||||
010036E00FB20000,"The Great Ace Attorney Chronicles",,playable,2023-06-22 21:26:29
|
||||
@@ -2981,6 +2987,8 @@
|
||||
010015D003EE4000,"The Jackbox Party Pack 2",online-working,playable,2022-08-22 18:23:40
|
||||
0100CC80013D6000,"The Jackbox Party Pack 3",slow;online-working,playable,2022-08-22 18:41:06
|
||||
0100E1F003EE8000,"The Jackbox Party Pack 4",online-working,playable,2022-08-22 18:56:34
|
||||
01006fe0096ac000,"The Jackbox Party Pack 5",ldn-untested,boots,2025-02-03 22:32:00
|
||||
01005a400db52000,"The Jackbox Party Pack 6",ldn-untested,boots,2025-02-03 22:32:00
|
||||
010052C00B184000,"The Journey Down: Chapter One",nvdec,playable,2021-02-24 13:32:41
|
||||
01006BC00B188000,"The Journey Down: Chapter Three",nvdec,playable,2021-02-24 13:45:27
|
||||
01009AB00B186000,"The Journey Down: Chapter Two",nvdec,playable,2021-02-24 13:32:13
|
||||
@@ -3159,6 +3167,7 @@
|
||||
010055E00CA68000,"Trine 4: The Nightmare Prince",gpu,nothing,2025-01-07 05:47:46
|
||||
0100D9000A930000,"Trine Enchanted Edition",ldn-untested;nvdec,playable,2021-06-03 11:28:15
|
||||
01002D7010A54000,"Trinity Trigger",crash,ingame,2023-03-03 03:09:09
|
||||
010020700a5e0000,"TRIVIAL PURSUIT Live!",ldn-untested,ingame,2025-02-03 22:35:00
|
||||
0100868013FFC000,"TRIVIAL PURSUIT Live! 2",,boots,2022-12-19 00:04:33
|
||||
0100F78002040000,"Troll and I™",gpu;nvdec,ingame,2021-06-04 16:58:50
|
||||
0100145011008000,"Trollhunters: Defenders of Arcadia",gpu;nvdec,ingame,2020-11-30 13:27:09
|
||||
@@ -3208,6 +3217,7 @@
|
||||
0100AB2010B4C000,"Unlock The King",,playable,2020-09-01 13:58:27
|
||||
0100A3E011CB0000,"Unlock the King 2",,playable,2021-06-15 20:43:55
|
||||
01005AA00372A000,"UNO® for Nintendo Switch",nvdec;ldn-untested,playable,2022-07-28 14:49:47
|
||||
0100b6e012ebe000,"UNO",ldn-untested,ingame,2025-02-03 22:40:00
|
||||
0100E5D00CC0C000,"Unravel Two",nvdec,playable,2024-05-23 15:45:05
|
||||
010001300CC4A000,"Unruly Heroes",,playable,2021-01-07 18:09:31
|
||||
0100B410138C0000,"Unspottable",,playable,2022-10-25 19:28:49
|
||||
@@ -3372,6 +3382,7 @@
|
||||
0100F47016F26000,"Yomawari 3",,playable,2022-05-10 08:26:51
|
||||
010012F00B6F2000,"Yomawari: The Long Night Collection",,playable,2022-09-03 14:36:59
|
||||
0100CC600ABB2000,"Yonder: The Cloud Catcher Chronicles (Retail Only)",,playable,2021-01-28 14:06:25
|
||||
0100534009ff2000,"Yonder: The Cloud Catcher Chronicles",,playable,2025-02-03 22:19:13
|
||||
0100BE50042F6000,"Yono and the Celestial Elephants",,playable,2021-01-28 18:23:58
|
||||
0100F110029C8000,"Yooka-Laylee",,playable,2021-01-28 14:21:45
|
||||
010022F00DA66000,"Yooka-Laylee and the Impossible Lair",,playable,2021-03-05 17:32:21
|
||||
|
||||
|
@@ -3,7 +3,7 @@ using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Memory
|
||||
namespace Ryujinx.Cpu
|
||||
{
|
||||
public interface IVirtualMemoryManagerTracked : IVirtualMemoryManager
|
||||
{
|
||||
@@ -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,8 +106,6 @@ 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;
|
||||
|
||||
@@ -144,7 +142,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
|
||||
sharedMemorySize,
|
||||
_channel.BufferManager.HasUnalignedStorageBuffers);
|
||||
|
||||
CachedShaderProgram cs = shaderCache.GetComputeShader(_channel, samplerPoolMaximumId, poolState, computeState, shaderGpuVa);
|
||||
CachedShaderProgram cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, samplerPoolMaximumId, poolState, computeState, shaderGpuVa);
|
||||
|
||||
_context.Renderer.Pipeline.SetProgram(cs.HostProgram);
|
||||
|
||||
@@ -158,10 +156,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
|
||||
{
|
||||
BufferDescriptor sb = info.SBuffers[index];
|
||||
|
||||
(PhysicalMemory physical, ulong sbDescAddress) = _channel.BufferManager.GetComputeUniformBufferAddress(sb.SbCbSlot);
|
||||
ulong sbDescAddress = _channel.BufferManager.GetComputeUniformBufferAddress(sb.SbCbSlot);
|
||||
sbDescAddress += (ulong)sb.SbCbOffset * 4;
|
||||
|
||||
SbDescriptor sbDescriptor = physical.Read<SbDescriptor>(sbDescAddress);
|
||||
SbDescriptor sbDescriptor = _channel.MemoryManager.Physical.Read<SbDescriptor>(sbDescAddress);
|
||||
|
||||
uint size;
|
||||
if (sb.SbCbSlot == Constants.DriverReservedUniformBuffer)
|
||||
@@ -189,7 +187,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
|
||||
sharedMemorySize,
|
||||
_channel.BufferManager.HasUnalignedStorageBuffers);
|
||||
|
||||
cs = shaderCache.GetComputeShader(_channel, samplerPoolMaximumId, poolState, computeState, shaderGpuVa);
|
||||
cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, samplerPoolMaximumId, poolState, computeState, shaderGpuVa);
|
||||
|
||||
_context.Renderer.Pipeline.SetProgram(cs.HostProgram);
|
||||
}
|
||||
|
||||
@@ -215,10 +215,7 @@ 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.
|
||||
@@ -296,7 +293,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
||||
|
||||
if (completeSource && completeDest && !srcLinear && isIdentityRemap)
|
||||
{
|
||||
Image.Texture source = srcPhysical.TextureCache.FindTexture(
|
||||
Image.Texture source = memoryManager.Physical.TextureCache.FindTexture(
|
||||
memoryManager,
|
||||
srcGpuVa,
|
||||
srcBpp,
|
||||
@@ -312,7 +309,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
||||
{
|
||||
source.SynchronizeMemory();
|
||||
|
||||
Image.Texture target = dstPhysical.TextureCache.FindOrCreateTexture(
|
||||
Image.Texture target = memoryManager.Physical.TextureCache.FindOrCreateTexture(
|
||||
memoryManager,
|
||||
source.Info.FormatInfo,
|
||||
dstGpuVa,
|
||||
@@ -342,7 +339,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
||||
|
||||
if (completeSource && completeDest && !(dstLinear && !srcLinear) && isIdentityRemap)
|
||||
{
|
||||
Image.Texture target = dstPhysical.TextureCache.FindTexture(
|
||||
Image.Texture target = memoryManager.Physical.TextureCache.FindTexture(
|
||||
memoryManager,
|
||||
dstGpuVa,
|
||||
dstBpp,
|
||||
@@ -465,7 +462,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
||||
}
|
||||
else
|
||||
{
|
||||
BufferCache bufferCache = dstPhysical.BufferCache;
|
||||
if (remap &&
|
||||
_state.State.SetRemapComponentsDstX == SetRemapComponentsDst.ConstA &&
|
||||
_state.State.SetRemapComponentsDstY == SetRemapComponentsDst.ConstA &&
|
||||
@@ -476,7 +472,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
||||
_state.State.SetRemapComponentsComponentSize == SetRemapComponentsComponentSize.Four)
|
||||
{
|
||||
// Fast path for clears when remap is enabled.
|
||||
bufferCache.ClearBuffer(memoryManager, dstGpuVa, size * 4, _state.State.SetRemapConstA);
|
||||
memoryManager.Physical.BufferCache.ClearBuffer(memoryManager, dstGpuVa, size * 4, _state.State.SetRemapConstA);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -496,7 +492,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
||||
}
|
||||
else
|
||||
{
|
||||
BufferCache.CopyBuffer(_context,memoryManager, srcGpuVa, dstGpuVa, size);
|
||||
memoryManager.Physical.BufferCache.CopyBuffer(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.GetBackingMemory(_dstGpuVa).TextureCache.FindTexture(
|
||||
Image.Texture target = memoryManager.Physical.TextureCache.FindTexture(
|
||||
memoryManager,
|
||||
_dstGpuVa,
|
||||
1,
|
||||
|
||||
@@ -384,7 +384,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
|
||||
|
||||
ulong indirectBufferGpuVa = count.GpuVa;
|
||||
|
||||
BufferCache bufferCache = _processor.MemoryManager.GetBackingMemory(indirectBufferGpuVa).BufferCache;
|
||||
BufferCache bufferCache = _processor.MemoryManager.Physical.BufferCache;
|
||||
|
||||
bool useBuffer = bufferCache.CheckModified(_processor.MemoryManager, indirectBufferGpuVa, IndirectIndexedDataEntrySize, out ulong indirectBufferAddress);
|
||||
|
||||
@@ -394,8 +394,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
|
||||
|
||||
_processor.ThreedClass.DrawIndirect(
|
||||
topology,
|
||||
bufferCache,
|
||||
null,
|
||||
new MultiRange(indirectBufferAddress, IndirectIndexedDataEntrySize),
|
||||
default,
|
||||
1,
|
||||
@@ -493,24 +491,22 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
|
||||
}
|
||||
}
|
||||
}
|
||||
BufferCache indirectBufferCache = _processor.MemoryManager.GetBackingMemory(indirectBufferGpuVa).BufferCache;
|
||||
BufferCache parameterBufferCache = _processor.MemoryManager.GetBackingMemory(parameterBufferGpuVa).BufferCache;
|
||||
|
||||
BufferCache bufferCache = _processor.MemoryManager.Physical.BufferCache;
|
||||
|
||||
ulong indirectBufferSize = (ulong)maxDrawCount * (ulong)stride;
|
||||
|
||||
MultiRange indirectBufferRange = indirectBufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, indirectBufferGpuVa, indirectBufferSize, BufferStage.Indirect);
|
||||
MultiRange parameterBufferRange = parameterBufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, parameterBufferGpuVa, 4, BufferStage.Indirect);
|
||||
MultiRange indirectBufferRange = bufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, indirectBufferGpuVa, indirectBufferSize, BufferStage.Indirect);
|
||||
MultiRange parameterBufferRange = bufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, parameterBufferGpuVa, 4, BufferStage.Indirect);
|
||||
|
||||
_processor.ThreedClass.DrawIndirect(
|
||||
topology,
|
||||
indirectBufferCache,
|
||||
parameterBufferCache,
|
||||
indirectBufferRange,
|
||||
parameterBufferRange,
|
||||
maxDrawCount,
|
||||
stride,
|
||||
indexCount,
|
||||
IndirectDrawType.DrawIndexedIndirectCount);
|
||||
Threed.IndirectDrawType.DrawIndexedIndirectCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -11,8 +11,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||
/// </summary>
|
||||
class VtgAsComputeContext : IDisposable
|
||||
{
|
||||
private const int DummyBufferSize = 16;
|
||||
|
||||
private readonly GpuContext _context;
|
||||
|
||||
/// <summary>
|
||||
@@ -48,7 +46,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
format.GetBytesPerElement(),
|
||||
format,
|
||||
DepthStencilMode.Depth,
|
||||
Target.TextureBuffer,
|
||||
@@ -521,21 +519,6 @@ 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,7 +147,6 @@ 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;
|
||||
}
|
||||
|
||||
@@ -163,15 +162,12 @@ 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();
|
||||
|
||||
@@ -200,11 +196,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(stackalloc[] { new BufferAssignment(vertexInfoBinding, vertexInfoRange) });
|
||||
_context.Renderer.Pipeline.SetUniformBuffers([new BufferAssignment(vertexInfoBinding, vertexInfoRange)]);
|
||||
|
||||
int vertexDataBinding = _vertexAsCompute.Reservations.VertexOutputStorageBufferBinding;
|
||||
BufferRange vertexDataRange = _vacContext.GetVertexDataBufferRange(_vertexDataOffset, _vertexDataSize, write: true);
|
||||
_context.Renderer.Pipeline.SetStorageBuffers(stackalloc[] { new BufferAssignment(vertexDataBinding, vertexDataRange) });
|
||||
_context.Renderer.Pipeline.SetStorageBuffers([new BufferAssignment(vertexDataBinding, vertexDataRange)]);
|
||||
|
||||
_vacContext.VertexInfoBufferUpdater.Commit();
|
||||
|
||||
@@ -232,7 +228,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(stackalloc[] { new BufferAssignment(vertexInfoBinding, vertexInfoRange) });
|
||||
_context.Renderer.Pipeline.SetUniformBuffers([new BufferAssignment(vertexInfoBinding, vertexInfoRange)]);
|
||||
|
||||
int vertexDataBinding = _vertexAsCompute.Reservations.VertexOutputStorageBufferBinding;
|
||||
|
||||
@@ -250,12 +246,11 @@ 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(stackalloc[]
|
||||
{
|
||||
_context.Renderer.Pipeline.SetStorageBuffers([
|
||||
new BufferAssignment(vertexDataBinding, vertexDataRange),
|
||||
new BufferAssignment(geometryVbBinding, vertexBuffer),
|
||||
new BufferAssignment(geometryIbBinding, indexBuffer),
|
||||
});
|
||||
new BufferAssignment(geometryIbBinding, indexBuffer)
|
||||
]);
|
||||
|
||||
_context.Renderer.Pipeline.DispatchCompute(
|
||||
BitUtils.DivRoundUp(primitivesCount, ComputeLocalSize),
|
||||
@@ -299,7 +294,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||
|
||||
_context.Renderer.Pipeline.SetProgram(_vertexPassthroughProgram);
|
||||
_context.Renderer.Pipeline.SetIndexBuffer(indexBuffer, IndexType.UInt);
|
||||
_context.Renderer.Pipeline.SetStorageBuffers(stackalloc[] { new BufferAssignment(vertexDataBinding, vertexBuffer) });
|
||||
_context.Renderer.Pipeline.SetStorageBuffers([new BufferAssignment(vertexDataBinding, vertexBuffer)]);
|
||||
|
||||
_context.Renderer.Pipeline.SetPrimitiveRestart(true, -1);
|
||||
_context.Renderer.Pipeline.SetPrimitiveTopology(GetGeometryOutputTopology(_geometryAsCompute.Info.GeometryVerticesPerPrimitive));
|
||||
@@ -314,7 +309,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(stackalloc[] { new BufferAssignment(vertexDataBinding, vertexDataRange) });
|
||||
_context.Renderer.Pipeline.SetStorageBuffers([new BufferAssignment(vertexDataBinding, vertexDataRange)]);
|
||||
_context.Renderer.Pipeline.Draw(_count, _instanceCount, 0, 0);
|
||||
}
|
||||
}
|
||||
@@ -345,20 +340,6 @@ 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>
|
||||
@@ -371,7 +352,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||
{
|
||||
MemoryManager memoryManager = _channel.MemoryManager;
|
||||
|
||||
BufferRange range = memoryManager.GetBackingMemory(address).BufferCache.GetBufferRange(memoryManager.GetPhysicalRegions(address, size), BufferStage.VertexBuffer);
|
||||
BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange(memoryManager.GetPhysicalRegions(address, size), BufferStage.VertexBuffer);
|
||||
|
||||
ITexture bufferTexture = _vacContext.EnsureBufferTexture(index + 2, format);
|
||||
bufferTexture.SetStorage(range);
|
||||
@@ -413,7 +394,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||
MemoryManager memoryManager = _channel.MemoryManager;
|
||||
|
||||
ulong misalign = address & ((ulong)_context.Capabilities.TextureBufferOffsetAlignment - 1);
|
||||
BufferRange range = memoryManager.GetBackingMemory(address).BufferCache.GetBufferRange(
|
||||
BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange(
|
||||
memoryManager.GetPhysicalRegions(address + indexOffset - misalign, size + misalign),
|
||||
BufferStage.IndexBuffer);
|
||||
misalignedOffset = (int)misalign >> shift;
|
||||
|
||||
@@ -16,7 +16,6 @@ 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;
|
||||
@@ -114,13 +113,12 @@ 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 (physicalMemory.WriteWithRedundancyCheck(_ubBeginCpuAddress, data))
|
||||
if (memoryManager.Physical.WriteWithRedundancyCheck(_ubBeginCpuAddress, data))
|
||||
{
|
||||
physicalMemory.BufferCache.ForceDirty(memoryManager, _ubFollowUpAddress - _ubByteCount, _ubByteCount);
|
||||
memoryManager.Physical.BufferCache.ForceDirty(memoryManager, _ubFollowUpAddress - _ubByteCount, _ubByteCount);
|
||||
}
|
||||
|
||||
_ubFollowUpAddress = 0;
|
||||
|
||||
@@ -641,8 +641,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
public void DrawIndirect(
|
||||
ThreedClass engine,
|
||||
PrimitiveTopology topology,
|
||||
BufferCache indirectBufferCache,
|
||||
BufferCache parameterBufferCache,
|
||||
MultiRange indirectBufferRange,
|
||||
MultiRange parameterBufferRange,
|
||||
int maxDrawCount,
|
||||
@@ -664,6 +662,8 @@ 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 = indirectBufferCache.GetBufferRange(indirectBufferRange, BufferStage.Indirect);
|
||||
BufferRange parameterBuffer = parameterBufferCache.GetBufferRange(parameterBufferRange, BufferStage.Indirect);
|
||||
BufferRange indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferRange, BufferStage.Indirect);
|
||||
BufferRange parameterBuffer = memory.BufferCache.GetBufferRange(parameterBufferRange, BufferStage.Indirect);
|
||||
|
||||
if (indexed)
|
||||
{
|
||||
@@ -698,7 +698,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
}
|
||||
else
|
||||
{
|
||||
BufferRange indirectBuffer = indirectBufferCache.GetBufferRange(indirectBufferRange, BufferStage.Indirect);
|
||||
BufferRange indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferRange, BufferStage.Indirect);
|
||||
|
||||
if (indexed)
|
||||
{
|
||||
|
||||
@@ -381,10 +381,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
BufferDescriptor sb = info.SBuffers[index];
|
||||
|
||||
(PhysicalMemory physical, ulong sbDescAddress) = _channel.BufferManager.GetGraphicsUniformBufferAddress(stage, sb.SbCbSlot);
|
||||
ulong sbDescAddress = _channel.BufferManager.GetGraphicsUniformBufferAddress(stage, sb.SbCbSlot);
|
||||
sbDescAddress += (ulong)sb.SbCbOffset * 4;
|
||||
|
||||
SbDescriptor sbDescriptor = physical.Read<SbDescriptor>(sbDescAddress);
|
||||
SbDescriptor sbDescriptor = _channel.MemoryManager.Physical.Read<SbDescriptor>(sbDescAddress);
|
||||
|
||||
uint size;
|
||||
if (sb.SbCbSlot == Constants.DriverReservedUniformBuffer)
|
||||
@@ -505,9 +505,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
rtNoAlphaMask |= 1u << index;
|
||||
}
|
||||
|
||||
TextureCache colorTextureCache = memoryManager.GetBackingMemory(colorState.Address.Pack()).TextureCache;
|
||||
|
||||
Image.Texture color = colorTextureCache.FindOrCreateTexture(
|
||||
Image.Texture color = memoryManager.Physical.TextureCache.FindOrCreateTexture(
|
||||
memoryManager,
|
||||
colorState,
|
||||
_vtgWritesRtLayer || layered,
|
||||
@@ -515,7 +513,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
samplesInX,
|
||||
samplesInY,
|
||||
sizeHint);
|
||||
|
||||
|
||||
changedScale |= _channel.TextureManager.SetRenderTargetColor(index, color);
|
||||
|
||||
if (color != null)
|
||||
@@ -545,9 +543,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
RtDepthStencilState dsState = _state.State.RtDepthStencilState;
|
||||
Size3D dsSize = _state.State.RtDepthStencilSize;
|
||||
TextureCache dsTextureCache = memoryManager.GetBackingMemory(dsState.Address.Pack()).TextureCache;
|
||||
|
||||
depthStencil = dsTextureCache.FindOrCreateTexture(
|
||||
|
||||
depthStencil = memoryManager.Physical.TextureCache.FindOrCreateTexture(
|
||||
memoryManager,
|
||||
dsState,
|
||||
dsSize,
|
||||
@@ -1412,6 +1409,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// </summary>
|
||||
private void UpdateShaderState()
|
||||
{
|
||||
ShaderCache shaderCache = _channel.MemoryManager.Physical.ShaderCache;
|
||||
|
||||
_vtgWritesRtLayer = false;
|
||||
|
||||
ShaderAddresses addresses = new();
|
||||
@@ -1434,9 +1433,6 @@ 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,7 +5,6 @@ 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;
|
||||
@@ -805,8 +804,6 @@ 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>
|
||||
@@ -815,8 +812,6 @@ 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,
|
||||
@@ -824,7 +819,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
int indexCount,
|
||||
IndirectDrawType drawType)
|
||||
{
|
||||
_drawManager.DrawIndirect(this, topology, indirectBufferCache, parameterBufferCache, indirectBufferRange, parameterBufferRange, maxDrawCount, stride, indexCount, drawType);
|
||||
_drawManager.DrawIndirect(this, topology, indirectBufferRange, parameterBufferRange, maxDrawCount, stride, indexCount, drawType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -233,9 +233,6 @@ 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;
|
||||
@@ -308,7 +305,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 = srcTextureCache.FindOrCreateTexture(
|
||||
Image.Texture srcTexture = memoryManager.Physical.TextureCache.FindOrCreateTexture(
|
||||
memoryManager,
|
||||
srcCopyTexture,
|
||||
offset,
|
||||
@@ -329,7 +326,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod
|
||||
return;
|
||||
}
|
||||
|
||||
srcTextureCache.Lift(srcTexture);
|
||||
memoryManager.Physical.TextureCache.Lift(srcTexture);
|
||||
|
||||
// When the source texture that was found has a depth format,
|
||||
// we must enforce the target texture also has a depth format,
|
||||
@@ -345,7 +342,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod
|
||||
dstCopyTextureFormat = dstCopyTexture.Format.Convert();
|
||||
}
|
||||
|
||||
Image.Texture dstTexture = dstTextureCache.FindOrCreateTexture(
|
||||
Image.Texture dstTexture = memoryManager.Physical.TextureCache.FindOrCreateTexture(
|
||||
memoryManager,
|
||||
dstCopyTexture,
|
||||
0,
|
||||
|
||||
@@ -58,24 +58,22 @@ 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.AttachToChannel(BufferManager.Rebind);
|
||||
memoryManager.Physical.IncrementReferenceCount();
|
||||
|
||||
if (oldMemoryManager != null)
|
||||
{
|
||||
oldMemoryManager.DetachFromChannel(BufferManager.Rebind);
|
||||
oldMemoryManager.Physical.BufferCache.NotifyBuffersModified -= BufferManager.Rebind;
|
||||
oldMemoryManager.Physical.DecrementReferenceCount();
|
||||
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.QueuePrune();
|
||||
memoryManager.Physical.BufferCache.QueuePrune();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -88,7 +86,7 @@ namespace Ryujinx.Graphics.Gpu
|
||||
TextureManager.ReloadPools();
|
||||
|
||||
MemoryManager memoryManager = Volatile.Read(ref _memoryManager);
|
||||
memoryManager?.QueuePrune();
|
||||
memoryManager?.Physical.BufferCache.QueuePrune();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -143,7 +141,8 @@ namespace Ryujinx.Graphics.Gpu
|
||||
MemoryManager oldMemoryManager = Interlocked.Exchange(ref _memoryManager, null);
|
||||
if (oldMemoryManager != null)
|
||||
{
|
||||
oldMemoryManager.DetachFromChannel(BufferManager.Rebind);
|
||||
oldMemoryManager.Physical.BufferCache.NotifyBuffersModified -= BufferManager.Rebind;
|
||||
oldMemoryManager.Physical.DecrementReferenceCount();
|
||||
oldMemoryManager.MemoryUnmapped -= MemoryUnmappedHandler;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ 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;
|
||||
@@ -173,7 +172,7 @@ namespace Ryujinx.Graphics.Gpu
|
||||
throw new ArgumentException("The PID is invalid or the process was not registered", nameof(pid));
|
||||
}
|
||||
|
||||
return new MemoryManager(this, physicalMemory, cpuMemorySize);
|
||||
return new MemoryManager(physicalMemory, cpuMemorySize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -198,7 +197,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, IVirtualMemoryManagerTracked cpuMemory)
|
||||
public void RegisterProcess(ulong pid, Cpu.IVirtualMemoryManagerTracked cpuMemory)
|
||||
{
|
||||
PhysicalMemory physicalMemory = new(this, cpuMemory);
|
||||
if (!PhysicalMemoryRegistry.TryAdd(pid, physicalMemory))
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
@@ -65,7 +64,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, PhysicalMemory physicalMemory, ulong address, int maximumId, TextureBindingsArrayCache bindingsArrayCache)
|
||||
public T FindOrCreate(GpuChannel channel, ulong address, int maximumId, TextureBindingsArrayCache bindingsArrayCache)
|
||||
{
|
||||
// Remove old entries from the cache, if possible.
|
||||
while (_pools.Count > MaxCapacity && (_currentTimestamp - _pools.First.Value.CacheTimestamp) >= MinDeltaForRemoval)
|
||||
@@ -100,7 +99,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
|
||||
// If not found, create a new one.
|
||||
pool = CreatePool(_context, channel, physicalMemory, address, maximumId);
|
||||
pool = CreatePool(_context, channel, address, maximumId);
|
||||
|
||||
pool.CacheNode = _pools.AddLast(pool);
|
||||
pool.CacheTimestamp = _currentTimestamp;
|
||||
@@ -113,10 +112,9 @@ 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, PhysicalMemory physicalMemory, ulong address, int maximumId);
|
||||
protected abstract T CreatePool(GpuContext context, GpuChannel channel, ulong address, int maximumId);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
@@ -22,12 +20,11 @@ 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, PhysicalMemory physicalMemory, ulong address, int maximumId)
|
||||
protected override SamplerPool CreatePool(GpuContext context, GpuChannel channel, ulong address, int maximumId)
|
||||
{
|
||||
return new SamplerPool(context, physicalMemory, address, maximumId);
|
||||
return new SamplerPool(context, channel.MemoryManager.Physical, address, maximumId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -660,7 +660,6 @@ 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++)
|
||||
{
|
||||
@@ -674,7 +673,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;
|
||||
@@ -703,10 +702,11 @@ 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, bufferCache, texture.Range, bindingInfo, index); }
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, entry.ImageArray, hostTexture, texture.Range, bindingInfo, index);
|
||||
}
|
||||
else
|
||||
{
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, bufferCache, texture.Range, bindingInfo, index);
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, texture.Range, bindingInfo, index);
|
||||
}
|
||||
}
|
||||
else if (isImage)
|
||||
@@ -797,11 +797,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return;
|
||||
}
|
||||
|
||||
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(textureBufferBounds.Physical.GetSpan(textureBufferBounds.Range));
|
||||
|
||||
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(textureBufferBounds.Range));
|
||||
|
||||
if (separateSamplerBuffer)
|
||||
{
|
||||
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(samplerBufferBounds.Physical.GetSpan(samplerBufferBounds.Range));
|
||||
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(samplerBufferBounds.Range));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -828,10 +828,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
else
|
||||
{
|
||||
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(textureBufferBounds.Physical.GetSpan(textureBufferBounds.Range));
|
||||
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(textureBufferBounds.Range));
|
||||
|
||||
if (separateSamplerBuffer)
|
||||
{
|
||||
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(samplerBufferBounds.Physical.GetSpan(samplerBufferBounds.Range));
|
||||
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(samplerBufferBounds.Range));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -900,18 +901,16 @@ 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, bufferCache, texture.Range, bindingInfo, index);
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, entry.ImageArray, hostTexture, texture.Range, bindingInfo, index);
|
||||
}
|
||||
else
|
||||
{
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, bufferCache, texture.Range, bindingInfo, index);
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, 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>(bounds.Physical.GetSpan(bounds.Range));
|
||||
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.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>(bounds.Physical.GetSpan(bounds.Range));
|
||||
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(bounds.Range));
|
||||
cachedSamplerBufferIndex = samplerBufferIndex;
|
||||
}
|
||||
}
|
||||
@@ -524,8 +524,7 @@ 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.
|
||||
BufferCache bufferCache = _channel.MemoryManager.GetBackingMemory(descriptor.UnpackAddress()).BufferCache;
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, bufferCache, texture.Range, bindingInfo, false);
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range, bindingInfo, false);
|
||||
|
||||
// Cache is not used for buffer texture, it must always rebind.
|
||||
state.CachedTexture = null;
|
||||
@@ -660,8 +659,7 @@ 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.
|
||||
|
||||
BufferCache bufferCache = _channel.MemoryManager.GetBackingMemory(descriptor.UnpackAddress()).BufferCache;
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, bufferCache, texture.Range, bindingInfo, true);
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range, bindingInfo, true);
|
||||
|
||||
// Cache is not used for buffer texture, it must always rebind.
|
||||
state.CachedTexture = null;
|
||||
@@ -717,10 +715,9 @@ 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, physical, poolAddress, maximumId, _bindingsArrayCache);
|
||||
TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId, _bindingsArrayCache);
|
||||
|
||||
TextureDescriptor descriptor;
|
||||
|
||||
@@ -754,12 +751,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
(int textureWordOffset, int samplerWordOffset, TextureHandleType handleType) = TextureHandle.UnpackOffsets(wordOffset);
|
||||
|
||||
(PhysicalMemory texturePhysicalMemory, ulong textureBufferAddress) = _isCompute
|
||||
ulong textureBufferAddress = _isCompute
|
||||
? _channel.BufferManager.GetComputeUniformBufferAddress(textureBufferIndex)
|
||||
: _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, textureBufferIndex);
|
||||
|
||||
int handle = textureBufferAddress != MemoryManager.PteUnmapped
|
||||
? texturePhysicalMemory.Read<int>(textureBufferAddress + (uint)textureWordOffset * 4)
|
||||
? _channel.MemoryManager.Physical.Read<int>(textureBufferAddress + (uint)textureWordOffset * 4)
|
||||
: 0;
|
||||
|
||||
// The "wordOffset" (which is really the immediate value used on texture instructions on the shader)
|
||||
@@ -774,12 +771,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
if (handleType != TextureHandleType.SeparateConstantSamplerHandle)
|
||||
{
|
||||
(PhysicalMemory samplerPhysicalMemory, ulong samplerBufferAddress) = _isCompute
|
||||
ulong samplerBufferAddress = _isCompute
|
||||
? _channel.BufferManager.GetComputeUniformBufferAddress(samplerBufferIndex)
|
||||
: _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, samplerBufferIndex);
|
||||
|
||||
samplerHandle = samplerBufferAddress != MemoryManager.PteUnmapped
|
||||
? samplerPhysicalMemory.Read<int>(samplerBufferAddress + (uint)samplerWordOffset * 4)
|
||||
? _channel.MemoryManager.Physical.Read<int>(samplerBufferAddress + (uint)samplerWordOffset * 4)
|
||||
: 0;
|
||||
}
|
||||
else
|
||||
@@ -816,8 +813,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
if (poolAddress != MemoryManager.PteUnmapped)
|
||||
{
|
||||
PhysicalMemory physical = _channel.MemoryManager.GetBackingMemory(_texturePoolGpuVa);
|
||||
texturePool = _texturePoolCache.FindOrCreate(_channel, physical, poolAddress, _texturePoolMaximumId, _bindingsArrayCache);
|
||||
texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, _texturePoolMaximumId, _bindingsArrayCache);
|
||||
_texturePool = texturePool;
|
||||
}
|
||||
}
|
||||
@@ -828,8 +824,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
if (poolAddress != MemoryManager.PteUnmapped)
|
||||
{
|
||||
PhysicalMemory physical = _channel.MemoryManager.GetBackingMemory(_samplerPoolGpuVa);
|
||||
samplerPool = _samplerPoolCache.FindOrCreate(_channel, physical, poolAddress, _samplerPoolMaximumId, _bindingsArrayCache);
|
||||
samplerPool = _samplerPoolCache.FindOrCreate(_channel, poolAddress, _samplerPoolMaximumId, _bindingsArrayCache);
|
||||
_samplerPool = samplerPool;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Gpu.Shader;
|
||||
using System;
|
||||
|
||||
@@ -386,9 +385,8 @@ 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, physical, poolAddress, maximumId, _bindingsArrayCache);
|
||||
TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId, _bindingsArrayCache);
|
||||
|
||||
return texturePool;
|
||||
}
|
||||
|
||||
@@ -160,10 +160,9 @@ 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, PhysicalMemory physicalMemory, ulong address, int maximumId) : base(context, physicalMemory, address, maximumId)
|
||||
public TexturePool(GpuContext context, GpuChannel channel, ulong address, int maximumId) : base(context, channel.MemoryManager.Physical, address, maximumId)
|
||||
{
|
||||
_channel = channel;
|
||||
_aliasLists = new Dictionary<Texture, TextureAliasList>();
|
||||
@@ -194,9 +193,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
|
||||
TextureInfo info = GetInfo(descriptor, out int layerSize);
|
||||
MemoryManager memoryManager = _channel.MemoryManager;
|
||||
TextureCache textureCache = memoryManager.GetBackingMemory(descriptor.UnpackAddress()).TextureCache;
|
||||
texture = textureCache.FindOrCreateTexture(memoryManager, TextureSearchFlags.ForSampler, info, layerSize);
|
||||
texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.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)
|
||||
@@ -424,8 +421,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
continue;
|
||||
}
|
||||
|
||||
TextureCache textureCache = _channel.MemoryManager.GetBackingMemory(address).TextureCache;
|
||||
MultiRange range = textureCache.UpdatePartiallyMapped(_channel.MemoryManager, address, texture);
|
||||
MultiRange range = _channel.MemoryManager.Physical.TextureCache.UpdatePartiallyMapped(_channel.MemoryManager, address, texture);
|
||||
|
||||
// If the texture is not mapped at all, delete its reference.
|
||||
|
||||
@@ -450,7 +446,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 (!textureCache.UpdateMapping(texture, range))
|
||||
if (!_channel.MemoryManager.Physical.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)
|
||||
@@ -485,7 +481,6 @@ 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;
|
||||
@@ -510,8 +505,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
if (texture.HasOneReference())
|
||||
{
|
||||
TextureCache textureCache = memoryManager.GetBackingMemory(descriptor.UnpackAddress()).TextureCache;
|
||||
textureCache.AddShortCache(texture, ref cachedDescriptor);
|
||||
_channel.MemoryManager.Physical.TextureCache.AddShortCache(texture, ref cachedDescriptor);
|
||||
}
|
||||
|
||||
if (Interlocked.Exchange(ref Items[id], null) != null)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
@@ -22,17 +20,11 @@ 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,
|
||||
PhysicalMemory physicalMemory,
|
||||
ulong address,
|
||||
int maximumId)
|
||||
protected override TexturePool CreatePool(GpuContext context, GpuChannel channel, ulong address, int maximumId)
|
||||
{
|
||||
return new TexturePool(context, channel, physicalMemory, address, maximumId);
|
||||
return new TexturePool(context, channel, address, maximumId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,16 +9,6 @@ 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>
|
||||
@@ -39,9 +29,8 @@ 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(PhysicalMemory physical, MultiRange range, BufferUsageFlags flags = BufferUsageFlags.None)
|
||||
public BufferBounds(MultiRange range, BufferUsageFlags flags = BufferUsageFlags.None)
|
||||
{
|
||||
Physical = physical;
|
||||
Range = range;
|
||||
Flags = flags;
|
||||
}
|
||||
|
||||
@@ -735,22 +735,18 @@ 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 static void CopyBuffer(GpuContext context, MemoryManager memoryManager, ulong srcVa, ulong dstVa, ulong size)
|
||||
public void CopyBuffer(MemoryManager memoryManager, ulong srcVa, ulong dstVa, ulong size)
|
||||
{
|
||||
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);
|
||||
MultiRange srcRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, srcVa, size, BufferStage.Copy);
|
||||
MultiRange dstRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, dstVa, size, BufferStage.Copy);
|
||||
|
||||
if (srcRange.Count == 1 && dstRange.Count == 1)
|
||||
{
|
||||
CopyBufferSingleRange(context, srcPhysical, dstPhysical, srcRange.GetSubRange(0).Address, dstRange.GetSubRange(0).Address, size);
|
||||
CopyBufferSingleRange(memoryManager, srcRange.GetSubRange(0).Address, dstRange.GetSubRange(0).Address, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -781,7 +777,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
ulong dstSize = dstSubRange.Size - dstOffset;
|
||||
ulong copySize = Math.Min(srcSize, dstSize);
|
||||
|
||||
CopyBufferSingleRange(context, srcPhysical, dstPhysical, srcSubRange.Address + srcOffset, dstSubRange.Address + dstOffset, copySize);
|
||||
CopyBufferSingleRange(memoryManager, srcSubRange.Address + srcOffset, dstSubRange.Address + dstOffset, copySize);
|
||||
|
||||
srcOffset += copySize;
|
||||
dstOffset += copySize;
|
||||
@@ -797,26 +793,18 @@ 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 static void CopyBufferSingleRange(
|
||||
GpuContext context,
|
||||
PhysicalMemory srcPhysical,
|
||||
PhysicalMemory dstPhysical,
|
||||
ulong srcAddress,
|
||||
ulong dstAddress,
|
||||
ulong size)
|
||||
private void CopyBufferSingleRange(MemoryManager memoryManager, ulong srcAddress, ulong dstAddress, ulong size)
|
||||
{
|
||||
Buffer srcBuffer = srcPhysical.BufferCache.GetBuffer(srcAddress, size, BufferStage.Copy);
|
||||
Buffer dstBuffer = dstPhysical.BufferCache.GetBuffer(dstAddress, size, BufferStage.Copy);
|
||||
Buffer srcBuffer = GetBuffer(srcAddress, size, BufferStage.Copy);
|
||||
Buffer dstBuffer = 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,
|
||||
@@ -832,7 +820,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);
|
||||
dstPhysical.WriteTrackedResource(dstAddress, srcPhysical.GetSpan(srcAddress, (int)size), ResourceKind.Buffer);
|
||||
memoryManager.Physical.WriteTrackedResource(dstAddress, memoryManager.Physical.GetSpan(srcAddress, (int)size), ResourceKind.Buffer);
|
||||
}
|
||||
|
||||
dstBuffer.CopyToDependantVirtualBuffers(dstAddress, size);
|
||||
@@ -861,7 +849,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
_context.Renderer.Pipeline.ClearBuffer(buffer.Handle, offset, (int)subRange.Size, value);
|
||||
|
||||
memoryManager.GetBackingMemory(gpuVa).FillTrackedResource(subRange.Address, subRange.Size, value, ResourceKind.Buffer);
|
||||
memoryManager.Physical.FillTrackedResource(subRange.Address, subRange.Size, value, ResourceKind.Buffer);
|
||||
|
||||
buffer.CopyToDependantVirtualBuffers(subRange.Address, subRange.Size);
|
||||
}
|
||||
|
||||
@@ -66,19 +66,18 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
Buffers = new BufferBounds[count];
|
||||
Unaligned = new bool[count];
|
||||
|
||||
Buffers.AsSpan().Fill(new BufferBounds(null, new MultiRange(MemoryManager.PteUnmapped, 0UL)));
|
||||
Buffers.AsSpan().Fill(new BufferBounds(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, PhysicalMemory physical, MultiRange range, BufferUsageFlags flags = BufferUsageFlags.None)
|
||||
public void SetBounds(int index, MultiRange range, BufferUsageFlags flags = BufferUsageFlags.None)
|
||||
{
|
||||
Buffers[index] = new BufferBounds(physical, range, flags);
|
||||
Buffers[index] = new BufferBounds(range, flags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -157,10 +156,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="type">Type of each index buffer element</param>
|
||||
public void SetIndexBuffer(ulong gpuVa, ulong size, IndexType type)
|
||||
{
|
||||
BufferCache bufferCache = _channel.MemoryManager.GetBackingMemory(gpuVa).BufferCache;
|
||||
MultiRange range = bufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.IndexBuffer);
|
||||
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.IndexBuffer);
|
||||
|
||||
_indexBuffer.BufferCache = bufferCache;
|
||||
_indexBuffer.Range = range;
|
||||
_indexBuffer.Type = type;
|
||||
|
||||
@@ -189,15 +186,11 @@ 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)
|
||||
{
|
||||
BufferCache bufferCache = _channel.MemoryManager.GetBackingMemory(gpuVa).BufferCache;
|
||||
MultiRange range = bufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.VertexBuffer);
|
||||
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.VertexBuffer);
|
||||
|
||||
ref VertexBuffer vb = ref _vertexBuffers[index];
|
||||
|
||||
vb.BufferCache = bufferCache;
|
||||
vb.Range = range;
|
||||
vb.Stride = stride;
|
||||
vb.Divisor = divisor;
|
||||
_vertexBuffers[index].Range = range;
|
||||
_vertexBuffers[index].Stride = stride;
|
||||
_vertexBuffers[index].Divisor = divisor;
|
||||
|
||||
_vertexBuffersDirty = true;
|
||||
|
||||
@@ -220,10 +213,9 @@ 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)
|
||||
{
|
||||
PhysicalMemory physical = _channel.MemoryManager.GetBackingMemory(gpuVa);
|
||||
MultiRange range = physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStage.TransformFeedback);
|
||||
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStage.TransformFeedback);
|
||||
|
||||
_transformFeedbackBuffers[index] = new BufferBounds(physical, range);
|
||||
_transformFeedbackBuffers[index] = new BufferBounds(range);
|
||||
_transformFeedbackBuffersDirty = true;
|
||||
}
|
||||
|
||||
@@ -266,12 +258,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
RecordStorageAlignment(_cpStorageBuffers, index, gpuVa);
|
||||
|
||||
gpuVa = BitUtils.AlignDown(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment);
|
||||
gpuVa = BitUtils.AlignDown<ulong>(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment);
|
||||
|
||||
PhysicalMemory physical = _channel.MemoryManager.GetBackingMemory(gpuVa);
|
||||
MultiRange range = physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStageUtils.ComputeStorage(flags));
|
||||
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStageUtils.ComputeStorage(flags));
|
||||
|
||||
_cpStorageBuffers.SetBounds(index, physical, range, flags);
|
||||
_cpStorageBuffers.SetBounds(index, range, flags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -291,17 +282,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
RecordStorageAlignment(buffers, index, gpuVa);
|
||||
|
||||
gpuVa = BitUtils.AlignDown(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment);
|
||||
gpuVa = BitUtils.AlignDown<ulong>(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment);
|
||||
|
||||
PhysicalMemory physical = _channel.MemoryManager.GetBackingMemory(gpuVa);
|
||||
MultiRange range = physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStageUtils.GraphicsStorage(stage, flags));
|
||||
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStageUtils.GraphicsStorage(stage, flags));
|
||||
|
||||
if (!buffers.Buffers[index].Range.Equals(range))
|
||||
{
|
||||
_gpStorageBuffersDirty = true;
|
||||
}
|
||||
|
||||
buffers.SetBounds(index, physical, range, flags);
|
||||
buffers.SetBounds(index, range, flags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -313,10 +303,9 @@ 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)
|
||||
{
|
||||
PhysicalMemory physical = _channel.MemoryManager.GetBackingMemory(gpuVa);
|
||||
MultiRange range = physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.Compute);
|
||||
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.Compute);
|
||||
|
||||
_cpUniformBuffers.SetBounds(index, physical, range);
|
||||
_cpUniformBuffers.SetBounds(index, range);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -329,10 +318,9 @@ 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)
|
||||
{
|
||||
PhysicalMemory physical = _channel.MemoryManager.GetBackingMemory(gpuVa);
|
||||
MultiRange range = _channel.MemoryManager.GetBackingMemory(gpuVa).BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStageUtils.FromShaderStage(stage));
|
||||
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStageUtils.FromShaderStage(stage));
|
||||
|
||||
_gpUniformBuffers[stage].SetBounds(index, physical, range);
|
||||
_gpUniformBuffers[stage].SetBounds(index, range);
|
||||
_gpUniformBuffersDirty = true;
|
||||
}
|
||||
|
||||
@@ -428,10 +416,9 @@ 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 (PhysicalMemory, ulong) GetComputeUniformBufferAddress(int index)
|
||||
public ulong GetComputeUniformBufferAddress(int index)
|
||||
{
|
||||
ref BufferBounds buffer = ref _cpUniformBuffers.Buffers[index];
|
||||
return (buffer.Physical, buffer.Range.GetSubRange(0).Address);
|
||||
return _cpUniformBuffers.Buffers[index].Range.GetSubRange(0).Address;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -450,10 +437,9 @@ 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 (PhysicalMemory, ulong) GetGraphicsUniformBufferAddress(int stage, int index)
|
||||
public ulong GetGraphicsUniformBufferAddress(int stage, int index)
|
||||
{
|
||||
ref BufferBounds buffer = ref _gpUniformBuffers[stage].Buffers[index];
|
||||
return (buffer.Physical, buffer.Range.GetSubRange(0).Address);
|
||||
return _gpUniformBuffers[stage].Buffers[index].Range.GetSubRange(0).Address;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -492,10 +478,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </summary>
|
||||
public void CommitComputeBindings()
|
||||
{
|
||||
BindBuffers(_cpStorageBuffers, isStorage: true);
|
||||
BindBuffers(_cpUniformBuffers, isStorage: false);
|
||||
BufferCache bufferCache = _channel.MemoryManager.Physical.BufferCache;
|
||||
|
||||
CommitBufferTextureBindings();
|
||||
BindBuffers(bufferCache, _cpStorageBuffers, isStorage: true);
|
||||
BindBuffers(bufferCache, _cpUniformBuffers, isStorage: false);
|
||||
|
||||
CommitBufferTextureBindings(bufferCache);
|
||||
|
||||
// Force rebind after doing compute work.
|
||||
Rebind();
|
||||
@@ -507,14 +495,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// Commit any queued buffer texture bindings.
|
||||
/// </summary>
|
||||
/// <param name="bufferCache">Buffer cache</param>
|
||||
private void CommitBufferTextureBindings()
|
||||
private void CommitBufferTextureBindings(BufferCache bufferCache)
|
||||
{
|
||||
if (_bufferTextures.Count > 0)
|
||||
{
|
||||
foreach (BufferTextureBinding binding in _bufferTextures)
|
||||
{
|
||||
bool isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
|
||||
BufferRange range = binding.BufferCache.GetBufferRange(binding.Range, BufferStageUtils.TextureBuffer(binding.Stage, binding.BindingInfo.Flags), isStore);
|
||||
BufferRange range = 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.
|
||||
@@ -538,7 +526,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
foreach (BufferTextureArrayBinding<ITextureArray> binding in _bufferTextureArrays)
|
||||
{
|
||||
BufferRange range = binding.BufferCache.GetBufferRange(binding.Range, BufferStage.None);
|
||||
BufferRange range = bufferCache.GetBufferRange(binding.Range, BufferStage.None);
|
||||
binding.Texture.SetStorage(range);
|
||||
|
||||
textureArray[0] = binding.Texture;
|
||||
@@ -548,7 +536,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
foreach (BufferTextureArrayBinding<IImageArray> binding in _bufferImageArrays)
|
||||
{
|
||||
bool isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
|
||||
BufferRange range = binding.BufferCache.GetBufferRange(binding.Range, BufferStage.None, isStore);
|
||||
BufferRange range = bufferCache.GetBufferRange(binding.Range, BufferStage.None, isStore);
|
||||
binding.Texture.SetStorage(range);
|
||||
|
||||
textureArray[0] = binding.Texture;
|
||||
@@ -567,6 +555,8 @@ 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)
|
||||
@@ -575,14 +565,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
if (!_indexBuffer.Range.IsUnmapped)
|
||||
{
|
||||
BufferRange buffer = _indexBuffer.BufferCache.GetBufferRange(_indexBuffer.Range, BufferStage.IndexBuffer);
|
||||
BufferRange buffer = bufferCache.GetBufferRange(_indexBuffer.Range, BufferStage.IndexBuffer);
|
||||
|
||||
_context.Renderer.Pipeline.SetIndexBuffer(buffer, _indexBuffer.Type);
|
||||
}
|
||||
}
|
||||
else if (!_indexBuffer.Range.IsUnmapped)
|
||||
{
|
||||
_indexBuffer.BufferCache.SynchronizeBufferRange(_indexBuffer.Range);
|
||||
bufferCache.SynchronizeBufferRange(_indexBuffer.Range);
|
||||
}
|
||||
}
|
||||
else if (_rebind)
|
||||
@@ -607,7 +597,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
continue;
|
||||
}
|
||||
|
||||
BufferRange buffer = vb.BufferCache.GetBufferRange(vb.Range, BufferStage.VertexBuffer);
|
||||
BufferRange buffer = bufferCache.GetBufferRange(vb.Range, BufferStage.VertexBuffer);
|
||||
|
||||
vertexBuffers[index] = new VertexBufferDescriptor(buffer, vb.Stride, vb.Divisor);
|
||||
}
|
||||
@@ -625,7 +615,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
continue;
|
||||
}
|
||||
|
||||
vb.BufferCache.SynchronizeBufferRange(vb.Range);
|
||||
bufferCache.SynchronizeBufferRange(vb.Range);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -647,7 +637,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
continue;
|
||||
}
|
||||
|
||||
tfbs[index] = tfb.BufferCache.GetBufferRange(tfb.Range, BufferStage.TransformFeedback, write: true);
|
||||
tfbs[index] = bufferCache.GetBufferRange(tfb.Range, BufferStage.TransformFeedback, write: true);
|
||||
}
|
||||
|
||||
_context.Renderer.Pipeline.SetTransformFeedbackBuffers(tfbs);
|
||||
@@ -694,7 +684,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
_context.SupportBufferUpdater.SetTfeOffset(index, tfeOffset);
|
||||
|
||||
buffers[index] = new BufferAssignment(index, tfb.BufferCache.GetBufferRange(range, BufferStage.TransformFeedback, write: true));
|
||||
buffers[index] = new BufferAssignment(index, bufferCache.GetBufferRange(range, BufferStage.TransformFeedback, write: true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -712,7 +702,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
continue;
|
||||
}
|
||||
|
||||
tfb.BufferCache.SynchronizeBufferRange(tfb.Range);
|
||||
bufferCache.SynchronizeBufferRange(tfb.Range);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -720,7 +710,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
_gpStorageBuffersDirty = false;
|
||||
|
||||
BindBuffers(_gpStorageBuffers, isStorage: true);
|
||||
BindBuffers(bufferCache, _gpStorageBuffers, isStorage: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -731,14 +721,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
_gpUniformBuffersDirty = false;
|
||||
|
||||
BindBuffers(_gpUniformBuffers, isStorage: false);
|
||||
BindBuffers(bufferCache, _gpUniformBuffers, isStorage: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateBuffers(_gpUniformBuffers);
|
||||
}
|
||||
|
||||
CommitBufferTextureBindings();
|
||||
CommitBufferTextureBindings(bufferCache);
|
||||
|
||||
_rebind = false;
|
||||
|
||||
@@ -752,7 +742,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(BuffersPerStage[] bindings, bool isStorage)
|
||||
private void BindBuffers(BufferCache bufferCache, BuffersPerStage[] bindings, bool isStorage)
|
||||
{
|
||||
int rangesCount = 0;
|
||||
|
||||
@@ -773,8 +763,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
bool isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write);
|
||||
BufferRange range = isStorage
|
||||
? bounds.BufferCache.GetBufferRangeAligned(bounds.Range, bufferStage | BufferStageUtils.FromUsage(bounds.Flags), isWrite)
|
||||
: bounds.BufferCache.GetBufferRange(bounds.Range, bufferStage);
|
||||
? bufferCache.GetBufferRangeAligned(bounds.Range, bufferStage | BufferStageUtils.FromUsage(bounds.Flags), isWrite)
|
||||
: bufferCache.GetBufferRange(bounds.Range, bufferStage);
|
||||
|
||||
ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range);
|
||||
}
|
||||
@@ -790,10 +780,11 @@ 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(BuffersPerStage buffers, bool isStorage)
|
||||
private void BindBuffers(BufferCache bufferCache, BuffersPerStage buffers, bool isStorage)
|
||||
{
|
||||
int rangesCount = 0;
|
||||
|
||||
@@ -809,8 +800,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
bool isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write);
|
||||
BufferRange range = isStorage
|
||||
? bounds.BufferCache.GetBufferRangeAligned(bounds.Range, BufferStageUtils.ComputeStorage(bounds.Flags), isWrite)
|
||||
: bounds.BufferCache.GetBufferRange(bounds.Range, BufferStage.Compute);
|
||||
? bufferCache.GetBufferRangeAligned(bounds.Range, BufferStageUtils.ComputeStorage(bounds.Flags), isWrite)
|
||||
: bufferCache.GetBufferRange(bounds.Range, BufferStage.Compute);
|
||||
|
||||
ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range);
|
||||
}
|
||||
@@ -863,7 +854,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
continue;
|
||||
}
|
||||
|
||||
bounds.BufferCache.SynchronizeBufferRange(bounds.Range);
|
||||
_channel.MemoryManager.Physical.BufferCache.SynchronizeBufferRange(bounds.Range);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -880,14 +871,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
public void SetBufferTextureStorage(
|
||||
ShaderStage stage,
|
||||
ITexture texture,
|
||||
BufferCache bufferCache,
|
||||
MultiRange range,
|
||||
TextureBindingInfo bindingInfo,
|
||||
bool isImage)
|
||||
{
|
||||
bufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
|
||||
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
|
||||
|
||||
_bufferTextures.Add(new BufferTextureBinding(stage, texture, bufferCache, range, bindingInfo, isImage));
|
||||
_bufferTextures.Add(new BufferTextureBinding(stage, texture, range, bindingInfo, isImage));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -904,14 +894,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
ShaderStage stage,
|
||||
ITextureArray array,
|
||||
ITexture texture,
|
||||
BufferCache bufferCache,
|
||||
MultiRange range,
|
||||
TextureBindingInfo bindingInfo,
|
||||
int index)
|
||||
{
|
||||
bufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
|
||||
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
|
||||
|
||||
_bufferTextureArrays.Add(new BufferTextureArrayBinding<ITextureArray>(array, texture, bufferCache, range, bindingInfo, index));
|
||||
_bufferTextureArrays.Add(new BufferTextureArrayBinding<ITextureArray>(array, texture, range, bindingInfo, index));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -928,14 +917,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
ShaderStage stage,
|
||||
IImageArray array,
|
||||
ITexture texture,
|
||||
BufferCache bufferCache,
|
||||
MultiRange range,
|
||||
TextureBindingInfo bindingInfo,
|
||||
int index)
|
||||
{
|
||||
bufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
|
||||
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
|
||||
|
||||
_bufferImageArrays.Add(new BufferTextureArrayBinding<IImageArray>(array, texture, bufferCache, range, bindingInfo, index));
|
||||
_bufferImageArrays.Add(new BufferTextureArrayBinding<IImageArray>(array, texture, range, bindingInfo, index));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -19,11 +19,6 @@ 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>
|
||||
@@ -44,21 +39,18 @@ 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,11 +20,6 @@ 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>
|
||||
@@ -45,21 +40,18 @@ 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,7 +8,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </summary>
|
||||
struct IndexBuffer
|
||||
{
|
||||
public BufferCache BufferCache;
|
||||
public MultiRange Range;
|
||||
public IndexType Type;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Memory;
|
||||
using Ryujinx.Memory.Range;
|
||||
using System;
|
||||
@@ -36,9 +35,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
public event EventHandler<UnmapEventArgs> MemoryUnmapped;
|
||||
|
||||
private readonly GpuContext _context;
|
||||
private readonly List<PhysicalMemory> _physicalMemoryList;
|
||||
private readonly Dictionary<PhysicalMemory, byte> _physicalMemoryMap;
|
||||
/// <summary>
|
||||
/// Physical memory where the virtual memory is mapped into.
|
||||
/// </summary>
|
||||
internal PhysicalMemory Physical { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Virtual range cache.
|
||||
@@ -53,65 +53,19 @@ 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(GpuContext context, PhysicalMemory physicalMemory, ulong cpuMemorySize)
|
||||
internal MemoryManager(PhysicalMemory physicalMemory, ulong cpuMemorySize)
|
||||
{
|
||||
_context = context;
|
||||
|
||||
_physicalMemoryList = new List<PhysicalMemory>()
|
||||
{
|
||||
physicalMemory
|
||||
};
|
||||
|
||||
_physicalMemoryMap = new Dictionary<PhysicalMemory, byte>
|
||||
{
|
||||
{ physicalMemory, 0 }
|
||||
};
|
||||
|
||||
Physical = physicalMemory;
|
||||
VirtualRangeCache = new VirtualRangeCache(this);
|
||||
CounterCache = new CounterCache();
|
||||
_pageTable = new ulong[PtLvl0Size][];
|
||||
MemoryUnmapped += physicalMemory.TextureCache.MemoryUnmappedHandler;
|
||||
MemoryUnmapped += physicalMemory.BufferCache.MemoryUnmappedHandler;
|
||||
MemoryUnmapped += Physical.TextureCache.MemoryUnmappedHandler;
|
||||
MemoryUnmapped += Physical.BufferCache.MemoryUnmappedHandler;
|
||||
MemoryUnmapped += VirtualRangeCache.MemoryUnmappedHandler;
|
||||
MemoryUnmapped += CounterCache.MemoryUnmappedHandler;
|
||||
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();
|
||||
Physical.TextureCache.Initialize(cpuMemorySize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -127,15 +81,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
if (IsContiguous(va, size))
|
||||
{
|
||||
(PhysicalMemory physicalMemory, ulong address) = TranslateWithPhysicalMemory(va);
|
||||
ulong address = Translate(va);
|
||||
|
||||
if (tracked)
|
||||
{
|
||||
return physicalMemory.ReadTracked<T>(address);
|
||||
return Physical.ReadTracked<T>(address);
|
||||
}
|
||||
else
|
||||
{
|
||||
return physicalMemory.Read<T>(address);
|
||||
return Physical.Read<T>(address);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -159,9 +113,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
if (IsContiguous(va, size))
|
||||
{
|
||||
(PhysicalMemory physicalMemory, ulong address) = TranslateWithPhysicalMemory(va);
|
||||
|
||||
return physicalMemory.GetSpan(address, size, tracked);
|
||||
return Physical.GetSpan(Translate(va), size, tracked);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -186,7 +138,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
bool isContiguous = true;
|
||||
int mappedSize;
|
||||
|
||||
if (ValidateAddress(va) && IsMappedOnGpuAndPhysical(va))
|
||||
if (ValidateAddress(va) && GetPte(va) != PteUnmapped && Physical.IsMapped(Translate(va)))
|
||||
{
|
||||
ulong endVa = va + (ulong)size;
|
||||
ulong endVaAligned = (endVa + PageMask) & ~PageMask;
|
||||
@@ -199,7 +151,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
ulong nextVa = currentVa + PageSize;
|
||||
ulong nextPa = Translate(nextVa);
|
||||
|
||||
if (!ValidateAddress(nextVa) || !IsMappedOnGpuAndPhysical(nextVa))
|
||||
if (!ValidateAddress(nextVa) || GetPte(nextVa) == PteUnmapped || !Physical.IsMapped(nextPa))
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -228,9 +180,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
if (isContiguous)
|
||||
{
|
||||
(PhysicalMemory physicalMemory, ulong address) = TranslateWithPhysicalMemory(va);
|
||||
|
||||
return physicalMemory.GetSpan(address, mappedSize, tracked);
|
||||
return Physical.GetSpan(Translate(va), mappedSize, tracked);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -242,23 +192,6 @@ 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>
|
||||
@@ -276,22 +209,22 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
if ((va & PageMask) != 0)
|
||||
{
|
||||
(PhysicalMemory physicalMemory, ulong pa) = TranslateWithPhysicalMemory(va);
|
||||
ulong pa = Translate(va);
|
||||
|
||||
size = Math.Min(data.Length, (int)PageSize - (int)(va & PageMask));
|
||||
|
||||
physicalMemory.GetSpan(pa, size, tracked).CopyTo(data[..size]);
|
||||
Physical.GetSpan(pa, size, tracked).CopyTo(data[..size]);
|
||||
|
||||
offset += size;
|
||||
}
|
||||
|
||||
for (; offset < data.Length; offset += size)
|
||||
{
|
||||
(PhysicalMemory physicalMemory, ulong pa) = TranslateWithPhysicalMemory(va + (ulong)offset);
|
||||
ulong pa = Translate(va + (ulong)offset);
|
||||
|
||||
size = Math.Min(data.Length - offset, (int)PageSize);
|
||||
|
||||
physicalMemory.GetSpan(pa, size, tracked).CopyTo(data.Slice(offset, size));
|
||||
Physical.GetSpan(pa, size, tracked).CopyTo(data.Slice(offset, size));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,17 +239,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
if (IsContiguous(va, size))
|
||||
{
|
||||
(PhysicalMemory physicalMemory, ulong address) = TranslateWithPhysicalMemory(va);
|
||||
|
||||
return physicalMemory.GetWritableRegion(address, size, tracked);
|
||||
return Physical.GetWritableRegion(Translate(va), size, tracked);
|
||||
}
|
||||
else
|
||||
{
|
||||
Memory<byte> memory = new byte[size];
|
||||
MemoryOwner<byte> memoryOwner = MemoryOwner<byte>.Rent(size);
|
||||
|
||||
GetSpan(va, size).CopyTo(memory.Span);
|
||||
ReadImpl(va, memoryOwner.Span, tracked);
|
||||
|
||||
return new WritableRegion(this, va, memory, tracked);
|
||||
return new WritableRegion(this, va, memoryOwner, tracked);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,7 +269,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, va, data) => physical.Write(va, data));
|
||||
WriteImpl(va, data, Physical.Write);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -348,7 +279,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, va, data) => physical.WriteTrackedResource(va, data));
|
||||
WriteImpl(va, data, Physical.WriteTrackedResource);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -358,10 +289,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, va, data) => physical.WriteUntracked(va, data));
|
||||
WriteImpl(va, data, Physical.WriteUntracked);
|
||||
}
|
||||
|
||||
private delegate void WriteCallback(PhysicalMemory physicalMemory, ulong address, ReadOnlySpan<byte> data);
|
||||
private delegate void WriteCallback(ulong address, ReadOnlySpan<byte> data);
|
||||
|
||||
/// <summary>
|
||||
/// Writes data to possibly non-contiguous GPU mapped memory.
|
||||
@@ -373,9 +304,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
if (IsContiguous(va, data.Length))
|
||||
{
|
||||
(PhysicalMemory physicalMemory, ulong address) = TranslateWithPhysicalMemory(va);
|
||||
|
||||
writeCallback(physicalMemory, address, data);
|
||||
writeCallback(Translate(va), data);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -383,67 +312,22 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
if ((va & PageMask) != 0)
|
||||
{
|
||||
(PhysicalMemory physicalMemory, ulong pa) = TranslateWithPhysicalMemory(va);
|
||||
ulong pa = Translate(va);
|
||||
|
||||
size = Math.Min(data.Length, (int)PageSize - (int)(va & PageMask));
|
||||
|
||||
writeCallback(physicalMemory, pa, data[..size]);
|
||||
writeCallback(pa, data[..size]);
|
||||
|
||||
offset += size;
|
||||
}
|
||||
|
||||
for (; offset < data.Length; offset += size)
|
||||
{
|
||||
(PhysicalMemory physicalMemory, ulong pa) = TranslateWithPhysicalMemory(va + (ulong)offset);
|
||||
ulong pa = Translate(va + (ulong)offset);
|
||||
|
||||
size = Math.Min(data.Length - offset, (int)PageSize);
|
||||
|
||||
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));
|
||||
}
|
||||
writeCallback(pa, data.Slice(offset, size));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -475,51 +359,15 @@ 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, pIndex, kind));
|
||||
SetPte(va + offset, PackPte(pa + offset, kind));
|
||||
}
|
||||
|
||||
RunRemapActions(e);
|
||||
@@ -570,14 +418,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
for (int page = 0; page < pages - 1; page++)
|
||||
{
|
||||
ulong nextPte = GetPte(va + PageSize);
|
||||
|
||||
if (!ValidateAddress(va + PageSize) || nextPte == PteUnmapped)
|
||||
if (!ValidateAddress(va + PageSize) || GetPte(va + PageSize) == PteUnmapped)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (GetPte(va) + PageSize != nextPte)
|
||||
if (Translate(va) + PageSize != Translate(va + PageSize))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -611,7 +457,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
int pages = (int)((endVaRounded - va) / PageSize);
|
||||
|
||||
List<MemoryRange> regions = new();
|
||||
List<MemoryRange> regions = [];
|
||||
|
||||
for (int page = 0; page < pages - 1; page++)
|
||||
{
|
||||
@@ -689,49 +535,6 @@ 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>
|
||||
@@ -833,28 +636,6 @@ 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.
|
||||
@@ -878,18 +659,6 @@ 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>
|
||||
@@ -935,12 +704,11 @@ 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, byte pIndex, PteKind kind)
|
||||
private static ulong PackPte(ulong pa, PteKind kind)
|
||||
{
|
||||
return pa | ((ulong)pIndex << 48) | ((ulong)kind << 56);
|
||||
return pa | ((ulong)kind << 56);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -953,16 +721,6 @@ 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>
|
||||
@@ -970,7 +728,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <returns>Physical address</returns>
|
||||
private static ulong UnpackPaFromPte(ulong pte)
|
||||
{
|
||||
return pte & 0xffffffffffffUL;
|
||||
return pte & 0xffffffffffffffUL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </summary>
|
||||
struct VertexBuffer
|
||||
{
|
||||
public BufferCache BufferCache;
|
||||
public MultiRange Range;
|
||||
public int Stride;
|
||||
public int Divisor;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
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;
|
||||
@@ -67,11 +66,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// <inheritdoc/>
|
||||
public uint ConstantBuffer1Read(int offset)
|
||||
{
|
||||
(PhysicalMemory physical, ulong baseAddress) = _compute
|
||||
ulong baseAddress = _compute
|
||||
? _channel.BufferManager.GetComputeUniformBufferAddress(1)
|
||||
: _channel.BufferManager.GetGraphicsUniformBufferAddress(_stageIndex, 1);
|
||||
|
||||
return physical.Read<uint>(baseAddress + (ulong)offset);
|
||||
return _channel.MemoryManager.Physical.Read<uint>(baseAddress + (ulong)offset);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -733,15 +733,15 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
byte[] codeB,
|
||||
bool asCompute)
|
||||
{
|
||||
(PhysicalMemory physical, ulong cb1DataAddress) = channel.BufferManager.GetGraphicsUniformBufferAddress(0, 1);
|
||||
|
||||
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(physical, cb1DataAddress, vertexA.Cb1DataSize);
|
||||
byte[] cb1DataB = ReadArray(physical, cb1DataAddress, currentStage.Cb1DataSize);
|
||||
|
||||
byte[] cb1DataA = ReadArray(memoryManager, cb1DataAddress, vertexA.Cb1DataSize);
|
||||
byte[] cb1DataB = ReadArray(memoryManager, cb1DataAddress, currentStage.Cb1DataSize);
|
||||
|
||||
ShaderDumpPaths pathsA = default;
|
||||
ShaderDumpPaths pathsB = default;
|
||||
|
||||
@@ -775,11 +775,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
MemoryManager memoryManager = channel.MemoryManager;
|
||||
|
||||
(PhysicalMemory physical, ulong cb1DataAddress) = context.Stage == ShaderStage.Compute
|
||||
ulong cb1DataAddress = context.Stage == ShaderStage.Compute
|
||||
? channel.BufferManager.GetComputeUniformBufferAddress(1)
|
||||
: channel.BufferManager.GetGraphicsUniformBufferAddress(StageToStageIndex(context.Stage), 1);
|
||||
|
||||
byte[] cb1Data = ReadArray(physical, cb1DataAddress, context.Cb1DataSize);
|
||||
byte[] cb1Data = ReadArray(memoryManager, 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="physicalMemory">Physical memory to read the data from, might be null</param>
|
||||
/// <param name="memoryManager">Memory manager with the physical memory to read from</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(PhysicalMemory physicalMemory, ulong address, int size)
|
||||
private static byte[] ReadArray(MemoryManager memoryManager, ulong address, int size)
|
||||
{
|
||||
if (address == MemoryManager.PteUnmapped || size == 0 || physicalMemory == null)
|
||||
if (address == MemoryManager.PteUnmapped || size == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return physicalMemory.GetSpan(address, size).ToArray();
|
||||
return memoryManager.Physical.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>(bounds.Physical.GetSpan(bounds.Range));
|
||||
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(channel.MemoryManager.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>(bounds.Physical.GetSpan(bounds.Range));
|
||||
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(channel.MemoryManager.Physical.GetSpan(bounds.Range));
|
||||
cachedSamplerBufferIndex = samplerBufferIndex;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,18 +9,11 @@ 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>
|
||||
@@ -92,21 +85,7 @@ 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;
|
||||
|
||||
@@ -209,51 +188,6 @@ 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.
|
||||
@@ -271,10 +205,6 @@ namespace Ryujinx.Graphics.Gpu
|
||||
|
||||
pt.Cache.Tick();
|
||||
|
||||
EnsureCaptureTexture();
|
||||
|
||||
Volatile.Write(ref _lastPresentedTexture, new PresentedTexture(texture, pt.Crop));
|
||||
|
||||
texture.SynchronizeMemory();
|
||||
|
||||
ImageCrop crop = new(
|
||||
@@ -314,96 +244,6 @@ 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 IVirtualMemoryManagerTracked AddressSpace => _memoryManager;
|
||||
public IVirtualMemoryManager 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 ViServer ViServerS { get; private set; }
|
||||
internal ServerBase 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 ViServer(KernelContext, "ViServerS");
|
||||
ViServerS = new ServerBase(KernelContext, "ViServerS");
|
||||
LdnServer = new ServerBase(KernelContext, "LdnServer");
|
||||
|
||||
StartNewServices();
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
{
|
||||
interface IProcessContext : IDisposable
|
||||
{
|
||||
IVirtualMemoryManagerTracked AddressSpace { get; }
|
||||
IVirtualMemoryManager 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 IVirtualMemoryManagerTracked CpuMemory => Context.AddressSpace;
|
||||
public IVirtualMemoryManager CpuMemory => Context.AddressSpace;
|
||||
|
||||
public HleProcessDebugger Debugger { get; private set; }
|
||||
|
||||
|
||||
@@ -6,11 +6,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
{
|
||||
class ProcessContext : IProcessContext
|
||||
{
|
||||
public IVirtualMemoryManagerTracked AddressSpace { get; }
|
||||
public IVirtualMemoryManager AddressSpace { get; }
|
||||
|
||||
public ulong AddressSpaceSize { get; }
|
||||
|
||||
public ProcessContext(IVirtualMemoryManagerTracked asManager, ulong addressSpaceSize)
|
||||
public ProcessContext(IVirtualMemoryManager asManager, ulong addressSpaceSize)
|
||||
{
|
||||
AddressSpace = asManager;
|
||||
AddressSpaceSize = addressSpaceSize;
|
||||
|
||||
@@ -102,34 +102,5 @@ 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,6 +225,7 @@ 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);
|
||||
|
||||
@@ -235,27 +236,18 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||
// IsSystemBufferSharingEnabled()
|
||||
public ResultCode IsSystemBufferSharingEnabled(ServiceCtx context)
|
||||
{
|
||||
// TODO: Implement this once we have a way to check if we're not an AppletId.Application
|
||||
return ResultCode.Success;
|
||||
// NOTE: Service checks a private field and return an error if the SystemBufferSharing is disabled.
|
||||
|
||||
return ResultCode.NotImplemented;
|
||||
}
|
||||
|
||||
[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);
|
||||
|
||||
@@ -35,8 +35,6 @@ 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)
|
||||
{
|
||||
@@ -386,8 +384,6 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
return;
|
||||
}
|
||||
|
||||
LastActiveNpad = state.PlayerId;
|
||||
|
||||
ref RingLifo<NpadCommonState> lifo = ref GetCommonStateLifo(ref currentNpad);
|
||||
|
||||
NpadCommonState newState = new()
|
||||
@@ -643,20 +639,5 @@ 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,9 +24,15 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
// GetLastActiveNpad(u32) -> u8, u8
|
||||
public ResultCode GetLastActiveNpad(ServiceCtx context)
|
||||
{
|
||||
context.ResponseData.Write((byte)context.Device.Hid.Npads.GetLastActiveNpadId());
|
||||
// TODO: RequestData seems to have garbage data, reading an extra uint seems to fix the issue.
|
||||
context.RequestData.ReadUInt32();
|
||||
|
||||
return ResultCode.Success;
|
||||
ResultCode resultCode = GetAppletFooterUiTypeImpl(context, out AppletFooterUiType appletFooterUiType);
|
||||
|
||||
context.ResponseData.Write((byte)appletFooterUiType);
|
||||
context.ResponseData.Write((byte)0);
|
||||
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
[CommandCmif(307)]
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Ipc;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
@@ -106,26 +105,6 @@ 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)
|
||||
@@ -163,38 +142,5 @@ 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(arguments.NvMapHandle);
|
||||
|
||||
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, 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))
|
||||
{
|
||||
Map(physicalAddress, arguments.Offset, size, (PteKind)arguments.Kind, map.OwnerPid);
|
||||
_asContext.Gmm.Map(physicalAddress, arguments.Offset, size, (PteKind)arguments.Kind);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -301,7 +301,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu
|
||||
_memoryAllocator.AllocateRange(va, size, freeAddressStartPosition);
|
||||
}
|
||||
|
||||
Map(physicalAddress, va, size, (PteKind)arguments.Kind, map.OwnerPid);
|
||||
_asContext.Gmm.Map(physicalAddress, va, size, (PteKind)arguments.Kind);
|
||||
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(nvmapHandle);
|
||||
|
||||
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, nvmapHandle);
|
||||
|
||||
if (map == null)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid NvMap handle 0x{nvmapHandle:x8}!");
|
||||
@@ -389,25 +389,13 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu
|
||||
return NvInternalResult.InvalidInput;
|
||||
}
|
||||
|
||||
Map(mapOffs + map.Address, gpuVa, size, kind, map.OwnerPid);
|
||||
gmm.Map(mapOffs + map.Address, gpuVa, size, kind);
|
||||
}
|
||||
}
|
||||
|
||||
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(commandBuffer.Mem);
|
||||
|
||||
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, 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(commandBufferEntry.MapHandle);
|
||||
|
||||
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, 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(commandBufferEntry.MapHandle);
|
||||
|
||||
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, 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) { OwnerPid = Owner });
|
||||
|
||||
arguments.Handle = CreateHandleFromMap(new NvMapHandle(size));
|
||||
|
||||
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(arguments.Id);
|
||||
|
||||
NvMapHandle map = GetMapFromHandle(Owner, 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(arguments.Handle);
|
||||
|
||||
NvMapHandle map = GetMapFromHandle(Owner, 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(arguments.Handle);
|
||||
|
||||
NvMapHandle map = GetMapFromHandle(Owner, 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(arguments.Handle);
|
||||
|
||||
NvMapHandle map = GetMapFromHandle(Owner, 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(arguments.Handle);
|
||||
|
||||
NvMapHandle map = GetMapFromHandle(Owner, arguments.Handle);
|
||||
|
||||
if (map == null)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!");
|
||||
@@ -237,30 +237,25 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
||||
// _maps.TryRemove(GetOwner(), out _);
|
||||
}
|
||||
|
||||
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)
|
||||
private int CreateHandleFromMap(NvMapHandle map)
|
||||
{
|
||||
return _maps.Add(map);
|
||||
}
|
||||
|
||||
private static bool DeleteMapWithHandle(int handle)
|
||||
private static bool DeleteMapWithHandle(ulong pid, int handle)
|
||||
{
|
||||
return _maps.Delete(handle) != null;
|
||||
}
|
||||
|
||||
public static void IncrementMapRefCount(ulong pid, int handle)
|
||||
{
|
||||
GetMapFromHandle(handle)?.IncrementRefCount();
|
||||
GetMapFromHandle(pid, handle)?.IncrementRefCount();
|
||||
}
|
||||
|
||||
public static bool DecrementMapRefCount(ulong pid, int handle)
|
||||
{
|
||||
NvMapHandle map = GetMapFromHandle(handle);
|
||||
|
||||
NvMapHandle map = GetMapFromHandle(pid, handle);
|
||||
|
||||
if (map == null)
|
||||
{
|
||||
return false;
|
||||
@@ -268,8 +263,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
||||
|
||||
if (map.DecrementRefCount() <= 0)
|
||||
{
|
||||
DeleteMapWithHandle(handle);
|
||||
|
||||
DeleteMapWithHandle(pid, handle);
|
||||
|
||||
Logger.Debug?.Print(LogClass.ServiceNv, $"Deleted map {handle}!");
|
||||
|
||||
return true;
|
||||
@@ -280,7 +275,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
||||
}
|
||||
}
|
||||
|
||||
public static NvMapHandle GetMapFromHandle(int handle)
|
||||
public static NvMapHandle GetMapFromHandle(ulong pid, int handle)
|
||||
{
|
||||
return _maps.Get(handle);
|
||||
}
|
||||
|
||||
@@ -14,8 +14,7 @@ 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,15 +172,6 @@ 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()
|
||||
{
|
||||
@@ -205,14 +196,10 @@ 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, BitUtils.AlignUp(heapSize, 0x200000UL));
|
||||
|
||||
CustomInit(_context, _selfProcess.Pid, heapAddr + PointerBufferSize);
|
||||
|
||||
_context.Syscall.SetHeapSize(out ulong heapAddr, 0x200000);
|
||||
|
||||
_selfProcess.CpuMemory.Write(messagePtr + 0x0, 0);
|
||||
_selfProcess.CpuMemory.Write(messagePtr + 0x4, 2 << 10);
|
||||
_selfProcess.CpuMemory.Write(messagePtr + 0x8, heapAddr | ((ulong)PointerBufferSize << 48));
|
||||
|
||||
@@ -4,7 +4,6 @@ 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;
|
||||
@@ -39,6 +38,8 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
|
||||
private readonly Lock _lock = new();
|
||||
|
||||
public long RenderLayerId { get; private set; }
|
||||
|
||||
private class Layer
|
||||
{
|
||||
public int ProducerBinderId;
|
||||
@@ -59,6 +60,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
_device = device;
|
||||
_layers = new Dictionary<long, Layer>();
|
||||
RenderLayerId = 0;
|
||||
|
||||
_composerThread = new Thread(HandleComposition)
|
||||
{
|
||||
@@ -237,12 +239,34 @@ 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)
|
||||
@@ -336,55 +360,41 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
foreach (var (layerId, layer) in _layers)
|
||||
// TODO: support multilayers (& multidisplay ?)
|
||||
if (RenderLayerId == 0)
|
||||
{
|
||||
if (layer.State == LayerState.NotInitialized || layer.State == LayerState.ManagedClosed)
|
||||
continue;
|
||||
|
||||
if (_device.System.KernelContext.Processes.TryGetValue(layer.Owner, out var process))
|
||||
{
|
||||
if (process.State == ProcessState.Exiting || process.State == ProcessState.Exited)
|
||||
{
|
||||
HOSBinderDriverServer.UnregisterBinderObject(layer.ProducerBinderId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_layers.Remove(layerId))
|
||||
{
|
||||
CloseLayer(layerId, layer);
|
||||
}
|
||||
Layer layer = GetLayerByIdLocked(RenderLayerId);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
Status acquireStatus = layer.Consumer.AcquireBuffer(out BufferItem item, 0);
|
||||
|
||||
if (acquireStatus == Status.Success)
|
||||
Status acquireStatus = layer.Consumer.AcquireBuffer(out BufferItem item, 0);
|
||||
|
||||
if (acquireStatus == Status.Success)
|
||||
{
|
||||
if (_device.VSyncMode == VSyncMode.Unbounded)
|
||||
{
|
||||
if (_device.VSyncMode == VSyncMode.Unbounded)
|
||||
if (_swapInterval != 0)
|
||||
{
|
||||
if (_swapInterval != 0)
|
||||
{
|
||||
UpdateSwapInterval(0);
|
||||
_vSyncMode = _device.VSyncMode;
|
||||
}
|
||||
}
|
||||
else if (_device.VSyncMode != _vSyncMode)
|
||||
{
|
||||
UpdateSwapInterval(_device.VSyncMode == VSyncMode.Unbounded ? 0 : item.SwapInterval);
|
||||
UpdateSwapInterval(0);
|
||||
_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)
|
||||
else if (_device.VSyncMode != _vSyncMode)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.SurfaceFlinger, $"Failed to acquire buffer for layer {layerId} (status: {acquireStatus})");
|
||||
continue;
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -403,8 +413,8 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
|
||||
ulong bufferOffset = (ulong)item.GraphicBuffer.Object.Buffer.Surfaces[0].Offset;
|
||||
|
||||
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(nvMapHandle);
|
||||
|
||||
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(layer.Owner, nvMapHandle);
|
||||
|
||||
ulong frameBufferAddress = map.Address + bufferOffset;
|
||||
|
||||
Format format = ConvertColorFormat(item.GraphicBuffer.Object.Buffer.Surfaces[0].ColorFormat);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
@@ -37,6 +36,6 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
public int PlanesCount;
|
||||
|
||||
[FieldOffset(0x34)]
|
||||
public Array3<NvGraphicBufferSurface> Surfaces;
|
||||
public NvGraphicBufferSurfaceArray Surfaces;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ 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,11 +1,4 @@
|
||||
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
|
||||
{
|
||||
@@ -14,9 +7,6 @@ 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)
|
||||
{
|
||||
@@ -67,138 +57,5 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
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,6 +248,8 @@ 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");
|
||||
@@ -283,7 +285,9 @@ 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");
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,235 +0,0 @@
|
||||
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,5 +1,6 @@
|
||||
using MsgPack;
|
||||
using Ryujinx.Horizon.Common;
|
||||
using Ryujinx.Horizon.Prepo.Types;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Threading;
|
||||
@@ -8,7 +9,7 @@ namespace Ryujinx.Horizon
|
||||
{
|
||||
public static class HorizonStatic
|
||||
{
|
||||
internal static void HandlePlayReport(MessagePackObject report) =>
|
||||
internal static void HandlePlayReport(PlayReport report) =>
|
||||
new Thread(() => PlayReport?.Invoke(report))
|
||||
{
|
||||
Name = "HLE.PlayReportEvent",
|
||||
@@ -16,7 +17,7 @@ namespace Ryujinx.Horizon
|
||||
Priority = ThreadPriority.AboveNormal
|
||||
}.Start();
|
||||
|
||||
public static event Action<MessagePackObject> PlayReport;
|
||||
public static event Action<PlayReport> PlayReport;
|
||||
|
||||
[field: ThreadStatic]
|
||||
public static HorizonOptions Options { get; private set; }
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Gommon;
|
||||
using MsgPack;
|
||||
using MsgPack.Serialization;
|
||||
using Ryujinx.Common.Logging;
|
||||
@@ -12,19 +11,12 @@ using Ryujinx.Horizon.Sdk.Sf;
|
||||
using Ryujinx.Horizon.Sdk.Sf.Hipc;
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using ApplicationId = Ryujinx.Horizon.Sdk.Ncm.ApplicationId;
|
||||
|
||||
namespace Ryujinx.Horizon.Prepo.Ipc
|
||||
{
|
||||
partial class PrepoService : IPrepoService
|
||||
{
|
||||
enum PlayReportKind
|
||||
{
|
||||
Normal,
|
||||
System,
|
||||
}
|
||||
|
||||
private readonly ArpApi _arp;
|
||||
private readonly PrepoServicePermissionLevel _permissionLevel;
|
||||
private ulong _systemSessionId;
|
||||
@@ -196,10 +188,17 @@ namespace Ryujinx.Horizon.Prepo.Ipc
|
||||
{
|
||||
return PrepoResult.InvalidBufferSize;
|
||||
}
|
||||
|
||||
|
||||
StringBuilder builder = new();
|
||||
MessagePackObject deserializedReport = MessagePackSerializer.UnpackMessagePackObject(reportBuffer.ToArray());
|
||||
|
||||
PlayReport playReport = new()
|
||||
{
|
||||
Kind = playReportKind,
|
||||
Room = gameRoom,
|
||||
ReportData = deserializedReport
|
||||
};
|
||||
|
||||
builder.AppendLine();
|
||||
builder.AppendLine("PlayReport log:");
|
||||
builder.AppendLine($" Kind: {playReportKind}");
|
||||
@@ -209,10 +208,12 @@ namespace Ryujinx.Horizon.Prepo.Ipc
|
||||
if (pid != 0)
|
||||
{
|
||||
builder.AppendLine($" Pid: {pid}");
|
||||
playReport.Pid = pid;
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AppendLine($" ApplicationId: {applicationId}");
|
||||
playReport.AppId = applicationId;
|
||||
}
|
||||
|
||||
Result result = _arp.GetApplicationInstanceId(out ulong applicationInstanceId, pid);
|
||||
@@ -223,17 +224,20 @@ namespace Ryujinx.Horizon.Prepo.Ipc
|
||||
|
||||
_arp.GetApplicationLaunchProperty(out ApplicationLaunchProperty applicationLaunchProperty, applicationInstanceId).AbortOnFailure();
|
||||
|
||||
playReport.Version = applicationLaunchProperty.Version;
|
||||
|
||||
builder.AppendLine($" ApplicationVersion: {applicationLaunchProperty.Version}");
|
||||
|
||||
if (!userId.IsNull)
|
||||
{
|
||||
builder.AppendLine($" UserId: {userId}");
|
||||
playReport.UserId = userId;
|
||||
}
|
||||
|
||||
builder.AppendLine($" Room: {gameRoom}");
|
||||
builder.AppendLine($" Report: {MessagePackObjectFormatter.Format(deserializedReport)}");
|
||||
|
||||
HorizonStatic.HandlePlayReport(deserializedReport);
|
||||
HorizonStatic.HandlePlayReport(playReport);
|
||||
|
||||
Logger.Info?.Print(LogClass.ServicePrepo, builder.ToString());
|
||||
|
||||
|
||||
24
src/Ryujinx.Horizon/Prepo/Types/PlayReport.cs
Normal file
24
src/Ryujinx.Horizon/Prepo/Types/PlayReport.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using MsgPack;
|
||||
using Ryujinx.Horizon.Sdk.Account;
|
||||
using Ryujinx.Horizon.Sdk.Ncm;
|
||||
|
||||
namespace Ryujinx.Horizon.Prepo.Types
|
||||
{
|
||||
public struct PlayReport
|
||||
{
|
||||
public PlayReportKind Kind { get; init; }
|
||||
public string Room { get; init; }
|
||||
public MessagePackObject ReportData { get; init; }
|
||||
|
||||
public ApplicationId? AppId;
|
||||
public ulong? Pid;
|
||||
public uint Version;
|
||||
public Uid? UserId;
|
||||
}
|
||||
|
||||
public enum PlayReportKind
|
||||
{
|
||||
Normal,
|
||||
System,
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using Ryujinx.Memory.Range;
|
||||
using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
@@ -10,7 +9,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, IVirtualMemoryManagerTracked
|
||||
public sealed class AddressSpaceManager : VirtualMemoryManagerBase, IVirtualMemoryManager
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public bool UsesPrivateAllocations => false;
|
||||
@@ -22,8 +21,6 @@ namespace Ryujinx.Memory
|
||||
|
||||
private readonly MemoryBlock _backingMemory;
|
||||
private readonly PageTable<nuint> _pageTable;
|
||||
private readonly MemoryTracking _tracking;
|
||||
private bool _writeTracked;
|
||||
|
||||
protected override ulong AddressSpaceSize { get; }
|
||||
|
||||
@@ -47,7 +44,6 @@ namespace Ryujinx.Memory
|
||||
AddressSpaceSize = asSize;
|
||||
_backingMemory = backingMemory;
|
||||
_pageTable = new PageTable<nuint>();
|
||||
_tracking = new MemoryTracking(this, 0x1000);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -231,7 +227,7 @@ namespace Ryujinx.Memory
|
||||
/// <inheritdoc/>
|
||||
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest = false)
|
||||
{
|
||||
_writeTracked = true;
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected unsafe override Memory<byte> GetPhysicalAddressMemory(nuint pa, int size)
|
||||
@@ -244,29 +240,5 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1576,50 +1576,50 @@
|
||||
"ID": "GameListHeaderTimePlayed",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Spielzeit: {0}",
|
||||
"el_GR": "Χρόνος: {0}",
|
||||
"en_US": "Play Time: {0}",
|
||||
"es_ES": "Tiempo jugado: {0}",
|
||||
"fr_FR": "Temps de jeu: {0}",
|
||||
"de_DE": "Spielzeit:",
|
||||
"el_GR": "Χρόνος:",
|
||||
"en_US": "Play Time:",
|
||||
"es_ES": "Tiempo jugado:",
|
||||
"fr_FR": "Temps de jeu:",
|
||||
"he_IL": "",
|
||||
"it_IT": "Tempo di gioco: {0}",
|
||||
"ja_JP": "プレイ時間: {0}",
|
||||
"ko_KR": "플레이 타임: {0}",
|
||||
"no_NO": "Spilletid: {0}",
|
||||
"pl_PL": "Czas w grze: {0}",
|
||||
"pt_BR": "Tempo de jogo: {0}",
|
||||
"ru_RU": "Время в игре: {0}",
|
||||
"sv_SE": "Speltid: {0}",
|
||||
"th_TH": "เล่นไปแล้ว: {0}",
|
||||
"tr_TR": "Oynama Süresi: {0}",
|
||||
"uk_UA": "Зіграно часу: {0}",
|
||||
"zh_CN": "游玩时长: {0}",
|
||||
"zh_TW": "遊玩時數: {0}"
|
||||
"it_IT": "Tempo di gioco:",
|
||||
"ja_JP": "プレイ時間:",
|
||||
"ko_KR": "플레이 타임:",
|
||||
"no_NO": "Spilletid:",
|
||||
"pl_PL": "Czas w grze:",
|
||||
"pt_BR": "Tempo de jogo:",
|
||||
"ru_RU": "Время в игре:",
|
||||
"sv_SE": "Speltid:",
|
||||
"th_TH": "เล่นไปแล้ว:",
|
||||
"tr_TR": "Oynama Süresi:",
|
||||
"uk_UA": "Зіграно часу:",
|
||||
"zh_CN": "游玩时长:",
|
||||
"zh_TW": "遊玩時數:"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "GameListHeaderLastPlayed",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Zuletzt gespielt: {0}",
|
||||
"el_GR": "Παίχτηκε: {0}",
|
||||
"en_US": "Last Played: {0}",
|
||||
"es_ES": "Jugado por última vez: {0}",
|
||||
"fr_FR": "Dernière partie jouée: {0}",
|
||||
"de_DE": "Zuletzt gespielt: ",
|
||||
"el_GR": "Παίχτηκε: ",
|
||||
"en_US": "Last Played:",
|
||||
"es_ES": "Jugado por última vez:",
|
||||
"fr_FR": "Dernière partie jouée:",
|
||||
"he_IL": "",
|
||||
"it_IT": "Ultima partita: {0}",
|
||||
"ja_JP": "最終プレイ日時: {0}",
|
||||
"ko_KR": "마지막 플레이: {0}",
|
||||
"no_NO": "Sist Spilt: {0}",
|
||||
"pl_PL": "Ostatnio grane: {0}",
|
||||
"pt_BR": "Último jogo: {0}",
|
||||
"ru_RU": "Последний запуск: {0}",
|
||||
"sv_SE": "Senast spelad: {0}",
|
||||
"th_TH": "เล่นล่าสุด: {0}",
|
||||
"tr_TR": "Son Oynama Tarihi: {0}",
|
||||
"uk_UA": "Востаннє зіграно: {0}",
|
||||
"zh_CN": "最近游玩: {0}",
|
||||
"zh_TW": "最近遊玩: {0}"
|
||||
"it_IT": "Ultima partita:",
|
||||
"ja_JP": "最終プレイ日時:",
|
||||
"ko_KR": "마지막 플레이:",
|
||||
"no_NO": "Sist Spilt:",
|
||||
"pl_PL": "Ostatnio grane:",
|
||||
"pt_BR": "Último jogo:",
|
||||
"ru_RU": "Последний запуск:",
|
||||
"sv_SE": "Senast spelad:",
|
||||
"th_TH": "เล่นล่าสุด:",
|
||||
"tr_TR": "Son Oynama Tarihi:",
|
||||
"uk_UA": "Востаннє зіграно:",
|
||||
"zh_CN": "最近游玩:",
|
||||
"zh_TW": "最近遊玩:"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -23696,6 +23696,56 @@
|
||||
"zh_CN": "选择一个要解压的 DLC",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "GameInfoRpcImage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"el_GR": "",
|
||||
"en_US": "Rich Presence Image",
|
||||
"es_ES": "",
|
||||
"fr_FR": "",
|
||||
"he_IL": "",
|
||||
"it_IT": "",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "",
|
||||
"no_NO": "",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "",
|
||||
"ru_RU": "",
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "GameInfoRpcDynamic",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"el_GR": "",
|
||||
"en_US": "Dynamic Rich Presence",
|
||||
"es_ES": "",
|
||||
"fr_FR": "",
|
||||
"he_IL": "",
|
||||
"it_IT": "",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "",
|
||||
"no_NO": "",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "",
|
||||
"ru_RU": "",
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -10,6 +10,8 @@ using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE;
|
||||
using Ryujinx.HLE.Loaders.Processes;
|
||||
using Ryujinx.Horizon;
|
||||
using Ryujinx.Horizon.Prepo.Types;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.Ava
|
||||
@@ -37,6 +39,9 @@ namespace Ryujinx.Ava
|
||||
private static RichPresence _discordPresencePlaying;
|
||||
private static ApplicationMetadata _currentApp;
|
||||
|
||||
public static bool HasAssetImage(string titleId) => TitleIDs.DiscordGameAssetKeys.ContainsIgnoreCase(titleId);
|
||||
public static bool HasAnalyzer(string titleId) => PlayReports.Analyzer.TitleIds.ContainsIgnoreCase(titleId);
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
_discordPresenceMain = new RichPresence
|
||||
@@ -120,20 +125,22 @@ namespace Ryujinx.Ava
|
||||
_currentApp = null;
|
||||
}
|
||||
|
||||
private static void HandlePlayReport(MessagePackObject playReport)
|
||||
private static void HandlePlayReport(PlayReport playReport)
|
||||
{
|
||||
if (_discordClient is null) return;
|
||||
if (!TitleIDs.CurrentApplication.Value.HasValue) return;
|
||||
if (_discordPresencePlaying is null) return;
|
||||
|
||||
Analyzer.FormattedValue formattedValue =
|
||||
FormattedValue formattedValue =
|
||||
PlayReports.Analyzer.Format(TitleIDs.CurrentApplication.Value, _currentApp, playReport);
|
||||
|
||||
if (!formattedValue.Handled) return;
|
||||
|
||||
_discordPresencePlaying.Details = formattedValue.Reset
|
||||
? $"Playing {_currentApp.Title}"
|
||||
: formattedValue.FormattedString;
|
||||
_discordPresencePlaying.Details = TruncateToByteLength(
|
||||
formattedValue.Reset
|
||||
? $"Playing {_currentApp.Title}"
|
||||
: formattedValue.FormattedString
|
||||
);
|
||||
|
||||
if (_discordClient.CurrentPresence.Details.Equals(_discordPresencePlaying.Details))
|
||||
return; //don't trigger an update if the set presence Details are identical to current
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||
xmlns:ext="using:Ryujinx.Ava.Common.Markup"
|
||||
xmlns:viewModels="using:Ryujinx.Ava.UI.ViewModels"
|
||||
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Ryujinx.Ava.UI.Controls.ApplicationDataView"
|
||||
@@ -85,6 +86,49 @@
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Separator Margin="0, 10, 0, 10" Height="1" BorderBrush="Gray" Background="Gray" />
|
||||
<StackPanel Orientation="Vertical" Spacing="5">
|
||||
<StackPanel Orientation="Horizontal" Spacing="5">
|
||||
<ui:SymbolIcon Foreground="ForestGreen" Symbol="Checkmark" IsVisible="{Binding AppData.HasRichPresenceAsset}"/>
|
||||
<TextBlock
|
||||
Foreground="ForestGreen"
|
||||
HorizontalAlignment="Stretch"
|
||||
IsVisible="{Binding AppData.HasRichPresenceAsset}"
|
||||
Text="{ext:Locale GameInfoRpcImage}"
|
||||
TextAlignment="Start"
|
||||
TextWrapping="Wrap" >
|
||||
</TextBlock>
|
||||
<ui:SymbolIcon Foreground="Red" Symbol="Cancel" IsVisible="{Binding !AppData.HasRichPresenceAsset}"/>
|
||||
<TextBlock
|
||||
Foreground="Red"
|
||||
HorizontalAlignment="Stretch"
|
||||
IsVisible="{Binding !AppData.HasRichPresenceAsset}"
|
||||
Text="{ext:Locale GameInfoRpcImage}"
|
||||
TextAlignment="Start"
|
||||
TextWrapping="Wrap" >
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Spacing="5">
|
||||
<ui:SymbolIcon Foreground="ForestGreen" Symbol="Checkmark" IsVisible="{Binding AppData.HasDynamicRichPresenceSupport}"/>
|
||||
<TextBlock
|
||||
Foreground="ForestGreen"
|
||||
HorizontalAlignment="Stretch"
|
||||
IsVisible="{Binding AppData.HasDynamicRichPresenceSupport}"
|
||||
Text="{ext:Locale GameInfoRpcDynamic}"
|
||||
TextAlignment="Start"
|
||||
TextWrapping="Wrap" >
|
||||
</TextBlock>
|
||||
<ui:SymbolIcon Foreground="Red" Symbol="Cancel" IsVisible="{Binding !AppData.HasDynamicRichPresenceSupport}"/>
|
||||
<TextBlock
|
||||
Foreground="Red"
|
||||
HorizontalAlignment="Stretch"
|
||||
IsVisible="{Binding !AppData.HasDynamicRichPresenceSupport}"
|
||||
Text="{ext:Locale GameInfoRpcDynamic}"
|
||||
TextAlignment="Start"
|
||||
TextWrapping="Wrap" >
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<Separator Margin="0, 10, 0, 10" Height="1" BorderBrush="Gray" Background="Gray" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
IsVisible="{Binding AppData.HasLdnGames}"
|
||||
@@ -92,22 +136,35 @@
|
||||
TextAlignment="Start"
|
||||
TextWrapping="Wrap" />
|
||||
<Separator IsVisible="{Binding AppData.HasLdnGames}" Margin="0, 10, 0, 10" Height="1" BorderBrush="Gray" Background="Gray" />
|
||||
<StackPanel
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Orientation="Vertical"
|
||||
Spacing="5">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding FormattedLastPlayed}"
|
||||
TextAlignment="Start"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding FormattedPlayTime}"
|
||||
IsVisible="{Binding AppData.HasPlayedPreviously}"
|
||||
TextAlignment="Start"
|
||||
TextWrapping="Wrap" />
|
||||
<StackPanel Orientation="Vertical" Spacing="5">
|
||||
<Grid
|
||||
ColumnDefinitions="Auto,*,Auto">
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Text="{ext:Locale GameListHeaderLastPlayed}"
|
||||
VerticalAlignment="Top"
|
||||
TextAlignment="Start"
|
||||
TextWrapping="NoWrap" />
|
||||
<TextBlock
|
||||
Grid.Column="2"
|
||||
Text="{Binding AppData.LastPlayedString}"
|
||||
TextAlignment="End"
|
||||
TextWrapping="Wrap" />
|
||||
</Grid>
|
||||
<Grid
|
||||
ColumnDefinitions="Auto,*,Auto"
|
||||
IsVisible="{Binding AppData.HasPlayedPreviously}">
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Text="{ext:Locale GameListHeaderTimePlayed}"
|
||||
VerticalAlignment="Top"
|
||||
TextAlignment="Start"
|
||||
TextWrapping="NoWrap" />
|
||||
<TextBlock Grid.Column="2"
|
||||
Text="{Binding AppData.TimePlayedString}"
|
||||
TextAlignment="End"
|
||||
TextWrapping="Wrap" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
LocaleKeys.CompatibilityListNothing or
|
||||
LocaleKeys.CompatibilityListBoots or
|
||||
LocaleKeys.CompatibilityListMenus => Brushes.Red,
|
||||
LocaleKeys.CompatibilityListIngame => Brushes.Yellow,
|
||||
LocaleKeys.CompatibilityListIngame => Brushes.DarkOrange,
|
||||
_ => Brushes.ForestGreen
|
||||
};
|
||||
|
||||
|
||||
@@ -12,10 +12,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
public string FormattedVersion => LocaleManager.Instance[LocaleKeys.GameListHeaderVersion].Format(AppData.Version);
|
||||
public string FormattedDeveloper => LocaleManager.Instance[LocaleKeys.GameListHeaderDeveloper].Format(AppData.Developer);
|
||||
|
||||
public string FormattedFileExtension => LocaleManager.Instance[LocaleKeys.GameListHeaderFileExtension].Format(AppData.FileExtension);
|
||||
public string FormattedLastPlayed => LocaleManager.Instance[LocaleKeys.GameListHeaderLastPlayed].Format(AppData.LastPlayedString);
|
||||
public string FormattedPlayTime => LocaleManager.Instance[LocaleKeys.GameListHeaderTimePlayed].Format(AppData.TimePlayedString);
|
||||
public string FormattedFileSize => LocaleManager.Instance[LocaleKeys.GameListHeaderFileSize].Format(AppData.FileSizeString);
|
||||
|
||||
public string FormattedLdnInfo =>
|
||||
|
||||
@@ -63,6 +63,9 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
|
||||
public int GameCount { get; set; }
|
||||
|
||||
public bool HasLdnGames => PlayerCount != 0 && GameCount != 0;
|
||||
|
||||
public bool HasRichPresenceAsset => DiscordIntegrationModule.HasAssetImage(IdString);
|
||||
public bool HasDynamicRichPresenceSupport => DiscordIntegrationModule.HasAnalyzer(IdString);
|
||||
|
||||
public TimeSpan TimePlayed { get; set; }
|
||||
public DateTime? LastPlayed { get; set; }
|
||||
|
||||
@@ -3,6 +3,7 @@ using MsgPack;
|
||||
using Ryujinx.Ava.Utilities.AppLibrary;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
@@ -15,6 +16,10 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
{
|
||||
private readonly List<GameSpec> _specs = [];
|
||||
|
||||
public string[] TitleIds => Specs.SelectMany(x => x.TitleIds).ToArray();
|
||||
|
||||
public IReadOnlyList<GameSpec> Specs => new ReadOnlyCollection<GameSpec>(_specs);
|
||||
|
||||
/// <summary>
|
||||
/// Add an analysis spec matching a specific game by title ID, with the provided spec configuration.
|
||||
/// </summary>
|
||||
@@ -78,9 +83,9 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Runs the configured <see cref="GameSpec.FormatterSpec"/> for the specified game title ID.
|
||||
/// Runs the configured <see cref="FormatterSpec"/> for the specified game title ID.
|
||||
/// </summary>
|
||||
/// <param name="runningGameId">The game currently running.</param>
|
||||
/// <param name="appMeta">The Application metadata information, including localized game name and play time information.</param>
|
||||
@@ -89,270 +94,68 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
public FormattedValue Format(
|
||||
string runningGameId,
|
||||
ApplicationMetadata appMeta,
|
||||
MessagePackObject playReport
|
||||
Horizon.Prepo.Types.PlayReport playReport
|
||||
)
|
||||
{
|
||||
if (!playReport.IsDictionary)
|
||||
if (!playReport.ReportData.IsDictionary)
|
||||
return FormattedValue.Unhandled;
|
||||
|
||||
if (!_specs.TryGetFirst(s => runningGameId.EqualsAnyIgnoreCase(s.TitleIds), out GameSpec spec))
|
||||
return FormattedValue.Unhandled;
|
||||
|
||||
foreach (GameSpec.FormatterSpec formatSpec in spec.SimpleValueFormatters.OrderBy(x => x.Priority))
|
||||
foreach (FormatterSpec formatSpec in spec.SimpleValueFormatters.OrderBy(x => x.Priority))
|
||||
{
|
||||
if (!playReport.AsDictionary().TryGetValue(formatSpec.ReportKey, out MessagePackObject valuePackObject))
|
||||
if (!playReport.ReportData.AsDictionary().TryGetValue(formatSpec.ReportKey, out MessagePackObject valuePackObject))
|
||||
continue;
|
||||
|
||||
return formatSpec.ValueFormatter(new Value
|
||||
return formatSpec.Formatter(new SingleValue(valuePackObject)
|
||||
{
|
||||
Application = appMeta, PackedValue = valuePackObject
|
||||
Application = appMeta,
|
||||
PlayReport = playReport
|
||||
});
|
||||
}
|
||||
|
||||
foreach (GameSpec.MultiFormatterSpec formatSpec in spec.MultiValueFormatters.OrderBy(x => x.Priority))
|
||||
|
||||
foreach (MultiFormatterSpec formatSpec in spec.MultiValueFormatters.OrderBy(x => x.Priority))
|
||||
{
|
||||
List<MessagePackObject> packedObjects = [];
|
||||
foreach (var reportKey in formatSpec.ReportKeys)
|
||||
{
|
||||
if (!playReport.AsDictionary().TryGetValue(reportKey, out MessagePackObject valuePackObject))
|
||||
if (!playReport.ReportData.AsDictionary().TryGetValue(reportKey, out MessagePackObject valuePackObject))
|
||||
continue;
|
||||
|
||||
|
||||
packedObjects.Add(valuePackObject);
|
||||
}
|
||||
|
||||
|
||||
if (packedObjects.Count != formatSpec.ReportKeys.Length)
|
||||
return FormattedValue.Unhandled;
|
||||
|
||||
return formatSpec.ValueFormatter(packedObjects
|
||||
.Select(packObject => new Value { Application = appMeta, PackedValue = packObject })
|
||||
.ToArray());
|
||||
|
||||
return formatSpec.Formatter(new MultiValue(packedObjects)
|
||||
{
|
||||
Application = appMeta,
|
||||
PlayReport = playReport
|
||||
});
|
||||
}
|
||||
|
||||
foreach (SparseMultiFormatterSpec formatSpec in spec.SparseMultiValueFormatters.OrderBy(x => x.Priority))
|
||||
{
|
||||
Dictionary<string, MessagePackObject> packedObjects = [];
|
||||
foreach (var reportKey in formatSpec.ReportKeys)
|
||||
{
|
||||
if (!playReport.ReportData.AsDictionary().TryGetValue(reportKey, out MessagePackObject valuePackObject))
|
||||
continue;
|
||||
|
||||
packedObjects.Add(reportKey, valuePackObject);
|
||||
}
|
||||
|
||||
return formatSpec.Formatter(
|
||||
new SparseMultiValue(packedObjects)
|
||||
{
|
||||
Application = appMeta,
|
||||
PlayReport = playReport
|
||||
});
|
||||
}
|
||||
|
||||
return FormattedValue.Unhandled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A potential formatted value returned by a <see cref="ValueFormatter"/>.
|
||||
/// </summary>
|
||||
public readonly struct FormattedValue
|
||||
{
|
||||
/// <summary>
|
||||
/// Was any handler able to match anything in the Play Report?
|
||||
/// </summary>
|
||||
public bool Handled { get; private init; }
|
||||
|
||||
/// <summary>
|
||||
/// Did the handler request the caller of the <see cref="Analyzer"/> to reset the existing value?
|
||||
/// </summary>
|
||||
public bool Reset { get; private init; }
|
||||
|
||||
/// <summary>
|
||||
/// The formatted value, only present if <see cref="Handled"/> is true, and <see cref="Reset"/> is false.
|
||||
/// </summary>
|
||||
public string FormattedString { get; private init; }
|
||||
|
||||
/// <summary>
|
||||
/// The intended path of execution for having a string to return: simply return the string.
|
||||
/// This implicit conversion will make the struct for you.<br/><br/>
|
||||
///
|
||||
/// If the input is null, <see cref="Unhandled"/> is returned.
|
||||
/// </summary>
|
||||
/// <param name="formattedValue">The formatted string value.</param>
|
||||
/// <returns>The automatically constructed <see cref="FormattedValue"/> struct.</returns>
|
||||
public static implicit operator FormattedValue(string formattedValue)
|
||||
=> formattedValue is not null
|
||||
? new FormattedValue { Handled = true, FormattedString = formattedValue }
|
||||
: Unhandled;
|
||||
|
||||
/// <summary>
|
||||
/// Return this to tell the caller there is no value to return.
|
||||
/// </summary>
|
||||
public static FormattedValue Unhandled => default;
|
||||
|
||||
/// <summary>
|
||||
/// Return this to suggest the caller reset the value it's using the <see cref="Analyzer"/> for.
|
||||
/// </summary>
|
||||
public static FormattedValue ForceReset => new() { Handled = true, Reset = true };
|
||||
|
||||
/// <summary>
|
||||
/// A delegate singleton you can use to always return <see cref="ForceReset"/> in a <see cref="ValueFormatter"/>.
|
||||
/// </summary>
|
||||
public static readonly ValueFormatter AlwaysResets = _ => ForceReset;
|
||||
|
||||
/// <summary>
|
||||
/// A delegate factory you can use to always return the specified
|
||||
/// <paramref name="formattedValue"/> in a <see cref="ValueFormatter"/>.
|
||||
/// </summary>
|
||||
/// <param name="formattedValue">The string to always return for this delegate instance.</param>
|
||||
public static ValueFormatter AlwaysReturns(string formattedValue) => _ => formattedValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A mapping of title IDs to value formatter specs.
|
||||
///
|
||||
/// <remarks>Generally speaking, use the <see cref="Analyzer"/>.AddSpec(...) methods instead of creating this class yourself.</remarks>
|
||||
/// </summary>
|
||||
public class GameSpec
|
||||
{
|
||||
public required string[] TitleIds { get; init; }
|
||||
public List<FormatterSpec> SimpleValueFormatters { get; } = [];
|
||||
public List<MultiFormatterSpec> MultiValueFormatters { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Add a value formatter to the current <see cref="GameSpec"/>
|
||||
/// matching a specific key that could exist in a Play Report for the previously specified title IDs.
|
||||
/// </summary>
|
||||
/// <param name="reportKey">The key name to match.</param>
|
||||
/// <param name="valueFormatter">The function which can return a potential formatted value.</param>
|
||||
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
|
||||
public GameSpec AddValueFormatter(string reportKey, ValueFormatter valueFormatter)
|
||||
{
|
||||
SimpleValueFormatters.Add(new FormatterSpec
|
||||
{
|
||||
Priority = SimpleValueFormatters.Count, ReportKey = reportKey, ValueFormatter = valueFormatter
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a value formatter at a specific priority to the current <see cref="GameSpec"/>
|
||||
/// matching a specific key that could exist in a Play Report for the previously specified title IDs.
|
||||
/// </summary>
|
||||
/// <param name="priority">The resolution priority of this value formatter. Higher resolves sooner.</param>
|
||||
/// <param name="reportKey">The key name to match.</param>
|
||||
/// <param name="valueFormatter">The function which can return a potential formatted value.</param>
|
||||
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
|
||||
public GameSpec AddValueFormatter(int priority, string reportKey,
|
||||
ValueFormatter valueFormatter)
|
||||
{
|
||||
SimpleValueFormatters.Add(new FormatterSpec
|
||||
{
|
||||
Priority = priority, ReportKey = reportKey, ValueFormatter = valueFormatter
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a multi-value formatter to the current <see cref="GameSpec"/>
|
||||
/// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs.
|
||||
/// </summary>
|
||||
/// <param name="reportKeys">The key names to match.</param>
|
||||
/// <param name="valueFormatter">The function which can format the values.</param>
|
||||
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
|
||||
public GameSpec AddMultiValueFormatter(string[] reportKeys, MultiValueFormatter valueFormatter)
|
||||
{
|
||||
MultiValueFormatters.Add(new MultiFormatterSpec
|
||||
{
|
||||
Priority = SimpleValueFormatters.Count, ReportKeys = reportKeys, ValueFormatter = valueFormatter
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a multi-value formatter at a specific priority to the current <see cref="GameSpec"/>
|
||||
/// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs.
|
||||
/// </summary>
|
||||
/// <param name="priority">The resolution priority of this value formatter. Higher resolves sooner.</param>
|
||||
/// <param name="reportKeys">The key names to match.</param>
|
||||
/// <param name="valueFormatter">The function which can format the values.</param>
|
||||
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
|
||||
public GameSpec AddMultiValueFormatter(int priority, string[] reportKeys,
|
||||
MultiValueFormatter valueFormatter)
|
||||
{
|
||||
MultiValueFormatters.Add(new MultiFormatterSpec
|
||||
{
|
||||
Priority = priority, ReportKeys = reportKeys, ValueFormatter = valueFormatter
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A struct containing the data for a mapping of a key in a Play Report to a formatter for its potential value.
|
||||
/// </summary>
|
||||
public struct FormatterSpec
|
||||
{
|
||||
public required int Priority { get; init; }
|
||||
public required string ReportKey { get; init; }
|
||||
public ValueFormatter ValueFormatter { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A struct containing the data for a mapping of an arbitrary key set in a Play Report to a formatter for their potential values.
|
||||
/// </summary>
|
||||
public struct MultiFormatterSpec
|
||||
{
|
||||
public required int Priority { get; init; }
|
||||
public required string[] ReportKeys { get; init; }
|
||||
public MultiValueFormatter ValueFormatter { get; init; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The input data to a <see cref="ValueFormatter"/>,
|
||||
/// containing the currently running application's <see cref="ApplicationMetadata"/>,
|
||||
/// and the matched <see cref="MessagePackObject"/> from the Play Report.
|
||||
/// </summary>
|
||||
public class Value
|
||||
{
|
||||
/// <summary>
|
||||
/// The currently running application's <see cref="ApplicationMetadata"/>.
|
||||
/// </summary>
|
||||
public ApplicationMetadata Application { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The matched value from the Play Report.
|
||||
/// </summary>
|
||||
public MessagePackObject PackedValue { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Access the <see cref="PackedValue"/> as its underlying .NET type.<br/>
|
||||
///
|
||||
/// Does not seem to work well with comparing numeric types,
|
||||
/// so use XValue properties for that.
|
||||
/// </summary>
|
||||
public object BoxedValue => PackedValue.ToObject();
|
||||
|
||||
#region AsX accessors
|
||||
|
||||
public bool BooleanValue => PackedValue.AsBoolean();
|
||||
public byte ByteValye => PackedValue.AsByte();
|
||||
public sbyte SByteValye => PackedValue.AsSByte();
|
||||
public short ShortValye => PackedValue.AsInt16();
|
||||
public ushort UShortValye => PackedValue.AsUInt16();
|
||||
public int IntValye => PackedValue.AsInt32();
|
||||
public uint UIntValye => PackedValue.AsUInt32();
|
||||
public long LongValye => PackedValue.AsInt64();
|
||||
public ulong ULongValye => PackedValue.AsUInt64();
|
||||
public float FloatValue => PackedValue.AsSingle();
|
||||
public double DoubleValue => PackedValue.AsDouble();
|
||||
public string StringValue => PackedValue.AsString();
|
||||
public Span<byte> BinaryValue => PackedValue.AsBinary();
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The delegate type that powers single value formatters.<br/>
|
||||
/// Takes in the result value from the Play Report, and outputs:
|
||||
/// <br/>
|
||||
/// a formatted string,
|
||||
/// <br/>
|
||||
/// a signal that nothing was available to handle it,
|
||||
/// <br/>
|
||||
/// OR a signal to reset the value that the caller is using the <see cref="Analyzer"/> for.
|
||||
/// </summary>
|
||||
public delegate Analyzer.FormattedValue ValueFormatter(Value value);
|
||||
|
||||
/// <summary>
|
||||
/// The delegate type that powers multiple value formatters.<br/>
|
||||
/// Takes in the result value from the Play Report, and outputs:
|
||||
/// <br/>
|
||||
/// a formatted string,
|
||||
/// <br/>
|
||||
/// a signal that nothing was available to handle it,
|
||||
/// <br/>
|
||||
/// OR a signal to reset the value that the caller is using the <see cref="Analyzer"/> for.
|
||||
/// </summary>
|
||||
public delegate Analyzer.FormattedValue MultiValueFormatter(Value[] value);
|
||||
}
|
||||
|
||||
40
src/Ryujinx/Utilities/PlayReport/Delegates.cs
Normal file
40
src/Ryujinx/Utilities/PlayReport/Delegates.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
{
|
||||
/// <summary>
|
||||
/// The delegate type that powers single value formatters.<br/>
|
||||
/// Takes in the result value from the Play Report, and outputs:
|
||||
/// <br/>
|
||||
/// a formatted string,
|
||||
/// <br/>
|
||||
/// a signal that nothing was available to handle it,
|
||||
/// <br/>
|
||||
/// OR a signal to reset the value that the caller is using the <see cref="Analyzer"/> for.
|
||||
/// </summary>
|
||||
public delegate FormattedValue ValueFormatter(SingleValue value);
|
||||
|
||||
/// <summary>
|
||||
/// The delegate type that powers multiple value formatters.<br/>
|
||||
/// Takes in the result values from the Play Report, and outputs:
|
||||
/// <br/>
|
||||
/// a formatted string,
|
||||
/// <br/>
|
||||
/// a signal that nothing was available to handle it,
|
||||
/// <br/>
|
||||
/// OR a signal to reset the value that the caller is using the <see cref="Analyzer"/> for.
|
||||
/// </summary>
|
||||
public delegate FormattedValue MultiValueFormatter(MultiValue value);
|
||||
|
||||
/// <summary>
|
||||
/// The delegate type that powers multiple value formatters.
|
||||
/// The dictionary passed to this delegate is sparsely populated;
|
||||
/// that is, not every key specified in the Play Report needs to match for this to be used.<br/>
|
||||
/// Takes in the result values from the Play Report, and outputs:
|
||||
/// <br/>
|
||||
/// a formatted string,
|
||||
/// <br/>
|
||||
/// a signal that nothing was available to handle it,
|
||||
/// <br/>
|
||||
/// OR a signal to reset the value that the caller is using the <see cref="Analyzer"/> for.
|
||||
/// </summary>
|
||||
public delegate FormattedValue SparseMultiValueFormatter(SparseMultiValue value);
|
||||
}
|
||||
87
src/Ryujinx/Utilities/PlayReport/MatchedValues.cs
Normal file
87
src/Ryujinx/Utilities/PlayReport/MatchedValues.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using MsgPack;
|
||||
using Ryujinx.Ava.Utilities.AppLibrary;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
{
|
||||
public abstract class MatchedValue<T>
|
||||
{
|
||||
public MatchedValue(T matched)
|
||||
{
|
||||
Matched = matched;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The currently running application's <see cref="ApplicationMetadata"/>.
|
||||
/// </summary>
|
||||
public ApplicationMetadata Application { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The entire play report.
|
||||
/// </summary>
|
||||
public Horizon.Prepo.Types.PlayReport PlayReport { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The matched value from the Play Report.
|
||||
/// </summary>
|
||||
public T Matched { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The input data to a <see cref="ValueFormatter"/>,
|
||||
/// containing the currently running application's <see cref="ApplicationMetadata"/>,
|
||||
/// and the matched <see cref="MessagePackObject"/> from the Play Report.
|
||||
/// </summary>
|
||||
public class SingleValue : MatchedValue<Value>
|
||||
{
|
||||
public SingleValue(Value matched) : base(matched)
|
||||
{
|
||||
}
|
||||
|
||||
public static implicit operator SingleValue(MessagePackObject mpo) => new(mpo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The input data to a <see cref="MultiValueFormatter"/>,
|
||||
/// containing the currently running application's <see cref="ApplicationMetadata"/>,
|
||||
/// and the matched <see cref="MessagePackObject"/>s from the Play Report.
|
||||
/// </summary>
|
||||
public class MultiValue : MatchedValue<Value[]>
|
||||
{
|
||||
public MultiValue(Value[] matched) : base(matched)
|
||||
{
|
||||
}
|
||||
|
||||
public MultiValue(IEnumerable<MessagePackObject> matched) : base(Value.ConvertPackedObjects(matched))
|
||||
{
|
||||
}
|
||||
|
||||
public static implicit operator MultiValue(List<MessagePackObject> matched)
|
||||
=> new(matched.Select(x => new Value(x)).ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The input data to a <see cref="SparseMultiValueFormatter"/>,
|
||||
/// containing the currently running application's <see cref="ApplicationMetadata"/>,
|
||||
/// and the matched <see cref="MessagePackObject"/>s from the Play Report.
|
||||
/// </summary>
|
||||
public class SparseMultiValue : MatchedValue<Dictionary<string, Value>>
|
||||
{
|
||||
public SparseMultiValue(Dictionary<string, Value> matched) : base(matched)
|
||||
{
|
||||
}
|
||||
|
||||
public SparseMultiValue(Dictionary<string, MessagePackObject> matched) : base(Value.ConvertPackedObjectMap(matched))
|
||||
{
|
||||
}
|
||||
|
||||
public static implicit operator SparseMultiValue(Dictionary<string, MessagePackObject> matched)
|
||||
=> new(matched
|
||||
.ToDictionary(
|
||||
x => x.Key,
|
||||
x => new Value(x.Value)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using static Ryujinx.Ava.Utilities.PlayReport.Analyzer;
|
||||
|
||||
namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
{
|
||||
public static class PlayReports
|
||||
{
|
||||
@@ -10,7 +8,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
spec => spec
|
||||
.AddValueFormatter("IsHardMode", BreathOfTheWild_MasterMode)
|
||||
// reset to normal status when switching between normal & master mode in title screen
|
||||
.AddValueFormatter("AoCVer", FormattedValue.AlwaysResets)
|
||||
.AddValueFormatter("AoCVer", FormattedValue.SingleAlwaysResets)
|
||||
)
|
||||
.AddSpec(
|
||||
"0100f2c0115b6000",
|
||||
@@ -41,28 +39,28 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
.AddValueFormatter("team_circle", PokemonSVUnionCircle)
|
||||
);
|
||||
|
||||
private static FormattedValue BreathOfTheWild_MasterMode(Value value)
|
||||
=> value.BoxedValue is 1 ? "Playing Master Mode" : FormattedValue.ForceReset;
|
||||
private static FormattedValue BreathOfTheWild_MasterMode(SingleValue value)
|
||||
=> value.Matched.BoxedValue is 1 ? "Playing Master Mode" : FormattedValue.ForceReset;
|
||||
|
||||
private static FormattedValue TearsOfTheKingdom_CurrentField(Value value) =>
|
||||
value.DoubleValue switch
|
||||
private static FormattedValue TearsOfTheKingdom_CurrentField(SingleValue value) =>
|
||||
value.Matched.DoubleValue switch
|
||||
{
|
||||
> 800d => "Exploring the Sky Islands",
|
||||
< -201d => "Exploring the Depths",
|
||||
_ => "Roaming Hyrule"
|
||||
};
|
||||
|
||||
private static FormattedValue SuperMarioOdyssey_AssistMode(Value value)
|
||||
=> value.BoxedValue is 1 ? "Playing in Assist Mode" : "Playing in Regular Mode";
|
||||
private static FormattedValue SuperMarioOdyssey_AssistMode(SingleValue value)
|
||||
=> value.Matched.BoxedValue is 1 ? "Playing in Assist Mode" : "Playing in Regular Mode";
|
||||
|
||||
private static FormattedValue SuperMarioOdysseyChina_AssistMode(Value value)
|
||||
=> value.BoxedValue is 1 ? "Playing in 帮助模式" : "Playing in 普通模式";
|
||||
private static FormattedValue SuperMarioOdysseyChina_AssistMode(SingleValue value)
|
||||
=> value.Matched.BoxedValue is 1 ? "Playing in 帮助模式" : "Playing in 普通模式";
|
||||
|
||||
private static FormattedValue SuperMario3DWorldOrBowsersFury(Value value)
|
||||
=> value.BoxedValue is 0 ? "Playing Super Mario 3D World" : "Playing Bowser's Fury";
|
||||
private static FormattedValue SuperMario3DWorldOrBowsersFury(SingleValue value)
|
||||
=> value.Matched.BoxedValue is 0 ? "Playing Super Mario 3D World" : "Playing Bowser's Fury";
|
||||
|
||||
private static FormattedValue MarioKart8Deluxe_Mode(Value value)
|
||||
=> value.StringValue switch
|
||||
private static FormattedValue MarioKart8Deluxe_Mode(SingleValue value)
|
||||
=> value.Matched.StringValue switch
|
||||
{
|
||||
// Single Player
|
||||
"Single" => "Single Player",
|
||||
@@ -89,11 +87,11 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
_ => FormattedValue.ForceReset
|
||||
};
|
||||
|
||||
private static FormattedValue PokemonSVUnionCircle(Value value)
|
||||
=> value.BoxedValue is 0 ? "Playing Alone" : "Playing in a group";
|
||||
private static FormattedValue PokemonSVUnionCircle(SingleValue value)
|
||||
=> value.Matched.BoxedValue is 0 ? "Playing Alone" : "Playing in a group";
|
||||
|
||||
private static FormattedValue PokemonSVArea(Value value)
|
||||
=> value.StringValue switch
|
||||
private static FormattedValue PokemonSVArea(SingleValue value)
|
||||
=> value.Matched.StringValue switch
|
||||
{
|
||||
// Base Game Locations
|
||||
"a_w01" => "South Area One",
|
||||
|
||||
140
src/Ryujinx/Utilities/PlayReport/Specs.cs
Normal file
140
src/Ryujinx/Utilities/PlayReport/Specs.cs
Normal file
@@ -0,0 +1,140 @@
|
||||
using FluentAvalonia.Core;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
{
|
||||
/// <summary>
|
||||
/// A mapping of title IDs to value formatter specs.
|
||||
///
|
||||
/// <remarks>Generally speaking, use the <see cref="Analyzer"/>.AddSpec(...) methods instead of creating this class yourself.</remarks>
|
||||
/// </summary>
|
||||
public class GameSpec
|
||||
{
|
||||
public required string[] TitleIds { get; init; }
|
||||
public List<FormatterSpec> SimpleValueFormatters { get; } = [];
|
||||
public List<MultiFormatterSpec> MultiValueFormatters { get; } = [];
|
||||
public List<SparseMultiFormatterSpec> SparseMultiValueFormatters { get; } = [];
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Add a value formatter to the current <see cref="GameSpec"/>
|
||||
/// matching a specific key that could exist in a Play Report for the previously specified title IDs.
|
||||
/// </summary>
|
||||
/// <param name="reportKey">The key name to match.</param>
|
||||
/// <param name="valueFormatter">The function which can return a potential formatted value.</param>
|
||||
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
|
||||
public GameSpec AddValueFormatter(string reportKey, ValueFormatter valueFormatter)
|
||||
=> AddValueFormatter(SimpleValueFormatters.Count, reportKey, valueFormatter);
|
||||
|
||||
/// <summary>
|
||||
/// Add a value formatter at a specific priority to the current <see cref="GameSpec"/>
|
||||
/// matching a specific key that could exist in a Play Report for the previously specified title IDs.
|
||||
/// </summary>
|
||||
/// <param name="priority">The resolution priority of this value formatter. Higher resolves sooner.</param>
|
||||
/// <param name="reportKey">The key name to match.</param>
|
||||
/// <param name="valueFormatter">The function which can return a potential formatted value.</param>
|
||||
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
|
||||
public GameSpec AddValueFormatter(int priority, string reportKey,
|
||||
ValueFormatter valueFormatter)
|
||||
{
|
||||
SimpleValueFormatters.Add(new FormatterSpec
|
||||
{
|
||||
Priority = priority, ReportKey = reportKey, Formatter = valueFormatter
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a multi-value formatter to the current <see cref="GameSpec"/>
|
||||
/// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs.
|
||||
/// </summary>
|
||||
/// <param name="reportKeys">The key names to match.</param>
|
||||
/// <param name="valueFormatter">The function which can format the values.</param>
|
||||
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
|
||||
public GameSpec AddMultiValueFormatter(string[] reportKeys, MultiValueFormatter valueFormatter)
|
||||
=> AddMultiValueFormatter(MultiValueFormatters.Count, reportKeys, valueFormatter);
|
||||
|
||||
/// <summary>
|
||||
/// Add a multi-value formatter at a specific priority to the current <see cref="GameSpec"/>
|
||||
/// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs.
|
||||
/// </summary>
|
||||
/// <param name="priority">The resolution priority of this value formatter. Higher resolves sooner.</param>
|
||||
/// <param name="reportKeys">The key names to match.</param>
|
||||
/// <param name="valueFormatter">The function which can format the values.</param>
|
||||
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
|
||||
public GameSpec AddMultiValueFormatter(int priority, string[] reportKeys,
|
||||
MultiValueFormatter valueFormatter)
|
||||
{
|
||||
MultiValueFormatters.Add(new MultiFormatterSpec
|
||||
{
|
||||
Priority = priority, ReportKeys = reportKeys, Formatter = valueFormatter
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a multi-value formatter to the current <see cref="GameSpec"/>
|
||||
/// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs.
|
||||
/// <br/><br/>
|
||||
/// The 'Sparse' multi-value formatters do not require every key to be present.
|
||||
/// If you need this requirement, use <see cref="AddMultiValueFormatter(string[], Ryujinx.Ava.Utilities.PlayReport.MultiValueFormatter)"/>.
|
||||
/// </summary>
|
||||
/// <param name="reportKeys">The key names to match.</param>
|
||||
/// <param name="valueFormatter">The function which can format the values.</param>
|
||||
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
|
||||
public GameSpec AddSparseMultiValueFormatter(string[] reportKeys, SparseMultiValueFormatter valueFormatter)
|
||||
=> AddSparseMultiValueFormatter(SparseMultiValueFormatters.Count, reportKeys, valueFormatter);
|
||||
|
||||
/// <summary>
|
||||
/// Add a multi-value formatter at a specific priority to the current <see cref="GameSpec"/>
|
||||
/// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs.
|
||||
/// <br/><br/>
|
||||
/// The 'Sparse' multi-value formatters do not require every key to be present.
|
||||
/// If you need this requirement, use <see cref="AddMultiValueFormatter(int, string[], Ryujinx.Ava.Utilities.PlayReport.MultiValueFormatter)"/>.
|
||||
/// </summary>
|
||||
/// <param name="priority">The resolution priority of this value formatter. Higher resolves sooner.</param>
|
||||
/// <param name="reportKeys">The key names to match.</param>
|
||||
/// <param name="valueFormatter">The function which can format the values.</param>
|
||||
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
|
||||
public GameSpec AddSparseMultiValueFormatter(int priority, string[] reportKeys,
|
||||
SparseMultiValueFormatter valueFormatter)
|
||||
{
|
||||
SparseMultiValueFormatters.Add(new SparseMultiFormatterSpec
|
||||
{
|
||||
Priority = priority, ReportKeys = reportKeys, Formatter = valueFormatter
|
||||
});
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A struct containing the data for a mapping of a key in a Play Report to a formatter for its potential value.
|
||||
/// </summary>
|
||||
public struct FormatterSpec
|
||||
{
|
||||
public required int Priority { get; init; }
|
||||
public required string ReportKey { get; init; }
|
||||
public ValueFormatter Formatter { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A struct containing the data for a mapping of an arbitrary key set in a Play Report to a formatter for their potential values.
|
||||
/// </summary>
|
||||
public struct MultiFormatterSpec
|
||||
{
|
||||
public required int Priority { get; init; }
|
||||
public required string[] ReportKeys { get; init; }
|
||||
public MultiValueFormatter Formatter { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A struct containing the data for a mapping of an arbitrary key set in a Play Report to a formatter for their sparsely populated potential values.
|
||||
/// </summary>
|
||||
public struct SparseMultiFormatterSpec
|
||||
{
|
||||
public required int Priority { get; init; }
|
||||
public required string[] ReportKeys { get; init; }
|
||||
public SparseMultiValueFormatter Formatter { get; init; }
|
||||
}
|
||||
}
|
||||
143
src/Ryujinx/Utilities/PlayReport/Value.cs
Normal file
143
src/Ryujinx/Utilities/PlayReport/Value.cs
Normal file
@@ -0,0 +1,143 @@
|
||||
using MsgPack;
|
||||
using Ryujinx.Ava.Utilities.AppLibrary;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
{
|
||||
/// <summary>
|
||||
/// The input data to a <see cref="ValueFormatter"/>,
|
||||
/// containing the currently running application's <see cref="ApplicationMetadata"/>,
|
||||
/// and the matched <see cref="MessagePackObject"/> from the Play Report.
|
||||
/// </summary>
|
||||
public readonly struct Value
|
||||
{
|
||||
public Value(MessagePackObject packedValue)
|
||||
{
|
||||
PackedValue = packedValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The matched value from the Play Report.
|
||||
/// </summary>
|
||||
public MessagePackObject PackedValue { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Access the <see cref="PackedValue"/> as its underlying .NET type.<br/>
|
||||
///
|
||||
/// Does not seem to work well with comparing numeric types,
|
||||
/// so use XValue properties for that.
|
||||
/// </summary>
|
||||
public object BoxedValue => PackedValue.ToObject();
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
object boxed = BoxedValue;
|
||||
return boxed == null
|
||||
? "null"
|
||||
: boxed.ToString();
|
||||
}
|
||||
|
||||
public static implicit operator Value(MessagePackObject matched) => new(matched);
|
||||
|
||||
public static Value[] ConvertPackedObjects(IEnumerable<MessagePackObject> packObjects)
|
||||
=> packObjects.Select(packObject => new Value(packObject)).ToArray();
|
||||
|
||||
public static Dictionary<string, Value> ConvertPackedObjectMap(Dictionary<string, MessagePackObject> packObjects)
|
||||
=> packObjects.ToDictionary(
|
||||
x => x.Key,
|
||||
x => new Value(x.Value)
|
||||
);
|
||||
|
||||
#region AsX accessors
|
||||
|
||||
public bool BooleanValue => PackedValue.AsBoolean();
|
||||
public byte ByteValue => PackedValue.AsByte();
|
||||
public sbyte SByteValue => PackedValue.AsSByte();
|
||||
public short ShortValue => PackedValue.AsInt16();
|
||||
public ushort UShortValue => PackedValue.AsUInt16();
|
||||
public int IntValue => PackedValue.AsInt32();
|
||||
public uint UIntValue => PackedValue.AsUInt32();
|
||||
public long LongValue => PackedValue.AsInt64();
|
||||
public ulong ULongValue => PackedValue.AsUInt64();
|
||||
public float FloatValue => PackedValue.AsSingle();
|
||||
public double DoubleValue => PackedValue.AsDouble();
|
||||
public string StringValue => PackedValue.AsString();
|
||||
public Span<byte> BinaryValue => PackedValue.AsBinary();
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A potential formatted value returned by a <see cref="ValueFormatter"/>.
|
||||
/// </summary>
|
||||
public readonly struct FormattedValue
|
||||
{
|
||||
/// <summary>
|
||||
/// Was any handler able to match anything in the Play Report?
|
||||
/// </summary>
|
||||
public bool Handled { get; private init; }
|
||||
|
||||
/// <summary>
|
||||
/// Did the handler request the caller of the <see cref="Analyzer"/> to reset the existing value?
|
||||
/// </summary>
|
||||
public bool Reset { get; private init; }
|
||||
|
||||
/// <summary>
|
||||
/// The formatted value, only present if <see cref="Handled"/> is true, and <see cref="Reset"/> is false.
|
||||
/// </summary>
|
||||
public string FormattedString { get; private init; }
|
||||
|
||||
/// <summary>
|
||||
/// The intended path of execution for having a string to return: simply return the string.
|
||||
/// This implicit conversion will make the struct for you.<br/><br/>
|
||||
///
|
||||
/// If the input is null, <see cref="Unhandled"/> is returned.
|
||||
/// </summary>
|
||||
/// <param name="formattedValue">The formatted string value.</param>
|
||||
/// <returns>The automatically constructed <see cref="FormattedValue"/> struct.</returns>
|
||||
public static implicit operator FormattedValue(string formattedValue)
|
||||
=> formattedValue is not null
|
||||
? new FormattedValue { Handled = true, FormattedString = formattedValue }
|
||||
: Unhandled;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (!Handled)
|
||||
return "<Unhandled>";
|
||||
|
||||
if (Reset)
|
||||
return "<Reset>";
|
||||
|
||||
return FormattedString;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return this to tell the caller there is no value to return.
|
||||
/// </summary>
|
||||
public static FormattedValue Unhandled => default;
|
||||
|
||||
/// <summary>
|
||||
/// Return this to suggest the caller reset the value it's using the <see cref="Analyzer"/> for.
|
||||
/// </summary>
|
||||
public static FormattedValue ForceReset => new() { Handled = true, Reset = true };
|
||||
|
||||
/// <summary>
|
||||
/// A delegate singleton you can use to always return <see cref="ForceReset"/> in a <see cref="ValueFormatter"/>.
|
||||
/// </summary>
|
||||
public static readonly ValueFormatter SingleAlwaysResets = _ => ForceReset;
|
||||
|
||||
/// <summary>
|
||||
/// A delegate singleton you can use to always return <see cref="ForceReset"/> in a <see cref="MultiValueFormatter"/>.
|
||||
/// </summary>
|
||||
public static readonly MultiValueFormatter MultiAlwaysResets = _ => ForceReset;
|
||||
|
||||
/// <summary>
|
||||
/// A delegate factory you can use to always return the specified
|
||||
/// <paramref name="formattedValue"/> in a <see cref="ValueFormatter"/>.
|
||||
/// </summary>
|
||||
/// <param name="formattedValue">The string to always return for this delegate instance.</param>
|
||||
public static ValueFormatter AlwaysReturns(string formattedValue) => _ => formattedValue;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user