Compare commits

..

5 Commits

Author SHA1 Message Date
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
1a0a351a15 Resolve some Vulkan validation errors (#6915)
* Fix some validation errors

* Whitespace correction

* Resolve some runtime validation errors.

* Whitespace

* Properly fix usage realted validation error by setting Extended Usage image creation flag.

* Only if supported

* Remove checking extension for features that are core functionality of Vulkan 1.2
2024-06-26 09:21:44 -03:00
bd3335c143 Make sure the string is long enough before performing basic trim (#6982) 2024-06-26 11:27:23 +02:00
14 changed files with 240 additions and 58 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.IsConcurrentFenceWaitUnsupported,
isLight: true);
} }
} }

View File

@ -103,12 +103,19 @@ namespace Ryujinx.Graphics.Vulkan
usage |= BufferUsageFlags.IndirectBufferBit; usage |= BufferUsageFlags.IndirectBufferBit;
} }
var externalMemoryBuffer = new ExternalMemoryBufferCreateInfo
{
SType = StructureType.ExternalMemoryBufferCreateInfo,
HandleTypes = ExternalMemoryHandleTypeFlags.HostAllocationBitExt,
};
var bufferCreateInfo = new BufferCreateInfo var bufferCreateInfo = new BufferCreateInfo
{ {
SType = StructureType.BufferCreateInfo, SType = StructureType.BufferCreateInfo,
Size = (ulong)size, Size = (ulong)size,
Usage = usage, Usage = usage,
SharingMode = SharingMode.Exclusive, SharingMode = SharingMode.Exclusive,
PNext = &externalMemoryBuffer,
}; };
gd.Api.CreateBuffer(_device, in bufferCreateInfo, null, out var buffer).ThrowOnError(); gd.Api.CreateBuffer(_device, in bufferCreateInfo, null, out var buffer).ThrowOnError();

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];

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.Vendor == Vendor.Qualcomm && 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

@ -80,7 +80,7 @@ namespace Ryujinx.Graphics.Vulkan
var usage = GetImageUsage(info.Format, info.Target, gd.Capabilities.SupportsShaderStorageImageMultisample); var usage = GetImageUsage(info.Format, info.Target, gd.Capabilities.SupportsShaderStorageImageMultisample);
var flags = ImageCreateFlags.CreateMutableFormatBit; var flags = ImageCreateFlags.CreateMutableFormatBit | ImageCreateFlags.CreateExtendedUsageBit;
// This flag causes mipmapped texture arrays to break on AMD GCN, so for that copy dependencies are forced for aliasing as cube. // This flag causes mipmapped texture arrays to break on AMD GCN, so for that copy dependencies are forced for aliasing as cube.
bool isCube = info.Target == Target.Cubemap || info.Target == Target.CubemapArray; bool isCube = info.Target == Target.Cubemap || info.Target == Target.CubemapArray;

View File

@ -100,7 +100,7 @@ namespace Ryujinx.Graphics.Vulkan
unsafe Auto<DisposableImageView> CreateImageView(ComponentMapping cm, ImageSubresourceRange sr, ImageViewType viewType, ImageUsageFlags usageFlags) unsafe Auto<DisposableImageView> CreateImageView(ComponentMapping cm, ImageSubresourceRange sr, ImageViewType viewType, ImageUsageFlags usageFlags)
{ {
var usage = new ImageViewUsageCreateInfo var imageViewUsage = new ImageViewUsageCreateInfo
{ {
SType = StructureType.ImageViewUsageCreateInfo, SType = StructureType.ImageViewUsageCreateInfo,
Usage = usageFlags, Usage = usageFlags,
@ -114,7 +114,7 @@ namespace Ryujinx.Graphics.Vulkan
Format = format, Format = format,
Components = cm, Components = cm,
SubresourceRange = sr, SubresourceRange = sr,
PNext = &usage, PNext = &imageViewUsage,
}; };
gd.Api.CreateImageView(device, imageCreateInfo, null, out var imageView).ThrowOnError(); gd.Api.CreateImageView(device, imageCreateInfo, null, out var imageView).ThrowOnError();
@ -123,7 +123,7 @@ namespace Ryujinx.Graphics.Vulkan
ImageUsageFlags shaderUsage = ImageUsageFlags.SampledBit; ImageUsageFlags shaderUsage = ImageUsageFlags.SampledBit;
if (info.Format.IsImageCompatible()) if (info.Format.IsImageCompatible() && (_gd.Capabilities.SupportsShaderStorageImageMultisample || !info.Target.IsMultisample()))
{ {
shaderUsage |= ImageUsageFlags.StorageBit; shaderUsage |= ImageUsageFlags.StorageBit;
} }
@ -154,7 +154,7 @@ namespace Ryujinx.Graphics.Vulkan
} }
else else
{ {
subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, (uint)info.Depth); subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, 1, (uint)firstLayer, (uint)info.Depth);
_imageView2dArray = CreateImageView(identityComponentMapping, subresourceRange, ImageViewType.Type2DArray, usage); _imageView2dArray = CreateImageView(identityComponentMapping, subresourceRange, ImageViewType.Type2DArray, usage);
} }

View File

@ -42,6 +42,8 @@ namespace Ryujinx.Graphics.Vulkan
"VK_EXT_depth_clip_control", "VK_EXT_depth_clip_control",
"VK_KHR_portability_subset", // As per spec, we should enable this if present. "VK_KHR_portability_subset", // As per spec, we should enable this if present.
"VK_EXT_4444_formats", "VK_EXT_4444_formats",
"VK_KHR_8bit_storage",
"VK_KHR_maintenance2",
}; };
private static readonly string[] _requiredExtensions = { private static readonly string[] _requiredExtensions = {
@ -355,6 +357,14 @@ namespace Ryujinx.Graphics.Vulkan
features2.PNext = &supportedFeaturesDepthClipControl; features2.PNext = &supportedFeaturesDepthClipControl;
} }
PhysicalDeviceVulkan12Features supportedPhysicalDeviceVulkan12Features = new()
{
SType = StructureType.PhysicalDeviceVulkan12Features,
PNext = features2.PNext,
};
features2.PNext = &supportedPhysicalDeviceVulkan12Features;
api.GetPhysicalDeviceFeatures2(physicalDevice.PhysicalDevice, &features2); api.GetPhysicalDeviceFeatures2(physicalDevice.PhysicalDevice, &features2);
var supportedFeatures = features2.Features; var supportedFeatures = features2.Features;
@ -382,6 +392,7 @@ namespace Ryujinx.Graphics.Vulkan
TessellationShader = supportedFeatures.TessellationShader, TessellationShader = supportedFeatures.TessellationShader,
VertexPipelineStoresAndAtomics = supportedFeatures.VertexPipelineStoresAndAtomics, VertexPipelineStoresAndAtomics = supportedFeatures.VertexPipelineStoresAndAtomics,
RobustBufferAccess = useRobustBufferAccess, RobustBufferAccess = useRobustBufferAccess,
SampleRateShading = supportedFeatures.SampleRateShading,
}; };
void* pExtendedFeatures = null; void* pExtendedFeatures = null;
@ -451,9 +462,11 @@ namespace Ryujinx.Graphics.Vulkan
{ {
SType = StructureType.PhysicalDeviceVulkan12Features, SType = StructureType.PhysicalDeviceVulkan12Features,
PNext = pExtendedFeatures, PNext = pExtendedFeatures,
DescriptorIndexing = physicalDevice.IsDeviceExtensionPresent("VK_EXT_descriptor_indexing"), DescriptorIndexing = supportedPhysicalDeviceVulkan12Features.DescriptorIndexing,
DrawIndirectCount = physicalDevice.IsDeviceExtensionPresent(KhrDrawIndirectCount.ExtensionName), DrawIndirectCount = supportedPhysicalDeviceVulkan12Features.DrawIndirectCount,
UniformBufferStandardLayout = physicalDevice.IsDeviceExtensionPresent("VK_KHR_uniform_buffer_standard_layout"), UniformBufferStandardLayout = supportedPhysicalDeviceVulkan12Features.UniformBufferStandardLayout,
UniformAndStorageBuffer8BitAccess = supportedPhysicalDeviceVulkan12Features.UniformAndStorageBuffer8BitAccess,
StorageBuffer8BitAccess = supportedPhysicalDeviceVulkan12Features.StorageBuffer8BitAccess,
}; };
pExtendedFeatures = &featuresVk12; pExtendedFeatures = &featuresVk12;

View File

@ -90,6 +90,8 @@ namespace Ryujinx.Graphics.Vulkan
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; }
internal bool IsConcurrentFenceWaitUnsupported { 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; }
@ -323,6 +325,8 @@ namespace Ryujinx.Graphics.Vulkan
Vendor == Vendor.Broadcom || Vendor == Vendor.Broadcom ||
Vendor == Vendor.ImgTec; Vendor == Vendor.ImgTec;
IsConcurrentFenceWaitUnsupported = Vendor == Vendor.Qualcomm;
GpuVendor = VendorUtils.GetNameFromId(properties.VendorID); GpuVendor = VendorUtils.GetNameFromId(properties.VendorID);
GpuDriver = hasDriverProperties && !OperatingSystem.IsMacOS() ? GpuDriver = hasDriverProperties && !OperatingSystem.IsMacOS() ?
VendorUtils.GetFriendlyDriverName(driverProperties.DriverID) : GpuVendor; // Fallback to vendor name if driver is unavailable or on MacOS where vendor is preferred. VendorUtils.GetFriendlyDriverName(driverProperties.DriverID) : GpuVendor; // Fallback to vendor name if driver is unavailable or on MacOS where vendor is preferred.
@ -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, IsConcurrentFenceWaitUnsupported);
PipelineLayoutCache = new PipelineLayoutCache(); PipelineLayoutCache = new PipelineLayoutCache();

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

@ -104,8 +104,13 @@ namespace Ryujinx.UI.Common
// Find the length to trim the string to guarantee we have space for the trailing ellipsis. // Find the length to trim the string to guarantee we have space for the trailing ellipsis.
int trimLimit = byteLimit - Encoding.UTF8.GetByteCount(Ellipsis); int trimLimit = byteLimit - Encoding.UTF8.GetByteCount(Ellipsis);
// Basic trim to best case scenario of 1 byte characters. // Make sure the string is long enough to perform the basic trim.
input = input[..trimLimit]; // Amount of bytes != Length of the string
if (input.Length > trimLimit)
{
// Basic trim to best case scenario of 1 byte characters.
input = input[..trimLimit];
}
while (Encoding.UTF8.GetByteCount(input) > trimLimit) while (Encoding.UTF8.GetByteCount(input) > trimLimit)
{ {