Skip to content

processors/vulkan_gpu_probe.cpp

Namespaces

Name
sgns
sgns::sgprocessing
Artifact and manifest binary serialization.

Functions

Name
bool HasUsableVulkanDevice()
bool HasUsableVulkanDeviceCached()

Functions Documentation

function HasUsableVulkanDevice

bool HasUsableVulkanDevice()

Runtime probe answering "does this host have at least one usable Vulkan device?", mirroring RenderProcessor::IsAcceptable's DISCRETE_GPU/ INTEGRATED_GPU device-type filter (D-32). Builds and immediately tears down its own throwaway VkInstance – it never creates a VkDevice and never touches RenderProcessor's own Vulkan state.

Callers MUST treat a false return as "skip GPU-dependent work" (e.g. via GTEST_SKIP()), never as a hard error – a GPU-less host is an expected, valid environment (D-34), not a failure condition.

Never throws.

function HasUsableVulkanDeviceCached

bool HasUsableVulkanDeviceCached()

Process-lifetime cached variant of HasUsableVulkanDevice().

The uncached probe builds and destroys a whole VkInstance plus a full physical-device enumeration per call – far too heavy for per-session or per-chunk MNN backend selection (MNN_Volume creates a session per chunk). The device set does not change over a process's lifetime in any environment we care about, so the first caller's result is cached in a function-local static (thread-safe initialization guaranteed by C++11) and every later caller gets a plain bool read.

Intended use: MNN processors select MNN_FORWARD_VULKAN only when this returns true; on software-Vulkan-only hosts (llvmpipe/lavapipe in GPU-less CI containers) they select MNN_FORWARD_CPU instead – the native CPU backend is dramatically faster than Vulkan-on-lavapipe and restores the pre-WHOLEARCHIVE behavior these hosts always had (MNN's empty creator map silently CPU-fell-back then). Render passes keep their existing GTEST_SKIP policy instead.

Never throws.

Source code

#include "processors/vulkan_gpu_probe.hpp"
#include "processors/processing_processor_render.hpp"
#include "processingbase/vulkan_init_guard.hpp"
#include <VkBootstrap.h>
#include <algorithm>
#include <mutex>
#include <spdlog/spdlog.h>

namespace sgns::sgprocessing
{
    bool HasUsableVulkanDevice()
    {
        try
        {
            std::lock_guard<std::mutex> lock( sgns::sgprocessing::VulkanInitMutex() );

            // On macOS MoltenVK is statically linked (libMoltenVK.a), so there is
            // no libvulkan.dylib for vk-bootstrap's default dlopen path to find.
            // Pass the statically-available vkGetInstanceProcAddr directly to
            // bypass dynamic loading entirely.
#if defined(__APPLE__)
            vkb::InstanceBuilder instance_builder( vkGetInstanceProcAddr );
#else
            vkb::InstanceBuilder instance_builder;
#endif
            auto inst_ret = instance_builder.set_app_name( "SGProcessingManager GPU Probe" )
                                  .set_app_version( 1, 0, 0 )
                                  .request_validation_layers( false )
                                  .build();
            if ( !inst_ret )
            {
                // No instance was created -- nothing to destroy.
                return false;
            }
            auto vkb_instance = inst_ret.value();

            vkb::PhysicalDeviceSelector selector( vkb_instance );
            // Headless/offscreen probe -- no VkSurfaceKHR ever exists, same rationale
            // as RenderProcessor::InitializeContext()'s own require_present(false).
            selector.require_present( false );
            auto devices_ret = selector.select_devices();
            if ( !devices_ret )
            {
                vkb::destroy_instance( vkb_instance );
                return false;
            }

            auto devices = devices_ret.value();

            devices.erase( std::remove_if( devices.begin(),
                                            devices.end(),
                                            []( const vkb::PhysicalDevice &d )
                                            { return !RenderProcessor::IsAcceptable( d.properties.deviceType ); } ),
                           devices.end() );

            bool usable = !devices.empty();

            vkb::destroy_instance( vkb_instance );

            return usable;
        }
        catch ( ... )
        {
            // Never throw -- a probe failure of any kind means "no usable device".
            return false;
        }
    }

    bool HasUsableVulkanDeviceCached()
    {
        // Function-local static: C++11 guarantees thread-safe one-shot
        // initialization, so concurrent MNN session creations race through
        // the probe exactly once.
        static const bool usable = []()
        {
            const bool hasDevice = HasUsableVulkanDevice();
            if ( !hasDevice )
            {
                spdlog::info( "[SGProcessingManager] No usable Vulkan GPU (DISCRETE_GPU/INTEGRATED_GPU) "
                              "present; MNN inference sessions fall back to the CPU backend "
                              "(software Vulkan / llvmpipe is deliberately not used)" );
            }
            return hasDevice;
        }();
        return usable;
    }
}

Updated on 2026-09-17 at 06:29:15 +0000