Compare commits

..

7 Commits

Author SHA1 Message Date
TSRBerry
a5a9b9bc8b GUI: Small Updater refactor & Set correct permissions on Linux when extracting files (#4315)
* ava: Refactor Updater.cs

Fix typos
Remove unused usings
Rename variables to follow naming scheme

* ava: Set file permissions when extracting update files

* gtk: Apply the same refactor to Updater.cs

* updater: Replace assert with if statement

* updater: Remove await usings again
2023-02-15 22:36:35 +00:00
Mary
17078ad929 vulkan: Respect VK_KHR_portability_subset vertex stride alignment (#4419)
* vulkan: Respect VK_KHR_portability_subset vertex stride alignment

We were hardcoding alignment to 4, but by specs it can be any values that
is a power of 2.

This also enable VK_KHR_portability_subset if present as per specs
requirements.

* address gdkchan's comment

* Make NeedsVertexBufferAlignment internal
2023-02-15 08:41:48 +00:00
Mary
32450d45de vulkan: Clean up MemoryAllocator (#4418)
This started as an attempt to remove vkGetPhysicalDeviceMemoryProperties
in FindSuitableMemoryTypeIndex (As this could have some overhead and
shouldn't change at runtime) and turned in a little bigger cleanup.
2023-02-15 07:50:26 +01:00
Ac_K
ed7a0474c6 Infra: Issues template cleanup (#4421)
* Infra: Issues template cleanup

* applied
2023-02-14 15:58:57 +01:00
Mary
fe9c49949a vulkan: Enforce Vulkan 1.2+ at instance API level and 1.1+ at device level (#4408)
* vulkan: Enforce Vulkan 1.2+ at instance API level and 1.1+ at device level

This ensure we don't end up trying to initialize with anything currently incompatible.

* Address riperiperi's comment
2023-02-13 22:04:55 +00:00
Mary
052b23c83c vulkan: Do not call vkCmdSetViewport when viewportCount is 0 (#4406)
This fix validation error "VUID-vkCmdSetViewport-viewportCount-arraylength".
2023-02-13 20:32:20 +00:00
riperiperi
e4f68592c3 Fix partial updates for textures. (#4401)
I was forcing some types of texture to partially update when investigating performance with games that stream in data, and noticed that partially loading texture data was really broken on both backends.

Fixes Vulkan texture set by getting the correct expected size for the texture. Fixes partial upload on both backends for both Texture 2D Array and Cubemap using the wrong offset and uploading to the first layer/level for a handle. 3D might also be affected.

This might fix textures randomly having incorrect data in games that render to it - jumbled in the case of OpenGL, and outdated/black in the case of Vulkan. This case typically happens in UE4 games.
2023-02-12 10:30:26 +01:00
17 changed files with 315 additions and 324 deletions

View File

@@ -1,26 +1,27 @@
name: Bug Report
description: File a bug report
title: "[Bug] <title>"
title: "[Bug]"
labels: bug
body:
- type: textarea
id: issue
attributes:
label: Description of Issue
label: Description of the issue
description: What's the issue you encountered?
validations:
required: true
- type: textarea
id: repro
attributes:
label: Reproduction Steps
label: Reproduction steps
description: How can the issue be reproduced?
placeholder: Describe each step as precisely as possible
validations:
required: true
- type: textarea
id: log
attributes:
label: Log File
label: Log file
description: A log file will help our developers to better diagnose and fix the issue.
placeholder: Logs files can be found under "Logs" folder in Ryujinx program folder. You can drag and drop the log on to the text area
validations:
@@ -29,55 +30,44 @@ body:
id: os
attributes:
label: OS
placeholder: "Example: Windows 10"
placeholder: "e.g. Windows 10"
validations:
required: true
- type: input
id: ryujinx-version
attributes:
label: Ryujinx version
placeholder: |
- *(e.g. 1.0.470)*
placeholder: "e.g. 1.0.470"
validations:
required: true
- type: input
id: game-version
attributes:
label: Game version
placeholder: |
- *(e.g. 1.1.1)*
placeholder: "e.g. 1.1.1"
validations:
required: false
- type: input
id: cpu
attributes:
label: CPU
placeholder: |
- *(e.g. i7-6700)*
placeholder: "e.g. i7-6700"
validations:
required: false
- type: input
id: gpu
attributes:
label: GPU
placeholder: |
- *(e.g. NVIDIA RTX 2070)*
placeholder: "e.g. NVIDIA RTX 2070"
validations:
required: false
- type: input
id: ram
attributes:
label: RAM
placeholder: |
- *(e.g. 16GB)*
placeholder: "e.g. 16GB"
validations:
required: false
- type: checkboxes
attributes:
label: Applied Mods?
options:
- label: "Yes"
required: false
- type: textarea
id: mods
attributes:

View File

@@ -1,6 +1,6 @@
name: Feature Request
description: Suggest a new feature for Ryujinx.
title: "[Feature Request] <title>"
title: "[Feature Request]"
body:
- type: textarea
id: overview
@@ -12,14 +12,14 @@ body:
- type: textarea
id: details
attributes:
label: Smaller Details
label: Smaller details
description: These may include specific methods of implementation etc.
validations:
required: true
- type: textarea
id: request
attributes:
label: Nature of Request
label: Nature of request
validations:
required: true
- type: textarea

View File

@@ -1,6 +1,6 @@
name: Missing CPU Instruction
description: CPU Instruction is missing in Ryujinx.
title: "[CPU] <title>"
title: "[CPU]"
labels: [cpu, not-implemented]
body:
- type: textarea

View File

@@ -5,7 +5,7 @@ body:
- type: textarea
id: instruction
attributes:
label: Service Call
label: Service call
description: What service call is missing?
validations:
required: true

View File

@@ -132,8 +132,8 @@ namespace Ryujinx.Modules
}
}
// If build not done, assume no new update are availaible.
if (_buildUrl == null)
// If build not done, assume no new update are available.
if (_buildUrl is null)
{
if (showVersionUpToDate)
{
@@ -240,13 +240,13 @@ namespace Ryujinx.Modules
{
HttpClient result = new();
// Required by GitHub to interract with APIs.
// Required by GitHub to interact with APIs.
result.DefaultRequestHeaders.Add("User-Agent", "Ryujinx-Updater/1.0.0");
return result;
}
public static async void UpdateRyujinx(Window parent, string downloadUrl)
private static async void UpdateRyujinx(Window parent, string downloadUrl)
{
_updateSuccessful = false;
@@ -300,8 +300,6 @@ namespace Ryujinx.Modules
ryuExe = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx");
}
SetFileExecutable(ryuExe);
Process.Start(ryuExe, CommandLineState.Arguments);
Environment.Exit(0);
@@ -408,9 +406,9 @@ namespace Ryujinx.Modules
Logger.Warning?.Print(LogClass.Application, ex.Message);
Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater.");
for (int j = 0; j < webClients.Count; j++)
foreach (WebClient webClient in webClients)
{
webClients[j].CancelAsync();
webClient.CancelAsync();
}
DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile);
@@ -472,22 +470,6 @@ namespace Ryujinx.Modules
worker.Start();
}
private static void SetFileExecutable(string path)
{
const UnixFileMode ExecutableFileMode = UnixFileMode.UserExecute |
UnixFileMode.UserWrite |
UnixFileMode.UserRead |
UnixFileMode.GroupRead |
UnixFileMode.GroupWrite |
UnixFileMode.OtherRead |
UnixFileMode.OtherWrite;
if (!OperatingSystem.IsWindows() && File.Exists(path))
{
File.SetUnixFileMode(path, ExecutableFileMode);
}
}
private static async void InstallUpdate(TaskDialog taskDialog, string updateFile)
{
// Extract Update
@@ -503,27 +485,30 @@ namespace Ryujinx.Modules
await Task.Run(() =>
{
TarEntry tarEntry;
while ((tarEntry = tarStream.GetNextEntry()) != null)
if (!OperatingSystem.IsWindows())
{
if (tarEntry.IsDirectory) continue;
string outPath = Path.Combine(UpdateDir, tarEntry.Name);
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
using (FileStream outStream = File.OpenWrite(outPath))
while ((tarEntry = tarStream.GetNextEntry()) is not null)
{
tarStream.CopyEntryContents(outStream);
if (tarEntry.IsDirectory) continue;
string outPath = Path.Combine(UpdateDir, tarEntry.Name);
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
using (FileStream outStream = File.OpenWrite(outPath))
{
tarStream.CopyEntryContents(outStream);
}
File.SetUnixFileMode(outPath, (UnixFileMode)tarEntry.TarHeader.Mode);
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc));
Dispatcher.UIThread.Post(() =>
{
taskDialog.SetProgressBarState(GetPercentage(tarEntry.Size, inStream.Length), TaskDialogProgressState.Normal);
});
}
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc));
TarEntry entry = tarEntry;
Dispatcher.UIThread.Post(() =>
{
taskDialog.SetProgressBarState(GetPercentage(entry.Size, inStream.Length), TaskDialogProgressState.Normal);
});
}
});
@@ -603,8 +588,6 @@ namespace Ryujinx.Modules
Directory.Delete(UpdateDir, true);
SetFileExecutable(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx"));
_updateSuccessful = true;
taskDialog.Hide();

View File

@@ -336,24 +336,23 @@ namespace Ryujinx.Graphics.Gpu.Image
if (_loadNeeded[baseHandle + i])
{
var info = GetHandleInformation(baseHandle + i);
int offsetIndex = info.Index;
// Only one of these will be greater than 1, as partial sync is only called when there are sub-image views.
for (int layer = 0; layer < info.Layers; layer++)
{
for (int level = 0; level < info.Levels; level++)
{
int offsetIndex = GetOffsetIndex(info.BaseLayer + layer, info.BaseLevel + level);
int offset = _allOffsets[offsetIndex];
int endOffset = Math.Min(offset + _sliceSizes[info.BaseLevel + level], (int)Storage.Size);
int size = endOffset - offset;
ReadOnlySpan<byte> data = _physicalMemory.GetSpan(Storage.Range.GetSlice((ulong)offset, (ulong)size));
SpanOrArray<byte> result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel, true);
SpanOrArray<byte> result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel + level, true);
Storage.SetData(result, info.BaseLayer, info.BaseLevel);
offsetIndex++;
Storage.SetData(result, info.BaseLayer + layer, info.BaseLevel + level);
}
}
}

View File

@@ -39,7 +39,6 @@ namespace Ryujinx.Graphics.Vulkan
BufferUsageFlags.VertexBufferBit |
BufferUsageFlags.TransformFeedbackBufferBitExt;
private readonly PhysicalDevice _physicalDevice;
private readonly Device _device;
private readonly IdList<BufferHolder> _buffers;
@@ -48,9 +47,8 @@ namespace Ryujinx.Graphics.Vulkan
public StagingBuffer StagingBuffer { get; }
public BufferManager(VulkanRenderer gd, PhysicalDevice physicalDevice, Device device)
public BufferManager(VulkanRenderer gd, Device device)
{
_physicalDevice = physicalDevice;
_device = device;
_buffers = new IdList<BufferHolder>();
StagingBuffer = new StagingBuffer(gd, this);
@@ -114,7 +112,7 @@ namespace Ryujinx.Graphics.Vulkan
allocateFlagsAlt = DefaultBufferMemoryAltFlags;
}
var allocation = gd.MemoryAllocator.AllocateDeviceMemory(_physicalDevice, requirements, allocateFlags, allocateFlagsAlt);
var allocation = gd.MemoryAllocator.AllocateDeviceMemory(requirements, allocateFlags, allocateFlagsAlt);
if (allocation.Memory.Handle == 0UL)
{

View File

@@ -8,11 +8,10 @@ namespace Ryujinx.Graphics.Vulkan
{
None = 0,
VertexBufferAlignment4B = 1,
NoTriangleFans = 1 << 1,
NoPointMode = 1 << 2,
No3DImageView = 1 << 3,
NoLodBias = 1 << 4
NoTriangleFans = 1,
NoPointMode = 1 << 1,
No3DImageView = 1 << 2,
NoLodBias = 1 << 3
}
readonly struct HardwareCapabilities
@@ -40,6 +39,7 @@ namespace Ryujinx.Graphics.Vulkan
public readonly ShaderStageFlags RequiredSubgroupSizeStages;
public readonly SampleCountFlags SupportedSampleCounts;
public readonly PortabilitySubsetFlags PortabilitySubset;
public readonly uint VertexBufferAlignment;
public HardwareCapabilities(
bool supportsIndexTypeUint8,
@@ -64,7 +64,8 @@ namespace Ryujinx.Graphics.Vulkan
uint maxSubgroupSize,
ShaderStageFlags requiredSubgroupSizeStages,
SampleCountFlags supportedSampleCounts,
PortabilitySubsetFlags portabilitySubset)
PortabilitySubsetFlags portabilitySubset,
uint vertexBufferAlignment)
{
SupportsIndexTypeUint8 = supportsIndexTypeUint8;
SupportsCustomBorderColor = supportsCustomBorderColor;
@@ -89,6 +90,7 @@ namespace Ryujinx.Graphics.Vulkan
RequiredSubgroupSizeStages = requiredSubgroupSizeStages;
SupportedSampleCounts = supportedSampleCounts;
PortabilitySubset = portabilitySubset;
VertexBufferAlignment = vertexBufferAlignment;
}
}
}

View File

@@ -9,34 +9,36 @@ namespace Ryujinx.Graphics.Vulkan
private ulong MaxDeviceMemoryUsageEstimate = 16UL * 1024 * 1024 * 1024;
private readonly Vk _api;
private readonly PhysicalDevice _physicalDevice;
private readonly Device _device;
private readonly List<MemoryAllocatorBlockList> _blockLists;
private readonly int _blockAlignment;
private readonly PhysicalDeviceMemoryProperties _physicalDeviceMemoryProperties;
private int _blockAlignment;
public MemoryAllocator(Vk api, Device device, uint maxMemoryAllocationCount)
public MemoryAllocator(Vk api, PhysicalDevice physicalDevice, Device device, uint maxMemoryAllocationCount)
{
_api = api;
_physicalDevice = physicalDevice;
_device = device;
_blockLists = new List<MemoryAllocatorBlockList>();
_blockAlignment = (int)Math.Min(int.MaxValue, MaxDeviceMemoryUsageEstimate / (ulong)maxMemoryAllocationCount);
_api.GetPhysicalDeviceMemoryProperties(_physicalDevice, out _physicalDeviceMemoryProperties);
}
public MemoryAllocation AllocateDeviceMemory(
PhysicalDevice physicalDevice,
MemoryRequirements requirements,
MemoryPropertyFlags flags = 0)
{
return AllocateDeviceMemory(physicalDevice, requirements, flags, flags);
return AllocateDeviceMemory(requirements, flags, flags);
}
public MemoryAllocation AllocateDeviceMemory(
PhysicalDevice physicalDevice,
MemoryRequirements requirements,
MemoryPropertyFlags flags,
MemoryPropertyFlags alternativeFlags)
{
int memoryTypeIndex = FindSuitableMemoryTypeIndex(_api, physicalDevice, requirements.MemoryTypeBits, flags, alternativeFlags);
int memoryTypeIndex = FindSuitableMemoryTypeIndex(requirements.MemoryTypeBits, flags, alternativeFlags);
if (memoryTypeIndex < 0)
{
return default;
@@ -65,20 +67,16 @@ namespace Ryujinx.Graphics.Vulkan
return newBl.Allocate(size, alignment, map);
}
private static int FindSuitableMemoryTypeIndex(
Vk api,
PhysicalDevice physicalDevice,
private int FindSuitableMemoryTypeIndex(
uint memoryTypeBits,
MemoryPropertyFlags flags,
MemoryPropertyFlags alternativeFlags)
{
int bestCandidateIndex = -1;
api.GetPhysicalDeviceMemoryProperties(physicalDevice, out var properties);
for (int i = 0; i < properties.MemoryTypeCount; i++)
for (int i = 0; i < _physicalDeviceMemoryProperties.MemoryTypeCount; i++)
{
var type = properties.MemoryTypes[i];
var type = _physicalDeviceMemoryProperties.MemoryTypes[i];
if ((memoryTypeBits & (1 << i)) != 0)
{

View File

@@ -1,4 +1,5 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Common;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
using Silk.NET.Vulkan;
using System;
@@ -650,9 +651,7 @@ namespace Ryujinx.Graphics.Vulkan
_newState.DepthWriteEnable = oldDepthWriteEnable;
_newState.Topology = oldTopology;
DynamicState.Viewports = oldViewports;
DynamicState.ViewportsCount = (int)oldViewportsCount;
DynamicState.SetViewportsDirty();
DynamicState.SetViewports(ref oldViewports, oldViewportsCount);
_newState.ViewportsCount = oldViewportsCount;
SignalStateChange();
@@ -1138,7 +1137,7 @@ namespace Ryujinx.Graphics.Vulkan
buffer.Dispose();
if (!Gd.Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.VertexBufferAlignment4B) &&
if (Gd.Capabilities.VertexBufferAlignment < 2 &&
(vertexBuffer.Stride % FormatExtensions.MaxBufferFormatScalarSize) == 0)
{
buffer = new VertexBufferState(
@@ -1183,6 +1182,8 @@ namespace Ryujinx.Graphics.Vulkan
return Math.Clamp(value, 0f, 1f);
}
DynamicState.ViewportsCount = (uint)count;
for (int i = 0; i < count; i++)
{
var viewport = viewports[i];
@@ -1196,8 +1197,6 @@ namespace Ryujinx.Graphics.Vulkan
Clamp(viewport.DepthFar)));
}
DynamicState.ViewportsCount = count;
float disableTransformF = disableTransform ? 1.0f : 0.0f;
if (SupportBufferUpdater.Data.ViewportInverse.W != disableTransformF || disableTransform)
{

View File

@@ -1,4 +1,5 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Common;
using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan;
using System;
@@ -253,7 +254,7 @@ namespace Ryujinx.Graphics.Vulkan
if (gd.NeedsVertexBufferAlignment(vbScalarSizes[i], out int alignment))
{
alignedStride = (vertexBuffer.Stride + (alignment - 1)) & -alignment;
alignedStride = BitUtils.AlignUp(vertexBuffer.Stride, alignment);
}
// TODO: Support divisor > 1

View File

@@ -21,7 +21,7 @@ namespace Ryujinx.Graphics.Vulkan
private Array4<float> _blendConstants;
public int ViewportsCount;
public uint ViewportsCount;
public Array16<Viewport> Viewports;
private enum DirtyFlags
@@ -88,9 +88,15 @@ namespace Ryujinx.Graphics.Vulkan
_dirty |= DirtyFlags.Viewport;
}
public void SetViewportsDirty()
public void SetViewports(ref Array16<Viewport> viewports, uint viewportsCount)
{
_dirty |= DirtyFlags.Viewport;
Viewports = viewports;
ViewportsCount = viewportsCount;
if (ViewportsCount != 0)
{
_dirty |= DirtyFlags.Viewport;
}
}
public void ForceAllDirty()
@@ -155,7 +161,10 @@ namespace Ryujinx.Graphics.Vulkan
private void RecordViewport(Vk api, CommandBuffer commandBuffer)
{
api.CmdSetViewport(commandBuffer, 0, (uint)ViewportsCount, Viewports.AsSpan());
if (ViewportsCount != 0)
{
api.CmdSetViewport(commandBuffer, 0, ViewportsCount, Viewports.AsSpan());
}
}
}
}

View File

@@ -55,7 +55,6 @@ namespace Ryujinx.Graphics.Vulkan
public unsafe TextureStorage(
VulkanRenderer gd,
PhysicalDevice physicalDevice,
Device device,
TextureCreateInfo info,
float scaleFactor,
@@ -118,7 +117,7 @@ namespace Ryujinx.Graphics.Vulkan
if (foreignAllocation == null)
{
gd.Api.GetImageMemoryRequirements(device, _image, out var requirements);
var allocation = gd.MemoryAllocator.AllocateDeviceMemory(physicalDevice, requirements, DefaultImageMemoryFlags);
var allocation = gd.MemoryAllocator.AllocateDeviceMemory(requirements, DefaultImageMemoryFlags);
if (allocation.Memory.Handle == 0UL)
{
@@ -173,7 +172,7 @@ namespace Ryujinx.Graphics.Vulkan
var info = NewCreateInfoWith(ref _info, format, _info.BytesPerPixel);
storage = new TextureStorage(_gd, default, _device, info, ScaleFactor, _allocationAuto);
storage = new TextureStorage(_gd, _device, info, ScaleFactor, _allocationAuto);
_aliasedStorages.Add(format, storage);
}

View File

@@ -712,7 +712,7 @@ namespace Ryujinx.Graphics.Vulkan
for (int level = 0; level < levels; level++)
{
int mipSize = GetBufferDataLength(Info.GetMipSize(dstLevel + level));
int mipSize = GetBufferDataLength(Info.GetMipSize2D(dstLevel + level) * dstLayers);
int endOffset = offset + mipSize;

View File

@@ -14,6 +14,9 @@ namespace Ryujinx.Graphics.Vulkan
public unsafe static class VulkanInitialization
{
private const uint InvalidIndex = uint.MaxValue;
private static uint MinimalVulkanVersion = Vk.Version11.Value;
private static uint MinimalInstanceVulkanVersion = Vk.Version12.Value;
private static uint MaximumVulkanVersion = Vk.Version12.Value;
private const string AppName = "Ryujinx.Graphics.Vulkan";
private const int QueuesCount = 2;
@@ -33,7 +36,8 @@ namespace Ryujinx.Graphics.Vulkan
"VK_KHR_shader_float16_int8",
"VK_EXT_shader_subgroup_ballot",
"VK_EXT_subgroup_size_control",
"VK_NV_geometry_shader_passthrough"
"VK_NV_geometry_shader_passthrough",
"VK_KHR_portability_subset", // By spec, we should enable this if present.
};
public static string[] RequiredExtensions { get; } = new string[]
@@ -99,7 +103,7 @@ namespace Ryujinx.Graphics.Vulkan
ApplicationVersion = 1,
PEngineName = (byte*)appName,
EngineVersion = 1,
ApiVersion = Vk.Version12.Value
ApiVersion = MaximumVulkanVersion
};
IntPtr* ppEnabledExtensions = stackalloc IntPtr[enabledExtensions.Length];
@@ -224,7 +228,7 @@ namespace Ryujinx.Graphics.Vulkan
ApplicationVersion = 1,
PEngineName = (byte*)appName,
EngineVersion = 1,
ApiVersion = Vk.Version12.Value
ApiVersion = MaximumVulkanVersion
};
var instanceCreateInfo = new InstanceCreateInfo
@@ -239,6 +243,27 @@ namespace Ryujinx.Graphics.Vulkan
api.CreateInstance(in instanceCreateInfo, null, out var instance).ThrowOnError();
// We ensure that vkEnumerateInstanceVersion is present (added in 1.1).
// If the instance doesn't support it, no device is going to be 1.1 compatible.
if (api.GetInstanceProcAddr(instance, "vkEnumerateInstanceVersion") == IntPtr.Zero)
{
api.DestroyInstance(instance, null);
return Array.Empty<DeviceInfo>();
}
// We currently assume that the instance is compatible with Vulkan 1.2
// TODO: Remove this once we relax our initialization codepaths.
uint instanceApiVerison = 0;
api.EnumerateInstanceVersion(ref instanceApiVerison).ThrowOnError();
if (instanceApiVerison < MinimalInstanceVulkanVersion)
{
api.DestroyInstance(instance, null);
return Array.Empty<DeviceInfo>();
}
Marshal.FreeHGlobal(appName);
uint physicalDeviceCount;
@@ -259,6 +284,11 @@ namespace Ryujinx.Graphics.Vulkan
var physicalDevice = physicalDevices[i];
api.GetPhysicalDeviceProperties(physicalDevice, out var properties);
if (properties.ApiVersion < MinimalVulkanVersion)
{
continue;
}
devices[i] = new DeviceInfo(
StringFromIdPair(properties.VendorID, properties.DeviceID),
VendorUtils.GetNameFromId(properties.VendorID),

View File

@@ -234,10 +234,12 @@ namespace Ryujinx.Graphics.Vulkan
Api.GetPhysicalDeviceFeatures2(_physicalDevice, &features2);
var portabilityFlags = PortabilitySubsetFlags.None;
uint vertexBufferAlignment = 1;
if (usePortability)
{
portabilityFlags |= propertiesPortabilitySubset.MinVertexInputBindingStrideAlignment > 1 ? PortabilitySubsetFlags.VertexBufferAlignment4B : 0;
vertexBufferAlignment = propertiesPortabilitySubset.MinVertexInputBindingStrideAlignment;
portabilityFlags |= featuresPortabilitySubset.TriangleFans ? 0 : PortabilitySubsetFlags.NoTriangleFans;
portabilityFlags |= featuresPortabilitySubset.PointPolygons ? 0 : PortabilitySubsetFlags.NoPointMode;
portabilityFlags |= featuresPortabilitySubset.ImageView2DOn3DImage ? 0 : PortabilitySubsetFlags.No3DImageView;
@@ -278,9 +280,10 @@ namespace Ryujinx.Graphics.Vulkan
propertiesSubgroupSizeControl.MaxSubgroupSize,
propertiesSubgroupSizeControl.RequiredSubgroupSizeStages,
supportedSampleCounts,
portabilityFlags);
portabilityFlags,
vertexBufferAlignment);
MemoryAllocator = new MemoryAllocator(Api, _device, properties.Limits.MaxMemoryAllocationCount);
MemoryAllocator = new MemoryAllocator(Api, _physicalDevice, _device, properties.Limits.MaxMemoryAllocationCount);
CommandBufferPool = VulkanInitialization.CreateCommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex);
@@ -290,7 +293,7 @@ namespace Ryujinx.Graphics.Vulkan
BackgroundResources = new BackgroundResources(this, _device);
BufferManager = new BufferManager(this, _physicalDevice, _device);
BufferManager = new BufferManager(this, _device);
_syncManager = new SyncManager(this, _device);
_pipeline = new PipelineFull(this, _device);
@@ -388,7 +391,7 @@ namespace Ryujinx.Graphics.Vulkan
internal TextureStorage CreateTextureStorage(TextureCreateInfo info, float scale)
{
return new TextureStorage(this, _physicalDevice, _device, info, scale);
return new TextureStorage(this, _device, info, scale);
}
public void DeleteBuffer(BufferHandle buffer)
@@ -636,11 +639,11 @@ namespace Ryujinx.Graphics.Vulkan
PrintGpuInformation();
}
public bool NeedsVertexBufferAlignment(int attrScalarAlignment, out int alignment)
internal bool NeedsVertexBufferAlignment(int attrScalarAlignment, out int alignment)
{
if (Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.VertexBufferAlignment4B))
if (Capabilities.VertexBufferAlignment > 1)
{
alignment = 4;
alignment = (int)Capabilities.VertexBufferAlignment;
return true;
}

View File

@@ -9,6 +9,7 @@ using Ryujinx.Ui;
using Ryujinx.Ui.Widgets;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
@@ -23,20 +24,20 @@ namespace Ryujinx.Modules
{
public static class Updater
{
private const string GitHubApiURL = "https://api.github.com";
private const int ConnectionCount = 4;
internal static bool Running;
private static readonly string HomeDir = AppDomain.CurrentDomain.BaseDirectory;
private static readonly string UpdateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
private static readonly string UpdatePublishDir = Path.Combine(UpdateDir, "publish");
private static readonly int ConnectionCount = 4;
private static string _buildVer;
private static string _platformExt;
private static string _buildUrl;
private static long _buildSize;
private const string GitHubApiURL = "https://api.github.com";
// On Windows, GtkSharp.Dependencies adds these extra dirs that must be cleaned during updates.
private static readonly string[] WindowsDependencyDirs = new string[] { "bin", "etc", "lib", "share" };
@@ -44,7 +45,7 @@ namespace Ryujinx.Modules
{
HttpClient result = new HttpClient();
// Required by GitHub to interract with APIs.
// Required by GitHub to interact with APIs.
result.DefaultRequestHeaders.Add("User-Agent", "Ryujinx-Updater/1.0.0");
return result;
@@ -101,50 +102,48 @@ namespace Ryujinx.Modules
// Get latest version number from GitHub API
try
{
using (HttpClient jsonClient = ConstructHttpClient())
using HttpClient jsonClient = ConstructHttpClient();
string buildInfoURL = $"{GitHubApiURL}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest";
// Fetch latest build information
string fetchedJson = await jsonClient.GetStringAsync(buildInfoURL);
JObject jsonRoot = JObject.Parse(fetchedJson);
JToken assets = jsonRoot["assets"];
_buildVer = (string)jsonRoot["name"];
foreach (JToken asset in assets)
{
string buildInfoURL = $"{GitHubApiURL}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest";
string assetName = (string)asset["name"];
string assetState = (string)asset["state"];
string downloadURL = (string)asset["browser_download_url"];
// Fetch latest build information
string fetchedJson = await jsonClient.GetStringAsync(buildInfoURL);
JObject jsonRoot = JObject.Parse(fetchedJson);
JToken assets = jsonRoot["assets"];
_buildVer = (string)jsonRoot["name"];
foreach (JToken asset in assets)
if (assetName.StartsWith("ryujinx") && assetName.EndsWith(_platformExt))
{
string assetName = (string)asset["name"];
string assetState = (string)asset["state"];
string downloadURL = (string)asset["browser_download_url"];
_buildUrl = downloadURL;
if (assetName.StartsWith("ryujinx") && assetName.EndsWith(_platformExt))
if (assetState != "uploaded")
{
_buildUrl = downloadURL;
if (assetState != "uploaded")
if (showVersionUpToDate)
{
if (showVersionUpToDate)
{
GtkDialog.CreateUpdaterInfoDialog("You are already using the latest version of Ryujinx!", "");
}
return;
GtkDialog.CreateUpdaterInfoDialog("You are already using the latest version of Ryujinx!", "");
}
break;
return;
}
}
if (_buildUrl == null)
break;
}
}
if (_buildUrl == null)
{
if (showVersionUpToDate)
{
if (showVersionUpToDate)
{
GtkDialog.CreateUpdaterInfoDialog("You are already using the latest version of Ryujinx!", "");
}
return;
GtkDialog.CreateUpdaterInfoDialog("You are already using the latest version of Ryujinx!", "");
}
return;
}
}
catch (Exception exception)
@@ -247,160 +246,142 @@ namespace Ryujinx.Modules
for (int i = 0; i < ConnectionCount; i++)
{
list.Add(new byte[0]);
list.Add(Array.Empty<byte>());
}
for (int i = 0; i < ConnectionCount; i++)
{
#pragma warning disable SYSLIB0014
// TODO: WebClient is obsolete and need to be replaced with a more complex logic using HttpClient.
using (WebClient client = new WebClient())
using WebClient client = new WebClient();
#pragma warning restore SYSLIB0014
webClients.Add(client);
if (i == ConnectionCount - 1)
{
webClients.Add(client);
client.Headers.Add("Range", $"bytes={chunkSize * i}-{(chunkSize * (i + 1) - 1) + remainderChunk}");
}
else
{
client.Headers.Add("Range", $"bytes={chunkSize * i}-{chunkSize * (i + 1) - 1}");
}
if (i == ConnectionCount - 1)
client.DownloadProgressChanged += (_, args) =>
{
int index = (int)args.UserState;
Interlocked.Add(ref totalProgressPercentage, -1 * progressPercentage[index]);
Interlocked.Exchange(ref progressPercentage[index], args.ProgressPercentage);
Interlocked.Add(ref totalProgressPercentage, args.ProgressPercentage);
updateDialog.ProgressBar.Value = totalProgressPercentage / ConnectionCount;
};
client.DownloadDataCompleted += (_, args) =>
{
int index = (int)args.UserState;
if (args.Cancelled)
{
client.Headers.Add("Range", $"bytes={chunkSize * i}-{(chunkSize * (i + 1) - 1) + remainderChunk}");
}
else
{
client.Headers.Add("Range", $"bytes={chunkSize * i}-{chunkSize * (i + 1) - 1}");
}
client.DownloadProgressChanged += (_, args) =>
{
int index = (int)args.UserState;
Interlocked.Add(ref totalProgressPercentage, -1 * progressPercentage[index]);
Interlocked.Exchange(ref progressPercentage[index], args.ProgressPercentage);
Interlocked.Add(ref totalProgressPercentage, args.ProgressPercentage);
updateDialog.ProgressBar.Value = totalProgressPercentage / ConnectionCount;
};
client.DownloadDataCompleted += (_, args) =>
{
int index = (int)args.UserState;
if (args.Cancelled)
{
webClients[index].Dispose();
return;
}
list[index] = args.Result;
Interlocked.Increment(ref completedRequests);
if (Equals(completedRequests, ConnectionCount))
{
byte[] mergedFileBytes = new byte[_buildSize];
for (int connectionIndex = 0, destinationOffset = 0; connectionIndex < ConnectionCount; connectionIndex++)
{
Array.Copy(list[connectionIndex], 0, mergedFileBytes, destinationOffset, list[connectionIndex].Length);
destinationOffset += list[connectionIndex].Length;
}
File.WriteAllBytes(updateFile, mergedFileBytes);
try
{
InstallUpdate(updateDialog, updateFile);
}
catch (Exception e)
{
Logger.Warning?.Print(LogClass.Application, e.Message);
Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater.");
DoUpdateWithSingleThread(updateDialog, downloadUrl, updateFile);
return;
}
}
};
try
{
client.DownloadDataAsync(new Uri(downloadUrl), i);
}
catch (WebException ex)
{
Logger.Warning?.Print(LogClass.Application, ex.Message);
Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater.");
for (int j = 0; j < webClients.Count; j++)
{
webClients[j].CancelAsync();
}
DoUpdateWithSingleThread(updateDialog, downloadUrl, updateFile);
webClients[index].Dispose();
return;
}
list[index] = args.Result;
Interlocked.Increment(ref completedRequests);
if (Equals(completedRequests, ConnectionCount))
{
byte[] mergedFileBytes = new byte[_buildSize];
for (int connectionIndex = 0, destinationOffset = 0; connectionIndex < ConnectionCount; connectionIndex++)
{
Array.Copy(list[connectionIndex], 0, mergedFileBytes, destinationOffset, list[connectionIndex].Length);
destinationOffset += list[connectionIndex].Length;
}
File.WriteAllBytes(updateFile, mergedFileBytes);
try
{
InstallUpdate(updateDialog, updateFile);
}
catch (Exception e)
{
Logger.Warning?.Print(LogClass.Application, e.Message);
Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater.");
DoUpdateWithSingleThread(updateDialog, downloadUrl, updateFile);
return;
}
}
};
try
{
client.DownloadDataAsync(new Uri(downloadUrl), i);
}
catch (WebException ex)
{
Logger.Warning?.Print(LogClass.Application, ex.Message);
Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater.");
foreach (WebClient webClient in webClients)
{
webClient.CancelAsync();
}
DoUpdateWithSingleThread(updateDialog, downloadUrl, updateFile);
return;
}
}
}
private static void DoUpdateWithSingleThreadWorker(UpdateDialog updateDialog, string downloadUrl, string updateFile)
{
using (HttpClient client = new HttpClient())
using HttpClient client = new HttpClient();
// We do not want to timeout while downloading
client.Timeout = TimeSpan.FromDays(1);
using (HttpResponseMessage response = client.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead).Result)
using (Stream remoteFileStream = response.Content.ReadAsStreamAsync().Result)
{
// We do not want to timeout while downloading
client.Timeout = TimeSpan.FromDays(1);
using (HttpResponseMessage response = client.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead).Result)
using (Stream remoteFileStream = response.Content.ReadAsStreamAsync().Result)
using (Stream updateFileStream = File.Open(updateFile, FileMode.Create))
{
using (Stream updateFileStream = File.Open(updateFile, FileMode.Create))
long totalBytes = response.Content.Headers.ContentLength.Value;
long byteWritten = 0;
byte[] buffer = new byte[32 * 1024];
while (true)
{
long totalBytes = response.Content.Headers.ContentLength.Value;
long byteWritten = 0;
int readSize = remoteFileStream.Read(buffer);
byte[] buffer = new byte[32 * 1024];
while (true)
if (readSize == 0)
{
int readSize = remoteFileStream.Read(buffer);
if (readSize == 0)
{
break;
}
byteWritten += readSize;
updateDialog.ProgressBar.Value = ((double)byteWritten / totalBytes) * 100;
updateFileStream.Write(buffer, 0, readSize);
break;
}
byteWritten += readSize;
updateDialog.ProgressBar.Value = ((double)byteWritten / totalBytes) * 100;
updateFileStream.Write(buffer, 0, readSize);
}
}
InstallUpdate(updateDialog, updateFile);
}
InstallUpdate(updateDialog, updateFile);
}
private static void DoUpdateWithSingleThread(UpdateDialog updateDialog, string downloadUrl, string updateFile)
{
Thread worker = new Thread(() => DoUpdateWithSingleThreadWorker(updateDialog, downloadUrl, updateFile));
worker.Name = "Updater.SingleThreadWorker";
worker.Start();
}
private static void SetFileExecutable(string path)
{
const UnixFileMode ExecutableFileMode = UnixFileMode.UserExecute |
UnixFileMode.UserWrite |
UnixFileMode.UserRead |
UnixFileMode.GroupRead |
UnixFileMode.GroupWrite |
UnixFileMode.OtherRead |
UnixFileMode.OtherWrite;
if (!OperatingSystem.IsWindows() && File.Exists(path))
Thread worker = new Thread(() => DoUpdateWithSingleThreadWorker(updateDialog, downloadUrl, updateFile))
{
File.SetUnixFileMode(path, ExecutableFileMode);
}
Name = "Updater.SingleThreadWorker"
};
worker.Start();
}
private static async void InstallUpdate(UpdateDialog updateDialog, string updateFile)
@@ -411,15 +392,17 @@ namespace Ryujinx.Modules
if (OperatingSystem.IsLinux())
{
using (Stream inStream = File.OpenRead(updateFile))
using (Stream gzipStream = new GZipInputStream(inStream))
using (TarInputStream tarStream = new TarInputStream(gzipStream, Encoding.ASCII))
{
updateDialog.ProgressBar.MaxValue = inStream.Length;
using Stream inStream = File.OpenRead(updateFile);
using Stream gzipStream = new GZipInputStream(inStream);
using TarInputStream tarStream = new TarInputStream(gzipStream, Encoding.ASCII);
updateDialog.ProgressBar.MaxValue = inStream.Length;
await Task.Run(() =>
await Task.Run(() =>
{
TarEntry tarEntry;
if (!OperatingSystem.IsWindows())
{
TarEntry tarEntry;
while ((tarEntry = tarStream.GetNextEntry()) != null)
{
if (tarEntry.IsDirectory) continue;
@@ -433,6 +416,7 @@ namespace Ryujinx.Modules
tarStream.CopyEntryContents(outStream);
}
File.SetUnixFileMode(outPath, (UnixFileMode)tarEntry.TarHeader.Mode);
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc));
TarEntry entry = tarEntry;
@@ -442,43 +426,41 @@ namespace Ryujinx.Modules
updateDialog.ProgressBar.Value += entry.Size;
});
}
});
}
});
updateDialog.ProgressBar.Value = inStream.Length;
}
updateDialog.ProgressBar.Value = inStream.Length;
}
else
{
using (Stream inStream = File.OpenRead(updateFile))
using (ZipFile zipFile = new ZipFile(inStream))
using Stream inStream = File.OpenRead(updateFile);
using ZipFile zipFile = new ZipFile(inStream);
updateDialog.ProgressBar.MaxValue = zipFile.Count;
await Task.Run(() =>
{
updateDialog.ProgressBar.MaxValue = zipFile.Count;
await Task.Run(() =>
foreach (ZipEntry zipEntry in zipFile)
{
foreach (ZipEntry zipEntry in zipFile)
if (zipEntry.IsDirectory) continue;
string outPath = Path.Combine(UpdateDir, zipEntry.Name);
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
using (Stream zipStream = zipFile.GetInputStream(zipEntry))
using (FileStream outStream = File.OpenWrite(outPath))
{
if (zipEntry.IsDirectory) continue;
string outPath = Path.Combine(UpdateDir, zipEntry.Name);
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
using (Stream zipStream = zipFile.GetInputStream(zipEntry))
using (FileStream outStream = File.OpenWrite(outPath))
{
zipStream.CopyTo(outStream);
}
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc));
Application.Invoke(delegate
{
updateDialog.ProgressBar.Value++;
});
zipStream.CopyTo(outStream);
}
});
}
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc));
Application.Invoke(delegate
{
updateDialog.ProgressBar.Value++;
});
}
});
}
// Delete downloaded zip
@@ -522,8 +504,6 @@ namespace Ryujinx.Modules
Directory.Delete(UpdateDir, true);
SetFileExecutable(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx"));
updateDialog.MainText.Text = "Update Complete!";
updateDialog.SecondaryText.Text = "Do you want to restart Ryujinx now?";
updateDialog.Modal = true;