Skip to content

Commit 93dea84

Browse files
committed
Download DXVK 1.10.3 if no Vulkan 1.3 support is detected
A lot of older GPUs don't support Vulkan 1.3, which DXVK versions > 1.X.X requires. For those users, the "Auto-Install DXVK" function was entirely useless, as the DXVK Heroic installs will never work on their system. Now, Heroic will query the user's GPUs for their Vulkan support and install the older 1.10.3 version of DXVK if no Vulkan 1.3 support is detected. Internally, this is done by: - accepting not just a string as a download URL for tools, but also a function returning a string - DXVK's url function querying Vulkan support and choosing either the latest release or 1.10.3 based on that - new utility functions being added to interface with Vulkan directly Vulkan functions are called using Koffi (https://koffi.dev/), while their implementation is copied over from the spec (spec comments were added where applicable)
1 parent ebb3509 commit 93dea84

File tree

5 files changed

+431
-2
lines changed

5 files changed

+431
-2
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@
170170
"i18next-fs-backend": "^2.1.1",
171171
"i18next-http-backend": "^2.1.1",
172172
"ini": "^3.0.0",
173+
"koffi": "^2.3.20",
173174
"plist": "^3.0.5",
174175
"react": "^18.2.0",
175176
"react-dom": "^18.2.0",
@@ -178,6 +179,7 @@
178179
"react-router-dom": "^6.9.0",
179180
"recharts": "^2.4.3",
180181
"sanitize-filename": "^1.6.3",
182+
"semver": "^7.5.1",
181183
"shlex": "^2.1.2",
182184
"short-uuid": "^4.2.2",
183185
"simple-keyboard": "^3.5.33",

src/backend/tools.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ import { isOnline } from './online_monitor'
2020
import { showDialogBoxModalAuto } from './dialog/dialog'
2121
import { runWineCommand, validWine } from './launcher'
2222
import { chmod } from 'fs/promises'
23+
import {
24+
any_gpu_supports_version,
25+
get_vulkan_instance_version
26+
} from './utils/graphics/vulkan'
27+
import { lt as semverLt } from 'semver'
2328

2429
export const DXVK = {
2530
getLatest: async () => {
@@ -43,7 +48,36 @@ export const DXVK = {
4348
},
4449
{
4550
name: 'dxvk',
46-
url: 'https://api.github.com/repos/doitsujin/dxvk/releases/latest',
51+
url: () => {
52+
if (any_gpu_supports_version([1, 3, 0])) {
53+
const instance_version = get_vulkan_instance_version()
54+
if (
55+
instance_version &&
56+
semverLt(instance_version.join('.'), '1.3.0')
57+
) {
58+
// FIXME: How does the instance version matter? Even with 1.2, newer DXVK seems to work fine
59+
logWarning(
60+
'Vulkan 1.3 is supported by GPUs in this system, but instance version is outdated',
61+
LogPrefix.DXVKInstaller
62+
)
63+
}
64+
return 'https://api.github.com/repos/doitsujin/dxvk/releases/latest'
65+
}
66+
if (any_gpu_supports_version([1, 1, 0])) {
67+
logInfo(
68+
'The GPU(s) in this system only support Vulkan 1.1/1.2, falling back to DXVK 1.10.3',
69+
LogPrefix.DXVKInstaller
70+
)
71+
return 'https://api.github.com/repos/doitsujin/dxvk/releases/tags/v1.10.3'
72+
}
73+
logWarning(
74+
'No GPU with Vulkan 1.1 support found, DXVK will not work',
75+
LogPrefix.DXVKInstaller
76+
)
77+
// FIXME: We currently lack a "Don't download at all" option here, but
78+
// that would also need bigger changes in the frontend
79+
return 'https://api.github.com/repos/doitsujin/dxvk/releases/latest'
80+
},
4781
extractCommand: 'tar -xf',
4882
os: 'linux'
4983
},
@@ -60,9 +94,10 @@ export const DXVK = {
6094
return
6195
}
6296

97+
const download_url = typeof tool.url === 'string' ? tool.url : tool.url()
6398
const {
6499
data: { assets }
65-
} = await axios.get(tool.url)
100+
} = await axios.get(download_url)
66101

67102
const { name, browser_download_url: downloadUrl } = assets[0]
68103
const pkg = name.replace('.tar.gz', '').replace('.tar.xz', '')
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
import koffi from 'koffi'
2+
3+
// Part of https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkResult.html
4+
const VK_SUCCESS = 0
5+
koffi.alias('VkResult', 'int')
6+
7+
// Part of https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkStructureType.html
8+
const VK_STRUCTURE_TYPE_APPLICATION_INFO = 0
9+
const VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO = 1
10+
11+
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_MAX_PHYSICAL_DEVICE_NAME_SIZE.html
12+
const VK_MAX_PHYSICAL_DEVICE_NAME_SIZE = 256
13+
14+
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_UUID_SIZE.html
15+
const VK_UUID_SIZE = 16
16+
17+
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkFlags.html
18+
koffi.alias('VkFlags', 'uint32_t')
19+
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkSampleCountFlags.html
20+
koffi.alias('VkSampleCountFlags', 'VkFlags')
21+
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkDeviceSize.html
22+
koffi.alias('VkDeviceSize', 'uint64_t')
23+
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkPhysicalDeviceLimits.html
24+
const VkPhysicalDeviceLimits = koffi.struct('VkPhysicalDeviceLimits', {
25+
maxImageDimension1D: 'uint32_t',
26+
maxImageDimension2D: 'uint32_t',
27+
maxImageDimension3D: 'uint32_t',
28+
maxImageDimensionCube: 'uint32_t',
29+
maxImageArrayLayers: 'uint32_t',
30+
maxTexelBufferElements: 'uint32_t',
31+
maxUniformBufferRange: 'uint32_t',
32+
maxStorageBufferRange: 'uint32_t',
33+
maxPushConstantsSize: 'uint32_t',
34+
maxMemoryAllocationCount: 'uint32_t',
35+
maxSamplerAllocationCount: 'uint32_t',
36+
bufferImageGranularity: 'uint64_t',
37+
sparseAddressSpaceSize: 'uint64_t',
38+
maxBoundDescriptorSets: 'uint32_t',
39+
maxPerStageDescriptorSamplers: 'uint32_t',
40+
maxPerStageDescriptorUniformBuffers: 'uint32_t',
41+
maxPerStageDescriptorStorageBuffers: 'uint32_t',
42+
maxPerStageDescriptorSampledImages: 'uint32_t',
43+
maxPerStageDescriptorStorageImages: 'uint32_t',
44+
maxPerStageDescriptorInputAttachments: 'uint32_t',
45+
maxPerStageResources: 'uint32_t',
46+
maxDescriptorSetSamplers: 'uint32_t',
47+
maxDescriptorSetUniformBuffers: 'uint32_t',
48+
maxDescriptorSetUniformBuffersDynamic: 'uint32_t',
49+
maxDescriptorSetStorageBuffers: 'uint32_t',
50+
maxDescriptorSetStorageBuffersDynamic: 'uint32_t',
51+
maxDescriptorSetSampledImages: 'uint32_t',
52+
maxDescriptorSetStorageImages: 'uint32_t',
53+
maxDescriptorSetInputAttachments: 'uint32_t',
54+
maxVertexInputAttributes: 'uint32_t',
55+
maxVertexInputBindings: 'uint32_t',
56+
maxVertexInputAttributeOffset: 'uint32_t',
57+
maxVertexInputBindingStride: 'uint32_t',
58+
maxVertexOutputComponents: 'uint32_t',
59+
maxTessellationGenerationLevel: 'uint32_t',
60+
maxTessellationPatchSize: 'uint32_t',
61+
maxTessellationControlPerVertexInputComponents: 'uint32_t',
62+
maxTessellationControlPerVertexOutputComponents: 'uint32_t',
63+
maxTessellationControlPerPatchOutputComponents: 'uint32_t',
64+
maxTessellationControlTotalOutputComponents: 'uint32_t',
65+
maxTessellationEvaluationInputComponents: 'uint32_t',
66+
maxTessellationEvaluationOutputComponents: 'uint32_t',
67+
maxGeometryShaderInvocations: 'uint32_t',
68+
maxGeometryInputComponents: 'uint32_t',
69+
maxGeometryOutputComponents: 'uint32_t',
70+
maxGeometryOutputVertices: 'uint32_t',
71+
maxGeometryTotalOutputComponents: 'uint32_t',
72+
maxFragmentInputComponents: 'uint32_t',
73+
maxFragmentOutputAttachments: 'uint32_t',
74+
maxFragmentDualSrcAttachments: 'uint32_t',
75+
maxFragmentCombinedOutputResources: 'uint32_t',
76+
maxComputeSharedMemorySize: 'uint32_t',
77+
maxComputeWorkGroupCount: koffi.array('uint32_t', 3),
78+
maxComputeWorkGroupInvocations: 'uint32_t',
79+
maxComputeWorkGroupSize: koffi.array('uint32_t', 3),
80+
subPixelPrecisionBits: 'uint32_t',
81+
subTexelPrecisionBits: 'uint32_t',
82+
mipmapPrecisionBits: 'uint32_t',
83+
maxDrawIndexedIndexValue: 'uint32_t',
84+
maxDrawIndirectCount: 'uint32_t',
85+
maxSamplerLodBias: 'float',
86+
maxSamplerAnisotropy: 'float',
87+
maxViewports: 'uint32_t',
88+
maxViewportDimensions: koffi.array('uint32_t', 2),
89+
viewportBoundsRange: koffi.array('float', 2),
90+
viewportSubPixelBits: 'uint32_t',
91+
minMemoryMapAlignment: 'size_t',
92+
minTexelBufferOffsetAlignment: 'uint64_t',
93+
minUniformBufferOffsetAlignment: 'uint64_t',
94+
minStorageBufferOffsetAlignment: 'uint64_t',
95+
minTexelOffset: 'int32_t',
96+
maxTexelOffset: 'uint32_t',
97+
minTexelGatherOffset: 'int32_t',
98+
maxTexelGatherOffset: 'uint32_t',
99+
minInterpolationOffset: 'float',
100+
maxInterpolationOffset: 'float',
101+
subPixelInterpolationOffsetBits: 'uint32_t',
102+
maxFramebufferWidth: 'uint32_t',
103+
maxFramebufferHeight: 'uint32_t',
104+
maxFramebufferLayers: 'uint32_t',
105+
framebufferColorSampleCounts: 'uint32_t',
106+
framebufferDepthSampleCounts: 'uint32_t',
107+
framebufferStencilSampleCounts: 'uint32_t',
108+
framebufferNoAttachmentsSampleCounts: 'uint32_t',
109+
maxColorAttachments: 'uint32_t',
110+
sampledImageColorSampleCounts: 'uint32_t',
111+
sampledImageIntegerSampleCounts: 'uint32_t',
112+
sampledImageDepthSampleCounts: 'uint32_t',
113+
sampledImageStencilSampleCounts: 'uint32_t',
114+
storageImageSampleCounts: 'uint32_t',
115+
maxSampleMaskWords: 'uint32_t',
116+
timestampComputeAndGraphics: 'uint32_t',
117+
timestampPeriod: 'float',
118+
maxClipDistances: 'uint32_t',
119+
maxCullDistances: 'uint32_t',
120+
maxCombinedClipAndCullDistances: 'uint32_t',
121+
discreteQueuePriorities: 'uint32_t',
122+
pointSizeRange: koffi.array('float', 2),
123+
lineWidthRange: koffi.array('float', 2),
124+
pointSizeGranularity: 'float',
125+
lineWidthGranularity: 'float',
126+
strictLines: 'uint32_t',
127+
standardSampleLocations: 'uint32_t',
128+
optimalBufferCopyOffsetAlignment: 'uint64_t',
129+
optimalBufferCopyRowPitchAlignment: 'uint64_t',
130+
nonCoherentAtomSize: 'uint64_t'
131+
})
132+
133+
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkBool32.html
134+
koffi.alias('VkBool32', 'uint32_t')
135+
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkPhysicalDeviceSparseProperties.html
136+
const VkPhysicalDeviceSparseProperties = koffi.struct(
137+
'VkPhysicalDeviceSparseProperties',
138+
{
139+
residencyStandard2DBlockShape: 'VkBool32',
140+
residencyStandard2DMultisampleBlockShape: 'VkBool32',
141+
residencyStandard3DBlockShape: 'VkBool32',
142+
residencyAlignedMipSize: 'VkBool32',
143+
residencyNonResidentStrict: 'VkBool32'
144+
}
145+
)
146+
147+
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkPhysicalDeviceType.html
148+
koffi.alias('VkPhysicalDeviceType', 'int')
149+
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkPhysicalDeviceProperties.html
150+
const VkPhysicalDeviceProperties = koffi.struct('VkPhysicalDeviceProperties', {
151+
apiVersion: 'uint32_t',
152+
driverVersion: 'uint32_t',
153+
vendorID: 'uint32_t',
154+
deviceID: 'uint32_t',
155+
deviceType: 'VkPhysicalDeviceType',
156+
deviceName: koffi.array('char', VK_MAX_PHYSICAL_DEVICE_NAME_SIZE),
157+
pipelineCacheUUID: koffi.array('uint8_t', VK_UUID_SIZE),
158+
limits: VkPhysicalDeviceLimits,
159+
sparseProperties: VkPhysicalDeviceSparseProperties
160+
})
161+
162+
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkStructureType.html
163+
koffi.alias('VkStructureType', 'int')
164+
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkApplicationInfo.html
165+
koffi.struct('VkApplicationInfo', {
166+
sType: 'VkStructureType',
167+
pNext: 'const void*',
168+
pApplicationName: 'const char*',
169+
applicationVersion: 'uint32_t',
170+
pEngineName: 'const char*',
171+
engineVersion: 'uint32_t',
172+
apiVersion: 'uint32_t'
173+
})
174+
175+
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkInstanceCreateInfo.html
176+
koffi.struct('VkInstanceCreateInfo', {
177+
sType: 'VkStructureType',
178+
pNext: 'const void*',
179+
flags: 'uint32_t',
180+
pApplicationInfo: 'const VkApplicationInfo*',
181+
enabledLayerCount: 'uint32_t',
182+
ppEnabledLayerNames: 'const char* const*',
183+
enabledExtensionCount: 'uint32_t',
184+
ppEnabledExtensionNames: 'const char* const*'
185+
})
186+
187+
koffi.pointer('VkAllocationCallbacks', koffi.opaque(), 1)
188+
189+
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkInstance.html
190+
koffi.pointer('VkInstance', koffi.opaque(), 1)
191+
192+
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkPhysicalDevice.html
193+
koffi.pointer('VkPhysicalDevice', koffi.opaque(), 1)
194+
195+
// Part of https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkPhysicalDeviceType.html
196+
const VK_PHYSICAL_DEVICE_TYPE_CPU = 4
197+
198+
export {
199+
VK_SUCCESS,
200+
VK_STRUCTURE_TYPE_APPLICATION_INFO,
201+
VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
202+
VkPhysicalDeviceProperties,
203+
VK_PHYSICAL_DEVICE_TYPE_CPU
204+
}

0 commit comments

Comments
 (0)