Vulkan: GPU device selection

Allows users to override the auto detection for GPU
selection. Normally the GPU selection is done by looping
over the order Vulkan provides and finding the highest
performing device based on its type (discrete, integrated,
software).

However users might have multiple discrete cards and want
to switch between them. Or developers want to validate other
GPUs without rebooting.

This PR adds the ability to override the auto detection
for the vulkan backend.

![image](/attachments/5d9198a8-af08-4eee-aa73-363edea11cd9)

**Future improvements**:
- This PR does not include a command line option. This can be added
  later for render farms.

Pull Request: https://projects.blender.org/blender/blender/pulls/127860
This commit is contained in:
Jeroen Bakker
2024-09-23 11:18:24 +02:00
parent f4c8845b60
commit ddb2179e37
24 changed files with 347 additions and 116 deletions
+10
View File
@@ -713,9 +713,19 @@ typedef struct {
uint32_t frequency;
} GHOST_DisplaySetting;
typedef struct {
/** Index of the GPU device in the list provided by the platform. */
int index;
/** (PCI) Vendor ID of the GPU. */
uint vendor_id;
/** Device ID of the GPU provided by the vendor. */
uint device_id;
} GHOST_GPUDevice;
typedef struct {
int flags;
GHOST_TDrawingContextType context_type;
GHOST_GPUDevice preferred_device;
} GHOST_GPUSettings;
#ifdef WITH_VULKAN_BACKEND
+17 -2
View File
@@ -320,6 +320,7 @@ static std::optional<GHOST_DeviceVK> vulkan_device;
static GHOST_TSuccess ensure_vulkan_device(VkInstance vk_instance,
VkSurfaceKHR vk_surface,
const GHOST_GPUDevice &preferred_device,
const vector<const char *> &required_extensions)
{
if (vulkan_device.has_value()) {
@@ -335,8 +336,10 @@ static GHOST_TSuccess ensure_vulkan_device(VkInstance vk_instance,
vkEnumeratePhysicalDevices(vk_instance, &device_count, physical_devices.data());
int best_device_score = -1;
int device_index = -1;
for (const auto &physical_device : physical_devices) {
GHOST_DeviceVK device_vk(vk_instance, physical_device);
device_index++;
if (!device_vk.has_extensions(required_extensions)) {
continue;
@@ -386,6 +389,16 @@ static GHOST_TSuccess ensure_vulkan_device(VkInstance vk_instance,
default:
break;
}
/* User has configured a preferred device. Add bonus score when vendor and device match. Driver
* id isn't considered as drivers update more frequently and can break the device selection. */
if (device_vk.properties.deviceID == preferred_device.device_id &&
device_vk.properties.vendorID == preferred_device.vendor_id)
{
device_score += 500;
if (preferred_device.index == device_index) {
device_score += 10;
}
}
if (device_score > best_device_score) {
best_physical_device = physical_device;
best_device_score = device_score;
@@ -421,7 +434,8 @@ GHOST_ContextVK::GHOST_ContextVK(bool stereoVisual,
#endif
int contextMajorVersion,
int contextMinorVersion,
int debug)
int debug,
const GHOST_GPUDevice &preferred_device)
: GHOST_Context(stereoVisual),
#ifdef _WIN32
m_hwnd(hwnd),
@@ -440,6 +454,7 @@ GHOST_ContextVK::GHOST_ContextVK(bool stereoVisual,
m_context_major_version(contextMajorVersion),
m_context_minor_version(contextMinorVersion),
m_debug(debug),
m_preferred_device(preferred_device),
m_command_pool(VK_NULL_HANDLE),
m_command_buffer(VK_NULL_HANDLE),
m_surface(VK_NULL_HANDLE),
@@ -1052,7 +1067,7 @@ GHOST_TSuccess GHOST_ContextVK::initializeDrawingContext()
#endif
}
if (!ensure_vulkan_device(instance, m_surface, required_device_extensions)) {
if (!ensure_vulkan_device(instance, m_surface, m_preferred_device, required_device_extensions)) {
return GHOST_kFailure;
}
+3 -1
View File
@@ -80,7 +80,8 @@ class GHOST_ContextVK : public GHOST_Context {
#endif
int contextMajorVersion,
int contextMinorVersion,
int debug);
int debug,
const GHOST_GPUDevice &preferred_device);
/**
* Destructor.
@@ -173,6 +174,7 @@ class GHOST_ContextVK : public GHOST_Context {
const int m_context_major_version;
const int m_context_minor_version;
const int m_debug;
const GHOST_GPUDevice m_preferred_device;
VkCommandPool m_command_pool;
VkCommandBuffer m_command_buffer;
+4 -2
View File
@@ -758,7 +758,8 @@ GHOST_IWindow *GHOST_SystemCocoa::createWindow(const char *title,
gpuSettings.flags & GHOST_gpuStereoVisual,
gpuSettings.flags & GHOST_gpuDebugContext,
is_dialog,
(GHOST_WindowCocoa *)parentWindow);
(GHOST_WindowCocoa *)parentWindow,
gpuSettings.preferred_device);
if (window->getValid()) {
/* Store the pointer to the window. */
@@ -791,7 +792,8 @@ GHOST_IContext *GHOST_SystemCocoa::createOffscreenContext(GHOST_GPUSettings gpuS
switch (gpuSettings.context_type) {
#ifdef WITH_VULKAN_BACKEND
case GHOST_kDrawingContextTypeVulkan: {
GHOST_Context *context = new GHOST_ContextVK(false, nullptr, 1, 2, debug_context);
GHOST_Context *context = new GHOST_ContextVK(
false, nullptr, 1, 2, debug_context, gpuSettings.preferred_device);
if (context->initializeDrawingContext()) {
return context;
}
+4 -2
View File
@@ -8273,7 +8273,8 @@ GHOST_IContext *GHOST_SystemWayland::createOffscreenContext(GHOST_GPUSettings gp
nullptr,
1,
2,
debug_context);
debug_context,
gpuSettings.preferred_device);
if (context->initializeDrawingContext()) {
context->setUserData(wl_surface);
@@ -8400,7 +8401,8 @@ GHOST_IWindow *GHOST_SystemWayland::createWindow(const char *title,
is_dialog,
((gpuSettings.flags & GHOST_gpuStereoVisual) != 0),
exclusive,
(gpuSettings.flags & GHOST_gpuDebugContext) != 0);
(gpuSettings.flags & GHOST_gpuDebugContext) != 0,
gpuSettings.preferred_device);
if (window) {
if (window->getValid()) {
+4 -2
View File
@@ -288,7 +288,8 @@ GHOST_IWindow *GHOST_SystemWin32::createWindow(const char *title,
false,
(GHOST_WindowWin32 *)parentWindow,
((gpuSettings.flags & GHOST_gpuDebugContext) != 0),
is_dialog);
is_dialog,
gpuSettings.preferred_device);
if (window->getValid()) {
/* Store the pointer to the window */
@@ -316,7 +317,8 @@ GHOST_IContext *GHOST_SystemWin32::createOffscreenContext(GHOST_GPUSettings gpuS
switch (gpuSettings.context_type) {
#ifdef WITH_VULKAN_BACKEND
case GHOST_kDrawingContextTypeVulkan: {
GHOST_Context *context = new GHOST_ContextVK(false, (HWND)0, 1, 2, debug_context);
GHOST_Context *context = new GHOST_ContextVK(
false, (HWND)0, 1, 2, debug_context, gpuSettings.preferred_device);
if (context->initializeDrawingContext()) {
return context;
}
+4 -2
View File
@@ -384,7 +384,8 @@ GHOST_IWindow *GHOST_SystemX11::createWindow(const char *title,
is_dialog,
((gpuSettings.flags & GHOST_gpuStereoVisual) != 0),
exclusive,
(gpuSettings.flags & GHOST_gpuDebugContext) != 0);
(gpuSettings.flags & GHOST_gpuDebugContext) != 0,
gpuSettings.preferred_device);
if (window) {
/* Both are now handle in GHOST_WindowX11.cc
@@ -419,7 +420,8 @@ GHOST_IContext *GHOST_SystemX11::createOffscreenContext(GHOST_GPUSettings gpuSet
nullptr,
1,
2,
debug_context);
debug_context,
gpuSettings.preferred_device);
if (context->initializeDrawingContext()) {
return context;
}
+8 -5
View File
@@ -42,6 +42,7 @@ class GHOST_WindowCocoa : public GHOST_Window {
* \param state: The state the window is initially opened with.
* \param type: The type of drawing context installed in this window.
* \param stereoVisual: Stereo visual for quad buffered stereo.
* \param preferred_device: Preferred device to use when new device will be created.
*/
GHOST_WindowCocoa(GHOST_SystemCocoa *systemCocoa,
const char *title,
@@ -50,11 +51,12 @@ class GHOST_WindowCocoa : public GHOST_Window {
uint32_t width,
uint32_t height,
GHOST_TWindowState state,
GHOST_TDrawingContextType type = GHOST_kDrawingContextTypeNone,
const bool stereoVisual = false,
bool is_debug = false,
bool dialog = false,
GHOST_WindowCocoa *parentWindow = nullptr);
GHOST_TDrawingContextType type,
const bool stereoVisual,
bool is_debug,
bool dialog,
GHOST_WindowCocoa *parentWindow,
const GHOST_GPUDevice &preferred_device);
/**
* Destructor.
@@ -312,6 +314,7 @@ class GHOST_WindowCocoa : public GHOST_Window {
bool m_immediateDraw;
bool m_debug_context; // for debug messages during context setup
bool m_is_dialog;
GHOST_GPUDevice m_preferred_device;
};
#ifdef WITH_INPUT_IME
+6 -3
View File
@@ -332,7 +332,8 @@ GHOST_WindowCocoa::GHOST_WindowCocoa(GHOST_SystemCocoa *systemCocoa,
const bool stereoVisual,
bool is_debug,
bool is_dialog,
GHOST_WindowCocoa *parentWindow)
GHOST_WindowCocoa *parentWindow,
const GHOST_GPUDevice &preferred_device)
: GHOST_Window(width, height, state, stereoVisual, false),
m_openGLView(nil),
m_metalView(nil),
@@ -341,7 +342,8 @@ GHOST_WindowCocoa::GHOST_WindowCocoa(GHOST_SystemCocoa *systemCocoa,
m_customCursor(nullptr),
m_immediateDraw(false),
m_debug_context(is_debug),
m_is_dialog(is_dialog)
m_is_dialog(is_dialog),
m_preferred_device(preferred_device)
{
m_fullScreen = false;
@@ -847,7 +849,8 @@ GHOST_Context *GHOST_WindowCocoa::newDrawingContext(GHOST_TDrawingContextType ty
switch (type) {
#ifdef WITH_VULKAN_BACKEND
case GHOST_kDrawingContextTypeVulkan: {
GHOST_Context *context = new GHOST_ContextVK(m_wantStereoVisual, m_metalLayer, 1, 2, true);
GHOST_Context *context = new GHOST_ContextVK(
m_wantStereoVisual, m_metalLayer, 1, 2, true, m_preferred_device);
if (context->initializeDrawingContext()) {
return context;
}
+6 -3
View File
@@ -1755,11 +1755,13 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system,
const bool is_dialog,
const bool stereoVisual,
const bool exclusive,
const bool is_debug)
const bool is_debug,
const GHOST_GPUDevice &preferred_device)
: GHOST_Window(width, height, state, stereoVisual, exclusive),
system_(system),
window_(new GWL_Window),
is_debug_context_(is_debug)
is_debug_context_(is_debug),
preferred_device_(preferred_device)
{
#ifdef USE_EVENT_BACKGROUND_THREAD
std::lock_guard lock_server_guard{*system->server_mutex};
@@ -2505,7 +2507,8 @@ GHOST_Context *GHOST_WindowWayland::newDrawingContext(GHOST_TDrawingContextType
window_->backend.vulkan_window_info,
1,
2,
is_debug_context_);
is_debug_context_,
preferred_device_);
if (context->initializeDrawingContext()) {
return context;
}
+3 -1
View File
@@ -78,7 +78,8 @@ class GHOST_WindowWayland : public GHOST_Window {
const bool is_dialog,
const bool stereoVisual,
const bool exclusive,
const bool is_debug);
const bool is_debug,
const GHOST_GPUDevice &preferred_device);
~GHOST_WindowWayland() override;
@@ -206,6 +207,7 @@ class GHOST_WindowWayland : public GHOST_Window {
GHOST_SystemWayland *system_;
struct GWL_Window *window_;
bool is_debug_context_;
GHOST_GPUDevice preferred_device_;
/**
* \param type: The type of rendering context create.
+6 -3
View File
@@ -61,7 +61,8 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system,
bool alphaBackground,
GHOST_WindowWin32 *parentwindow,
bool is_debug,
bool dialog)
bool dialog,
const GHOST_GPUDevice &preferred_device)
: GHOST_Window(width, height, state, wantStereoVisual, false),
m_mousePresent(false),
m_inLiveResize(false),
@@ -82,7 +83,8 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system,
m_user32(::LoadLibrary("user32.dll")),
m_parentWindowHwnd(parentwindow ? parentwindow->m_hWnd : HWND_DESKTOP),
m_directManipulationHelper(nullptr),
m_debug_context(is_debug)
m_debug_context(is_debug),
m_preferred_device(preferred_device)
{
DWORD style = parentwindow ?
WS_POPUPWINDOW | WS_CAPTION | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_SIZEBOX :
@@ -620,7 +622,8 @@ GHOST_Context *GHOST_WindowWin32::newDrawingContext(GHOST_TDrawingContextType ty
switch (type) {
#ifdef WITH_VULKAN_BACKEND
case GHOST_kDrawingContextTypeVulkan: {
GHOST_Context *context = new GHOST_ContextVK(false, m_hWnd, 1, 2, m_debug_context);
GHOST_Context *context = new GHOST_ContextVK(
false, m_hWnd, 1, 2, m_debug_context, m_preferred_device);
if (context->initializeDrawingContext()) {
return context;
}
+9 -6
View File
@@ -65,6 +65,7 @@ class GHOST_WindowWin32 : public GHOST_Window {
* \param state: The state the window is initially opened with.
* \param type: The type of drawing context installed in this window.
* \param wantStereoVisual: Stereo visual for quad buffered stereo.
* \param preferred_device: Preferred device to use when new device will be created.
* \param parentWindowHwnd: TODO.
*/
GHOST_WindowWin32(GHOST_SystemWin32 *system,
@@ -74,12 +75,13 @@ class GHOST_WindowWin32 : public GHOST_Window {
uint32_t width,
uint32_t height,
GHOST_TWindowState state,
GHOST_TDrawingContextType type = GHOST_kDrawingContextTypeNone,
bool wantStereoVisual = false,
bool alphaBackground = false,
GHOST_WindowWin32 *parentWindow = 0,
bool is_debug = false,
bool dialog = false);
GHOST_TDrawingContextType type,
bool wantStereoVisual,
bool alphaBackground,
GHOST_WindowWin32 *parentWindow,
bool is_debug,
bool dialog,
const GHOST_GPUDevice &preferred_device);
/**
* Destructor.
@@ -384,6 +386,7 @@ class GHOST_WindowWin32 : public GHOST_Window {
HDC m_hDC;
bool m_isDialog;
GHOST_GPUDevice m_preferred_device;
/** Flag for if window has captured the mouse. */
bool m_hasMouseCaptured;
+6 -3
View File
@@ -112,7 +112,8 @@ GHOST_WindowX11::GHOST_WindowX11(GHOST_SystemX11 *system,
const bool is_dialog,
const bool stereoVisual,
const bool exclusive,
const bool is_debug)
const bool is_debug,
const GHOST_GPUDevice &preferred_device)
: GHOST_Window(width, height, state, stereoVisual, exclusive),
m_display(display),
m_visualInfo(nullptr),
@@ -132,7 +133,8 @@ GHOST_WindowX11::GHOST_WindowX11(GHOST_SystemX11 *system,
m_xic(nullptr),
#endif
m_valid_setup(false),
m_is_debug_context(is_debug)
m_is_debug_context(is_debug),
m_preferred_device(preferred_device)
{
#ifdef WITH_OPENGL_BACKEND
if (type == GHOST_kDrawingContextTypeOpenGL) {
@@ -1198,7 +1200,8 @@ GHOST_Context *GHOST_WindowX11::newDrawingContext(GHOST_TDrawingContextType type
nullptr,
1,
2,
m_is_debug_context);
m_is_debug_context,
m_preferred_device);
if (context->initializeDrawingContext()) {
return context;
}
+8 -5
View File
@@ -47,6 +47,7 @@ class GHOST_WindowX11 : public GHOST_Window {
* \param parentWindow: Parent (embedder) window.
* \param type: The type of drawing context installed in this window.
* \param stereoVisual: Stereo visual for quad buffered stereo.
* \param preferred_device: Preferred device to use when new device will be created.
*/
GHOST_WindowX11(GHOST_SystemX11 *system,
Display *display,
@@ -57,11 +58,12 @@ class GHOST_WindowX11 : public GHOST_Window {
uint32_t height,
GHOST_TWindowState state,
GHOST_WindowX11 *parentWindow,
GHOST_TDrawingContextType type = GHOST_kDrawingContextTypeNone,
const bool is_dialog = false,
const bool stereoVisual = false,
const bool exclusive = false,
const bool is_debug = false);
GHOST_TDrawingContextType type,
const bool is_dialog,
const bool stereoVisual,
const bool exclusive,
const bool is_debug,
const GHOST_GPUDevice &preferred_device);
bool getValid() const override;
@@ -250,6 +252,7 @@ class GHOST_WindowX11 : public GHOST_Window {
bool m_valid_setup;
bool m_is_debug_context;
GHOST_GPUDevice m_preferred_device;
void icccmSetState(int state);
int icccmGetState() const;
+4
View File
@@ -696,6 +696,10 @@ class USERPREF_PT_system_display_graphics(SystemPanel, CenterAlignMixIn, Panel):
if system.gpu_backend != gpu.platform.backend_type_get():
layout.label(text="A restart of Blender is required", icon="INFO")
if gpu.platform.backend_type_get() == 'VULKAN':
col = layout.column()
col.prop(system, "gpu_preferred_device")
if system.gpu_backend == 'VULKAN':
col = layout.column()
col.label(text="The Vulkan backend is experimental:", icon="INFO")
+12
View File
@@ -8,6 +8,9 @@
#pragma once
#include <string>
#include "BLI_span.hh"
#include "BLI_sys_types.h"
#include "BLI_utildefines.h"
@@ -63,6 +66,14 @@ enum GPUArchitectureType {
GPU_ARCHITECTURE_TBDR = 1,
};
typedef struct GPUDevice {
std::string identifier;
int index;
uint32_t vendor_id;
uint32_t device_id;
std::string name;
} GPUDevice;
/* GPU Types */
/* TODO: Verify all use-cases of GPU_type_matches to determine which graphics API it should apply
* to, and replace with `GPU_type_matches_ex` where appropriate. */
@@ -79,3 +90,4 @@ const char *GPU_platform_version();
const char *GPU_platform_support_level_key();
const char *GPU_platform_gpu_name();
GPUArchitectureType GPU_platform_architecture();
blender::Span<GPUDevice> GPU_platform_devices_list();
@@ -14,6 +14,7 @@
#include "BLI_dynstr.h"
#include "BLI_string.h"
#include "BLI_string_utils.hh"
#include "BLI_vector.hh"
#include "GPU_platform.hh"
@@ -172,4 +173,9 @@ bool GPU_type_matches_ex(eGPUDeviceType device,
(GPG.backend & backend);
}
blender::Span<GPUDevice> GPU_platform_devices_list()
{
return GPG.devices.as_span();
}
/** \} */
@@ -8,6 +8,8 @@
#pragma once
#include "BLI_vector.hh"
#include "GPU_platform.hh"
namespace blender::gpu {
@@ -26,6 +28,7 @@ class GPUPlatformGlobal {
char *gpu_name = nullptr;
eGPUBackendType backend = GPU_BACKEND_NONE;
GPUArchitectureType architecture_type = GPU_ARCHITECTURE_IMR;
Vector<GPUDevice> devices;
public:
void init(eGPUDeviceType gpu_device,
+126 -76
View File
@@ -38,6 +38,84 @@ static CLG_LogRef LOG = {"gpu.vulkan"};
namespace blender::gpu {
static Vector<StringRefNull> missing_capabilities_get(VkPhysicalDevice vk_physical_device)
{
Vector<StringRefNull> missing_capabilities;
/* Check device features. */
VkPhysicalDeviceFeatures2 features = {};
VkPhysicalDeviceDynamicRenderingFeatures dynamic_rendering = {};
features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
dynamic_rendering.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES;
VkPhysicalDeviceDynamicRenderingUnusedAttachmentsFeaturesEXT
dynamic_rendering_unused_attachments = {};
dynamic_rendering_unused_attachments.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_UNUSED_ATTACHMENTS_FEATURES_EXT;
features.pNext = &dynamic_rendering;
dynamic_rendering.pNext = &dynamic_rendering_unused_attachments;
vkGetPhysicalDeviceFeatures2(vk_physical_device, &features);
#ifndef __APPLE__
if (features.features.geometryShader == VK_FALSE) {
missing_capabilities.append("geometry shaders");
}
if (features.features.logicOp == VK_FALSE) {
missing_capabilities.append("logical operations");
}
#endif
if (features.features.dualSrcBlend == VK_FALSE) {
missing_capabilities.append("dual source blending");
}
if (features.features.imageCubeArray == VK_FALSE) {
missing_capabilities.append("image cube array");
}
if (features.features.multiDrawIndirect == VK_FALSE) {
missing_capabilities.append("multi draw indirect");
}
if (features.features.multiViewport == VK_FALSE) {
missing_capabilities.append("multi viewport");
}
if (features.features.shaderClipDistance == VK_FALSE) {
missing_capabilities.append("shader clip distance");
}
if (features.features.drawIndirectFirstInstance == VK_FALSE) {
missing_capabilities.append("draw indirect first instance");
}
if (features.features.fragmentStoresAndAtomics == VK_FALSE) {
missing_capabilities.append("fragment stores and atomics");
}
if (dynamic_rendering.dynamicRendering == VK_FALSE) {
missing_capabilities.append("dynamic rendering");
}
/* Check device extensions. */
uint32_t vk_extension_count;
vkEnumerateDeviceExtensionProperties(vk_physical_device, nullptr, &vk_extension_count, nullptr);
Array<VkExtensionProperties> vk_extensions(vk_extension_count);
vkEnumerateDeviceExtensionProperties(
vk_physical_device, nullptr, &vk_extension_count, vk_extensions.data());
Set<StringRefNull> extensions;
for (VkExtensionProperties &vk_extension : vk_extensions) {
extensions.add(vk_extension.extensionName);
}
if (!extensions.contains(VK_KHR_SWAPCHAIN_EXTENSION_NAME)) {
missing_capabilities.append(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
}
if (!extensions.contains(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME)) {
missing_capabilities.append(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME);
}
if (!extensions.contains(VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME)) {
missing_capabilities.append(VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME);
}
if (!extensions.contains(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME)) {
missing_capabilities.append(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME);
}
return missing_capabilities;
}
bool VKBackend::is_supported()
{
CLG_logref_init(&LOG);
@@ -71,86 +149,11 @@ bool VKBackend::is_supported()
vkEnumeratePhysicalDevices(vk_instance, &physical_devices_count, vk_physical_devices.data());
for (VkPhysicalDevice vk_physical_device : vk_physical_devices) {
Vector<StringRefNull> missing_capabilities = missing_capabilities_get(vk_physical_device);
/* Check minimum device property limits. */
VkPhysicalDeviceProperties vk_properties = {};
vkGetPhysicalDeviceProperties(vk_physical_device, &vk_properties);
Vector<StringRefNull> missing_capabilities;
/* Check device features. */
VkPhysicalDeviceFeatures2 features = {};
VkPhysicalDeviceDynamicRenderingFeatures dynamic_rendering = {};
features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
dynamic_rendering.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES;
VkPhysicalDeviceDynamicRenderingUnusedAttachmentsFeaturesEXT
dynamic_rendering_unused_attachments = {};
dynamic_rendering_unused_attachments.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_UNUSED_ATTACHMENTS_FEATURES_EXT;
features.pNext = &dynamic_rendering;
dynamic_rendering.pNext = &dynamic_rendering_unused_attachments;
vkGetPhysicalDeviceFeatures2(vk_physical_device, &features);
#ifndef __APPLE__
if (features.features.geometryShader == VK_FALSE) {
missing_capabilities.append("geometry shaders");
}
if (features.features.logicOp == VK_FALSE) {
missing_capabilities.append("logical operations");
}
#endif
if (features.features.dualSrcBlend == VK_FALSE) {
missing_capabilities.append("dual source blending");
}
if (features.features.imageCubeArray == VK_FALSE) {
missing_capabilities.append("image cube array");
}
if (features.features.multiDrawIndirect == VK_FALSE) {
missing_capabilities.append("multi draw indirect");
}
if (features.features.multiViewport == VK_FALSE) {
missing_capabilities.append("multi viewport");
}
if (features.features.shaderClipDistance == VK_FALSE) {
missing_capabilities.append("shader clip distance");
}
if (features.features.drawIndirectFirstInstance == VK_FALSE) {
missing_capabilities.append("draw indirect first instance");
}
if (features.features.fragmentStoresAndAtomics == VK_FALSE) {
missing_capabilities.append("fragment stores and atomics");
}
if (dynamic_rendering.dynamicRendering == VK_FALSE) {
missing_capabilities.append("dynamic rendering");
}
/* Check device extensions. */
uint32_t vk_extension_count;
vkEnumerateDeviceExtensionProperties(
vk_physical_device, nullptr, &vk_extension_count, nullptr);
Array<VkExtensionProperties> vk_extensions(vk_extension_count);
vkEnumerateDeviceExtensionProperties(
vk_physical_device, nullptr, &vk_extension_count, vk_extensions.data());
Set<StringRefNull> extensions;
for (VkExtensionProperties &vk_extension : vk_extensions) {
extensions.add(vk_extension.extensionName);
}
if (!extensions.contains(VK_KHR_SWAPCHAIN_EXTENSION_NAME)) {
missing_capabilities.append(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
}
if (!extensions.contains(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME)) {
missing_capabilities.append(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME);
}
if (!extensions.contains(VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME)) {
missing_capabilities.append(VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME);
}
if (!extensions.contains(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME)) {
missing_capabilities.append(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME);
}
/* Report result. */
if (missing_capabilities.is_empty()) {
/* This device meets minimum requirements. */
@@ -206,6 +209,53 @@ void VKBackend::platform_init()
"",
"",
GPU_ARCHITECTURE_IMR);
/* Query for all compatible devices */
VkApplicationInfo vk_application_info = {VK_STRUCTURE_TYPE_APPLICATION_INFO};
vk_application_info.pApplicationName = "Blender";
vk_application_info.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
vk_application_info.pEngineName = "Blender";
vk_application_info.engineVersion = VK_MAKE_VERSION(1, 0, 0);
vk_application_info.apiVersion = VK_API_VERSION_1_2;
const char *instance_extensions[] = {VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME};
VkInstanceCreateInfo vk_instance_info = {VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO};
vk_instance_info.pApplicationInfo = &vk_application_info;
vk_instance_info.enabledExtensionCount = 1;
vk_instance_info.ppEnabledExtensionNames = instance_extensions;
VkInstance vk_instance = VK_NULL_HANDLE;
vkCreateInstance(&vk_instance_info, nullptr, &vk_instance);
BLI_assert(vk_instance != VK_NULL_HANDLE);
uint32_t physical_devices_count = 0;
vkEnumeratePhysicalDevices(vk_instance, &physical_devices_count, nullptr);
Array<VkPhysicalDevice> vk_physical_devices(physical_devices_count);
vkEnumeratePhysicalDevices(vk_instance, &physical_devices_count, vk_physical_devices.data());
int index = 0;
for (VkPhysicalDevice vk_physical_device : vk_physical_devices) {
if (missing_capabilities_get(vk_physical_device).is_empty()) {
VkPhysicalDeviceProperties vk_properties = {};
vkGetPhysicalDeviceProperties(vk_physical_device, &vk_properties);
std::stringstream identifier;
identifier << std::hex << vk_properties.vendorID << "/" << vk_properties.deviceID << "/"
<< index;
GPG.devices.append({identifier.str(),
index,
vk_properties.vendorID,
vk_properties.deviceID,
std::string(vk_properties.deviceName)});
}
index++;
}
vkDestroyInstance(vk_instance, nullptr);
std::sort(GPG.devices.begin(), GPG.devices.end(), [&](const GPUDevice &a, const GPUDevice &b) {
if (a.name == b.name) {
return a.index < b.index;
}
return a.name < b.name;
});
}
void VKBackend::platform_init(const VKDevice &device)
@@ -1002,6 +1002,11 @@ typedef struct UserDef {
/** Seconds to zoom around current frame. */
float view_frame_seconds;
/** Preferred device/vendor for GPU device selection. */
int gpu_preferred_index;
uint32_t gpu_preferred_vendor_id;
uint32_t gpu_preferred_device_id;
char _pad16[4];
/** #eGPUBackendType */
short gpu_backend;
@@ -8,6 +8,7 @@
#include <climits>
#include <cstdlib>
#include <sstream>
#include "DNA_brush_types.h"
#include "DNA_curve_types.h"
@@ -174,6 +175,11 @@ static const EnumPropertyItem rna_enum_preference_gpu_backend_items[] = {
{GPU_BACKEND_VULKAN, "VULKAN", 0, "Vulkan (experimental)", "Use Vulkan backend"},
{0, nullptr, 0, nullptr, nullptr},
};
static const EnumPropertyItem rna_enum_preference_gpu_preferred_device_items[] = {
{0, "AUTO", 0, "Auto", "Auto detect best GPU for running Blender"},
RNA_ENUM_ITEM_SEPR,
{0, nullptr, 0, nullptr, nullptr},
};
static const EnumPropertyItem rna_enum_preferences_extension_repo_source_type_items[] = {
{USER_EXTENSION_REPO_SOURCE_USER,
@@ -1421,6 +1427,71 @@ static const EnumPropertyItem *rna_preference_gpu_backend_itemf(bContext * /*C*/
return result;
}
static const EnumPropertyItem *rna_preference_gpu_preferred_device_itemf(bContext * /*C*/,
PointerRNA * /*ptr*/,
PropertyRNA * /*prop*/,
bool *r_free)
{
int totitem = 0;
EnumPropertyItem *result = nullptr;
for (int i = 0; rna_enum_preference_gpu_preferred_device_items[i].identifier != nullptr; i++) {
const EnumPropertyItem *item = &rna_enum_preference_gpu_preferred_device_items[i];
RNA_enum_item_add(&result, &totitem, item);
}
int index = 1;
for (const GPUDevice &gpu_device : GPU_platform_devices_list()) {
EnumPropertyItem item = {};
item.value = index;
item.identifier = gpu_device.identifier.c_str();
item.name = gpu_device.name.c_str();
item.description = gpu_device.name.c_str();
RNA_enum_item_add(&result, &totitem, &item);
index += 1;
}
RNA_enum_item_end(&result, &totitem);
*r_free = true;
return result;
}
static int rna_preference_gpu_preferred_device_get(PointerRNA *ptr)
{
UserDef *preferences = (UserDef *)ptr->data;
int index = 1;
for (const GPUDevice &gpu_device : GPU_platform_devices_list()) {
if (gpu_device.index == preferences->gpu_preferred_index &&
gpu_device.vendor_id == preferences->gpu_preferred_vendor_id &&
gpu_device.device_id == preferences->gpu_preferred_device_id)
{
/* Offset by one as first item in the list is always autodetection. */
return index;
}
index += 1;
}
return 0;
}
static void rna_preference_gpu_preferred_device_set(PointerRNA *ptr, int value)
{
UserDef *preferences = (UserDef *)ptr->data;
if (value > 0) {
value -= 1;
blender::Span<GPUDevice> devices = GPU_platform_devices_list();
if (value < devices.size()) {
const GPUDevice &device = devices[value];
preferences->gpu_preferred_index = device.index;
preferences->gpu_preferred_vendor_id = device.vendor_id;
preferences->gpu_preferred_device_id = device.device_id;
return;
}
}
preferences->gpu_preferred_index = 0;
preferences->gpu_preferred_vendor_id = 0u;
preferences->gpu_preferred_device_id = 0u;
}
#else
# define USERDEF_TAG_DIRTY_PROPERTY_UPDATE_ENABLE \
@@ -6259,6 +6330,18 @@ static void rna_def_userdef_system(BlenderRNA *brna)
"GPU Backend",
"GPU backend to use (requires restarting Blender for changes to take effect)");
prop = RNA_def_property(srna, "gpu_preferred_device", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, rna_enum_preference_gpu_preferred_device_items);
RNA_def_property_enum_funcs(prop,
"rna_preference_gpu_preferred_device_get",
"rna_preference_gpu_preferred_device_set",
"rna_preference_gpu_preferred_device_itemf");
RNA_def_property_enum_default(prop, 0);
RNA_def_property_ui_text(prop,
"Device",
"Preferred device to select during detection (requires restarting "
"Blender for changes to take effect)");
prop = RNA_def_property(srna, "max_shader_compilation_subprocesses", PROP_INT, PROP_NONE);
RNA_def_property_range(prop, 0, INT16_MAX);
RNA_def_property_ui_text(prop,
@@ -1583,6 +1583,9 @@ static GHOST_WindowHandle playanim_window_open(
GHOST_GPUSettings gpusettings = {0};
const eGPUBackendType gpu_backend = GPU_backend_type_selection_get();
gpusettings.context_type = wm_ghost_drawing_context_type(gpu_backend);
gpusettings.preferred_device.index = U.gpu_preferred_index;
gpusettings.preferred_device.vendor_id = U.gpu_preferred_vendor_id;
gpusettings.preferred_device.device_id = U.gpu_preferred_device_id;
{
bool screen_size_valid = false;
@@ -736,6 +736,9 @@ static void wm_window_ghostwindow_add(wmWindowManager *wm,
eGPUBackendType gpu_backend = GPU_backend_type_selection_get();
gpuSettings.context_type = wm_ghost_drawing_context_type(gpu_backend);
gpuSettings.preferred_device.index = U.gpu_preferred_index;
gpuSettings.preferred_device.vendor_id = U.gpu_preferred_vendor_id;
gpuSettings.preferred_device.device_id = U.gpu_preferred_device_id;
int posx = 0;
int posy = 0;
@@ -3039,6 +3042,10 @@ void *WM_system_gpu_context_create()
if (G.debug & G_DEBUG_GPU) {
gpuSettings.flags |= GHOST_gpuDebugContext;
}
gpuSettings.preferred_device.index = U.gpu_preferred_index;
gpuSettings.preferred_device.vendor_id = U.gpu_preferred_vendor_id;
gpuSettings.preferred_device.device_id = U.gpu_preferred_device_id;
return GHOST_CreateGPUContext(g_system, gpuSettings);
}