From ddb2179e37ae861ce8f2fe3dfff4d3528b3a897c Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Mon, 23 Sep 2024 11:18:24 +0200 Subject: [PATCH] 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 --- intern/ghost/GHOST_Types.h | 10 + intern/ghost/intern/GHOST_ContextVK.cc | 19 +- intern/ghost/intern/GHOST_ContextVK.hh | 4 +- intern/ghost/intern/GHOST_SystemCocoa.mm | 6 +- intern/ghost/intern/GHOST_SystemWayland.cc | 6 +- intern/ghost/intern/GHOST_SystemWin32.cc | 6 +- intern/ghost/intern/GHOST_SystemX11.cc | 6 +- intern/ghost/intern/GHOST_WindowCocoa.hh | 13 +- intern/ghost/intern/GHOST_WindowCocoa.mm | 9 +- intern/ghost/intern/GHOST_WindowWayland.cc | 9 +- intern/ghost/intern/GHOST_WindowWayland.hh | 4 +- intern/ghost/intern/GHOST_WindowWin32.cc | 9 +- intern/ghost/intern/GHOST_WindowWin32.hh | 15 +- intern/ghost/intern/GHOST_WindowX11.cc | 9 +- intern/ghost/intern/GHOST_WindowX11.hh | 13 +- scripts/startup/bl_ui/space_userpref.py | 4 + source/blender/gpu/GPU_platform.hh | 12 ++ source/blender/gpu/intern/gpu_platform.cc | 6 + .../gpu/intern/gpu_platform_private.hh | 3 + source/blender/gpu/vulkan/vk_backend.cc | 202 +++++++++++------- source/blender/makesdna/DNA_userdef_types.h | 5 + source/blender/makesrna/intern/rna_userdef.cc | 83 +++++++ .../windowmanager/intern/wm_playanim.cc | 3 + .../blender/windowmanager/intern/wm_window.cc | 7 + 24 files changed, 347 insertions(+), 116 deletions(-) diff --git a/intern/ghost/GHOST_Types.h b/intern/ghost/GHOST_Types.h index 21045a7975a..4c4e9916587 100644 --- a/intern/ghost/GHOST_Types.h +++ b/intern/ghost/GHOST_Types.h @@ -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 diff --git a/intern/ghost/intern/GHOST_ContextVK.cc b/intern/ghost/intern/GHOST_ContextVK.cc index 7556c935e62..f9c1be9e34b 100644 --- a/intern/ghost/intern/GHOST_ContextVK.cc +++ b/intern/ghost/intern/GHOST_ContextVK.cc @@ -320,6 +320,7 @@ static std::optional vulkan_device; static GHOST_TSuccess ensure_vulkan_device(VkInstance vk_instance, VkSurfaceKHR vk_surface, + const GHOST_GPUDevice &preferred_device, const vector &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; } diff --git a/intern/ghost/intern/GHOST_ContextVK.hh b/intern/ghost/intern/GHOST_ContextVK.hh index 6712b173bea..c262c197219 100644 --- a/intern/ghost/intern/GHOST_ContextVK.hh +++ b/intern/ghost/intern/GHOST_ContextVK.hh @@ -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; diff --git a/intern/ghost/intern/GHOST_SystemCocoa.mm b/intern/ghost/intern/GHOST_SystemCocoa.mm index e20f2902c16..5e2834b6ba3 100644 --- a/intern/ghost/intern/GHOST_SystemCocoa.mm +++ b/intern/ghost/intern/GHOST_SystemCocoa.mm @@ -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; } diff --git a/intern/ghost/intern/GHOST_SystemWayland.cc b/intern/ghost/intern/GHOST_SystemWayland.cc index 1d6cc37bf19..addbb3d4ddc 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cc +++ b/intern/ghost/intern/GHOST_SystemWayland.cc @@ -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()) { diff --git a/intern/ghost/intern/GHOST_SystemWin32.cc b/intern/ghost/intern/GHOST_SystemWin32.cc index bb2bc7c3df8..dc96d0e768d 100644 --- a/intern/ghost/intern/GHOST_SystemWin32.cc +++ b/intern/ghost/intern/GHOST_SystemWin32.cc @@ -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; } diff --git a/intern/ghost/intern/GHOST_SystemX11.cc b/intern/ghost/intern/GHOST_SystemX11.cc index 0c76388c286..2f848319958 100644 --- a/intern/ghost/intern/GHOST_SystemX11.cc +++ b/intern/ghost/intern/GHOST_SystemX11.cc @@ -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; } diff --git a/intern/ghost/intern/GHOST_WindowCocoa.hh b/intern/ghost/intern/GHOST_WindowCocoa.hh index 46098ac4bed..7c20f20e1e0 100644 --- a/intern/ghost/intern/GHOST_WindowCocoa.hh +++ b/intern/ghost/intern/GHOST_WindowCocoa.hh @@ -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 diff --git a/intern/ghost/intern/GHOST_WindowCocoa.mm b/intern/ghost/intern/GHOST_WindowCocoa.mm index 2154749a428..925bc64745e 100644 --- a/intern/ghost/intern/GHOST_WindowCocoa.mm +++ b/intern/ghost/intern/GHOST_WindowCocoa.mm @@ -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; } diff --git a/intern/ghost/intern/GHOST_WindowWayland.cc b/intern/ghost/intern/GHOST_WindowWayland.cc index 9786ac22994..e0d08c7f5f4 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.cc +++ b/intern/ghost/intern/GHOST_WindowWayland.cc @@ -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; } diff --git a/intern/ghost/intern/GHOST_WindowWayland.hh b/intern/ghost/intern/GHOST_WindowWayland.hh index 0526d87e7c5..fe7b2754b1b 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.hh +++ b/intern/ghost/intern/GHOST_WindowWayland.hh @@ -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. diff --git a/intern/ghost/intern/GHOST_WindowWin32.cc b/intern/ghost/intern/GHOST_WindowWin32.cc index b2de8093dca..f497fd9961c 100644 --- a/intern/ghost/intern/GHOST_WindowWin32.cc +++ b/intern/ghost/intern/GHOST_WindowWin32.cc @@ -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; } diff --git a/intern/ghost/intern/GHOST_WindowWin32.hh b/intern/ghost/intern/GHOST_WindowWin32.hh index aeb64514784..0886b48128b 100644 --- a/intern/ghost/intern/GHOST_WindowWin32.hh +++ b/intern/ghost/intern/GHOST_WindowWin32.hh @@ -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; diff --git a/intern/ghost/intern/GHOST_WindowX11.cc b/intern/ghost/intern/GHOST_WindowX11.cc index 2d540bb4767..177f1496bfe 100644 --- a/intern/ghost/intern/GHOST_WindowX11.cc +++ b/intern/ghost/intern/GHOST_WindowX11.cc @@ -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; } diff --git a/intern/ghost/intern/GHOST_WindowX11.hh b/intern/ghost/intern/GHOST_WindowX11.hh index 97779418cf7..9ced9106d6e 100644 --- a/intern/ghost/intern/GHOST_WindowX11.hh +++ b/intern/ghost/intern/GHOST_WindowX11.hh @@ -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; diff --git a/scripts/startup/bl_ui/space_userpref.py b/scripts/startup/bl_ui/space_userpref.py index 5d063a47711..d2b905b1c1f 100644 --- a/scripts/startup/bl_ui/space_userpref.py +++ b/scripts/startup/bl_ui/space_userpref.py @@ -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") diff --git a/source/blender/gpu/GPU_platform.hh b/source/blender/gpu/GPU_platform.hh index 49875f5da4a..6843d62b74b 100644 --- a/source/blender/gpu/GPU_platform.hh +++ b/source/blender/gpu/GPU_platform.hh @@ -8,6 +8,9 @@ #pragma once +#include + +#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 GPU_platform_devices_list(); diff --git a/source/blender/gpu/intern/gpu_platform.cc b/source/blender/gpu/intern/gpu_platform.cc index af0f85d5ce8..9842df4dcb8 100644 --- a/source/blender/gpu/intern/gpu_platform.cc +++ b/source/blender/gpu/intern/gpu_platform.cc @@ -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 GPU_platform_devices_list() +{ + return GPG.devices.as_span(); +} + /** \} */ diff --git a/source/blender/gpu/intern/gpu_platform_private.hh b/source/blender/gpu/intern/gpu_platform_private.hh index d4b410bb4ed..68c67ad2fd9 100644 --- a/source/blender/gpu/intern/gpu_platform_private.hh +++ b/source/blender/gpu/intern/gpu_platform_private.hh @@ -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 devices; public: void init(eGPUDeviceType gpu_device, diff --git a/source/blender/gpu/vulkan/vk_backend.cc b/source/blender/gpu/vulkan/vk_backend.cc index 24cf6ff506e..1fdf6c0e888 100644 --- a/source/blender/gpu/vulkan/vk_backend.cc +++ b/source/blender/gpu/vulkan/vk_backend.cc @@ -38,6 +38,84 @@ static CLG_LogRef LOG = {"gpu.vulkan"}; namespace blender::gpu { +static Vector missing_capabilities_get(VkPhysicalDevice vk_physical_device) +{ + Vector 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 vk_extensions(vk_extension_count); + vkEnumerateDeviceExtensionProperties( + vk_physical_device, nullptr, &vk_extension_count, vk_extensions.data()); + Set 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 missing_capabilities = missing_capabilities_get(vk_physical_device); - /* Check minimum device property limits. */ VkPhysicalDeviceProperties vk_properties = {}; vkGetPhysicalDeviceProperties(vk_physical_device, &vk_properties); - Vector 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 vk_extensions(vk_extension_count); - vkEnumerateDeviceExtensionProperties( - vk_physical_device, nullptr, &vk_extension_count, vk_extensions.data()); - Set 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 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) diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h index 0787fecea7b..2124df14e7d 100644 --- a/source/blender/makesdna/DNA_userdef_types.h +++ b/source/blender/makesdna/DNA_userdef_types.h @@ -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; diff --git a/source/blender/makesrna/intern/rna_userdef.cc b/source/blender/makesrna/intern/rna_userdef.cc index c0f64272dca..9d45862795f 100644 --- a/source/blender/makesrna/intern/rna_userdef.cc +++ b/source/blender/makesrna/intern/rna_userdef.cc @@ -8,6 +8,7 @@ #include #include +#include #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 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, diff --git a/source/blender/windowmanager/intern/wm_playanim.cc b/source/blender/windowmanager/intern/wm_playanim.cc index 131b0ca9f70..12e2e43b1b7 100644 --- a/source/blender/windowmanager/intern/wm_playanim.cc +++ b/source/blender/windowmanager/intern/wm_playanim.cc @@ -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; diff --git a/source/blender/windowmanager/intern/wm_window.cc b/source/blender/windowmanager/intern/wm_window.cc index 369c2769d3c..7bd6525ba91 100644 --- a/source/blender/windowmanager/intern/wm_window.cc +++ b/source/blender/windowmanager/intern/wm_window.cc @@ -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); }