Compare commits

..

7 Commits

Author SHA1 Message Date
a6dbb2ad2b replace ByteMemoryPool usage in Ryujinx.HLE (#6953) 2024-07-15 19:21:53 -03:00
595e514f18 Use SkiaSharp for Avalonia in place of ImageSharp (#6269)
* Rebased

Transformation all at once

Use SkiaSharp instead of ImageSharp

* Apply suggestions from code review

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Change back unintentionally changed comment

---------

Co-authored-by: Ac_K <Acoustik666@gmail.com>
Co-authored-by: Emmanuel Hansen <emmausssss@gmail.com>
2024-07-14 08:16:14 +00:00
07435ad844 Use draw clear on Adreno, instead of vkCmdClearAttachments (#7013)
* Use draw clear on Adreno, instead of vkCmdClearAttachments

* Fix GTX TITAN detection
2024-07-10 17:52:45 -03:00
1668ba913f Force dynamic state update after rasterizer discard disable (#7007) 2024-07-09 23:31:01 -03:00
a830eb666b Disallow concurrent fence waits on Adreno (#7001)
* Disallow concurrent fence waits on Adreno

* Ensure locks are released if exceptions are thrown
2024-07-07 19:33:28 -03:00
cfc75d7e78 Disable descriptor set template updates for buffer textures on Adreno (#7002)
* Do not use template updates for buffer textures and buffer images

* No need to do it for images

* Simply buffer texture existence check

* Pipeline is now unused on DescriptorSetUpdater
2024-07-07 19:19:55 -03:00
c525d7d9a9 Force Vulkan swapchain re-creation when window size changes (#7003) 2024-07-07 19:02:11 -03:00
20 changed files with 329 additions and 131 deletions

View File

@ -29,7 +29,14 @@ namespace Ryujinx.Graphics.Vulkan
lock (queueLock) lock (queueLock)
{ {
_pool = new CommandBufferPool(_gd.Api, _device, queue, queueLock, _gd.QueueFamilyIndex, isLight: true); _pool = new CommandBufferPool(
_gd.Api,
_device,
queue,
queueLock,
_gd.QueueFamilyIndex,
_gd.IsQualcommProprietary,
isLight: true);
} }
} }

View File

@ -18,6 +18,7 @@ namespace Ryujinx.Graphics.Vulkan
private readonly Device _device; private readonly Device _device;
private readonly Queue _queue; private readonly Queue _queue;
private readonly object _queueLock; private readonly object _queueLock;
private readonly bool _concurrentFenceWaitUnsupported;
private readonly CommandPool _pool; private readonly CommandPool _pool;
private readonly Thread _owner; private readonly Thread _owner;
@ -61,12 +62,20 @@ namespace Ryujinx.Graphics.Vulkan
private int _queuedCount; private int _queuedCount;
private int _inUseCount; private int _inUseCount;
public unsafe CommandBufferPool(Vk api, Device device, Queue queue, object queueLock, uint queueFamilyIndex, bool isLight = false) public unsafe CommandBufferPool(
Vk api,
Device device,
Queue queue,
object queueLock,
uint queueFamilyIndex,
bool concurrentFenceWaitUnsupported,
bool isLight = false)
{ {
_api = api; _api = api;
_device = device; _device = device;
_queue = queue; _queue = queue;
_queueLock = queueLock; _queueLock = queueLock;
_concurrentFenceWaitUnsupported = concurrentFenceWaitUnsupported;
_owner = Thread.CurrentThread; _owner = Thread.CurrentThread;
var commandPoolCreateInfo = new CommandPoolCreateInfo var commandPoolCreateInfo = new CommandPoolCreateInfo
@ -357,7 +366,7 @@ namespace Ryujinx.Graphics.Vulkan
if (refreshFence) if (refreshFence)
{ {
entry.Fence = new FenceHolder(_api, _device); entry.Fence = new FenceHolder(_api, _device, _concurrentFenceWaitUnsupported);
} }
else else
{ {

View File

@ -73,7 +73,6 @@ namespace Ryujinx.Graphics.Vulkan
private readonly VulkanRenderer _gd; private readonly VulkanRenderer _gd;
private readonly Device _device; private readonly Device _device;
private readonly PipelineBase _pipeline;
private ShaderCollection _program; private ShaderCollection _program;
private readonly BufferRef[] _uniformBufferRefs; private readonly BufferRef[] _uniformBufferRefs;
@ -125,11 +124,10 @@ namespace Ryujinx.Graphics.Vulkan
private readonly TextureView _dummyTexture; private readonly TextureView _dummyTexture;
private readonly SamplerHolder _dummySampler; private readonly SamplerHolder _dummySampler;
public DescriptorSetUpdater(VulkanRenderer gd, Device device, PipelineBase pipeline) public DescriptorSetUpdater(VulkanRenderer gd, Device device)
{ {
_gd = gd; _gd = gd;
_device = device; _device = device;
_pipeline = pipeline;
// Some of the bindings counts needs to be multiplied by 2 because we have buffer and // Some of the bindings counts needs to be multiplied by 2 because we have buffer and
// regular textures/images interleaved on the same descriptor set. // regular textures/images interleaved on the same descriptor set.
@ -684,7 +682,14 @@ namespace Ryujinx.Graphics.Vulkan
if (_dirty.HasFlag(DirtyFlags.Texture)) if (_dirty.HasFlag(DirtyFlags.Texture))
{ {
UpdateAndBind(cbs, program, PipelineBase.TextureSetIndex, pbp); if (program.UpdateTexturesWithoutTemplate)
{
UpdateAndBindTexturesWithoutTemplate(cbs, program, pbp);
}
else
{
UpdateAndBind(cbs, program, PipelineBase.TextureSetIndex, pbp);
}
} }
if (_dirty.HasFlag(DirtyFlags.Image)) if (_dirty.HasFlag(DirtyFlags.Image))
@ -918,31 +923,84 @@ namespace Ryujinx.Graphics.Vulkan
_gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, _program.PipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan<uint>.Empty); _gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, _program.PipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan<uint>.Empty);
} }
private unsafe void UpdateBuffers( private void UpdateAndBindTexturesWithoutTemplate(CommandBufferScoped cbs, ShaderCollection program, PipelineBindPoint pbp)
CommandBufferScoped cbs,
PipelineBindPoint pbp,
int baseBinding,
ReadOnlySpan<DescriptorBufferInfo> bufferInfo,
DescriptorType type)
{ {
if (bufferInfo.Length == 0) int setIndex = PipelineBase.TextureSetIndex;
var bindingSegments = program.BindingSegments[setIndex];
if (bindingSegments.Length == 0)
{ {
return; return;
} }
fixed (DescriptorBufferInfo* pBufferInfo = bufferInfo) if (_updateDescriptorCacheCbIndex)
{ {
var writeDescriptorSet = new WriteDescriptorSet _updateDescriptorCacheCbIndex = false;
{ program.UpdateDescriptorCacheCommandBufferIndex(cbs.CommandBufferIndex);
SType = StructureType.WriteDescriptorSet,
DstBinding = (uint)baseBinding,
DescriptorType = type,
DescriptorCount = (uint)bufferInfo.Length,
PBufferInfo = pBufferInfo,
};
_gd.PushDescriptorApi.CmdPushDescriptorSet(cbs.CommandBuffer, pbp, _program.PipelineLayout, 0, 1, &writeDescriptorSet);
} }
var dsc = program.GetNewDescriptorSetCollection(setIndex, out _).Get(cbs);
foreach (ResourceBindingSegment segment in bindingSegments)
{
int binding = segment.Binding;
int count = segment.Count;
if (!segment.IsArray)
{
if (segment.Type != ResourceType.BufferTexture)
{
Span<DescriptorImageInfo> textures = _textures;
for (int i = 0; i < count; i++)
{
ref var texture = ref textures[i];
ref var refs = ref _textureRefs[binding + i];
texture.ImageView = refs.View?.Get(cbs).Value ?? default;
texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default;
if (texture.ImageView.Handle == 0)
{
texture.ImageView = _dummyTexture.GetImageView().Get(cbs).Value;
}
if (texture.Sampler.Handle == 0)
{
texture.Sampler = _dummySampler.GetSampler().Get(cbs).Value;
}
}
dsc.UpdateImages(0, binding, textures[..count], DescriptorType.CombinedImageSampler);
}
else
{
Span<BufferView> bufferTextures = _bufferTextures;
for (int i = 0; i < count; i++)
{
bufferTextures[i] = _bufferTextureRefs[binding + i]?.GetBufferView(cbs, false) ?? default;
}
dsc.UpdateBufferImages(0, binding, bufferTextures[..count], DescriptorType.UniformTexelBuffer);
}
}
else
{
if (segment.Type != ResourceType.BufferTexture)
{
dsc.UpdateImages(0, binding, _textureArrayRefs[binding].Array.GetImageInfos(_gd, cbs, _dummyTexture, _dummySampler), DescriptorType.CombinedImageSampler);
}
else
{
dsc.UpdateBufferImages(0, binding, _textureArrayRefs[binding].Array.GetBufferViews(cbs), DescriptorType.UniformTexelBuffer);
}
}
}
var sets = dsc.GetSets();
_gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, _program.PipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan<uint>.Empty);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]

View File

@ -10,12 +10,15 @@ namespace Ryujinx.Graphics.Vulkan
private readonly Device _device; private readonly Device _device;
private Fence _fence; private Fence _fence;
private int _referenceCount; private int _referenceCount;
private int _lock;
private readonly bool _concurrentWaitUnsupported;
private bool _disposed; private bool _disposed;
public unsafe FenceHolder(Vk api, Device device) public unsafe FenceHolder(Vk api, Device device, bool concurrentWaitUnsupported)
{ {
_api = api; _api = api;
_device = device; _device = device;
_concurrentWaitUnsupported = concurrentWaitUnsupported;
var fenceCreateInfo = new FenceCreateInfo var fenceCreateInfo = new FenceCreateInfo
{ {
@ -47,6 +50,11 @@ namespace Ryujinx.Graphics.Vulkan
} }
while (Interlocked.CompareExchange(ref _referenceCount, lastValue + 1, lastValue) != lastValue); while (Interlocked.CompareExchange(ref _referenceCount, lastValue + 1, lastValue) != lastValue);
if (_concurrentWaitUnsupported)
{
AcquireLock();
}
fence = _fence; fence = _fence;
return true; return true;
} }
@ -57,6 +65,16 @@ namespace Ryujinx.Graphics.Vulkan
return _fence; return _fence;
} }
public void PutLock()
{
Put();
if (_concurrentWaitUnsupported)
{
ReleaseLock();
}
}
public void Put() public void Put()
{ {
if (Interlocked.Decrement(ref _referenceCount) == 0) if (Interlocked.Decrement(ref _referenceCount) == 0)
@ -66,24 +84,67 @@ namespace Ryujinx.Graphics.Vulkan
} }
} }
private void AcquireLock()
{
while (!TryAcquireLock())
{
Thread.SpinWait(32);
}
}
private bool TryAcquireLock()
{
return Interlocked.Exchange(ref _lock, 1) == 0;
}
private void ReleaseLock()
{
Interlocked.Exchange(ref _lock, 0);
}
public void Wait() public void Wait()
{ {
Span<Fence> fences = stackalloc Fence[] if (_concurrentWaitUnsupported)
{ {
_fence, AcquireLock();
};
FenceHelper.WaitAllIndefinitely(_api, _device, fences); try
{
FenceHelper.WaitAllIndefinitely(_api, _device, stackalloc Fence[] { _fence });
}
finally
{
ReleaseLock();
}
}
else
{
FenceHelper.WaitAllIndefinitely(_api, _device, stackalloc Fence[] { _fence });
}
} }
public bool IsSignaled() public bool IsSignaled()
{ {
Span<Fence> fences = stackalloc Fence[] if (_concurrentWaitUnsupported)
{ {
_fence, if (!TryAcquireLock())
}; {
return false;
}
return FenceHelper.AllSignaled(_api, _device, fences); try
{
return FenceHelper.AllSignaled(_api, _device, stackalloc Fence[] { _fence });
}
finally
{
ReleaseLock();
}
}
else
{
return FenceHelper.AllSignaled(_api, _device, stackalloc Fence[] { _fence });
}
} }
public void Dispose() public void Dispose()

View File

@ -196,18 +196,23 @@ namespace Ryujinx.Graphics.Vulkan
bool signaled = true; bool signaled = true;
if (hasTimeout) try
{ {
signaled = FenceHelper.AllSignaled(api, device, fences[..fenceCount], timeout); if (hasTimeout)
{
signaled = FenceHelper.AllSignaled(api, device, fences[..fenceCount], timeout);
}
else
{
FenceHelper.WaitAllIndefinitely(api, device, fences[..fenceCount]);
}
} }
else finally
{ {
FenceHelper.WaitAllIndefinitely(api, device, fences[..fenceCount]); for (int i = 0; i < fenceCount; i++)
} {
fenceHolders[i].PutLock();
for (int i = 0; i < fenceCount; i++) }
{
fenceHolders[i].Put();
} }
return signaled; return signaled;

View File

@ -105,7 +105,7 @@ namespace Ryujinx.Graphics.Vulkan
gd.Api.CreatePipelineCache(device, pipelineCacheCreateInfo, null, out PipelineCache).ThrowOnError(); gd.Api.CreatePipelineCache(device, pipelineCacheCreateInfo, null, out PipelineCache).ThrowOnError();
_descriptorSetUpdater = new DescriptorSetUpdater(gd, device, this); _descriptorSetUpdater = new DescriptorSetUpdater(gd, device);
_vertexBufferUpdater = new VertexBufferUpdater(gd); _vertexBufferUpdater = new VertexBufferUpdater(gd);
_transformFeedbackBuffers = new BufferState[Constants.MaxTransformFeedbackBuffers]; _transformFeedbackBuffers = new BufferState[Constants.MaxTransformFeedbackBuffers];
@ -1020,6 +1020,13 @@ namespace Ryujinx.Graphics.Vulkan
{ {
_newState.RasterizerDiscardEnable = discard; _newState.RasterizerDiscardEnable = discard;
SignalStateChange(); SignalStateChange();
if (!discard && Gd.IsQualcommProprietary)
{
// On Adreno, enabling rasterizer discard somehow corrupts the viewport state.
// Force it to be updated on next use to work around this bug.
DynamicState.ForceAllDirty();
}
} }
public void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask) public void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask)

View File

@ -47,10 +47,11 @@ namespace Ryujinx.Graphics.Vulkan
return; return;
} }
if (componentMask != 0xf) if (componentMask != 0xf || Gd.IsQualcommProprietary)
{ {
// We can't use CmdClearAttachments if not writing all components, // We can't use CmdClearAttachments if not writing all components,
// because on Vulkan, the pipeline state does not affect clears. // because on Vulkan, the pipeline state does not affect clears.
// On proprietary Adreno drivers, CmdClearAttachments appears to execute out of order, so it's better to not use it at all.
var dstTexture = FramebufferParams.GetColorView(index); var dstTexture = FramebufferParams.GetColorView(index);
if (dstTexture == null) if (dstTexture == null)
{ {
@ -87,10 +88,11 @@ namespace Ryujinx.Graphics.Vulkan
return; return;
} }
if (stencilMask != 0 && stencilMask != 0xff) if ((stencilMask != 0 && stencilMask != 0xff) || Gd.IsQualcommProprietary)
{ {
// We can't use CmdClearAttachments if not clearing all (mask is all ones, 0xFF) or none (mask is 0) of the stencil bits, // We can't use CmdClearAttachments if not clearing all (mask is all ones, 0xFF) or none (mask is 0) of the stencil bits,
// because on Vulkan, the pipeline state does not affect clears. // because on Vulkan, the pipeline state does not affect clears.
// On proprietary Adreno drivers, CmdClearAttachments appears to execute out of order, so it's better to not use it at all.
var dstTexture = FramebufferParams.GetDepthStencilView(); var dstTexture = FramebufferParams.GetDepthStencilView();
if (dstTexture == null) if (dstTexture == null)
{ {

View File

@ -23,6 +23,8 @@ namespace Ryujinx.Graphics.Vulkan
public bool IsCompute { get; } public bool IsCompute { get; }
public bool HasTessellationControlShader => (Stages & (1u << 3)) != 0; public bool HasTessellationControlShader => (Stages & (1u << 3)) != 0;
public bool UpdateTexturesWithoutTemplate { get; }
public uint Stages { get; } public uint Stages { get; }
public ResourceBindingSegment[][] ClearSegments { get; } public ResourceBindingSegment[][] ClearSegments { get; }
@ -127,9 +129,12 @@ namespace Ryujinx.Graphics.Vulkan
Stages = stages; Stages = stages;
ClearSegments = BuildClearSegments(sets); ClearSegments = BuildClearSegments(sets);
BindingSegments = BuildBindingSegments(resourceLayout.SetUsages); BindingSegments = BuildBindingSegments(resourceLayout.SetUsages, out bool usesBufferTextures);
Templates = BuildTemplates(usePushDescriptors); Templates = BuildTemplates(usePushDescriptors);
// Updating buffer texture bindings using template updates crashes the Adreno driver on Windows.
UpdateTexturesWithoutTemplate = gd.IsQualcommProprietary && usesBufferTextures;
_compileTask = Task.CompletedTask; _compileTask = Task.CompletedTask;
_firstBackgroundUse = false; _firstBackgroundUse = false;
} }
@ -280,8 +285,10 @@ namespace Ryujinx.Graphics.Vulkan
return segments; return segments;
} }
private static ResourceBindingSegment[][] BuildBindingSegments(ReadOnlyCollection<ResourceUsageCollection> setUsages) private static ResourceBindingSegment[][] BuildBindingSegments(ReadOnlyCollection<ResourceUsageCollection> setUsages, out bool usesBufferTextures)
{ {
usesBufferTextures = false;
ResourceBindingSegment[][] segments = new ResourceBindingSegment[setUsages.Count][]; ResourceBindingSegment[][] segments = new ResourceBindingSegment[setUsages.Count][];
for (int setIndex = 0; setIndex < setUsages.Count; setIndex++) for (int setIndex = 0; setIndex < setUsages.Count; setIndex++)
@ -295,6 +302,11 @@ namespace Ryujinx.Graphics.Vulkan
{ {
ResourceUsage usage = setUsages[setIndex].Usages[index]; ResourceUsage usage = setUsages[setIndex].Usages[index];
if (usage.Type == ResourceType.BufferTexture)
{
usesBufferTextures = true;
}
if (currentUsage.Binding + currentCount != usage.Binding || if (currentUsage.Binding + currentCount != usage.Binding ||
currentUsage.Type != usage.Type || currentUsage.Type != usage.Type ||
currentUsage.Stages != usage.Stages || currentUsage.Stages != usage.Stages ||

View File

@ -87,9 +87,11 @@ namespace Ryujinx.Graphics.Vulkan
internal bool IsAmdGcn { get; private set; } internal bool IsAmdGcn { get; private set; }
internal bool IsNvidiaPreTuring { get; private set; } internal bool IsNvidiaPreTuring { get; private set; }
internal bool IsIntelArc { get; private set; } internal bool IsIntelArc { get; private set; }
internal bool IsQualcommProprietary { get; private set; }
internal bool IsMoltenVk { get; private set; } internal bool IsMoltenVk { get; private set; }
internal bool IsTBDR { get; private set; } internal bool IsTBDR { get; private set; }
internal bool IsSharedMemory { get; private set; } internal bool IsSharedMemory { get; private set; }
public string GpuVendor { get; private set; } public string GpuVendor { get; private set; }
public string GpuDriver { get; private set; } public string GpuDriver { get; private set; }
public string GpuRenderer { get; private set; } public string GpuRenderer { get; private set; }
@ -344,7 +346,7 @@ namespace Ryujinx.Graphics.Vulkan
{ {
IsNvidiaPreTuring = gpuNumber < 2000; IsNvidiaPreTuring = gpuNumber < 2000;
} }
else if (GpuDriver.Contains("TITAN") && !GpuDriver.Contains("RTX")) else if (GpuRenderer.Contains("TITAN") && !GpuRenderer.Contains("RTX"))
{ {
IsNvidiaPreTuring = true; IsNvidiaPreTuring = true;
} }
@ -354,6 +356,8 @@ namespace Ryujinx.Graphics.Vulkan
IsIntelArc = GpuRenderer.StartsWith("Intel(R) Arc(TM)"); IsIntelArc = GpuRenderer.StartsWith("Intel(R) Arc(TM)");
} }
IsQualcommProprietary = hasDriverProperties && driverProperties.DriverID == DriverId.QualcommProprietary;
ulong minResourceAlignment = Math.Max( ulong minResourceAlignment = Math.Max(
Math.Max( Math.Max(
properties.Limits.MinStorageBufferOffsetAlignment, properties.Limits.MinStorageBufferOffsetAlignment,
@ -411,7 +415,7 @@ namespace Ryujinx.Graphics.Vulkan
Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtExternalMemoryHost hostMemoryApi); Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtExternalMemoryHost hostMemoryApi);
HostMemoryAllocator = new HostMemoryAllocator(MemoryAllocator, Api, hostMemoryApi, _device); HostMemoryAllocator = new HostMemoryAllocator(MemoryAllocator, Api, hostMemoryApi, _device);
CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex); CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex, IsQualcommProprietary);
PipelineLayoutCache = new PipelineLayoutCache(); PipelineLayoutCache = new PipelineLayoutCache();
@ -688,7 +692,7 @@ namespace Ryujinx.Graphics.Vulkan
GpuVendor, GpuVendor,
memoryType: memoryType, memoryType: memoryType,
hasFrontFacingBug: IsIntelWindows, hasFrontFacingBug: IsIntelWindows,
hasVectorIndexingBug: Vendor == Vendor.Qualcomm, hasVectorIndexingBug: IsQualcommProprietary,
needsFragmentOutputSpecialization: IsMoltenVk, needsFragmentOutputSpecialization: IsMoltenVk,
reduceShaderPrecision: IsMoltenVk, reduceShaderPrecision: IsMoltenVk,
supportsAstcCompression: features2.Features.TextureCompressionAstcLdr && supportsAstcFormats, supportsAstcCompression: features2.Features.TextureCompressionAstcLdr && supportsAstcFormats,

View File

@ -623,7 +623,8 @@ namespace Ryujinx.Graphics.Vulkan
public override void SetSize(int width, int height) public override void SetSize(int width, int height)
{ {
// Not needed as we can get the size from the surface. // We don't need to use width and height as we can get the size from the surface.
_swapchainIsDirty = true;
} }
public override void ChangeVSyncMode(bool vsyncEnabled) public override void ChangeVSyncMode(bool vsyncEnabled)

View File

@ -474,9 +474,9 @@ namespace Ryujinx.HLE.HOS.Services
{ {
const int MessageSize = 0x100; const int MessageSize = 0x100;
using IMemoryOwner<byte> reqDataOwner = ByteMemoryPool.Rent(MessageSize); using SpanOwner<byte> reqDataOwner = SpanOwner<byte>.Rent(MessageSize);
Span<byte> reqDataSpan = reqDataOwner.Memory.Span; Span<byte> reqDataSpan = reqDataOwner.Span;
_selfProcess.CpuMemory.Read(_selfThread.TlsAddress, reqDataSpan); _selfProcess.CpuMemory.Read(_selfThread.TlsAddress, reqDataSpan);

View File

@ -85,9 +85,9 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
ReadOnlySpan<byte> inputParcel = context.Memory.GetSpan(dataPos, (int)dataSize); ReadOnlySpan<byte> inputParcel = context.Memory.GetSpan(dataPos, (int)dataSize);
using IMemoryOwner<byte> outputParcelOwner = ByteMemoryPool.RentCleared(replySize); using SpanOwner<byte> outputParcelOwner = SpanOwner<byte>.RentCleared(checked((int)replySize));
Span<byte> outputParcel = outputParcelOwner.Memory.Span; Span<byte> outputParcel = outputParcelOwner.Span;
ResultCode result = OnTransact(binderId, code, flags, inputParcel, outputParcel); ResultCode result = OnTransact(binderId, code, flags, inputParcel, outputParcel);

View File

@ -3,7 +3,6 @@ using Ryujinx.Common.Memory;
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types; using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
using System; using System;
using System.Buffers;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -13,7 +12,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
{ {
sealed class Parcel : IDisposable sealed class Parcel : IDisposable
{ {
private readonly IMemoryOwner<byte> _rawDataOwner; private readonly MemoryOwner<byte> _rawDataOwner;
private Span<byte> Raw => _rawDataOwner.Memory.Span; private Span<byte> Raw => _rawDataOwner.Memory.Span;
@ -30,7 +29,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
public Parcel(ReadOnlySpan<byte> data) public Parcel(ReadOnlySpan<byte> data)
{ {
_rawDataOwner = ByteMemoryPool.RentCopy(data); _rawDataOwner = MemoryOwner<byte>.RentCopy(data);
_payloadPosition = 0; _payloadPosition = 0;
_objectPosition = 0; _objectPosition = 0;
@ -40,7 +39,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
{ {
uint headerSize = (uint)Unsafe.SizeOf<ParcelHeader>(); uint headerSize = (uint)Unsafe.SizeOf<ParcelHeader>();
_rawDataOwner = ByteMemoryPool.RentCleared(BitUtils.AlignUp<uint>(headerSize + payloadSize + objectsSize, 4)); _rawDataOwner = MemoryOwner<byte>.RentCleared(checked((int)BitUtils.AlignUp<uint>(headerSize + payloadSize + objectsSize, 4)));
Header.PayloadSize = payloadSize; Header.PayloadSize = payloadSize;
Header.ObjectsSize = objectsSize; Header.ObjectsSize = objectsSize;

View File

@ -40,20 +40,17 @@ using Ryujinx.UI.Common;
using Ryujinx.UI.Common.Configuration; using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Helper; using Ryujinx.UI.Common.Helper;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
using SixLabors.ImageSharp; using SkiaSharp;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SPB.Graphics.Vulkan; using SPB.Graphics.Vulkan;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop; using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop;
using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing; using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing;
using Image = SixLabors.ImageSharp.Image;
using InputManager = Ryujinx.Input.HLE.InputManager; using InputManager = Ryujinx.Input.HLE.InputManager;
using IRenderer = Ryujinx.Graphics.GAL.IRenderer; using IRenderer = Ryujinx.Graphics.GAL.IRenderer;
using Key = Ryujinx.Input.Key; using Key = Ryujinx.Input.Key;
@ -366,25 +363,33 @@ namespace Ryujinx.Ava
return; return;
} }
Image image = e.IsBgra ? Image.LoadPixelData<Bgra32>(e.Data, e.Width, e.Height) var colorType = e.IsBgra ? SKColorType.Bgra8888 : SKColorType.Rgba8888;
: Image.LoadPixelData<Rgba32>(e.Data, e.Width, e.Height); using var bitmap = new SKBitmap(new SKImageInfo(e.Width, e.Height, colorType, SKAlphaType.Premul));
if (e.FlipX) Marshal.Copy(e.Data, 0, bitmap.GetPixels(), e.Data.Length);
SKBitmap bitmapToSave = null;
if (e.FlipX || e.FlipY)
{ {
image.Mutate(x => x.Flip(FlipMode.Horizontal)); bitmapToSave = new SKBitmap(bitmap.Width, bitmap.Height);
using var canvas = new SKCanvas(bitmapToSave);
canvas.Clear(SKColors.Transparent);
float scaleX = e.FlipX ? -1 : 1;
float scaleY = e.FlipY ? -1 : 1;
var matrix = SKMatrix.CreateScale(scaleX, scaleY, bitmap.Width / 2f, bitmap.Height / 2f);
canvas.SetMatrix(matrix);
canvas.DrawBitmap(bitmap, new SKPoint(e.FlipX ? -bitmap.Width : 0, e.FlipY ? -bitmap.Height : 0));
} }
if (e.FlipY) SaveBitmapAsPng(bitmapToSave ?? bitmap, path);
{ bitmapToSave?.Dispose();
image.Mutate(x => x.Flip(FlipMode.Vertical));
}
image.SaveAsPng(path, new PngEncoder
{
ColorType = PngColorType.Rgb,
});
image.Dispose();
Logger.Notice.Print(LogClass.Application, $"Screenshot saved to {path}", "Screenshot"); Logger.Notice.Print(LogClass.Application, $"Screenshot saved to {path}", "Screenshot");
} }
@ -396,6 +401,14 @@ namespace Ryujinx.Ava
} }
} }
private void SaveBitmapAsPng(SKBitmap bitmap, string path)
{
using var data = bitmap.Encode(SKEncodedImageFormat.Png, 100);
using var stream = File.OpenWrite(path);
data.SaveTo(stream);
}
public void Start() public void Start()
{ {
if (OperatingSystem.IsWindows()) if (OperatingSystem.IsWindows())

View File

@ -54,7 +54,6 @@
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" /> <PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" />
<PackageReference Include="SPB" /> <PackageReference Include="SPB" />
<PackageReference Include="SharpZipLib" /> <PackageReference Include="SharpZipLib" />
<PackageReference Include="SixLabors.ImageSharp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -32,7 +32,7 @@ using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common; using Ryujinx.UI.Common;
using Ryujinx.UI.Common.Configuration; using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Helper; using Ryujinx.UI.Common.Helper;
using SixLabors.ImageSharp.PixelFormats; using SkiaSharp;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
@ -40,7 +40,6 @@ using System.Globalization;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Image = SixLabors.ImageSharp.Image;
using Key = Ryujinx.Input.Key; using Key = Ryujinx.Input.Key;
using MissingKeyException = LibHac.Common.Keys.MissingKeyException; using MissingKeyException = LibHac.Common.Keys.MissingKeyException;
using ShaderCacheLoadingState = Ryujinx.Graphics.Gpu.Shader.ShaderCacheState; using ShaderCacheLoadingState = Ryujinx.Graphics.Gpu.Shader.ShaderCacheState;
@ -1164,17 +1163,17 @@ namespace Ryujinx.Ava.UI.ViewModels
private void PrepareLoadScreen() private void PrepareLoadScreen()
{ {
using MemoryStream stream = new(SelectedIcon); using MemoryStream stream = new(SelectedIcon);
using var gameIconBmp = Image.Load<Bgra32>(stream); using var gameIconBmp = SKBitmap.Decode(stream);
var dominantColor = IconColorPicker.GetFilteredColor(gameIconBmp).ToPixel<Bgra32>(); var dominantColor = IconColorPicker.GetFilteredColor(gameIconBmp);
const float ColorMultiple = 0.5f; const float ColorMultiple = 0.5f;
Color progressFgColor = Color.FromRgb(dominantColor.R, dominantColor.G, dominantColor.B); Color progressFgColor = Color.FromRgb(dominantColor.Red, dominantColor.Green, dominantColor.Blue);
Color progressBgColor = Color.FromRgb( Color progressBgColor = Color.FromRgb(
(byte)(dominantColor.R * ColorMultiple), (byte)(dominantColor.Red * ColorMultiple),
(byte)(dominantColor.G * ColorMultiple), (byte)(dominantColor.Green * ColorMultiple),
(byte)(dominantColor.B * ColorMultiple)); (byte)(dominantColor.Blue * ColorMultiple));
ProgressBarForegroundColor = new SolidColorBrush(progressFgColor); ProgressBarForegroundColor = new SolidColorBrush(progressFgColor);
ProgressBarBackgroundColor = new SolidColorBrush(progressBgColor); ProgressBarBackgroundColor = new SolidColorBrush(progressBgColor);

View File

@ -9,14 +9,14 @@ using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils; using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.UI.Models; using Ryujinx.Ava.UI.Models;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using SixLabors.ImageSharp; using SkiaSharp;
using SixLabors.ImageSharp.PixelFormats;
using System; using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.IO; using System.IO;
using Color = Avalonia.Media.Color; using Color = Avalonia.Media.Color;
using Image = SkiaSharp.SKImage;
namespace Ryujinx.Ava.UI.ViewModels namespace Ryujinx.Ava.UI.ViewModels
{ {
@ -130,9 +130,12 @@ namespace Ryujinx.Ava.UI.ViewModels
stream.Position = 0; stream.Position = 0;
Image avatarImage = Image.LoadPixelData<Rgba32>(DecompressYaz0(stream), 256, 256); Image avatarImage = Image.FromPixelCopy(new SKImageInfo(256, 256, SKColorType.Rgba8888, SKAlphaType.Premul), DecompressYaz0(stream));
avatarImage.SaveAsPng(streamPng); using (SKData data = avatarImage.Encode(SKEncodedImageFormat.Png, 100))
{
data.SaveTo(streamPng);
}
_avatarStore.Add(item.FullPath, streamPng.ToArray()); _avatarStore.Add(item.FullPath, streamPng.ToArray());
} }

View File

@ -6,12 +6,8 @@ using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Models; using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using SixLabors.ImageSharp; using SkiaSharp;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using System.IO; using System.IO;
using Image = SixLabors.ImageSharp.Image;
namespace Ryujinx.Ava.UI.Views.User namespace Ryujinx.Ava.UI.Views.User
{ {
@ -70,15 +66,25 @@ namespace Ryujinx.Ava.UI.Views.User
{ {
if (ViewModel.SelectedImage != null) if (ViewModel.SelectedImage != null)
{ {
MemoryStream streamJpg = new(); using var streamJpg = new MemoryStream();
Image avatarImage = Image.Load(ViewModel.SelectedImage, new PngDecoder()); using var bitmap = SKBitmap.Decode(ViewModel.SelectedImage);
using var newBitmap = new SKBitmap(bitmap.Width, bitmap.Height);
avatarImage.Mutate(x => x.BackgroundColor(new Rgba32( using (var canvas = new SKCanvas(newBitmap))
ViewModel.BackgroundColor.R, {
ViewModel.BackgroundColor.G, canvas.Clear(new SKColor(
ViewModel.BackgroundColor.B, ViewModel.BackgroundColor.R,
ViewModel.BackgroundColor.A))); ViewModel.BackgroundColor.G,
avatarImage.SaveAsJpeg(streamJpg); ViewModel.BackgroundColor.B,
ViewModel.BackgroundColor.A));
canvas.DrawBitmap(bitmap, 0, 0);
}
using (var image = SKImage.FromBitmap(newBitmap))
using (var dataJpeg = image.Encode(SKEncodedImageFormat.Jpeg, 100))
{
dataJpeg.SaveTo(streamJpg);
}
_profile.Image = streamJpg.ToArray(); _profile.Image = streamJpg.ToArray();

View File

@ -9,11 +9,9 @@ using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Models; using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using SixLabors.ImageSharp; using SkiaSharp;
using SixLabors.ImageSharp.Processing;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using Image = SixLabors.ImageSharp.Image;
namespace Ryujinx.Ava.UI.Views.User namespace Ryujinx.Ava.UI.Views.User
{ {
@ -102,13 +100,19 @@ namespace Ryujinx.Ava.UI.Views.User
private static byte[] ProcessProfileImage(byte[] buffer) private static byte[] ProcessProfileImage(byte[] buffer)
{ {
using Image image = Image.Load(buffer); using var bitmap = SKBitmap.Decode(buffer);
image.Mutate(x => x.Resize(256, 256)); var resizedBitmap = bitmap.Resize(new SKImageInfo(256, 256), SKFilterQuality.High);
using MemoryStream streamJpg = new(); using var streamJpg = new MemoryStream();
image.SaveAsJpeg(streamJpg); if (resizedBitmap != null)
{
using var image = SKImage.FromBitmap(resizedBitmap);
using var dataJpeg = image.Encode(SKEncodedImageFormat.Jpeg, 100);
dataJpeg.SaveTo(streamJpg);
}
return streamJpg.ToArray(); return streamJpg.ToArray();
} }

View File

@ -1,5 +1,4 @@
using SixLabors.ImageSharp; using SkiaSharp;
using SixLabors.ImageSharp.PixelFormats;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -36,35 +35,34 @@ namespace Ryujinx.Ava.UI.Windows
} }
} }
public static Color GetFilteredColor(Image<Bgra32> image) public static SKColor GetFilteredColor(SKBitmap image)
{ {
var color = GetColor(image).ToPixel<Bgra32>(); var color = GetColor(image);
// We don't want colors that are too dark. // We don't want colors that are too dark.
// If the color is too dark, make it brighter by reducing the range // If the color is too dark, make it brighter by reducing the range
// and adding a constant color. // and adding a constant color.
int luminosity = GetColorApproximateLuminosity(color.R, color.G, color.B); int luminosity = GetColorApproximateLuminosity(color.Red, color.Green, color.Blue);
if (luminosity < CutOffLuminosity) if (luminosity < CutOffLuminosity)
{ {
color = Color.FromRgb( color = new SKColor(
(byte)Math.Min(CutOffLuminosity + color.R, byte.MaxValue), (byte)Math.Min(CutOffLuminosity + color.Red, byte.MaxValue),
(byte)Math.Min(CutOffLuminosity + color.G, byte.MaxValue), (byte)Math.Min(CutOffLuminosity + color.Green, byte.MaxValue),
(byte)Math.Min(CutOffLuminosity + color.B, byte.MaxValue)); (byte)Math.Min(CutOffLuminosity + color.Blue, byte.MaxValue));
} }
return color; return color;
} }
public static Color GetColor(Image<Bgra32> image) public static SKColor GetColor(SKBitmap image)
{ {
var colors = new PaletteColor[TotalColors]; var colors = new PaletteColor[TotalColors];
var dominantColorBin = new Dictionary<int, int>(); var dominantColorBin = new Dictionary<int, int>();
var buffer = GetBuffer(image); var buffer = GetBuffer(image);
int w = image.Width; int w = image.Width;
int w8 = w << 8; int w8 = w << 8;
int h8 = image.Height << 8; int h8 = image.Height << 8;
@ -84,9 +82,10 @@ namespace Ryujinx.Ava.UI.Windows
{ {
int offset = x + yOffset; int offset = x + yOffset;
byte cb = buffer[offset].B; SKColor pixel = buffer[offset];
byte cg = buffer[offset].G; byte cr = pixel.Red;
byte cr = buffer[offset].R; byte cg = pixel.Green;
byte cb = pixel.Blue;
var qck = GetQuantizedColorKey(cr, cg, cb); var qck = GetQuantizedColorKey(cr, cg, cb);
@ -122,12 +121,22 @@ namespace Ryujinx.Ava.UI.Windows
} }
} }
return Color.FromRgb(bestCandidate.R, bestCandidate.G, bestCandidate.B); return new SKColor(bestCandidate.R, bestCandidate.G, bestCandidate.B);
} }
public static Bgra32[] GetBuffer(Image<Bgra32> image) public static SKColor[] GetBuffer(SKBitmap image)
{ {
return image.DangerousTryGetSinglePixelMemory(out var data) ? data.ToArray() : Array.Empty<Bgra32>(); var pixels = new SKColor[image.Width * image.Height];
for (int y = 0; y < image.Height; y++)
{
for (int x = 0; x < image.Width; x++)
{
pixels[x + y * image.Width] = image.GetPixel(x, y);
}
}
return pixels;
} }
private static int GetColorScore(Dictionary<int, int> dominantColorBin, int maxHitCount, PaletteColor color) private static int GetColorScore(Dictionary<int, int> dominantColorBin, int maxHitCount, PaletteColor color)