Compare commits
18 Commits
Canary-1.2
...
256da81166
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
256da81166 | ||
|
|
c0340590d2 | ||
|
|
2ffaeb2803 | ||
|
|
b16b844760 | ||
|
|
bc07bc482d | ||
|
|
61975ca44d | ||
|
|
66054dd225 | ||
|
|
b1f61e5143 | ||
|
|
0d7d0e8092 | ||
|
|
aa2178dbe5 | ||
|
|
f92d09711b | ||
|
|
45ee8cd0e8 | ||
|
|
395bbd144a | ||
|
|
744d813b87 | ||
|
|
7d59ada798 | ||
|
|
a4b5304935 | ||
|
|
0965ee905d | ||
|
|
855161b23b |
@@ -969,6 +969,7 @@
|
|||||||
0100751007ADA000,"Don't Starve: Nintendo Switch Edition",nvdec,playable,2022-02-05 20:43:34
|
0100751007ADA000,"Don't Starve: Nintendo Switch Edition",nvdec,playable,2022-02-05 20:43:34
|
||||||
010088B010DD2000,"Dongo Adventure",,playable,2022-10-04 16:22:26
|
010088B010DD2000,"Dongo Adventure",,playable,2022-10-04 16:22:26
|
||||||
0100C1F0051B6000,"Donkey Kong Country™: Tropical Freeze",,playable,2024-08-05 16:46:10
|
0100C1F0051B6000,"Donkey Kong Country™: Tropical Freeze",,playable,2024-08-05 16:46:10
|
||||||
|
01009D901BC56000,"Donkey Kong Country™: Returns HD",gpu,ingame,2025-02-16 13:44:12
|
||||||
0100F2C00F060000,"Doodle Derby",,boots,2020-12-04 22:51:48
|
0100F2C00F060000,"Doodle Derby",,boots,2020-12-04 22:51:48
|
||||||
0100416004C00000,"DOOM",gpu;slow;nvdec;online-broken,ingame,2024-09-23 15:40:07
|
0100416004C00000,"DOOM",gpu;slow;nvdec;online-broken,ingame,2024-09-23 15:40:07
|
||||||
010018900DD00000,"DOOM (1993)",nvdec;online-broken,menus,2022-09-06 13:32:19
|
010018900DD00000,"DOOM (1993)",nvdec;online-broken,menus,2022-09-06 13:32:19
|
||||||
|
|||||||
|
@@ -186,7 +186,7 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
|
|
||||||
int newRegionNumber = _activeRegionIndex;
|
int newRegionNumber = _activeRegionIndex;
|
||||||
|
|
||||||
Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {newRegionNumber} ({((newRegionNumber + 1) * CacheSize).Bytes()} Total Allocation).");
|
Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {newRegionNumber} ({((long)(newRegionNumber + 1) * CacheSize).Bytes()} Total Allocation).");
|
||||||
|
|
||||||
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
|
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using Ryujinx.Common.Utilities;
|
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Ryujinx.Common.GraphicsDriver
|
namespace Ryujinx.Common.GraphicsDriver
|
||||||
@@ -11,7 +10,7 @@ namespace Ryujinx.Common.GraphicsDriver
|
|||||||
|
|
||||||
string flags = existingFlags == null ? newFlags : $"{existingFlags},{newFlags}";
|
string flags = existingFlags == null ? newFlags : $"{existingFlags},{newFlags}";
|
||||||
|
|
||||||
OsUtils.SetEnvironmentVariableNoCaching(envVar, flags);
|
Environment.SetEnvironmentVariable(envVar, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void InitDriverConfig(bool oglThreading)
|
public static void InitDriverConfig(bool oglThreading)
|
||||||
@@ -23,11 +22,10 @@ namespace Ryujinx.Common.GraphicsDriver
|
|||||||
|
|
||||||
ToggleOGLThreading(oglThreading);
|
ToggleOGLThreading(oglThreading);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ToggleOGLThreading(bool enabled)
|
public static void ToggleOGLThreading(bool enabled)
|
||||||
{
|
{
|
||||||
OsUtils.SetEnvironmentVariableNoCaching("mesa_glthread", enabled.ToString().ToLower());
|
Environment.SetEnvironmentVariable("mesa_glthread", enabled.ToString().ToLower());
|
||||||
OsUtils.SetEnvironmentVariableNoCaching("__GL_THREADED_OPTIMIZATIONS", enabled ? "1" : "0");
|
Environment.SetEnvironmentVariable("__GL_THREADED_OPTIMIZATIONS", enabled ? "1" : "0");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.Common.Utilities
|
|
||||||
{
|
|
||||||
public partial class OsUtils
|
|
||||||
{
|
|
||||||
[LibraryImport("libc", SetLastError = true)]
|
|
||||||
private static partial int setenv([MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] string value, int overwrite);
|
|
||||||
|
|
||||||
public static void SetEnvironmentVariableNoCaching(string key, string value)
|
|
||||||
{
|
|
||||||
// Set the value in the cached environment variables, too.
|
|
||||||
Environment.SetEnvironmentVariable(key, value);
|
|
||||||
|
|
||||||
if (!OperatingSystem.IsWindows())
|
|
||||||
{
|
|
||||||
int res = setenv(key, value, 1);
|
|
||||||
Debug.Assert(res != -1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -166,7 +166,7 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
|||||||
|
|
||||||
int newRegionNumber = _activeRegionIndex;
|
int newRegionNumber = _activeRegionIndex;
|
||||||
|
|
||||||
Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {newRegionNumber} ({((newRegionNumber + 1) * CacheSize).Bytes()} Total Allocation).");
|
Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {newRegionNumber} ({((long)(newRegionNumber + 1) * CacheSize).Bytes()} Total Allocation).");
|
||||||
|
|
||||||
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
|
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
|
||||||
|
|
||||||
|
|||||||
@@ -32,12 +32,10 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
CommandBuffer
|
CommandBuffer
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool _feedbackLoopActive;
|
|
||||||
private PipelineStageFlags _incoherentBufferWriteStages;
|
private PipelineStageFlags _incoherentBufferWriteStages;
|
||||||
private PipelineStageFlags _incoherentTextureWriteStages;
|
private PipelineStageFlags _incoherentTextureWriteStages;
|
||||||
private PipelineStageFlags _extraStages;
|
private PipelineStageFlags _extraStages;
|
||||||
private IncoherentBarrierType _queuedIncoherentBarrier;
|
private IncoherentBarrierType _queuedIncoherentBarrier;
|
||||||
private bool _queuedFeedbackLoopBarrier;
|
|
||||||
|
|
||||||
public BarrierBatch(VulkanRenderer gd)
|
public BarrierBatch(VulkanRenderer gd)
|
||||||
{
|
{
|
||||||
@@ -55,6 +53,17 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
stages |= PipelineStageFlags.TransformFeedbackBitExt;
|
stages |= PipelineStageFlags.TransformFeedbackBitExt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!gd.IsTBDR)
|
||||||
|
{
|
||||||
|
// Desktop GPUs can transform image barriers into memory barriers.
|
||||||
|
|
||||||
|
access |= AccessFlags.DepthStencilAttachmentWriteBit | AccessFlags.ColorAttachmentWriteBit;
|
||||||
|
access |= AccessFlags.DepthStencilAttachmentReadBit | AccessFlags.ColorAttachmentReadBit;
|
||||||
|
|
||||||
|
stages |= PipelineStageFlags.EarlyFragmentTestsBit | PipelineStageFlags.LateFragmentTestsBit;
|
||||||
|
stages |= PipelineStageFlags.ColorAttachmentOutputBit;
|
||||||
|
}
|
||||||
|
|
||||||
return (access, stages);
|
return (access, stages);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,34 +178,16 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
|
|
||||||
_queuedIncoherentBarrier = IncoherentBarrierType.None;
|
_queuedIncoherentBarrier = IncoherentBarrierType.None;
|
||||||
_queuedFeedbackLoopBarrier = false;
|
|
||||||
}
|
}
|
||||||
else if (_feedbackLoopActive && _queuedFeedbackLoopBarrier)
|
|
||||||
{
|
|
||||||
// Feedback loop barrier.
|
|
||||||
|
|
||||||
MemoryBarrier barrier = new()
|
|
||||||
{
|
|
||||||
SType = StructureType.MemoryBarrier,
|
|
||||||
SrcAccessMask = AccessFlags.ShaderWriteBit,
|
|
||||||
DstAccessMask = AccessFlags.ShaderReadBit
|
|
||||||
};
|
|
||||||
|
|
||||||
QueueBarrier(barrier, PipelineStageFlags.FragmentShaderBit, PipelineStageFlags.AllGraphicsBit);
|
|
||||||
|
|
||||||
_queuedFeedbackLoopBarrier = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_feedbackLoopActive = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe void Flush(CommandBufferScoped cbs, bool inRenderPass, RenderPassHolder rpHolder, Action endRenderPass)
|
public unsafe void Flush(CommandBufferScoped cbs, bool inRenderPass, RenderPassHolder rpHolder, Action endRenderPass)
|
||||||
{
|
{
|
||||||
Flush(cbs, null, false, inRenderPass, rpHolder, endRenderPass);
|
Flush(cbs, null, inRenderPass, rpHolder, endRenderPass);
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe void Flush(CommandBufferScoped cbs, ShaderCollection program, bool feedbackLoopActive, bool inRenderPass, RenderPassHolder rpHolder, Action endRenderPass)
|
public unsafe void Flush(CommandBufferScoped cbs, ShaderCollection program, bool inRenderPass, RenderPassHolder rpHolder, Action endRenderPass)
|
||||||
{
|
{
|
||||||
if (program != null)
|
if (program != null)
|
||||||
{
|
{
|
||||||
@@ -204,8 +195,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_incoherentTextureWriteStages |= program.IncoherentTextureWriteStages;
|
_incoherentTextureWriteStages |= program.IncoherentTextureWriteStages;
|
||||||
}
|
}
|
||||||
|
|
||||||
_feedbackLoopActive |= feedbackLoopActive;
|
|
||||||
|
|
||||||
FlushMemoryBarrier(program, inRenderPass);
|
FlushMemoryBarrier(program, inRenderPass);
|
||||||
|
|
||||||
if (!inRenderPass && rpHolder != null)
|
if (!inRenderPass && rpHolder != null)
|
||||||
@@ -417,8 +406,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
_queuedIncoherentBarrier = type;
|
_queuedIncoherentBarrier = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
_queuedFeedbackLoopBarrier = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void QueueTextureBarrier()
|
public void QueueTextureBarrier()
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ using Ryujinx.Graphics.GAL;
|
|||||||
using Ryujinx.Graphics.Shader;
|
using Ryujinx.Graphics.Shader;
|
||||||
using Silk.NET.Vulkan;
|
using Silk.NET.Vulkan;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using Buffer = Silk.NET.Vulkan.Buffer;
|
using Buffer = Silk.NET.Vulkan.Buffer;
|
||||||
@@ -43,15 +42,15 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
private record struct TextureRef
|
private record struct TextureRef
|
||||||
{
|
{
|
||||||
public ShaderStage Stage;
|
public ShaderStage Stage;
|
||||||
public TextureView View;
|
public TextureStorage Storage;
|
||||||
public Auto<DisposableImageView> ImageView;
|
public Auto<DisposableImageView> View;
|
||||||
public Auto<DisposableSampler> Sampler;
|
public Auto<DisposableSampler> Sampler;
|
||||||
|
|
||||||
public TextureRef(ShaderStage stage, TextureView view, Auto<DisposableImageView> imageView, Auto<DisposableSampler> sampler)
|
public TextureRef(ShaderStage stage, TextureStorage storage, Auto<DisposableImageView> view, Auto<DisposableSampler> sampler)
|
||||||
{
|
{
|
||||||
Stage = stage;
|
Stage = stage;
|
||||||
|
Storage = storage;
|
||||||
View = view;
|
View = view;
|
||||||
ImageView = imageView;
|
|
||||||
Sampler = sampler;
|
Sampler = sampler;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,14 +58,14 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
private record struct ImageRef
|
private record struct ImageRef
|
||||||
{
|
{
|
||||||
public ShaderStage Stage;
|
public ShaderStage Stage;
|
||||||
public TextureView View;
|
public TextureStorage Storage;
|
||||||
public Auto<DisposableImageView> ImageView;
|
public Auto<DisposableImageView> View;
|
||||||
|
|
||||||
public ImageRef(ShaderStage stage, TextureView view, Auto<DisposableImageView> imageView)
|
public ImageRef(ShaderStage stage, TextureStorage storage, Auto<DisposableImageView> view)
|
||||||
{
|
{
|
||||||
Stage = stage;
|
Stage = stage;
|
||||||
|
Storage = storage;
|
||||||
View = view;
|
View = view;
|
||||||
ImageView = imageView;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,8 +123,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
private readonly TextureView _dummyTexture;
|
private readonly TextureView _dummyTexture;
|
||||||
private readonly SamplerHolder _dummySampler;
|
private readonly SamplerHolder _dummySampler;
|
||||||
|
|
||||||
public List<TextureView> FeedbackLoopHazards { get; private set; }
|
|
||||||
|
|
||||||
public DescriptorSetUpdater(VulkanRenderer gd, Device device)
|
public DescriptorSetUpdater(VulkanRenderer gd, Device device)
|
||||||
{
|
{
|
||||||
_gd = gd;
|
_gd = gd;
|
||||||
@@ -210,15 +207,10 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_templateUpdater = new();
|
_templateUpdater = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Initialize(bool isMainPipeline)
|
public void Initialize()
|
||||||
{
|
{
|
||||||
MemoryOwner<byte> dummyTextureData = MemoryOwner<byte>.RentCleared(4);
|
MemoryOwner<byte> dummyTextureData = MemoryOwner<byte>.RentCleared(4);
|
||||||
_dummyTexture.SetData(dummyTextureData);
|
_dummyTexture.SetData(dummyTextureData);
|
||||||
|
|
||||||
if (isMainPipeline)
|
|
||||||
{
|
|
||||||
FeedbackLoopHazards = [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool BindingOverlaps(ref DescriptorBufferInfo info, int bindingOffset, int offset, int size)
|
private static bool BindingOverlaps(ref DescriptorBufferInfo info, int bindingOffset, int offset, int size)
|
||||||
@@ -281,18 +273,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
public void InsertBindingBarriers(CommandBufferScoped cbs)
|
public void InsertBindingBarriers(CommandBufferScoped cbs)
|
||||||
{
|
{
|
||||||
if ((FeedbackLoopHazards?.Count ?? 0) > 0)
|
|
||||||
{
|
|
||||||
// Clear existing hazards - they will be rebuilt.
|
|
||||||
|
|
||||||
foreach (TextureView hazard in FeedbackLoopHazards)
|
|
||||||
{
|
|
||||||
hazard.DecrementHazardUses();
|
|
||||||
}
|
|
||||||
|
|
||||||
FeedbackLoopHazards.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (ResourceBindingSegment segment in _program.BindingSegments[PipelineBase.TextureSetIndex])
|
foreach (ResourceBindingSegment segment in _program.BindingSegments[PipelineBase.TextureSetIndex])
|
||||||
{
|
{
|
||||||
if (segment.Type == ResourceType.TextureAndSampler)
|
if (segment.Type == ResourceType.TextureAndSampler)
|
||||||
@@ -302,7 +282,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
for (int i = 0; i < segment.Count; i++)
|
for (int i = 0; i < segment.Count; i++)
|
||||||
{
|
{
|
||||||
ref TextureRef texture = ref _textureRefs[segment.Binding + i];
|
ref TextureRef texture = ref _textureRefs[segment.Binding + i];
|
||||||
texture.View?.PrepareForUsage(cbs, texture.Stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards);
|
texture.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, texture.Stage.ConvertToPipelineStageFlags());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -323,7 +303,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
for (int i = 0; i < segment.Count; i++)
|
for (int i = 0; i < segment.Count; i++)
|
||||||
{
|
{
|
||||||
ref ImageRef image = ref _imageRefs[segment.Binding + i];
|
ref ImageRef image = ref _imageRefs[segment.Binding + i];
|
||||||
image.View?.PrepareForUsage(cbs, image.Stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards);
|
image.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, image.Stage.ConvertToPipelineStageFlags());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -397,12 +377,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
else if (image is TextureView view)
|
else if (image is TextureView view)
|
||||||
{
|
{
|
||||||
ref ImageRef iRef = ref _imageRefs[binding];
|
view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
|
||||||
|
_imageRefs[binding] = new(stage, view.Storage, view.GetIdentityImageView());
|
||||||
iRef.View?.ClearUsage(FeedbackLoopHazards);
|
|
||||||
view?.PrepareForUsage(cbs, stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards);
|
|
||||||
|
|
||||||
iRef = new(stage, view, view.GetIdentityImageView());
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -500,12 +476,9 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
else if (texture is TextureView view)
|
else if (texture is TextureView view)
|
||||||
{
|
{
|
||||||
ref TextureRef iRef = ref _textureRefs[binding];
|
view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
|
||||||
|
|
||||||
iRef.View?.ClearUsage(FeedbackLoopHazards);
|
_textureRefs[binding] = new(stage, view.Storage, view.GetImageView(), ((SamplerHolder)sampler)?.GetSampler());
|
||||||
view?.PrepareForUsage(cbs, stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards);
|
|
||||||
|
|
||||||
iRef = new(stage, view, view.GetImageView(), ((SamplerHolder)sampler)?.GetSampler());
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -527,7 +500,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
|
view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
|
||||||
|
|
||||||
_textureRefs[binding] = new(stage, view, view.GetIdentityImageView(), ((SamplerHolder)sampler)?.GetSampler());
|
_textureRefs[binding] = new(stage, view.Storage, view.GetIdentityImageView(), ((SamplerHolder)sampler)?.GetSampler());
|
||||||
|
|
||||||
SignalDirty(DirtyFlags.Texture);
|
SignalDirty(DirtyFlags.Texture);
|
||||||
}
|
}
|
||||||
@@ -853,7 +826,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
ref DescriptorImageInfo texture = ref textures[i];
|
ref DescriptorImageInfo texture = ref textures[i];
|
||||||
ref TextureRef refs = ref _textureRefs[binding + i];
|
ref TextureRef refs = ref _textureRefs[binding + i];
|
||||||
|
|
||||||
texture.ImageView = refs.ImageView?.Get(cbs).Value ?? default;
|
texture.ImageView = refs.View?.Get(cbs).Value ?? default;
|
||||||
texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default;
|
texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default;
|
||||||
|
|
||||||
if (texture.ImageView.Handle == 0)
|
if (texture.ImageView.Handle == 0)
|
||||||
@@ -903,7 +876,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
images[i].ImageView = _imageRefs[binding + i].ImageView?.Get(cbs).Value ?? default;
|
images[i].ImageView = _imageRefs[binding + i].View?.Get(cbs).Value ?? default;
|
||||||
}
|
}
|
||||||
|
|
||||||
tu.Push<DescriptorImageInfo>(images[..count]);
|
tu.Push<DescriptorImageInfo>(images[..count]);
|
||||||
@@ -974,7 +947,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
ref DescriptorImageInfo texture = ref textures[i];
|
ref DescriptorImageInfo texture = ref textures[i];
|
||||||
ref TextureRef refs = ref _textureRefs[binding + i];
|
ref TextureRef refs = ref _textureRefs[binding + i];
|
||||||
|
|
||||||
texture.ImageView = refs.ImageView?.Get(cbs).Value ?? default;
|
texture.ImageView = refs.View?.Get(cbs).Value ?? default;
|
||||||
texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default;
|
texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default;
|
||||||
|
|
||||||
if (texture.ImageView.Handle == 0)
|
if (texture.ImageView.Handle == 0)
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Vulkan
|
|
||||||
{
|
|
||||||
[Flags]
|
|
||||||
internal enum FeedbackLoopAspects
|
|
||||||
{
|
|
||||||
None = 0,
|
|
||||||
Color = 1 << 0,
|
|
||||||
Depth = 1 << 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -303,27 +303,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_depthStencil?.Storage?.AddStoreOpUsage(true);
|
_depthStencil?.Storage?.AddStoreOpUsage(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClearBindings()
|
|
||||||
{
|
|
||||||
_depthStencil?.Storage.ClearBindings();
|
|
||||||
|
|
||||||
for (int i = 0; i < _colorsCanonical.Length; i++)
|
|
||||||
{
|
|
||||||
_colorsCanonical[i]?.Storage.ClearBindings();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddBindings()
|
|
||||||
{
|
|
||||||
_depthStencil?.Storage.AddBinding(_depthStencil);
|
|
||||||
|
|
||||||
for (int i = 0; i < _colorsCanonical.Length; i++)
|
|
||||||
{
|
|
||||||
TextureView color = _colorsCanonical[i];
|
|
||||||
color?.Storage.AddBinding(color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public (RenderPassHolder rpHolder, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
|
public (RenderPassHolder rpHolder, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
|
||||||
VulkanRenderer gd,
|
VulkanRenderer gd,
|
||||||
Device device,
|
Device device,
|
||||||
|
|||||||
@@ -46,8 +46,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
public readonly bool SupportsViewportArray2;
|
public readonly bool SupportsViewportArray2;
|
||||||
public readonly bool SupportsHostImportedMemory;
|
public readonly bool SupportsHostImportedMemory;
|
||||||
public readonly bool SupportsDepthClipControl;
|
public readonly bool SupportsDepthClipControl;
|
||||||
public readonly bool SupportsAttachmentFeedbackLoop;
|
|
||||||
public readonly bool SupportsDynamicAttachmentFeedbackLoop;
|
|
||||||
public readonly uint SubgroupSize;
|
public readonly uint SubgroupSize;
|
||||||
public readonly SampleCountFlags SupportedSampleCounts;
|
public readonly SampleCountFlags SupportedSampleCounts;
|
||||||
public readonly PortabilitySubsetFlags PortabilitySubset;
|
public readonly PortabilitySubsetFlags PortabilitySubset;
|
||||||
@@ -86,8 +84,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
bool supportsViewportArray2,
|
bool supportsViewportArray2,
|
||||||
bool supportsHostImportedMemory,
|
bool supportsHostImportedMemory,
|
||||||
bool supportsDepthClipControl,
|
bool supportsDepthClipControl,
|
||||||
bool supportsAttachmentFeedbackLoop,
|
|
||||||
bool supportsDynamicAttachmentFeedbackLoop,
|
|
||||||
uint subgroupSize,
|
uint subgroupSize,
|
||||||
SampleCountFlags supportedSampleCounts,
|
SampleCountFlags supportedSampleCounts,
|
||||||
PortabilitySubsetFlags portabilitySubset,
|
PortabilitySubsetFlags portabilitySubset,
|
||||||
@@ -125,8 +121,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
SupportsViewportArray2 = supportsViewportArray2;
|
SupportsViewportArray2 = supportsViewportArray2;
|
||||||
SupportsHostImportedMemory = supportsHostImportedMemory;
|
SupportsHostImportedMemory = supportsHostImportedMemory;
|
||||||
SupportsDepthClipControl = supportsDepthClipControl;
|
SupportsDepthClipControl = supportsDepthClipControl;
|
||||||
SupportsAttachmentFeedbackLoop = supportsAttachmentFeedbackLoop;
|
|
||||||
SupportsDynamicAttachmentFeedbackLoop = supportsDynamicAttachmentFeedbackLoop;
|
|
||||||
SubgroupSize = subgroupSize;
|
SubgroupSize = subgroupSize;
|
||||||
SupportedSampleCounts = supportedSampleCounts;
|
SupportedSampleCounts = supportedSampleCounts;
|
||||||
PortabilitySubset = portabilitySubset;
|
PortabilitySubset = portabilitySubset;
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ using Ryujinx.Graphics.GAL;
|
|||||||
using Ryujinx.Graphics.Shader;
|
using Ryujinx.Graphics.Shader;
|
||||||
using Silk.NET.Vulkan;
|
using Silk.NET.Vulkan;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
@@ -36,7 +35,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
public readonly Action EndRenderPassDelegate;
|
public readonly Action EndRenderPassDelegate;
|
||||||
|
|
||||||
protected PipelineDynamicState DynamicState;
|
protected PipelineDynamicState DynamicState;
|
||||||
protected bool IsMainPipeline;
|
|
||||||
private PipelineState _newState;
|
private PipelineState _newState;
|
||||||
private bool _graphicsStateDirty;
|
private bool _graphicsStateDirty;
|
||||||
private bool _computeStateDirty;
|
private bool _computeStateDirty;
|
||||||
@@ -89,9 +87,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
private bool _tfEnabled;
|
private bool _tfEnabled;
|
||||||
private bool _tfActive;
|
private bool _tfActive;
|
||||||
|
|
||||||
private FeedbackLoopAspects _feedbackLoop;
|
|
||||||
private bool _passWritesDepthStencil;
|
|
||||||
|
|
||||||
private readonly PipelineColorBlendAttachmentState[] _storedBlend;
|
private readonly PipelineColorBlendAttachmentState[] _storedBlend;
|
||||||
public ulong DrawCount { get; private set; }
|
public ulong DrawCount { get; private set; }
|
||||||
public bool RenderPassActive { get; private set; }
|
public bool RenderPassActive { get; private set; }
|
||||||
@@ -133,7 +128,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
public void Initialize()
|
public void Initialize()
|
||||||
{
|
{
|
||||||
_descriptorSetUpdater.Initialize(IsMainPipeline);
|
_descriptorSetUpdater.Initialize();
|
||||||
|
|
||||||
QuadsToTrisPattern = new IndexBufferPattern(Gd, 4, 6, 0, [0, 1, 2, 0, 2, 3], 4, false);
|
QuadsToTrisPattern = new IndexBufferPattern(Gd, 4, 6, 0, [0, 1, 2, 0, 2, 3], 4, false);
|
||||||
TriFanToTrisPattern = new IndexBufferPattern(Gd, 3, 3, 2, [int.MinValue, -1, 0], 1, true);
|
TriFanToTrisPattern = new IndexBufferPattern(Gd, 3, 3, 2, [int.MinValue, -1, 0], 1, true);
|
||||||
@@ -821,8 +816,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_newState.DepthTestEnable = depthTest.TestEnable;
|
_newState.DepthTestEnable = depthTest.TestEnable;
|
||||||
_newState.DepthWriteEnable = depthTest.WriteEnable;
|
_newState.DepthWriteEnable = depthTest.WriteEnable;
|
||||||
_newState.DepthCompareOp = depthTest.Func.Convert();
|
_newState.DepthCompareOp = depthTest.Func.Convert();
|
||||||
|
|
||||||
UpdatePassDepthStencil();
|
|
||||||
SignalStateChange();
|
SignalStateChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1088,8 +1081,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_newState.StencilFrontPassOp = stencilTest.FrontDpPass.Convert();
|
_newState.StencilFrontPassOp = stencilTest.FrontDpPass.Convert();
|
||||||
_newState.StencilFrontDepthFailOp = stencilTest.FrontDpFail.Convert();
|
_newState.StencilFrontDepthFailOp = stencilTest.FrontDpFail.Convert();
|
||||||
_newState.StencilFrontCompareOp = stencilTest.FrontFunc.Convert();
|
_newState.StencilFrontCompareOp = stencilTest.FrontFunc.Convert();
|
||||||
|
|
||||||
UpdatePassDepthStencil();
|
|
||||||
SignalStateChange();
|
SignalStateChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1437,23 +1428,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsMainPipeline)
|
|
||||||
{
|
|
||||||
FramebufferParams?.ClearBindings();
|
|
||||||
}
|
|
||||||
|
|
||||||
FramebufferParams = new FramebufferParams(Device, colors, depthStencil);
|
FramebufferParams = new FramebufferParams(Device, colors, depthStencil);
|
||||||
|
|
||||||
if (IsMainPipeline)
|
|
||||||
{
|
|
||||||
FramebufferParams.AddBindings();
|
|
||||||
|
|
||||||
_newState.FeedbackLoopAspects = FeedbackLoopAspects.None;
|
|
||||||
_bindingBarriersDirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
_passWritesDepthStencil = false;
|
|
||||||
UpdatePassDepthStencil();
|
|
||||||
UpdatePipelineAttachmentFormats();
|
UpdatePipelineAttachmentFormats();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1520,82 +1495,11 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Gd.Barriers.Flush(Cbs, _program, _feedbackLoop != 0, RenderPassActive, _rpHolder, EndRenderPassDelegate);
|
Gd.Barriers.Flush(Cbs, _program, RenderPassActive, _rpHolder, EndRenderPassDelegate);
|
||||||
|
|
||||||
_descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Compute);
|
_descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Compute);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ChangeFeedbackLoop(FeedbackLoopAspects aspects)
|
|
||||||
{
|
|
||||||
if (_feedbackLoop != aspects)
|
|
||||||
{
|
|
||||||
if (Gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop)
|
|
||||||
{
|
|
||||||
DynamicState.SetFeedbackLoop(aspects);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_newState.FeedbackLoopAspects = aspects;
|
|
||||||
}
|
|
||||||
|
|
||||||
_feedbackLoop = aspects;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private bool UpdateFeedbackLoop()
|
|
||||||
{
|
|
||||||
List<TextureView> hazards = _descriptorSetUpdater.FeedbackLoopHazards;
|
|
||||||
|
|
||||||
if ((hazards?.Count ?? 0) > 0)
|
|
||||||
{
|
|
||||||
FeedbackLoopAspects aspects = 0;
|
|
||||||
|
|
||||||
foreach (TextureView view in hazards)
|
|
||||||
{
|
|
||||||
// May need to enforce feedback loop layout here in the future.
|
|
||||||
// Though technically, it should always work with the general layout.
|
|
||||||
|
|
||||||
if (view.Info.Format.IsDepthOrStencil())
|
|
||||||
{
|
|
||||||
if (_passWritesDepthStencil)
|
|
||||||
{
|
|
||||||
// If depth/stencil isn't written in the pass, it doesn't count as a feedback loop.
|
|
||||||
|
|
||||||
aspects |= FeedbackLoopAspects.Depth;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
aspects |= FeedbackLoopAspects.Color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ChangeFeedbackLoop(aspects);
|
|
||||||
}
|
|
||||||
else if (_feedbackLoop != 0)
|
|
||||||
{
|
|
||||||
return ChangeFeedbackLoop(FeedbackLoopAspects.None);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdatePassDepthStencil()
|
|
||||||
{
|
|
||||||
if (!RenderPassActive)
|
|
||||||
{
|
|
||||||
_passWritesDepthStencil = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stencil test being enabled doesn't necessarily mean a write, but it's not critical to check.
|
|
||||||
_passWritesDepthStencil |= (_newState.DepthTestEnable && _newState.DepthWriteEnable) || _newState.StencilTestEnable;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool RecreateGraphicsPipelineIfNeeded()
|
private bool RecreateGraphicsPipelineIfNeeded()
|
||||||
{
|
{
|
||||||
if (AutoFlush.ShouldFlushDraw(DrawCount))
|
if (AutoFlush.ShouldFlushDraw(DrawCount))
|
||||||
@@ -1603,7 +1507,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
Gd.FlushAllCommands();
|
Gd.FlushAllCommands();
|
||||||
}
|
}
|
||||||
|
|
||||||
DynamicState.ReplayIfDirty(Gd, CommandBuffer);
|
DynamicState.ReplayIfDirty(Gd.Api, CommandBuffer);
|
||||||
|
|
||||||
if (_needsIndexBufferRebind && _indexBufferPattern == null)
|
if (_needsIndexBufferRebind && _indexBufferPattern == null)
|
||||||
{
|
{
|
||||||
@@ -1637,15 +1541,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_vertexBufferUpdater.Commit(Cbs);
|
_vertexBufferUpdater.Commit(Cbs);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_bindingBarriersDirty)
|
if (_graphicsStateDirty || Pbp != PipelineBindPoint.Graphics)
|
||||||
{
|
|
||||||
// Stale barriers may have been activated by switching program. Emit any that are relevant.
|
|
||||||
_descriptorSetUpdater.InsertBindingBarriers(Cbs);
|
|
||||||
|
|
||||||
_bindingBarriersDirty = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (UpdateFeedbackLoop() || _graphicsStateDirty || Pbp != PipelineBindPoint.Graphics)
|
|
||||||
{
|
{
|
||||||
if (!CreatePipeline(PipelineBindPoint.Graphics))
|
if (!CreatePipeline(PipelineBindPoint.Graphics))
|
||||||
{
|
{
|
||||||
@@ -1654,9 +1550,17 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
_graphicsStateDirty = false;
|
_graphicsStateDirty = false;
|
||||||
Pbp = PipelineBindPoint.Graphics;
|
Pbp = PipelineBindPoint.Graphics;
|
||||||
|
|
||||||
|
if (_bindingBarriersDirty)
|
||||||
|
{
|
||||||
|
// Stale barriers may have been activated by switching program. Emit any that are relevant.
|
||||||
|
_descriptorSetUpdater.InsertBindingBarriers(Cbs);
|
||||||
|
|
||||||
|
_bindingBarriersDirty = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Gd.Barriers.Flush(Cbs, _program, _feedbackLoop != 0, RenderPassActive, _rpHolder, EndRenderPassDelegate);
|
Gd.Barriers.Flush(Cbs, _program, RenderPassActive, _rpHolder, EndRenderPassDelegate);
|
||||||
|
|
||||||
_descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Graphics);
|
_descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Graphics);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Ryujinx.Common.Memory;
|
using Ryujinx.Common.Memory;
|
||||||
using Silk.NET.Vulkan;
|
using Silk.NET.Vulkan;
|
||||||
using Silk.NET.Vulkan.Extensions.EXT;
|
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Vulkan
|
namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
@@ -22,8 +21,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
private Array4<float> _blendConstants;
|
private Array4<float> _blendConstants;
|
||||||
|
|
||||||
private FeedbackLoopAspects _feedbackLoopAspects;
|
|
||||||
|
|
||||||
public uint ViewportsCount;
|
public uint ViewportsCount;
|
||||||
public Array16<Viewport> Viewports;
|
public Array16<Viewport> Viewports;
|
||||||
|
|
||||||
@@ -35,8 +32,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
Scissor = 1 << 2,
|
Scissor = 1 << 2,
|
||||||
Stencil = 1 << 3,
|
Stencil = 1 << 3,
|
||||||
Viewport = 1 << 4,
|
Viewport = 1 << 4,
|
||||||
FeedbackLoop = 1 << 5,
|
All = Blend | DepthBias | Scissor | Stencil | Viewport,
|
||||||
All = Blend | DepthBias | Scissor | Stencil | Viewport | FeedbackLoop,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private DirtyFlags _dirty;
|
private DirtyFlags _dirty;
|
||||||
@@ -103,22 +99,13 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetFeedbackLoop(FeedbackLoopAspects aspects)
|
|
||||||
{
|
|
||||||
_feedbackLoopAspects = aspects;
|
|
||||||
|
|
||||||
_dirty |= DirtyFlags.FeedbackLoop;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ForceAllDirty()
|
public void ForceAllDirty()
|
||||||
{
|
{
|
||||||
_dirty = DirtyFlags.All;
|
_dirty = DirtyFlags.All;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReplayIfDirty(VulkanRenderer gd, CommandBuffer commandBuffer)
|
public void ReplayIfDirty(Vk api, CommandBuffer commandBuffer)
|
||||||
{
|
{
|
||||||
Vk api = gd.Api;
|
|
||||||
|
|
||||||
if (_dirty.HasFlag(DirtyFlags.Blend))
|
if (_dirty.HasFlag(DirtyFlags.Blend))
|
||||||
{
|
{
|
||||||
RecordBlend(api, commandBuffer);
|
RecordBlend(api, commandBuffer);
|
||||||
@@ -144,11 +131,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
RecordViewport(api, commandBuffer);
|
RecordViewport(api, commandBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_dirty.HasFlag(DirtyFlags.FeedbackLoop) && gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop)
|
|
||||||
{
|
|
||||||
RecordFeedbackLoop(gd.DynamicFeedbackLoopApi, commandBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
_dirty = DirtyFlags.None;
|
_dirty = DirtyFlags.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,17 +169,5 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
api.CmdSetViewport(commandBuffer, 0, ViewportsCount, Viewports.AsSpan());
|
api.CmdSetViewport(commandBuffer, 0, ViewportsCount, Viewports.AsSpan());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly void RecordFeedbackLoop(ExtAttachmentFeedbackLoopDynamicState api, CommandBuffer commandBuffer)
|
|
||||||
{
|
|
||||||
ImageAspectFlags aspects = (_feedbackLoopAspects & FeedbackLoopAspects.Color) != 0 ? ImageAspectFlags.ColorBit : 0;
|
|
||||||
|
|
||||||
if ((_feedbackLoopAspects & FeedbackLoopAspects.Depth) != 0)
|
|
||||||
{
|
|
||||||
aspects |= ImageAspectFlags.DepthBit | ImageAspectFlags.StencilBit;
|
|
||||||
}
|
|
||||||
|
|
||||||
api.CmdSetAttachmentFeedbackLoopEnable(commandBuffer, aspects);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,8 +28,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_activeBufferMirrors = [];
|
_activeBufferMirrors = [];
|
||||||
|
|
||||||
CommandBuffer = (Cbs = gd.CommandBufferPool.Rent()).CommandBuffer;
|
CommandBuffer = (Cbs = gd.CommandBufferPool.Rent()).CommandBuffer;
|
||||||
|
|
||||||
IsMainPipeline = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CopyPendingQuery()
|
private void CopyPendingQuery()
|
||||||
@@ -237,7 +235,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
if (Pipeline != null && Pbp == PipelineBindPoint.Graphics)
|
if (Pipeline != null && Pbp == PipelineBindPoint.Graphics)
|
||||||
{
|
{
|
||||||
DynamicState.ReplayIfDirty(Gd, CommandBuffer);
|
DynamicState.ReplayIfDirty(Gd.Api, CommandBuffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
struct PipelineState : IDisposable
|
struct PipelineState : IDisposable
|
||||||
{
|
{
|
||||||
private const int RequiredSubgroupSize = 32;
|
private const int RequiredSubgroupSize = 32;
|
||||||
private const int MaxDynamicStatesCount = 9;
|
|
||||||
|
|
||||||
public PipelineUid Internal;
|
public PipelineUid Internal;
|
||||||
|
|
||||||
@@ -300,12 +299,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFBF) | ((value ? 1UL : 0UL) << 6);
|
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFBF) | ((value ? 1UL : 0UL) << 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
public FeedbackLoopAspects FeedbackLoopAspects
|
|
||||||
{
|
|
||||||
readonly get => (FeedbackLoopAspects)((Internal.Id8 >> 7) & 0x3);
|
|
||||||
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFE7F) | (((ulong)value) << 7);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool HasTessellationControlShader;
|
public bool HasTessellationControlShader;
|
||||||
public NativeArray<PipelineShaderStageCreateInfo> Stages;
|
public NativeArray<PipelineShaderStageCreateInfo> Stages;
|
||||||
public PipelineLayout PipelineLayout;
|
public PipelineLayout PipelineLayout;
|
||||||
@@ -571,11 +564,9 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool supportsExtDynamicState = gd.Capabilities.SupportsExtendedDynamicState;
|
bool supportsExtDynamicState = gd.Capabilities.SupportsExtendedDynamicState;
|
||||||
bool supportsFeedbackLoopDynamicState = gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop;
|
int dynamicStatesCount = supportsExtDynamicState ? 8 : 7;
|
||||||
|
|
||||||
DynamicState* dynamicStates = stackalloc DynamicState[MaxDynamicStatesCount];
|
DynamicState* dynamicStates = stackalloc DynamicState[dynamicStatesCount];
|
||||||
|
|
||||||
int dynamicStatesCount = 7;
|
|
||||||
|
|
||||||
dynamicStates[0] = DynamicState.Viewport;
|
dynamicStates[0] = DynamicState.Viewport;
|
||||||
dynamicStates[1] = DynamicState.Scissor;
|
dynamicStates[1] = DynamicState.Scissor;
|
||||||
@@ -587,12 +578,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
if (supportsExtDynamicState)
|
if (supportsExtDynamicState)
|
||||||
{
|
{
|
||||||
dynamicStates[dynamicStatesCount++] = DynamicState.VertexInputBindingStrideExt;
|
dynamicStates[7] = DynamicState.VertexInputBindingStrideExt;
|
||||||
}
|
|
||||||
|
|
||||||
if (supportsFeedbackLoopDynamicState)
|
|
||||||
{
|
|
||||||
dynamicStates[dynamicStatesCount++] = DynamicState.AttachmentFeedbackLoopEnableExt;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PipelineDynamicStateCreateInfo pipelineDynamicStateCreateInfo = new()
|
PipelineDynamicStateCreateInfo pipelineDynamicStateCreateInfo = new()
|
||||||
@@ -602,27 +588,9 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
PDynamicStates = dynamicStates,
|
PDynamicStates = dynamicStates,
|
||||||
};
|
};
|
||||||
|
|
||||||
PipelineCreateFlags flags = 0;
|
|
||||||
|
|
||||||
if (gd.Capabilities.SupportsAttachmentFeedbackLoop)
|
|
||||||
{
|
|
||||||
FeedbackLoopAspects aspects = FeedbackLoopAspects;
|
|
||||||
|
|
||||||
if ((aspects & FeedbackLoopAspects.Color) != 0)
|
|
||||||
{
|
|
||||||
flags |= PipelineCreateFlags.CreateColorAttachmentFeedbackLoopBitExt;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((aspects & FeedbackLoopAspects.Depth) != 0)
|
|
||||||
{
|
|
||||||
flags |= PipelineCreateFlags.CreateDepthStencilAttachmentFeedbackLoopBitExt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GraphicsPipelineCreateInfo pipelineCreateInfo = new()
|
GraphicsPipelineCreateInfo pipelineCreateInfo = new()
|
||||||
{
|
{
|
||||||
SType = StructureType.GraphicsPipelineCreateInfo,
|
SType = StructureType.GraphicsPipelineCreateInfo,
|
||||||
Flags = flags,
|
|
||||||
StageCount = StagesCount,
|
StageCount = StagesCount,
|
||||||
PStages = Stages.Pointer,
|
PStages = Stages.Pointer,
|
||||||
PVertexInputState = &vertexInputState,
|
PVertexInputState = &vertexInputState,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ using Silk.NET.Vulkan;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using Format = Ryujinx.Graphics.GAL.Format;
|
using Format = Ryujinx.Graphics.GAL.Format;
|
||||||
using VkBuffer = Silk.NET.Vulkan.Buffer;
|
using VkBuffer = Silk.NET.Vulkan.Buffer;
|
||||||
using VkFormat = Silk.NET.Vulkan.Format;
|
using VkFormat = Silk.NET.Vulkan.Format;
|
||||||
@@ -13,11 +12,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
class TextureStorage : IDisposable
|
class TextureStorage : IDisposable
|
||||||
{
|
{
|
||||||
private struct TextureSliceInfo
|
|
||||||
{
|
|
||||||
public int BindCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
private const MemoryPropertyFlags DefaultImageMemoryFlags =
|
private const MemoryPropertyFlags DefaultImageMemoryFlags =
|
||||||
MemoryPropertyFlags.DeviceLocalBit;
|
MemoryPropertyFlags.DeviceLocalBit;
|
||||||
|
|
||||||
@@ -49,7 +43,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
private readonly Image _image;
|
private readonly Image _image;
|
||||||
private readonly Auto<DisposableImage> _imageAuto;
|
private readonly Auto<DisposableImage> _imageAuto;
|
||||||
private readonly Auto<MemoryAllocation> _allocationAuto;
|
private readonly Auto<MemoryAllocation> _allocationAuto;
|
||||||
private readonly int _depthOrLayers;
|
|
||||||
private Auto<MemoryAllocation> _foreignAllocationAuto;
|
private Auto<MemoryAllocation> _foreignAllocationAuto;
|
||||||
|
|
||||||
private Dictionary<Format, TextureStorage> _aliasedStorages;
|
private Dictionary<Format, TextureStorage> _aliasedStorages;
|
||||||
@@ -62,9 +55,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
private int _viewsCount;
|
private int _viewsCount;
|
||||||
private readonly ulong _size;
|
private readonly ulong _size;
|
||||||
|
|
||||||
private int _bindCount;
|
|
||||||
private readonly TextureSliceInfo[] _slices;
|
|
||||||
|
|
||||||
public VkFormat VkFormat { get; }
|
public VkFormat VkFormat { get; }
|
||||||
|
|
||||||
public unsafe TextureStorage(
|
public unsafe TextureStorage(
|
||||||
@@ -85,7 +75,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
uint depth = (uint)(info.Target == Target.Texture3D ? info.Depth : 1);
|
uint depth = (uint)(info.Target == Target.Texture3D ? info.Depth : 1);
|
||||||
|
|
||||||
VkFormat = format;
|
VkFormat = format;
|
||||||
_depthOrLayers = info.GetDepthOrLayers();
|
|
||||||
|
|
||||||
ImageType type = info.Target.Convert();
|
ImageType type = info.Target.Convert();
|
||||||
|
|
||||||
@@ -161,8 +150,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
InitialTransition(ImageLayout.Preinitialized, ImageLayout.General);
|
InitialTransition(ImageLayout.Preinitialized, ImageLayout.General);
|
||||||
}
|
}
|
||||||
|
|
||||||
_slices = new TextureSliceInfo[levels * _depthOrLayers];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TextureStorage CreateAliasedColorForDepthStorageUnsafe(Format format)
|
public TextureStorage CreateAliasedColorForDepthStorageUnsafe(Format format)
|
||||||
@@ -325,12 +312,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
usage |= ImageUsageFlags.StorageBit;
|
usage |= ImageUsageFlags.StorageBit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (capabilities.SupportsAttachmentFeedbackLoop &&
|
|
||||||
(usage & (ImageUsageFlags.DepthStencilAttachmentBit | ImageUsageFlags.ColorAttachmentBit)) != 0)
|
|
||||||
{
|
|
||||||
usage |= ImageUsageFlags.AttachmentFeedbackLoopBitExt;
|
|
||||||
}
|
|
||||||
|
|
||||||
return usage;
|
return usage;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,55 +512,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddBinding(TextureView view)
|
|
||||||
{
|
|
||||||
// Assumes a view only has a first level.
|
|
||||||
|
|
||||||
int index = view.FirstLevel * _depthOrLayers + view.FirstLayer;
|
|
||||||
int layers = view.Layers;
|
|
||||||
|
|
||||||
for (int i = 0; i < layers; i++)
|
|
||||||
{
|
|
||||||
ref TextureSliceInfo info = ref _slices[index++];
|
|
||||||
|
|
||||||
info.BindCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
_bindCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ClearBindings()
|
|
||||||
{
|
|
||||||
if (_bindCount != 0)
|
|
||||||
{
|
|
||||||
Array.Clear(_slices, 0, _slices.Length);
|
|
||||||
|
|
||||||
_bindCount = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public bool IsBound(TextureView view)
|
|
||||||
{
|
|
||||||
if (_bindCount != 0)
|
|
||||||
{
|
|
||||||
int index = view.FirstLevel * _depthOrLayers + view.FirstLayer;
|
|
||||||
int layers = view.Layers;
|
|
||||||
|
|
||||||
for (int i = 0; i < layers; i++)
|
|
||||||
{
|
|
||||||
ref TextureSliceInfo info = ref _slices[index++];
|
|
||||||
|
|
||||||
if (info.BindCount != 0)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void IncrementViewsCount()
|
public void IncrementViewsCount()
|
||||||
{
|
{
|
||||||
_viewsCount++;
|
_viewsCount++;
|
||||||
|
|||||||
@@ -23,8 +23,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
private readonly Auto<DisposableImageView> _imageView2dArray;
|
private readonly Auto<DisposableImageView> _imageView2dArray;
|
||||||
private Dictionary<Format, TextureView> _selfManagedViews;
|
private Dictionary<Format, TextureView> _selfManagedViews;
|
||||||
|
|
||||||
private int _hazardUses;
|
|
||||||
|
|
||||||
private readonly TextureCreateInfo _info;
|
private readonly TextureCreateInfo _info;
|
||||||
|
|
||||||
private HashTableSlim<RenderPassCacheKey, RenderPassHolder> _renderPasses;
|
private HashTableSlim<RenderPassCacheKey, RenderPassHolder> _renderPasses;
|
||||||
@@ -1039,34 +1037,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PrepareForUsage(CommandBufferScoped cbs, PipelineStageFlags flags, List<TextureView> feedbackLoopHazards)
|
|
||||||
{
|
|
||||||
Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, flags);
|
|
||||||
|
|
||||||
if (feedbackLoopHazards != null && Storage.IsBound(this))
|
|
||||||
{
|
|
||||||
feedbackLoopHazards.Add(this);
|
|
||||||
_hazardUses++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ClearUsage(List<TextureView> feedbackLoopHazards)
|
|
||||||
{
|
|
||||||
if (_hazardUses != 0 && feedbackLoopHazards != null)
|
|
||||||
{
|
|
||||||
feedbackLoopHazards.Remove(this);
|
|
||||||
_hazardUses--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DecrementHazardUses()
|
|
||||||
{
|
|
||||||
if (_hazardUses != 0)
|
|
||||||
{
|
|
||||||
_hazardUses--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public (RenderPassHolder rpHolder, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
|
public (RenderPassHolder rpHolder, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
|
||||||
VulkanRenderer gd,
|
VulkanRenderer gd,
|
||||||
Device device,
|
Device device,
|
||||||
|
|||||||
@@ -46,8 +46,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
"VK_EXT_4444_formats",
|
"VK_EXT_4444_formats",
|
||||||
"VK_KHR_8bit_storage",
|
"VK_KHR_8bit_storage",
|
||||||
"VK_KHR_maintenance2",
|
"VK_KHR_maintenance2",
|
||||||
"VK_EXT_attachment_feedback_loop_layout",
|
|
||||||
"VK_EXT_attachment_feedback_loop_dynamic_state"
|
|
||||||
];
|
];
|
||||||
|
|
||||||
private static readonly string[] _requiredExtensions =
|
private static readonly string[] _requiredExtensions =
|
||||||
@@ -362,28 +360,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
features2.PNext = &supportedFeaturesDepthClipControl;
|
features2.PNext = &supportedFeaturesDepthClipControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT supportedFeaturesAttachmentFeedbackLoopLayout = new()
|
|
||||||
{
|
|
||||||
SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesExt,
|
|
||||||
PNext = features2.PNext,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_layout"))
|
|
||||||
{
|
|
||||||
features2.PNext = &supportedFeaturesAttachmentFeedbackLoopLayout;
|
|
||||||
}
|
|
||||||
|
|
||||||
PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT supportedFeaturesDynamicAttachmentFeedbackLoopLayout = new()
|
|
||||||
{
|
|
||||||
SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesExt,
|
|
||||||
PNext = features2.PNext,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_dynamic_state"))
|
|
||||||
{
|
|
||||||
features2.PNext = &supportedFeaturesDynamicAttachmentFeedbackLoopLayout;
|
|
||||||
}
|
|
||||||
|
|
||||||
PhysicalDeviceVulkan12Features supportedPhysicalDeviceVulkan12Features = new()
|
PhysicalDeviceVulkan12Features supportedPhysicalDeviceVulkan12Features = new()
|
||||||
{
|
{
|
||||||
SType = StructureType.PhysicalDeviceVulkan12Features,
|
SType = StructureType.PhysicalDeviceVulkan12Features,
|
||||||
@@ -558,36 +534,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
pExtendedFeatures = &featuresDepthClipControl;
|
pExtendedFeatures = &featuresDepthClipControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT featuresAttachmentFeedbackLoopLayout;
|
|
||||||
|
|
||||||
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_layout") &&
|
|
||||||
supportedFeaturesAttachmentFeedbackLoopLayout.AttachmentFeedbackLoopLayout)
|
|
||||||
{
|
|
||||||
featuresAttachmentFeedbackLoopLayout = new()
|
|
||||||
{
|
|
||||||
SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesExt,
|
|
||||||
PNext = pExtendedFeatures,
|
|
||||||
AttachmentFeedbackLoopLayout = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
pExtendedFeatures = &featuresAttachmentFeedbackLoopLayout;
|
|
||||||
}
|
|
||||||
|
|
||||||
PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT featuresDynamicAttachmentFeedbackLoopLayout;
|
|
||||||
|
|
||||||
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_dynamic_state") &&
|
|
||||||
supportedFeaturesDynamicAttachmentFeedbackLoopLayout.AttachmentFeedbackLoopDynamicState)
|
|
||||||
{
|
|
||||||
featuresDynamicAttachmentFeedbackLoopLayout = new()
|
|
||||||
{
|
|
||||||
SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesExt,
|
|
||||||
PNext = pExtendedFeatures,
|
|
||||||
AttachmentFeedbackLoopDynamicState = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
pExtendedFeatures = &featuresDynamicAttachmentFeedbackLoopLayout;
|
|
||||||
}
|
|
||||||
|
|
||||||
string[] enabledExtensions = _requiredExtensions.Union(_desirableExtensions.Intersect(physicalDevice.DeviceExtensions)).ToArray();
|
string[] enabledExtensions = _requiredExtensions.Union(_desirableExtensions.Intersect(physicalDevice.DeviceExtensions)).ToArray();
|
||||||
|
|
||||||
nint* ppEnabledExtensions = stackalloc nint[enabledExtensions.Length];
|
nint* ppEnabledExtensions = stackalloc nint[enabledExtensions.Length];
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
internal KhrPushDescriptor PushDescriptorApi { get; private set; }
|
internal KhrPushDescriptor PushDescriptorApi { get; private set; }
|
||||||
internal ExtTransformFeedback TransformFeedbackApi { get; private set; }
|
internal ExtTransformFeedback TransformFeedbackApi { get; private set; }
|
||||||
internal KhrDrawIndirectCount DrawIndirectCountApi { get; private set; }
|
internal KhrDrawIndirectCount DrawIndirectCountApi { get; private set; }
|
||||||
internal ExtAttachmentFeedbackLoopDynamicState DynamicFeedbackLoopApi { get; private set; }
|
|
||||||
|
|
||||||
internal uint QueueFamilyIndex { get; private set; }
|
internal uint QueueFamilyIndex { get; private set; }
|
||||||
internal Queue Queue { get; private set; }
|
internal Queue Queue { get; private set; }
|
||||||
@@ -158,11 +157,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
DrawIndirectCountApi = drawIndirectCountApi;
|
DrawIndirectCountApi = drawIndirectCountApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtAttachmentFeedbackLoopDynamicState dynamicFeedbackLoopApi))
|
|
||||||
{
|
|
||||||
DynamicFeedbackLoopApi = dynamicFeedbackLoopApi;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (maxQueueCount >= 2)
|
if (maxQueueCount >= 2)
|
||||||
{
|
{
|
||||||
Api.GetDeviceQueue(_device, queueFamilyIndex, 1, out Queue backgroundQueue);
|
Api.GetDeviceQueue(_device, queueFamilyIndex, 1, out Queue backgroundQueue);
|
||||||
@@ -257,16 +251,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
SType = StructureType.PhysicalDeviceDepthClipControlFeaturesExt,
|
SType = StructureType.PhysicalDeviceDepthClipControlFeaturesExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT featuresAttachmentFeedbackLoop = new()
|
|
||||||
{
|
|
||||||
SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesExt,
|
|
||||||
};
|
|
||||||
|
|
||||||
PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT featuresDynamicAttachmentFeedbackLoop = new()
|
|
||||||
{
|
|
||||||
SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesExt,
|
|
||||||
};
|
|
||||||
|
|
||||||
PhysicalDevicePortabilitySubsetFeaturesKHR featuresPortabilitySubset = new()
|
PhysicalDevicePortabilitySubsetFeaturesKHR featuresPortabilitySubset = new()
|
||||||
{
|
{
|
||||||
SType = StructureType.PhysicalDevicePortabilitySubsetFeaturesKhr,
|
SType = StructureType.PhysicalDevicePortabilitySubsetFeaturesKhr,
|
||||||
@@ -303,22 +287,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
features2.PNext = &featuresDepthClipControl;
|
features2.PNext = &featuresDepthClipControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool supportsAttachmentFeedbackLoop = _physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_layout");
|
|
||||||
|
|
||||||
if (supportsAttachmentFeedbackLoop)
|
|
||||||
{
|
|
||||||
featuresAttachmentFeedbackLoop.PNext = features2.PNext;
|
|
||||||
features2.PNext = &featuresAttachmentFeedbackLoop;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool supportsDynamicAttachmentFeedbackLoop = _physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_dynamic_state");
|
|
||||||
|
|
||||||
if (supportsDynamicAttachmentFeedbackLoop)
|
|
||||||
{
|
|
||||||
featuresDynamicAttachmentFeedbackLoop.PNext = features2.PNext;
|
|
||||||
features2.PNext = &featuresDynamicAttachmentFeedbackLoop;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool usePortability = _physicalDevice.IsDeviceExtensionPresent("VK_KHR_portability_subset");
|
bool usePortability = _physicalDevice.IsDeviceExtensionPresent("VK_KHR_portability_subset");
|
||||||
|
|
||||||
if (usePortability)
|
if (usePortability)
|
||||||
@@ -441,8 +409,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_physicalDevice.IsDeviceExtensionPresent("VK_NV_viewport_array2"),
|
_physicalDevice.IsDeviceExtensionPresent("VK_NV_viewport_array2"),
|
||||||
_physicalDevice.IsDeviceExtensionPresent(ExtExternalMemoryHost.ExtensionName),
|
_physicalDevice.IsDeviceExtensionPresent(ExtExternalMemoryHost.ExtensionName),
|
||||||
supportsDepthClipControl && featuresDepthClipControl.DepthClipControl,
|
supportsDepthClipControl && featuresDepthClipControl.DepthClipControl,
|
||||||
supportsAttachmentFeedbackLoop && featuresAttachmentFeedbackLoop.AttachmentFeedbackLoopLayout,
|
|
||||||
supportsDynamicAttachmentFeedbackLoop && featuresDynamicAttachmentFeedbackLoop.AttachmentFeedbackLoopDynamicState,
|
|
||||||
propertiesSubgroup.SubgroupSize,
|
propertiesSubgroup.SubgroupSize,
|
||||||
supportedSampleCounts,
|
supportedSampleCounts,
|
||||||
portabilityFlags,
|
portabilityFlags,
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy
|
|||||||
public async Task<ushort> NatPunch()
|
public async Task<ushort> NatPunch()
|
||||||
{
|
{
|
||||||
NatDiscoverer discoverer = new();
|
NatDiscoverer discoverer = new();
|
||||||
CancellationTokenSource cts = new(5000);
|
CancellationTokenSource cts = new(2500);
|
||||||
|
|
||||||
NatDevice device;
|
NatDevice device;
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
|
|||||||
{
|
{
|
||||||
if (errorCode != LinuxError.SUCCESS)
|
if (errorCode != LinuxError.SUCCESS)
|
||||||
{
|
{
|
||||||
|
if (errorCode != LinuxError.EWOULDBLOCK)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.ServiceBsd, $"Operation failed with error {errorCode}.");
|
||||||
|
}
|
||||||
result = -1;
|
result = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,6 +70,8 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
|
|||||||
BsdSocketType type = (BsdSocketType)context.RequestData.ReadInt32();
|
BsdSocketType type = (BsdSocketType)context.RequestData.ReadInt32();
|
||||||
ProtocolType protocol = (ProtocolType)context.RequestData.ReadInt32();
|
ProtocolType protocol = (ProtocolType)context.RequestData.ReadInt32();
|
||||||
|
|
||||||
|
Logger.Info?.PrintMsg(LogClass.ServiceBsd, $"Creating socket with domain={domain}, type={type}, protocol={protocol}");
|
||||||
|
|
||||||
BsdSocketCreationFlags creationFlags = (BsdSocketCreationFlags)((int)type >> (int)BsdSocketCreationFlags.FlagsShift);
|
BsdSocketCreationFlags creationFlags = (BsdSocketCreationFlags)((int)type >> (int)BsdSocketCreationFlags.FlagsShift);
|
||||||
type &= BsdSocketType.TypeMask;
|
type &= BsdSocketType.TypeMask;
|
||||||
|
|
||||||
@@ -95,12 +101,21 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ISocket newBsdSocket = new ManagedSocket(netDomain, (SocketType)type, protocol, context.Device.Configuration.MultiplayerLanInterfaceId)
|
|
||||||
{
|
|
||||||
Blocking = !creationFlags.HasFlag(BsdSocketCreationFlags.NonBlocking),
|
|
||||||
};
|
|
||||||
|
|
||||||
LinuxError errno = LinuxError.SUCCESS;
|
LinuxError errno = LinuxError.SUCCESS;
|
||||||
|
ISocket newBsdSocket;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
newBsdSocket = new ManagedSocket(netDomain, (SocketType)type, protocol, context.Device.Configuration.MultiplayerLanInterfaceId)
|
||||||
|
{
|
||||||
|
Blocking = !creationFlags.HasFlag(BsdSocketCreationFlags.NonBlocking),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (SocketException exception)
|
||||||
|
{
|
||||||
|
LinuxError errNo = WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
||||||
|
return WriteBsdResult(context, 0, errNo);
|
||||||
|
}
|
||||||
|
|
||||||
int newSockFd = _context.RegisterFileDescriptor(newBsdSocket);
|
int newSockFd = _context.RegisterFileDescriptor(newBsdSocket);
|
||||||
|
|
||||||
@@ -111,6 +126,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
|
|||||||
|
|
||||||
if (exempt)
|
if (exempt)
|
||||||
{
|
{
|
||||||
|
Logger.Info?.Print(LogClass.ServiceBsd, "Disconnecting exempt socket.");
|
||||||
newBsdSocket.Disconnect();
|
newBsdSocket.Disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -797,6 +813,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
|
|||||||
{
|
{
|
||||||
errno = socket.Listen(backlog);
|
errno = socket.Listen(backlog);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Warning?.PrintMsg(LogClass.ServiceBsd, $"Invalid socket fd '{socketFd}'.");
|
||||||
|
}
|
||||||
|
|
||||||
return WriteBsdResult(context, 0, errno);
|
return WriteBsdResult(context, 0, errno);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,18 +92,30 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
{
|
{
|
||||||
newSocket = new ManagedSocket(Socket.Accept());
|
newSocket = new ManagedSocket(Socket.Accept());
|
||||||
|
|
||||||
|
IPEndPoint remoteEndPoint = newSocket.RemoteEndPoint;
|
||||||
|
bool isPrivateIp = remoteEndPoint.Address.ToString().StartsWith("192.168.");
|
||||||
|
Logger.Info?.PrintMsg(LogClass.ServiceBsd,
|
||||||
|
isPrivateIp
|
||||||
|
? $"Accepted connection from {ProtocolType}/{remoteEndPoint.Address}:{remoteEndPoint.Port}"
|
||||||
|
: $"Accepted connection from {ProtocolType}/***:{remoteEndPoint.Port}");
|
||||||
|
|
||||||
return LinuxError.SUCCESS;
|
return LinuxError.SUCCESS;
|
||||||
}
|
}
|
||||||
catch (SocketException exception)
|
catch (SocketException exception)
|
||||||
{
|
{
|
||||||
newSocket = null;
|
newSocket = null;
|
||||||
|
|
||||||
|
if (exception.SocketErrorCode != SocketError.WouldBlock)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
|
||||||
|
}
|
||||||
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public LinuxError Bind(IPEndPoint localEndPoint)
|
public LinuxError Bind(IPEndPoint localEndPoint)
|
||||||
{
|
{
|
||||||
|
Logger.Info?.PrintMsg(LogClass.ServiceBsd, $"Socket binding to: {ProtocolType}/{localEndPoint.Port}");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Socket.Bind(localEndPoint);
|
Socket.Bind(localEndPoint);
|
||||||
@@ -112,6 +124,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
}
|
}
|
||||||
catch (SocketException exception)
|
catch (SocketException exception)
|
||||||
{
|
{
|
||||||
|
if (exception.SocketErrorCode != SocketError.WouldBlock)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
|
||||||
|
}
|
||||||
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,6 +139,15 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
|
|
||||||
public LinuxError Connect(IPEndPoint remoteEndPoint)
|
public LinuxError Connect(IPEndPoint remoteEndPoint)
|
||||||
{
|
{
|
||||||
|
bool isLDNPrivateIP = remoteEndPoint.Address.ToString().StartsWith("192.168.");
|
||||||
|
if (isLDNPrivateIP)
|
||||||
|
{
|
||||||
|
Logger.Info?.PrintMsg(LogClass.ServiceBsd, $"Connecting to: {ProtocolType}/{remoteEndPoint.Address}:{remoteEndPoint.Port}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Info?.PrintMsg(LogClass.ServiceBsd, $"Connecting to: {ProtocolType}/***:{remoteEndPoint.Port}");
|
||||||
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Socket.Connect(remoteEndPoint);
|
Socket.Connect(remoteEndPoint);
|
||||||
@@ -137,6 +162,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
if (exception.SocketErrorCode != SocketError.WouldBlock)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
|
||||||
|
}
|
||||||
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,11 +173,13 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
|
|
||||||
public void Disconnect()
|
public void Disconnect()
|
||||||
{
|
{
|
||||||
|
Logger.Info?.Print(LogClass.ServiceBsd, "Socket disconnecting");
|
||||||
Socket.Disconnect(true);
|
Socket.Disconnect(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
Logger.Info?.Print(LogClass.ServiceBsd, "Socket closed");
|
||||||
Socket.Close();
|
Socket.Close();
|
||||||
Socket.Dispose();
|
Socket.Dispose();
|
||||||
}
|
}
|
||||||
@@ -159,10 +190,16 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
{
|
{
|
||||||
Socket.Listen(backlog);
|
Socket.Listen(backlog);
|
||||||
|
|
||||||
|
Logger.Info?.PrintMsg(LogClass.ServiceBsd, $"Socket listening: {ProtocolType}/{(Socket.LocalEndPoint as IPEndPoint).Port}");
|
||||||
|
|
||||||
return LinuxError.SUCCESS;
|
return LinuxError.SUCCESS;
|
||||||
}
|
}
|
||||||
catch (SocketException exception)
|
catch (SocketException exception)
|
||||||
{
|
{
|
||||||
|
if (exception.SocketErrorCode != SocketError.WouldBlock)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
|
||||||
|
}
|
||||||
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -182,11 +219,15 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
}
|
}
|
||||||
catch (SocketException exception)
|
catch (SocketException exception)
|
||||||
{
|
{
|
||||||
|
if (exception.SocketErrorCode != SocketError.WouldBlock)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
|
||||||
|
}
|
||||||
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hasEmittedBlockingWarning = false;
|
private bool _hasEmittedBlockingWarning;
|
||||||
|
|
||||||
public LinuxError Receive(out int receiveSize, Span<byte> buffer, BsdSocketFlags flags)
|
public LinuxError Receive(out int receiveSize, Span<byte> buffer, BsdSocketFlags flags)
|
||||||
{
|
{
|
||||||
@@ -202,10 +243,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
shouldBlockAfterOperation = true;
|
shouldBlockAfterOperation = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Blocking && !hasEmittedBlockingWarning)
|
if (Blocking && !_hasEmittedBlockingWarning)
|
||||||
{
|
{
|
||||||
Logger.Warning?.PrintMsg(LogClass.ServiceBsd, "Blocking socket operations are not yet working properly. Expect network errors.");
|
Logger.Warning?.PrintMsg(LogClass.ServiceBsd, "Blocking socket operations are not yet working properly. Expect network errors.");
|
||||||
hasEmittedBlockingWarning = true;
|
_hasEmittedBlockingWarning = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
receiveSize = Socket.Receive(buffer, ConvertBsdSocketFlags(flags));
|
receiveSize = Socket.Receive(buffer, ConvertBsdSocketFlags(flags));
|
||||||
@@ -214,6 +255,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
}
|
}
|
||||||
catch (SocketException exception)
|
catch (SocketException exception)
|
||||||
{
|
{
|
||||||
|
if (exception.SocketErrorCode != SocketError.WouldBlock)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
|
||||||
|
}
|
||||||
receiveSize = -1;
|
receiveSize = -1;
|
||||||
|
|
||||||
result = WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
result = WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
||||||
@@ -245,10 +290,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
shouldBlockAfterOperation = true;
|
shouldBlockAfterOperation = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Blocking && !hasEmittedBlockingWarning)
|
if (Blocking && !_hasEmittedBlockingWarning)
|
||||||
{
|
{
|
||||||
Logger.Warning?.PrintMsg(LogClass.ServiceBsd, "Blocking socket operations are not yet working properly. Expect network errors.");
|
Logger.Warning?.PrintMsg(LogClass.ServiceBsd, "Blocking socket operations are not yet working properly. Expect network errors.");
|
||||||
hasEmittedBlockingWarning = true;
|
_hasEmittedBlockingWarning = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Socket.IsBound)
|
if (!Socket.IsBound)
|
||||||
@@ -265,6 +310,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
}
|
}
|
||||||
catch (SocketException exception)
|
catch (SocketException exception)
|
||||||
{
|
{
|
||||||
|
if (exception.SocketErrorCode != SocketError.WouldBlock)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
|
||||||
|
}
|
||||||
receiveSize = -1;
|
receiveSize = -1;
|
||||||
|
|
||||||
result = WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
result = WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
||||||
@@ -288,6 +337,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
}
|
}
|
||||||
catch (SocketException exception)
|
catch (SocketException exception)
|
||||||
{
|
{
|
||||||
|
if (exception.SocketErrorCode != SocketError.WouldBlock)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
|
||||||
|
}
|
||||||
sendSize = -1;
|
sendSize = -1;
|
||||||
|
|
||||||
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
||||||
@@ -304,6 +357,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
}
|
}
|
||||||
catch (SocketException exception)
|
catch (SocketException exception)
|
||||||
{
|
{
|
||||||
|
if (exception.SocketErrorCode != SocketError.WouldBlock)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
|
||||||
|
}
|
||||||
sendSize = -1;
|
sendSize = -1;
|
||||||
|
|
||||||
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
||||||
@@ -341,6 +398,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
}
|
}
|
||||||
catch (SocketException exception)
|
catch (SocketException exception)
|
||||||
{
|
{
|
||||||
|
if (exception.SocketErrorCode != SocketError.WouldBlock)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
|
||||||
|
}
|
||||||
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -387,6 +448,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
}
|
}
|
||||||
catch (SocketException exception)
|
catch (SocketException exception)
|
||||||
{
|
{
|
||||||
|
if (exception.SocketErrorCode != SocketError.WouldBlock)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
|
||||||
|
}
|
||||||
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -519,6 +584,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
}
|
}
|
||||||
catch (SocketException exception)
|
catch (SocketException exception)
|
||||||
{
|
{
|
||||||
|
if (exception.SocketErrorCode != SocketError.WouldBlock)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
|
||||||
|
}
|
||||||
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -557,6 +626,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
}
|
}
|
||||||
catch (SocketException exception)
|
catch (SocketException exception)
|
||||||
{
|
{
|
||||||
|
if (exception.SocketErrorCode != SocketError.WouldBlock)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
|
||||||
|
}
|
||||||
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy;
|
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -64,10 +66,18 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy
|
|||||||
{
|
{
|
||||||
if (_proxy.Supported(domain, type, protocol))
|
if (_proxy.Supported(domain, type, protocol))
|
||||||
{
|
{
|
||||||
|
Logger.Info?.PrintMsg(LogClass.ServiceBsd, $"Socket is using LDN proxy");
|
||||||
return new LdnProxySocket(domain, type, protocol, _proxy);
|
return new LdnProxySocket(domain, type, protocol, _proxy);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Warning?.PrintMsg(LogClass.ServiceBsd, $"LDN proxy does not support socket {domain}, {type}, {protocol}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Info?.PrintMsg(LogClass.ServiceBsd, $"Opening socket using host networking stack");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new DefaultSocket(domain, type, protocol, lanInterfaceId);
|
return new DefaultSocket(domain, type, protocol, lanInterfaceId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -24,7 +24,7 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
private static readonly string _description =
|
private static readonly string _description =
|
||||||
ReleaseInformation.IsValid
|
ReleaseInformation.IsValid
|
||||||
? $"{VersionString} {ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelSourceRepo}@{ReleaseInformation.BuildGitHash}"
|
? $"{VersionString} {ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelSourceRepo}"
|
||||||
: "dev build";
|
: "dev build";
|
||||||
|
|
||||||
private const string ApplicationId = "1293250299716173864";
|
private const string ApplicationId = "1293250299716173864";
|
||||||
@@ -56,6 +56,7 @@ namespace Ryujinx.Ava
|
|||||||
ConfigurationState.Instance.EnableDiscordIntegration.Event += Update;
|
ConfigurationState.Instance.EnableDiscordIntegration.Event += Update;
|
||||||
TitleIDs.CurrentApplication.Event += (_, e) => Use(e.NewValue);
|
TitleIDs.CurrentApplication.Event += (_, e) => Use(e.NewValue);
|
||||||
HorizonStatic.PlayReport += HandlePlayReport;
|
HorizonStatic.PlayReport += HandlePlayReport;
|
||||||
|
PlayReports.Initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Update(object sender, ReactiveEventArgs<bool> evnt)
|
private static void Update(object sender, ReactiveEventArgs<bool> evnt)
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ namespace Ryujinx.Headless
|
|||||||
IgnoreMissingServices = configurationState.System.IgnoreMissingServices;
|
IgnoreMissingServices = configurationState.System.IgnoreMissingServices;
|
||||||
|
|
||||||
if (NeedsOverride(nameof(IgnoreControllerApplet)))
|
if (NeedsOverride(nameof(IgnoreControllerApplet)))
|
||||||
IgnoreControllerApplet = configurationState.System.IgnoreApplet;
|
IgnoreControllerApplet = configurationState.System.IgnoreControllerApplet;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|||||||
@@ -137,8 +137,8 @@ namespace Ryujinx.Ava
|
|||||||
// Logging system information.
|
// Logging system information.
|
||||||
PrintSystemInfo();
|
PrintSystemInfo();
|
||||||
|
|
||||||
// Enable OGL multithreading on the driver, and some other flags.
|
// Enable OGL multithreading on the driver, when available.
|
||||||
DriverUtilities.InitDriverConfig(ConfigurationState.Instance.Graphics.BackendThreading == BackendThreading.Off);
|
DriverUtilities.ToggleOGLThreading(ConfigurationState.Instance.Graphics.BackendThreading == BackendThreading.Off);
|
||||||
|
|
||||||
// Check if keys exists.
|
// Check if keys exists.
|
||||||
if (!File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys")))
|
if (!File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys")))
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
|
|
||||||
bool okPressed = false;
|
bool okPressed = false;
|
||||||
|
|
||||||
if (ConfigurationState.Instance.System.IgnoreApplet)
|
if (ConfigurationState.Instance.System.IgnoreControllerApplet)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||||
|
|||||||
@@ -119,17 +119,23 @@
|
|||||||
TextWrapping="Wrap" >
|
TextWrapping="Wrap" >
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Orientation="Horizontal" Spacing="5">
|
<StackPanel Orientation="Horizontal" Spacing="5" ToolTip.Tip="{Binding DynamicRichPresenceDescription}">
|
||||||
<ui:SymbolIcon Foreground="ForestGreen" Symbol="Checkmark" IsVisible="{Binding AppData.HasDynamicRichPresenceSupport}"/>
|
<ui:SymbolIcon
|
||||||
|
Foreground="ForestGreen"
|
||||||
|
Symbol="Checkmark"
|
||||||
|
IsVisible="{Binding AppData.HasDynamicRichPresenceSupport}"/>
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Foreground="ForestGreen"
|
Foreground="ForestGreen"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
IsVisible="{Binding AppData.HasDynamicRichPresenceSupport}"
|
IsVisible="{Binding AppData.HasDynamicRichPresenceSupport}"
|
||||||
Text="{ext:Locale GameInfoRpcDynamic}"
|
Text="{ext:Locale GameInfoRpcDynamic}"
|
||||||
TextAlignment="Start"
|
TextAlignment="Start"
|
||||||
TextWrapping="Wrap" >
|
TextWrapping="Wrap">
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
<ui:SymbolIcon Foreground="Red" Symbol="Cancel" IsVisible="{Binding !AppData.HasDynamicRichPresenceSupport}"/>
|
<ui:SymbolIcon
|
||||||
|
Foreground="Red"
|
||||||
|
Symbol="Cancel"
|
||||||
|
IsVisible="{Binding !AppData.HasDynamicRichPresenceSupport}"/>
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Foreground="Red"
|
Foreground="Red"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Gommon;
|
using Gommon;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.Utilities.AppLibrary;
|
using Ryujinx.Ava.Utilities.AppLibrary;
|
||||||
|
using Ryujinx.Ava.Utilities.PlayReport;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.ViewModels
|
namespace Ryujinx.Ava.UI.ViewModels
|
||||||
{
|
{
|
||||||
@@ -10,6 +11,11 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public ApplicationDataViewModel(ApplicationData appData) => AppData = appData;
|
public ApplicationDataViewModel(ApplicationData appData) => AppData = appData;
|
||||||
|
|
||||||
|
public string DynamicRichPresenceDescription =>
|
||||||
|
AppData.HasDynamicRichPresenceSupport
|
||||||
|
? AppData.RichPresenceSpec.Value.Description
|
||||||
|
: GameSpec.DefaultDescription;
|
||||||
|
|
||||||
public string FormattedVersion => LocaleManager.Instance[LocaleKeys.GameListHeaderVersion].Format(AppData.Version);
|
public string FormattedVersion => LocaleManager.Instance[LocaleKeys.GameListHeaderVersion].Format(AppData.Version);
|
||||||
public string FormattedDeveloper => LocaleManager.Instance[LocaleKeys.GameListHeaderDeveloper].Format(AppData.Developer);
|
public string FormattedDeveloper => LocaleManager.Instance[LocaleKeys.GameListHeaderDeveloper].Format(AppData.Developer);
|
||||||
public string FormattedFileExtension => LocaleManager.Instance[LocaleKeys.GameListHeaderFileExtension].Format(AppData.FileExtension);
|
public string FormattedFileExtension => LocaleManager.Instance[LocaleKeys.GameListHeaderFileExtension].Format(AppData.FileExtension);
|
||||||
|
|||||||
@@ -1347,6 +1347,25 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
OpenHelper.OpenFolder(AppDataManager.BaseDirPath);
|
OpenHelper.OpenFolder(AppDataManager.BaseDirPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OpenScreenshotsFolder()
|
||||||
|
{
|
||||||
|
string screenshotsDir = Path.Combine(AppDataManager.BaseDirPath, "screenshots");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(screenshotsDir))
|
||||||
|
Directory.CreateDirectory(screenshotsDir);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Application, $"Failed to create directory at path {screenshotsDir}. Error : {ex.GetType().Name}", "Screenshot");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenHelper.OpenFolder(screenshotsDir);
|
||||||
|
}
|
||||||
|
|
||||||
public void OpenLogsFolder()
|
public void OpenLogsFolder()
|
||||||
{
|
{
|
||||||
string logPath = AppDataManager.GetOrCreateLogsDir();
|
string logPath = AppDataManager.GetOrCreateLogsDir();
|
||||||
|
|||||||
@@ -49,8 +49,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
private int _graphicsBackendMultithreadingIndex;
|
private int _graphicsBackendMultithreadingIndex;
|
||||||
private float _volume;
|
private float _volume;
|
||||||
[ObservableProperty] private bool _isVulkanAvailable = true;
|
[ObservableProperty] private bool _isVulkanAvailable = true;
|
||||||
[ObservableProperty] private bool _gameDirectoryChanged;
|
[ObservableProperty] private bool _gameListNeedsRefresh;
|
||||||
[ObservableProperty] private bool _autoloadDirectoryChanged;
|
|
||||||
private readonly List<string> _gpuIds = [];
|
private readonly List<string> _gpuIds = [];
|
||||||
private int _graphicsBackendIndex;
|
private int _graphicsBackendIndex;
|
||||||
private int _scalingFilter;
|
private int _scalingFilter;
|
||||||
@@ -527,7 +526,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
EnableFsIntegrityChecks = config.System.EnableFsIntegrityChecks;
|
EnableFsIntegrityChecks = config.System.EnableFsIntegrityChecks;
|
||||||
DramSize = config.System.DramSize;
|
DramSize = config.System.DramSize;
|
||||||
IgnoreMissingServices = config.System.IgnoreMissingServices;
|
IgnoreMissingServices = config.System.IgnoreMissingServices;
|
||||||
IgnoreApplet = config.System.IgnoreApplet;
|
IgnoreApplet = config.System.IgnoreControllerApplet;
|
||||||
|
|
||||||
// CPU
|
// CPU
|
||||||
EnablePptc = config.System.EnablePtc;
|
EnablePptc = config.System.EnablePtc;
|
||||||
@@ -593,16 +592,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
config.HideCursor.Value = (HideCursorMode)HideCursor;
|
config.HideCursor.Value = (HideCursorMode)HideCursor;
|
||||||
config.UpdateCheckerType.Value = (UpdaterType)UpdateCheckerType;
|
config.UpdateCheckerType.Value = (UpdaterType)UpdateCheckerType;
|
||||||
config.FocusLostActionType.Value = (FocusLostType)FocusLostActionType;
|
config.FocusLostActionType.Value = (FocusLostType)FocusLostActionType;
|
||||||
|
config.UI.GameDirs.Value = [..GameDirectories];
|
||||||
if (GameDirectoryChanged)
|
config.UI.AutoloadDirs.Value = [..AutoloadDirectories];
|
||||||
{
|
|
||||||
config.UI.GameDirs.Value = [..GameDirectories];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (AutoloadDirectoryChanged)
|
|
||||||
{
|
|
||||||
config.UI.AutoloadDirs.Value = [..AutoloadDirectories];
|
|
||||||
}
|
|
||||||
|
|
||||||
config.UI.BaseStyle.Value = BaseStyleIndex switch
|
config.UI.BaseStyle.Value = BaseStyleIndex switch
|
||||||
{
|
{
|
||||||
@@ -623,8 +614,11 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
// System
|
// System
|
||||||
config.System.Region.Value = (Region)Region;
|
config.System.Region.Value = (Region)Region;
|
||||||
config.System.Language.Value = (Language)Language;
|
|
||||||
|
|
||||||
|
if (config.System.Language.Value != (Language)Language)
|
||||||
|
GameListNeedsRefresh = true;
|
||||||
|
|
||||||
|
config.System.Language.Value = (Language)Language;
|
||||||
if (_validTzRegions.Contains(TimeZone))
|
if (_validTzRegions.Contains(TimeZone))
|
||||||
{
|
{
|
||||||
config.System.TimeZone.Value = TimeZone;
|
config.System.TimeZone.Value = TimeZone;
|
||||||
@@ -635,7 +629,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
config.System.EnableFsIntegrityChecks.Value = EnableFsIntegrityChecks;
|
config.System.EnableFsIntegrityChecks.Value = EnableFsIntegrityChecks;
|
||||||
config.System.DramSize.Value = DramSize;
|
config.System.DramSize.Value = DramSize;
|
||||||
config.System.IgnoreMissingServices.Value = IgnoreMissingServices;
|
config.System.IgnoreMissingServices.Value = IgnoreMissingServices;
|
||||||
config.System.IgnoreApplet.Value = IgnoreApplet;
|
config.System.IgnoreControllerApplet.Value = IgnoreApplet;
|
||||||
|
|
||||||
// CPU
|
// CPU
|
||||||
config.System.EnablePtc.Value = EnablePptc;
|
config.System.EnablePtc.Value = EnablePptc;
|
||||||
@@ -715,8 +709,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
SaveSettingsEvent?.Invoke();
|
SaveSettingsEvent?.Invoke();
|
||||||
|
|
||||||
GameDirectoryChanged = false;
|
GameListNeedsRefresh = false;
|
||||||
AutoloadDirectoryChanged = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RevertIfNotSaved()
|
private static void RevertIfNotSaved()
|
||||||
|
|||||||
@@ -66,6 +66,10 @@
|
|||||||
Command="{Binding OpenRyujinxFolder}"
|
Command="{Binding OpenRyujinxFolder}"
|
||||||
Header="{ext:Locale MenuBarFileOpenEmuFolder}"
|
Header="{ext:Locale MenuBarFileOpenEmuFolder}"
|
||||||
ToolTip.Tip="{ext:Locale OpenRyujinxFolderTooltip}" />
|
ToolTip.Tip="{ext:Locale OpenRyujinxFolderTooltip}" />
|
||||||
|
<MenuItem
|
||||||
|
Command="{Binding OpenScreenshotsFolder}"
|
||||||
|
Header="{ext:Locale MenuBarFileOpenScreenshotsFolder}"
|
||||||
|
ToolTip.Tip="{ext:Locale OpenScreenshotFolderTooltip}"/>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
Command="{Binding OpenLogsFolder}"
|
Command="{Binding OpenLogsFolder}"
|
||||||
Header="{ext:Locale MenuBarFileOpenLogsFolder}"
|
Header="{ext:Locale MenuBarFileOpenLogsFolder}"
|
||||||
|
|||||||
@@ -316,8 +316,8 @@
|
|||||||
</CheckBox>
|
</CheckBox>
|
||||||
<CheckBox
|
<CheckBox
|
||||||
IsChecked="{Binding IgnoreApplet}"
|
IsChecked="{Binding IgnoreApplet}"
|
||||||
ToolTip.Tip="{ext:Locale IgnoreAppletTooltip}">
|
ToolTip.Tip="{ext:Locale IgnoreControllerAppletTooltip}">
|
||||||
<TextBlock Text="{ext:Locale SettingsTabSystemIgnoreApplet}" />
|
<TextBlock Text="{ext:Locale SettingsTabSystemIgnoreControllerApplet}" />
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
<CheckBox
|
<CheckBox
|
||||||
IsChecked="{Binding EnableCustomVSyncInterval}"
|
IsChecked="{Binding EnableCustomVSyncInterval}"
|
||||||
|
|||||||
@@ -37,10 +37,7 @@ namespace Ryujinx.Ava.UI.Views.Settings
|
|||||||
|
|
||||||
addDirBox.Clear();
|
addDirBox.Clear();
|
||||||
|
|
||||||
if (isGameList)
|
ViewModel.GameListNeedsRefresh = true;
|
||||||
ViewModel.GameDirectoryChanged = true;
|
|
||||||
else
|
|
||||||
ViewModel.AutoloadDirectoryChanged = true;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -50,10 +47,7 @@ namespace Ryujinx.Ava.UI.Views.Settings
|
|||||||
{
|
{
|
||||||
directories.Add(folder.Value.Path.LocalPath);
|
directories.Add(folder.Value.Path.LocalPath);
|
||||||
|
|
||||||
if (isGameList)
|
ViewModel.GameListNeedsRefresh = true;
|
||||||
ViewModel.GameDirectoryChanged = true;
|
|
||||||
else
|
|
||||||
ViewModel.AutoloadDirectoryChanged = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,7 +59,7 @@ namespace Ryujinx.Ava.UI.Views.Settings
|
|||||||
foreach (string path in new List<string>(GameDirsList.SelectedItems.Cast<string>()))
|
foreach (string path in new List<string>(GameDirsList.SelectedItems.Cast<string>()))
|
||||||
{
|
{
|
||||||
ViewModel.GameDirectories.Remove(path);
|
ViewModel.GameDirectories.Remove(path);
|
||||||
ViewModel.GameDirectoryChanged = true;
|
ViewModel.GameListNeedsRefresh = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GameDirsList.ItemCount > 0)
|
if (GameDirsList.ItemCount > 0)
|
||||||
@@ -81,7 +75,7 @@ namespace Ryujinx.Ava.UI.Views.Settings
|
|||||||
foreach (string path in new List<string>(AutoloadDirsList.SelectedItems.Cast<string>()))
|
foreach (string path in new List<string>(AutoloadDirsList.SelectedItems.Cast<string>()))
|
||||||
{
|
{
|
||||||
ViewModel.AutoloadDirectories.Remove(path);
|
ViewModel.AutoloadDirectories.Remove(path);
|
||||||
ViewModel.AutoloadDirectoryChanged = true;
|
ViewModel.GameListNeedsRefresh = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (AutoloadDirsList.ItemCount > 0)
|
if (AutoloadDirsList.ItemCount > 0)
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
{
|
{
|
||||||
InputPage.InputView?.SaveCurrentProfile();
|
InputPage.InputView?.SaveCurrentProfile();
|
||||||
|
|
||||||
if (Owner is MainWindow window && (ViewModel.GameDirectoryChanged || ViewModel.AutoloadDirectoryChanged))
|
if (Owner is MainWindow window && ViewModel.GameListNeedsRefresh)
|
||||||
{
|
{
|
||||||
window.LoadApplications();
|
window.LoadApplications();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ using LibHac.Tools.FsSystem;
|
|||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.Utilities.Compat;
|
using Ryujinx.Ava.Utilities.Compat;
|
||||||
|
using Ryujinx.Ava.Utilities.PlayReport;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||||
@@ -35,9 +36,14 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
|
|||||||
{
|
{
|
||||||
_id = value;
|
_id = value;
|
||||||
|
|
||||||
Compatibility = CompatibilityCsv.Find(Id);
|
Compatibility = CompatibilityCsv.Find(value);
|
||||||
|
RichPresenceSpec = PlayReports.Analyzer.TryGetSpec(IdString, out GameSpec gameSpec)
|
||||||
|
? gameSpec
|
||||||
|
: default(Optional<GameSpec>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public Optional<GameSpec> RichPresenceSpec { get; set; }
|
||||||
|
|
||||||
public string Developer { get; set; } = "Unknown";
|
public string Developer { get; set; } = "Unknown";
|
||||||
public string Version { get; set; } = "0";
|
public string Version { get; set; } = "0";
|
||||||
public int PlayerCount { get; set; }
|
public int PlayerCount { get; set; }
|
||||||
@@ -46,7 +52,7 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
|
|||||||
public bool HasLdnGames => PlayerCount != 0 && GameCount != 0;
|
public bool HasLdnGames => PlayerCount != 0 && GameCount != 0;
|
||||||
|
|
||||||
public bool HasRichPresenceAsset => DiscordIntegrationModule.HasAssetImage(IdString);
|
public bool HasRichPresenceAsset => DiscordIntegrationModule.HasAssetImage(IdString);
|
||||||
public bool HasDynamicRichPresenceSupport => DiscordIntegrationModule.HasAnalyzer(IdString);
|
public bool HasDynamicRichPresenceSupport => RichPresenceSpec.HasValue;
|
||||||
|
|
||||||
public TimeSpan TimePlayed { get; set; }
|
public TimeSpan TimePlayed { get; set; }
|
||||||
public DateTime? LastPlayed { get; set; }
|
public DateTime? LastPlayed { get; set; }
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
public bool ShowConfirmExit { get; set; }
|
public bool ShowConfirmExit { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ignore "Applet" dialog
|
/// Ignore Controller Applet dialog
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IgnoreApplet { get; set; }
|
public bool IgnoreApplet { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
System.Region.Value = cff.SystemRegion;
|
System.Region.Value = cff.SystemRegion;
|
||||||
System.TimeZone.Value = cff.SystemTimeZone;
|
System.TimeZone.Value = cff.SystemTimeZone;
|
||||||
System.SystemTimeOffset.Value = cff.SystemTimeOffset;
|
System.SystemTimeOffset.Value = cff.SystemTimeOffset;
|
||||||
|
System.MatchSystemTime.Value = cff.MatchSystemTime;
|
||||||
System.EnableDockedMode.Value = cff.DockedMode;
|
System.EnableDockedMode.Value = cff.DockedMode;
|
||||||
System.EnablePtc.Value = cff.EnablePtc;
|
System.EnablePtc.Value = cff.EnablePtc;
|
||||||
System.EnableLowPowerPtc.Value = cff.EnableLowPowerPtc;
|
System.EnableLowPowerPtc.Value = cff.EnableLowPowerPtc;
|
||||||
@@ -99,7 +100,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
System.MemoryManagerMode.Value = cff.MemoryManagerMode;
|
System.MemoryManagerMode.Value = cff.MemoryManagerMode;
|
||||||
System.DramSize.Value = cff.DramSize;
|
System.DramSize.Value = cff.DramSize;
|
||||||
System.IgnoreMissingServices.Value = cff.IgnoreMissingServices;
|
System.IgnoreMissingServices.Value = cff.IgnoreMissingServices;
|
||||||
System.IgnoreApplet.Value = cff.IgnoreApplet;
|
System.IgnoreControllerApplet.Value = cff.IgnoreApplet;
|
||||||
System.UseHypervisor.Value = cff.UseHypervisor;
|
System.UseHypervisor.Value = cff.UseHypervisor;
|
||||||
|
|
||||||
UI.GuiColumns.FavColumn.Value = cff.GuiColumns.FavColumn;
|
UI.GuiColumns.FavColumn.Value = cff.GuiColumns.FavColumn;
|
||||||
|
|||||||
@@ -383,7 +383,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ignore Controller Applet
|
/// Ignore Controller Applet
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ReactiveObject<bool> IgnoreApplet { get; private set; }
|
public ReactiveObject<bool> IgnoreControllerApplet { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Uses Hypervisor over JIT if available
|
/// Uses Hypervisor over JIT if available
|
||||||
@@ -424,8 +424,8 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
DramSize.LogChangesToValue(nameof(DramSize));
|
DramSize.LogChangesToValue(nameof(DramSize));
|
||||||
IgnoreMissingServices = new ReactiveObject<bool>();
|
IgnoreMissingServices = new ReactiveObject<bool>();
|
||||||
IgnoreMissingServices.LogChangesToValue(nameof(IgnoreMissingServices));
|
IgnoreMissingServices.LogChangesToValue(nameof(IgnoreMissingServices));
|
||||||
IgnoreApplet = new ReactiveObject<bool>();
|
IgnoreControllerApplet = new ReactiveObject<bool>();
|
||||||
IgnoreApplet.LogChangesToValue(nameof(IgnoreApplet));
|
IgnoreControllerApplet.LogChangesToValue(nameof(IgnoreControllerApplet));
|
||||||
AudioVolume = new ReactiveObject<float>();
|
AudioVolume = new ReactiveObject<float>();
|
||||||
AudioVolume.LogChangesToValue(nameof(AudioVolume));
|
AudioVolume.LogChangesToValue(nameof(AudioVolume));
|
||||||
UseHypervisor = new ReactiveObject<bool>();
|
UseHypervisor = new ReactiveObject<bool>();
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
SystemRegion = System.Region,
|
SystemRegion = System.Region,
|
||||||
SystemTimeZone = System.TimeZone,
|
SystemTimeZone = System.TimeZone,
|
||||||
SystemTimeOffset = System.SystemTimeOffset,
|
SystemTimeOffset = System.SystemTimeOffset,
|
||||||
|
MatchSystemTime = System.MatchSystemTime,
|
||||||
DockedMode = System.EnableDockedMode,
|
DockedMode = System.EnableDockedMode,
|
||||||
EnableDiscordIntegration = EnableDiscordIntegration,
|
EnableDiscordIntegration = EnableDiscordIntegration,
|
||||||
CheckUpdatesOnStart = CheckUpdatesOnStart,
|
CheckUpdatesOnStart = CheckUpdatesOnStart,
|
||||||
@@ -80,7 +81,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
MemoryManagerMode = System.MemoryManagerMode,
|
MemoryManagerMode = System.MemoryManagerMode,
|
||||||
DramSize = System.DramSize,
|
DramSize = System.DramSize,
|
||||||
IgnoreMissingServices = System.IgnoreMissingServices,
|
IgnoreMissingServices = System.IgnoreMissingServices,
|
||||||
IgnoreApplet = System.IgnoreApplet,
|
IgnoreApplet = System.IgnoreControllerApplet,
|
||||||
UseHypervisor = System.UseHypervisor,
|
UseHypervisor = System.UseHypervisor,
|
||||||
GuiColumns = new GuiColumns
|
GuiColumns = new GuiColumns
|
||||||
{
|
{
|
||||||
@@ -204,7 +205,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
System.MemoryManagerMode.Value = MemoryManagerMode.HostMappedUnsafe;
|
System.MemoryManagerMode.Value = MemoryManagerMode.HostMappedUnsafe;
|
||||||
System.DramSize.Value = MemoryConfiguration.MemoryConfiguration4GiB;
|
System.DramSize.Value = MemoryConfiguration.MemoryConfiguration4GiB;
|
||||||
System.IgnoreMissingServices.Value = false;
|
System.IgnoreMissingServices.Value = false;
|
||||||
System.IgnoreApplet.Value = false;
|
System.IgnoreControllerApplet.Value = false;
|
||||||
System.UseHypervisor.Value = true;
|
System.UseHypervisor.Value = true;
|
||||||
Multiplayer.LanInterfaceId.Value = "0";
|
Multiplayer.LanInterfaceId.Value = "0";
|
||||||
Multiplayer.Mode.Value = MultiplayerMode.Disabled;
|
Multiplayer.Mode.Value = MultiplayerMode.Disabled;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Gommon;
|
using Gommon;
|
||||||
using Ryujinx.Ava.Utilities.AppLibrary;
|
using Ryujinx.Ava.Utilities.AppLibrary;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
@@ -19,6 +20,11 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
|||||||
|
|
||||||
public IReadOnlyList<GameSpec> Specs => new ReadOnlyCollection<GameSpec>(_specs);
|
public IReadOnlyList<GameSpec> Specs => new ReadOnlyCollection<GameSpec>(_specs);
|
||||||
|
|
||||||
|
public GameSpec GetSpec(string titleId) => _specs.First(x => x.TitleIds.ContainsIgnoreCase(titleId));
|
||||||
|
|
||||||
|
public bool TryGetSpec(string titleId, out GameSpec gameSpec)
|
||||||
|
=> (gameSpec = _specs.FirstOrDefault(x => x.TitleIds.ContainsIgnoreCase(titleId))) != null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add an analysis spec matching a specific game by title ID, with the provided spec configuration.
|
/// Add an analysis spec matching a specific game by title ID, with the provided spec configuration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -27,10 +33,12 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
|||||||
/// <returns>The current <see cref="Analyzer"/>, for chaining convenience.</returns>
|
/// <returns>The current <see cref="Analyzer"/>, for chaining convenience.</returns>
|
||||||
public Analyzer AddSpec(string titleId, Func<GameSpec, GameSpec> transform)
|
public Analyzer AddSpec(string titleId, Func<GameSpec, GameSpec> transform)
|
||||||
{
|
{
|
||||||
Guard.Ensure(ulong.TryParse(titleId, NumberStyles.HexNumber, null, out _),
|
if (ulong.TryParse(titleId, NumberStyles.HexNumber, null, out _))
|
||||||
$"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}.");
|
return AddSpec(transform(GameSpec.Create(titleId)));
|
||||||
|
|
||||||
return AddSpec(transform(GameSpec.Create(titleId)));
|
Logger.Notice.PrintMsg(LogClass.Application,
|
||||||
|
$"Tried to add a {nameof(GameSpec)} with a non-hexadecimal title ID value. Input: '{titleId}'");
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -41,10 +49,12 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
|||||||
/// <returns>The current <see cref="Analyzer"/>, for chaining convenience.</returns>
|
/// <returns>The current <see cref="Analyzer"/>, for chaining convenience.</returns>
|
||||||
public Analyzer AddSpec(string titleId, Action<GameSpec> transform)
|
public Analyzer AddSpec(string titleId, Action<GameSpec> transform)
|
||||||
{
|
{
|
||||||
Guard.Ensure(ulong.TryParse(titleId, NumberStyles.HexNumber, null, out _),
|
if (ulong.TryParse(titleId, NumberStyles.HexNumber, null, out _))
|
||||||
$"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}.");
|
return AddSpec(GameSpec.Create(titleId).Apply(transform));
|
||||||
|
|
||||||
return AddSpec(GameSpec.Create(titleId).Apply(transform));
|
Logger.Notice.PrintMsg(LogClass.Application,
|
||||||
|
$"Tried to add a {nameof(GameSpec)} with a non-hexadecimal title ID value. Input: '{titleId}'");
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -57,10 +67,19 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
|||||||
Func<GameSpec, GameSpec> transform)
|
Func<GameSpec, GameSpec> transform)
|
||||||
{
|
{
|
||||||
string[] tids = titleIds.ToArray();
|
string[] tids = titleIds.ToArray();
|
||||||
Guard.Ensure(tids.All(x => ulong.TryParse(x, NumberStyles.HexNumber, null, out _)),
|
if (tids.All(x => ulong.TryParse(x, NumberStyles.HexNumber, null, out _) && !string.IsNullOrEmpty(x)))
|
||||||
$"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}.");
|
return AddSpec(transform(GameSpec.Create(tids)));
|
||||||
|
|
||||||
return AddSpec(transform(GameSpec.Create(tids)));
|
Logger.Notice.PrintMsg(LogClass.Application,
|
||||||
|
$"Tried to add a {nameof(GameSpec)} with a non-hexadecimal title ID value. Input: '{
|
||||||
|
tids.FormatCollection(
|
||||||
|
x => x,
|
||||||
|
separator: ", ",
|
||||||
|
prefix: "[",
|
||||||
|
suffix: "]"
|
||||||
|
)
|
||||||
|
}'");
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -72,10 +91,19 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
|||||||
public Analyzer AddSpec(IEnumerable<string> titleIds, Action<GameSpec> transform)
|
public Analyzer AddSpec(IEnumerable<string> titleIds, Action<GameSpec> transform)
|
||||||
{
|
{
|
||||||
string[] tids = titleIds.ToArray();
|
string[] tids = titleIds.ToArray();
|
||||||
Guard.Ensure(tids.All(x => ulong.TryParse(x, NumberStyles.HexNumber, null, out _)),
|
if (tids.All(x => ulong.TryParse(x, NumberStyles.HexNumber, null, out _) && !string.IsNullOrEmpty(x)))
|
||||||
$"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}.");
|
return AddSpec(GameSpec.Create(tids).Apply(transform));
|
||||||
|
|
||||||
return AddSpec(GameSpec.Create(tids).Apply(transform));
|
Logger.Notice.PrintMsg(LogClass.Application,
|
||||||
|
$"Tried to add a {nameof(GameSpec)} with a non-hexadecimal title ID value. Input: '{
|
||||||
|
tids.FormatCollection(
|
||||||
|
x => x,
|
||||||
|
separator: ", ",
|
||||||
|
prefix: "[",
|
||||||
|
suffix: "]"
|
||||||
|
)
|
||||||
|
}'");
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -106,12 +134,12 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
|||||||
if (!playReport.ReportData.IsDictionary)
|
if (!playReport.ReportData.IsDictionary)
|
||||||
return FormattedValue.Unhandled;
|
return FormattedValue.Unhandled;
|
||||||
|
|
||||||
if (!_specs.TryGetFirst(s => runningGameId.EqualsAnyIgnoreCase(s.TitleIds), out GameSpec spec))
|
if (!TryGetSpec(runningGameId, out GameSpec spec))
|
||||||
return FormattedValue.Unhandled;
|
return FormattedValue.Unhandled;
|
||||||
|
|
||||||
foreach (FormatterSpecBase formatSpec in spec.ValueFormatters.OrderBy(x => x.Priority))
|
foreach (FormatterSpecBase formatSpec in spec.ValueFormatters.OrderBy(x => x.Priority))
|
||||||
{
|
{
|
||||||
if (!formatSpec.Format(appMeta, playReport, out FormattedValue value))
|
if (!formatSpec.TryFormat(appMeta, playReport, out FormattedValue value))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Gommon;
|
using Gommon;
|
||||||
|
using Humanizer;
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -19,6 +20,9 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
|||||||
_ => "Roaming Hyrule"
|
_ => "Roaming Hyrule"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static FormattedValue SkywardSwordHD_Rupees(SingleValue value)
|
||||||
|
=> "rupee".ToQuantity(value.Matched.IntValue);
|
||||||
|
|
||||||
private static FormattedValue SuperMarioOdyssey_AssistMode(SingleValue value)
|
private static FormattedValue SuperMarioOdyssey_AssistMode(SingleValue value)
|
||||||
=> value.Matched.BoxedValue is 1 ? "Playing in Assist Mode" : "Playing in Regular Mode";
|
=> value.Matched.BoxedValue is 1 ? "Playing in Assist Mode" : "Playing in Regular Mode";
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,22 @@
|
|||||||
namespace Ryujinx.Ava.Utilities.PlayReport
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Utilities.PlayReport
|
||||||
{
|
{
|
||||||
public static partial class PlayReports
|
public static partial class PlayReports
|
||||||
{
|
{
|
||||||
public static Analyzer Analyzer { get; } = new Analyzer()
|
public static void Initialize()
|
||||||
|
{
|
||||||
|
// init lazy value
|
||||||
|
_ = Analyzer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Analyzer Analyzer => _analyzerLazy.Value;
|
||||||
|
|
||||||
|
private static readonly Lazy<Analyzer> _analyzerLazy = new(() => new Analyzer()
|
||||||
.AddSpec(
|
.AddSpec(
|
||||||
"01007ef00011e000",
|
"01007ef00011e000",
|
||||||
spec => spec
|
spec => spec
|
||||||
|
.WithDescription("based on being in Master Mode.")
|
||||||
.AddValueFormatter("IsHardMode", BreathOfTheWild_MasterMode)
|
.AddValueFormatter("IsHardMode", BreathOfTheWild_MasterMode)
|
||||||
// reset to normal status when switching between normal & master mode in title screen
|
// reset to normal status when switching between normal & master mode in title screen
|
||||||
.AddValueFormatter("AoCVer", FormattedValue.SingleAlwaysResets)
|
.AddValueFormatter("AoCVer", FormattedValue.SingleAlwaysResets)
|
||||||
@@ -13,34 +24,49 @@
|
|||||||
.AddSpec(
|
.AddSpec(
|
||||||
"0100f2c0115b6000",
|
"0100f2c0115b6000",
|
||||||
spec => spec
|
spec => spec
|
||||||
|
.WithDescription("based on where you are in Hyrule (Depths, Surface, Sky).")
|
||||||
.AddValueFormatter("PlayerPosY", TearsOfTheKingdom_CurrentField))
|
.AddValueFormatter("PlayerPosY", TearsOfTheKingdom_CurrentField))
|
||||||
|
.AddSpec(
|
||||||
|
"01002da013484000",
|
||||||
|
spec => spec
|
||||||
|
.WithDescription("based on how many Rupees you have.")
|
||||||
|
.AddValueFormatter("rupees", SkywardSwordHD_Rupees))
|
||||||
.AddSpec(
|
.AddSpec(
|
||||||
"0100000000010000",
|
"0100000000010000",
|
||||||
spec =>
|
spec => spec
|
||||||
spec.AddValueFormatter("is_kids_mode", SuperMarioOdyssey_AssistMode)
|
.WithDescription("based on if you're playing with Assist Mode.")
|
||||||
|
.AddValueFormatter("is_kids_mode", SuperMarioOdyssey_AssistMode)
|
||||||
)
|
)
|
||||||
.AddSpec(
|
.AddSpec(
|
||||||
"010075000ecbe000",
|
"010075000ecbe000",
|
||||||
spec =>
|
spec => spec
|
||||||
spec.AddValueFormatter("is_kids_mode", SuperMarioOdysseyChina_AssistMode)
|
.WithDescription("based on if you're playing with Assist Mode.")
|
||||||
|
.AddValueFormatter("is_kids_mode", SuperMarioOdysseyChina_AssistMode)
|
||||||
)
|
)
|
||||||
.AddSpec(
|
.AddSpec(
|
||||||
"010028600ebda000",
|
"010028600ebda000",
|
||||||
spec => spec.AddValueFormatter("mode", SuperMario3DWorldOrBowsersFury)
|
spec => spec
|
||||||
|
.WithDescription("based on being in either Super Mario 3D World or Bowser's Fury.")
|
||||||
|
.AddValueFormatter("mode", SuperMario3DWorldOrBowsersFury)
|
||||||
)
|
)
|
||||||
.AddSpec( // Global & China IDs
|
.AddSpec( // Global & China IDs
|
||||||
["0100152000022000", "010075100e8ec000"],
|
["0100152000022000", "010075100e8ec000"],
|
||||||
spec => spec.AddValueFormatter("To", MarioKart8Deluxe_Mode)
|
spec => spec
|
||||||
|
.WithDescription(
|
||||||
|
"based on what modes you're selecting in the menu & whether or not you're in a race.")
|
||||||
|
.AddValueFormatter("To", MarioKart8Deluxe_Mode)
|
||||||
)
|
)
|
||||||
.AddSpec(
|
.AddSpec(
|
||||||
["0100a3d008c5c000", "01008f6008c5e000"],
|
["0100a3d008c5c000", "01008f6008c5e000"],
|
||||||
spec => spec
|
spec => spec
|
||||||
|
.WithDescription("based on what area of Paldea you're exploring.")
|
||||||
.AddValueFormatter("area_no", PokemonSVArea)
|
.AddValueFormatter("area_no", PokemonSVArea)
|
||||||
.AddValueFormatter("team_circle", PokemonSVUnionCircle)
|
.AddValueFormatter("team_circle", PokemonSVUnionCircle)
|
||||||
)
|
)
|
||||||
.AddSpec(
|
.AddSpec(
|
||||||
"01006a800016e000",
|
"01006a800016e000",
|
||||||
spec => spec
|
spec => spec
|
||||||
|
.WithDescription("based on what mode you're playing, who won, and what characters were present.")
|
||||||
.AddSparseMultiValueFormatter(
|
.AddSparseMultiValueFormatter(
|
||||||
[
|
[
|
||||||
// Metadata to figure out what PlayReport we have.
|
// Metadata to figure out what PlayReport we have.
|
||||||
@@ -59,9 +85,14 @@
|
|||||||
.AddSpec(
|
.AddSpec(
|
||||||
[
|
[
|
||||||
"0100c9a00ece6000", "01008d300c50c000", "0100d870045b6000",
|
"0100c9a00ece6000", "01008d300c50c000", "0100d870045b6000",
|
||||||
"010012f017576000", "0100c62011050000", "0100b3c014bda000"],
|
"010012f017576000", "0100c62011050000", "0100b3c014bda000"
|
||||||
spec => spec.AddValueFormatter("launch_title_id", NsoEmulator_LaunchedGame)
|
],
|
||||||
);
|
spec => spec
|
||||||
|
.WithDescription(
|
||||||
|
"based on what game you first launch.\n\nNSO emulators do not print any Play Report information past the first game launch so it's all we got.")
|
||||||
|
.AddValueFormatter("launch_title_id", NsoEmulator_LaunchedGame)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
private static string Playing(string game) => $"Playing {game}";
|
private static string Playing(string game) => $"Playing {game}";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,20 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
|||||||
|
|
||||||
public required string[] TitleIds { get; init; }
|
public required string[] TitleIds { get; init; }
|
||||||
|
|
||||||
|
public const string DefaultDescription = "Formats the details on your Discord presence based on logged data from the game.";
|
||||||
|
|
||||||
|
private string _valueDescription;
|
||||||
|
|
||||||
|
public string Description => _valueDescription ?? DefaultDescription;
|
||||||
|
|
||||||
|
public GameSpec WithDescription(string description)
|
||||||
|
{
|
||||||
|
_valueDescription = description != null
|
||||||
|
? $"Formats the details on your Discord presence {description}"
|
||||||
|
: null;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public List<FormatterSpecBase> ValueFormatters { get; } = [];
|
public List<FormatterSpecBase> ValueFormatters { get; } = [];
|
||||||
|
|
||||||
|
|
||||||
@@ -197,7 +211,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
|||||||
public string[] ReportKeys { get; init; }
|
public string[] ReportKeys { get; init; }
|
||||||
public Delegate Formatter { get; init; }
|
public Delegate Formatter { get; init; }
|
||||||
|
|
||||||
public bool Format(ApplicationMetadata appMeta, Horizon.Prepo.Types.PlayReport playReport,
|
public bool TryFormat(ApplicationMetadata appMeta, Horizon.Prepo.Types.PlayReport playReport,
|
||||||
out FormattedValue formattedValue)
|
out FormattedValue formattedValue)
|
||||||
{
|
{
|
||||||
formattedValue = default;
|
formattedValue = default;
|
||||||
|
|||||||
Reference in New Issue
Block a user