mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
2367 lines
148 KiB
C++
2367 lines
148 KiB
C++
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRendererDevice.cpp
|
|
// Copyright (c) 2025-2026 MobileGL-Dev
|
|
// Licensed under the GNU Lesser General Public License v3.0:
|
|
// https://www.gnu.org/licenses/gpl-3.0.txt
|
|
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
// End of Source File Header
|
|
|
|
#include "VulkanRendererInternal.h"
|
|
|
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
|
#if defined(__APPLE__)
|
|
namespace {
|
|
constexpr unsigned long kNSWindowStyleMaskBorderless = 0;
|
|
constexpr unsigned long kNSBackingStoreBuffered = 2;
|
|
|
|
id SendId(id receiver, const char* selector) {
|
|
return ObjcMsgSend<id (*)(id, SEL)>()(receiver, sel_registerName(selector));
|
|
}
|
|
|
|
void SendVoidBool(id receiver, const char* selector, bool value) {
|
|
ObjcMsgSend<void (*)(id, SEL, bool)>()(receiver, sel_registerName(selector), value);
|
|
}
|
|
|
|
void SendVoidId(id receiver, const char* selector, id value) {
|
|
ObjcMsgSend<void (*)(id, SEL, id)>()(receiver, sel_registerName(selector), value);
|
|
}
|
|
|
|
void SendVoidCGRect(id receiver, const char* selector, CGRect value) {
|
|
ObjcMsgSend<void (*)(id, SEL, CGRect)>()(receiver, sel_registerName(selector), value);
|
|
}
|
|
|
|
void SendVoidCGSize(id receiver, const char* selector, CGSize value) {
|
|
ObjcMsgSend<void (*)(id, SEL, CGSize)>()(receiver, sel_registerName(selector), value);
|
|
}
|
|
|
|
id Retain(id object) {
|
|
return object ? SendId(object, "retain") : nil;
|
|
}
|
|
|
|
void* CreateInternalMetalLayer(Uint32 width, Uint32 height, void** outWindow) {
|
|
const auto surfaceWidth = static_cast<CGFloat>(std::max<Uint32>(width, 1));
|
|
const auto surfaceHeight = static_cast<CGFloat>(std::max<Uint32>(height, 1));
|
|
id windowClass = reinterpret_cast<id>(objc_getClass("NSWindow"));
|
|
id metalLayerClass = reinterpret_cast<id>(objc_getClass("CAMetalLayer"));
|
|
MOBILEGL_ASSERT(windowClass && metalLayerClass,
|
|
"Failed to resolve NSWindow/CAMetalLayer for DirectVulkan pbuffer");
|
|
|
|
CGRect frame = {{0.0, 0.0}, {surfaceWidth, surfaceHeight}};
|
|
id window = SendId(windowClass, "alloc");
|
|
window = ObjcMsgSend<id (*)(id, SEL, CGRect, unsigned long, unsigned long, bool)>()(
|
|
window, sel_registerName("initWithContentRect:styleMask:backing:defer:"), frame,
|
|
kNSWindowStyleMaskBorderless, kNSBackingStoreBuffered, true);
|
|
MOBILEGL_ASSERT(window, "Failed to create hidden NSWindow for DirectVulkan pbuffer");
|
|
|
|
id contentView = SendId(window, "contentView");
|
|
MOBILEGL_ASSERT(contentView, "Failed to query hidden NSWindow contentView");
|
|
SendVoidBool(contentView, "setWantsLayer:", true);
|
|
|
|
id metalLayer = SendId(metalLayerClass, "layer");
|
|
MOBILEGL_ASSERT(metalLayer, "Failed to create hidden CAMetalLayer for DirectVulkan pbuffer");
|
|
Retain(metalLayer);
|
|
SendVoidCGRect(metalLayer, "setFrame:", frame);
|
|
SendVoidCGSize(metalLayer, "setDrawableSize:", frame.size);
|
|
SendVoidId(contentView, "setLayer:", metalLayer);
|
|
|
|
*outWindow = window;
|
|
return metalLayer;
|
|
}
|
|
} // namespace
|
|
#endif
|
|
|
|
void VulkanRenderer::CreateInstance() {
|
|
#if defined(VK_USE_PLATFORM_METAL_EXT)
|
|
// MoltenVK snapshots its configuration when the loader first discovers the ICD. Set
|
|
// this before instance-extension enumeration, while preserving an explicit user value.
|
|
if (std::getenv("MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS") == nullptr) {
|
|
if (::setenv("MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS", "1", 0) == 0) {
|
|
MGLOG_I("MoltenVK: enabling Metal argument buffers");
|
|
} else {
|
|
MGLOG_W("MoltenVK: could not enable Metal argument buffers before ICD discovery");
|
|
}
|
|
}
|
|
#endif
|
|
m_extensions = EnumerateInstanceExtensions();
|
|
MGLOG_I("Got %d Vulkan instance extensions: ", m_extensions.size());
|
|
for (auto& extension : m_extensions) {
|
|
MGLOG_I(" %s (r.%u)", extension.extensionName, extension.specVersion);
|
|
}
|
|
|
|
Bool validationLayerAvailable = CheckValidationLayerSupport();
|
|
MGLOG_I("Validation layers %s.", validationLayerAvailable ? "available" : "not available");
|
|
MGLOG_I("Validation layers %s.", m_config.EnableValidationLayers ? "requested" : "not requested");
|
|
|
|
if (m_config.EnableValidationLayers && !validationLayerAvailable) {
|
|
MGLOG_I("Validation layers not available! Disabling validation layers.");
|
|
}
|
|
|
|
m_validationLayersEnabled = m_config.EnableValidationLayers && validationLayerAvailable;
|
|
|
|
// The debug messenger is a VK_EXT_debug_utils object, but a driver can ship
|
|
// the validation layers while exposing only the older VK_EXT_debug_report
|
|
// (Adreno 650 / Vulkan 1.1.128 does exactly that). Requesting the extension
|
|
// unconditionally tripped the required-extension assert below, aborting every
|
|
// validation-enabled build in CreateInstance. Keep the layers - they still
|
|
// validate, and on Android they report to logcat on their own - and drop only
|
|
// the messenger.
|
|
const Bool debugUtilsAvailable =
|
|
m_validationLayersEnabled && IsExtensionSupported(m_extensions, VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
|
// Without a reporting channel the layers validate but say nothing, so fall
|
|
// back to VK_EXT_debug_report when debug_utils is missing.
|
|
const Bool debugReportAvailable = m_validationLayersEnabled && !debugUtilsAvailable &&
|
|
IsExtensionSupported(m_extensions, VK_EXT_DEBUG_REPORT_EXTENSION_NAME);
|
|
if (m_validationLayersEnabled && !debugUtilsAvailable) {
|
|
MGLOG_I("%s not available; validation reports via %s instead.", VK_EXT_DEBUG_UTILS_EXTENSION_NAME,
|
|
debugReportAvailable ? VK_EXT_DEBUG_REPORT_EXTENSION_NAME : "(no channel)");
|
|
}
|
|
|
|
// ---------------- App info -------------------
|
|
VkApplicationInfo appInfo = {};
|
|
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
|
|
appInfo.pApplicationName = m_config.AppName.c_str();
|
|
appInfo.applicationVersion = VK_MAKE_VERSION(m_config.CacheVersion, 0, 0);
|
|
appInfo.pEngineName = "MobileGL";
|
|
appInfo.engineVersion = VK_MAKE_VERSION(m_config.Version.Major, m_config.Version.Minor, m_config.Version.Patch);
|
|
#ifdef VK_USE_PLATFORM_WIN32_KHR
|
|
appInfo.apiVersion = VK_API_VERSION_1_3;
|
|
#else
|
|
appInfo.apiVersion = VK_API_VERSION_1_1;
|
|
#endif
|
|
|
|
// ---------------- Instance info -------------------
|
|
VkInstanceCreateInfo instanceInfo = {};
|
|
instanceInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
|
|
instanceInfo.pApplicationInfo = &appInfo;
|
|
|
|
// Extensions
|
|
Vector<const char*> exts = {VK_KHR_SURFACE_EXTENSION_NAME};
|
|
if (!m_window) {
|
|
#ifdef VK_USE_PLATFORM_METAL_EXT
|
|
exts.push_back(VK_EXT_METAL_SURFACE_EXTENSION_NAME);
|
|
#elif defined VK_USE_PLATFORM_ANDROID_KHR
|
|
m_headlessSurfaceSupported = IsExtensionSupported(m_extensions, VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME);
|
|
if (m_headlessSurfaceSupported) {
|
|
exts.push_back(VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME);
|
|
} else {
|
|
// No mobile ICD seen so far implements VK_EXT_headless_surface
|
|
// (Mali r32p1 does not), and this used to abort the process the
|
|
// moment an application asked for a pbuffer context. CreateSurface()
|
|
// gives the WSI an AImageReader window instead, so request the
|
|
// Android surface extension for it.
|
|
MGLOG_I("%s not available; falling back to an AImageReader %s surface for the pbuffer context.",
|
|
VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME, VK_KHR_ANDROID_SURFACE_EXTENSION_NAME);
|
|
exts.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME);
|
|
m_androidSurfaceEnabled = true;
|
|
}
|
|
#elif defined VK_USE_PLATFORM_XLIB_KHR
|
|
// An offscreen surface has ZERO window-system dependence, by design and on
|
|
// every machine - including ones that do have a display. There used to be a
|
|
// fallback here that requested VK_KHR_xlib_surface and had CreateSurface()
|
|
// open a hidden, never-mapped X window; it is gone. A pbuffer that quietly
|
|
// needs an X server is a pbuffer that works on a workstation and dies on a
|
|
// headless runner, which is exactly what it did: with no DISPLAY, XOpenDisplay
|
|
// returned null and the next Xlib call segfaulted. If the loader genuinely has
|
|
// no VK_EXT_headless_surface, that is an honest bring-up failure and is
|
|
// reported as one below - never papered over with a window.
|
|
m_headlessSurfaceSupported = IsExtensionSupported(m_extensions, VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME);
|
|
if (!m_headlessSurfaceSupported) {
|
|
MGLOG_F("%s is not available from this Vulkan loader, so an offscreen (pbuffer) DirectVulkan "
|
|
"surface cannot be created. Refusing to substitute a window: offscreen surfaces must not "
|
|
"depend on a window system. Install an ICD that implements it (lavapipe does).",
|
|
VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME);
|
|
throw RuntimeError("VK_EXT_headless_surface is unavailable for an offscreen DirectVulkan surface");
|
|
}
|
|
exts.push_back(VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME);
|
|
#else
|
|
exts.push_back(VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME);
|
|
#endif
|
|
} else {
|
|
#ifdef VK_USE_PLATFORM_ANDROID_KHR
|
|
exts.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME);
|
|
m_androidSurfaceEnabled = true;
|
|
#elif defined VK_USE_PLATFORM_WIN32_KHR
|
|
exts.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
|
|
#elif defined VK_USE_PLATFORM_METAL_EXT
|
|
exts.push_back(VK_EXT_METAL_SURFACE_EXTENSION_NAME);
|
|
#elif defined VK_USE_PLATFORM_XLIB_KHR
|
|
exts.push_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME);
|
|
#else
|
|
#warning "VulkanContext::CreateInstance: VK_KHR_*_surface extension not defined on this platform"
|
|
#endif
|
|
} // TODO: support more platforms
|
|
|
|
#if defined(VK_USE_PLATFORM_METAL_EXT)
|
|
if (IsExtensionSupported(m_extensions, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME)) {
|
|
exts.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
|
|
instanceInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
|
|
} else {
|
|
MGLOG_I("Optional Vulkan instance extension not supported: %s",
|
|
VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
|
|
}
|
|
#endif
|
|
|
|
if (debugUtilsAvailable) {
|
|
exts.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
|
} else if (debugReportAvailable) {
|
|
exts.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME);
|
|
}
|
|
|
|
MGLOG_I("Enabling %d Vulkan instance extensions:", exts.size());
|
|
for (const char* ext : exts) {
|
|
MGLOG_I(" %s", ext);
|
|
}
|
|
|
|
for (const char* ext : exts) {
|
|
if (!IsExtensionSupported(m_extensions, ext)) {
|
|
MGLOG_E("Required Vulkan instance extension not found: %s", ext);
|
|
}
|
|
MOBILEGL_ASSERT(IsExtensionSupported(m_extensions, ext), "Required Vulkan instance extension not found: %s",
|
|
ext);
|
|
}
|
|
|
|
instanceInfo.enabledExtensionCount = exts.size();
|
|
instanceInfo.ppEnabledExtensionNames = exts.data();
|
|
|
|
auto debugMessengerCreateInfo = PopulateDebugMessengerCreateInfo();
|
|
// Layers
|
|
const void* instanceCreatePNext = nullptr;
|
|
if (m_validationLayersEnabled) {
|
|
MGLOG_I("Enabling validation layer...");
|
|
instanceInfo.enabledLayerCount = static_cast<uint32_t>(std::size(s_validationLayerNames));
|
|
instanceInfo.ppEnabledLayerNames = s_validationLayerNames;
|
|
// Chaining the messenger create-info is only legal with the extension on.
|
|
instanceCreatePNext = debugUtilsAvailable ? &debugMessengerCreateInfo : nullptr;
|
|
} else {
|
|
instanceInfo.enabledLayerCount = 0;
|
|
}
|
|
|
|
instanceInfo.pNext = instanceCreatePNext;
|
|
|
|
VK_VERIFY(vkCreateInstance(&instanceInfo, nullptr, &m_instance), "vkCreateInstance failed");
|
|
|
|
if (debugUtilsAvailable) {
|
|
VK_VERIFY(SetupDebugMessenger());
|
|
} else if (debugReportAvailable) {
|
|
VK_VERIFY(SetupDebugReportCallback());
|
|
}
|
|
}
|
|
|
|
static VKAPI_ATTR VkBool32 VKAPI_CALL DebugReportCallback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT,
|
|
Uint64, size_t, Int32 messageCode,
|
|
const char* pLayerPrefix, const char* pMessage, void*) {
|
|
if ((flags & (VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT |
|
|
VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT)) != 0) {
|
|
// MGLOG_F, unlatched, on purpose: a validation-layer report means MobileGL fed
|
|
// Vulkan something illegal, which is a broken invariant rather than an expected
|
|
// failure mode. It stays loud and keeps repeating - the quietness rules that latch
|
|
// W/E are for expected failures, not for this. The callback is only installed when
|
|
// a build arms the debug report extension, so it costs shipping builds nothing.
|
|
MGLOG_F("[Vulkan %s %d] %s", pLayerPrefix ? pLayerPrefix : "?", messageCode, pMessage ? pMessage : "");
|
|
}
|
|
return VK_FALSE;
|
|
}
|
|
|
|
VkResult VulkanRenderer::SetupDebugReportCallback() {
|
|
auto vkCreateDebugReportCallbackEXT =
|
|
(PFN_vkCreateDebugReportCallbackEXT)vkGetInstanceProcAddr(m_instance, "vkCreateDebugReportCallbackEXT");
|
|
if (!vkCreateDebugReportCallbackEXT) return VK_ERROR_EXTENSION_NOT_PRESENT;
|
|
VkDebugReportCallbackCreateInfoEXT createInfo{VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT};
|
|
createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT |
|
|
VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT;
|
|
createInfo.pfnCallback = &DebugReportCallback;
|
|
return vkCreateDebugReportCallbackEXT(m_instance, &createInfo, nullptr, &m_debugReportCallback);
|
|
}
|
|
|
|
void VulkanRenderer::DestroyDebugReportCallback() {
|
|
if (m_debugReportCallback == VK_NULL_HANDLE) return;
|
|
auto func =
|
|
(PFN_vkDestroyDebugReportCallbackEXT)vkGetInstanceProcAddr(m_instance, "vkDestroyDebugReportCallbackEXT");
|
|
if (func != nullptr) func(m_instance, m_debugReportCallback, nullptr);
|
|
m_debugReportCallback = VK_NULL_HANDLE;
|
|
}
|
|
|
|
VkResult VulkanRenderer::SetupDebugMessenger() {
|
|
auto createInfo = PopulateDebugMessengerCreateInfo();
|
|
auto vkCreateDebugUtilsMessengerEXT =
|
|
(PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(m_instance, "vkCreateDebugUtilsMessengerEXT");
|
|
if (!vkCreateDebugUtilsMessengerEXT) return VK_ERROR_EXTENSION_NOT_PRESENT;
|
|
VK_VERIFY(vkCreateDebugUtilsMessengerEXT(m_instance, &createInfo, nullptr, &m_debugMessenger));
|
|
return VK_SUCCESS;
|
|
}
|
|
|
|
VkResult VulkanRenderer::DestroyDebugMessenger() {
|
|
if (m_debugMessenger != VK_NULL_HANDLE) {
|
|
auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(m_instance,
|
|
"vkDestroyDebugUtilsMessengerEXT");
|
|
if (func != nullptr) {
|
|
func(m_instance, m_debugMessenger, nullptr);
|
|
} else {
|
|
return VK_ERROR_EXTENSION_NOT_PRESENT;
|
|
}
|
|
}
|
|
return VK_SUCCESS;
|
|
}
|
|
|
|
VkDebugUtilsMessengerCreateInfoEXT VulkanRenderer::PopulateDebugMessengerCreateInfo() {
|
|
VkDebugUtilsMessengerCreateInfoEXT createInfo{};
|
|
createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
|
|
createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
|
|
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
|
|
VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
|
|
createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
|
|
VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
|
|
VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
|
|
createInfo.pfnUserCallback = DebugCallback;
|
|
createInfo.pUserData = this;
|
|
return createInfo;
|
|
}
|
|
|
|
void VulkanRenderer::PickPhysicalDevice() {
|
|
Uint32 deviceCount = 0;
|
|
VK_VERIFY(vkEnumeratePhysicalDevices(m_instance, &deviceCount, nullptr));
|
|
if (deviceCount == 0) {
|
|
// A real, reachable configuration, not a broken invariant: an instance can be
|
|
// created from ICDs that load perfectly and then expose no device at all - a
|
|
// GPU-less machine with the vendor ICDs installed (RADV/ANV/NVK on a CI runner)
|
|
// is exactly that. It has to be a bring-up failure the caller can report.
|
|
//
|
|
// It used to be MGLOG_E + MOBILEGL_ASSERT, and back then BOTH were compiled out at
|
|
// the INFO log level every shipping and CI build uses - the ordering bug that made
|
|
// MGLOG_E dead at INFO was only fixed in 2026-08. The count-zero case therefore fell
|
|
// through in silence to `devices[0]` on an EMPTY vector below and segfaulted in
|
|
// vkGetPhysicalDeviceProperties. MGLOG_F stays: E is live now, but MOBILEGL_ASSERT
|
|
// is still DEBUG-only and this is a genuine bring-up abort, not a recoverable error.
|
|
MGLOG_F("No Vulkan physical devices found: the instance loaded ICDs but none of them exposes a "
|
|
"device. Cannot bring up DirectVulkan. (A software ICD such as lavapipe provides one; "
|
|
"pin it with VK_ICD_FILENAMES if the machine has no GPU.)");
|
|
throw RuntimeError("No Vulkan physical devices available for DirectVulkan");
|
|
}
|
|
MGLOG_I("Found %d physical device(s).", deviceCount);
|
|
|
|
Vector<VkPhysicalDevice> devices(deviceCount);
|
|
// Same truncation hazard as the instance-extension enumeration: a VK_INCOMPLETE here
|
|
// leaves the tail of `devices` default-constructed (VK_NULL_HANDLE), and every one of
|
|
// those is a null handle waiting to be passed to the driver. Take only what was
|
|
// actually written.
|
|
const VkResult enumerateResult = vkEnumeratePhysicalDevices(m_instance, &deviceCount, devices.data());
|
|
if (enumerateResult != VK_SUCCESS && enumerateResult != VK_INCOMPLETE) {
|
|
VK_VERIFY(enumerateResult, "vkEnumeratePhysicalDevices failed");
|
|
}
|
|
devices.resize(deviceCount);
|
|
if (devices.empty()) {
|
|
MGLOG_F("vkEnumeratePhysicalDevices reported devices and then wrote none");
|
|
throw RuntimeError("No Vulkan physical devices available for DirectVulkan");
|
|
}
|
|
for (Int i = 0; i < deviceCount; i++) {
|
|
if (GetMoreCapablePhysicalDevice(devices[i], m_surface, m_physicalDevice, m_physicalDevice))
|
|
MGLOG_I("Picked physical device %d.", i);
|
|
}
|
|
|
|
if (m_physicalDevice.handle == VK_NULL_HANDLE) {
|
|
m_physicalDevice.handle = devices[0];
|
|
vkGetPhysicalDeviceProperties(devices[0], &m_physicalDevice.properties);
|
|
MGLOG_I("No suitable physical device picked yet, defaulting to device 0.");
|
|
MGLOG_W("No graphics queue found on physical device. Picking a device that doesn't do graphics?");
|
|
}
|
|
}
|
|
|
|
Bool VulkanRenderer::GetMoreCapablePhysicalDevice(VkPhysicalDevice newVkDevice, VkSurfaceKHR surface,
|
|
const PhysicalDevice& otherDevice,
|
|
PhysicalDevice& outBetterDevice) {
|
|
const auto deviceTypeToStr = [](VkPhysicalDeviceType type) {
|
|
switch (type) {
|
|
case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU:
|
|
return "INTEGRATED_GPU";
|
|
case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU:
|
|
return "DISCRETE_GPU";
|
|
case VK_PHYSICAL_DEVICE_TYPE_CPU:
|
|
return "CPU";
|
|
case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU:
|
|
return "VIRTUAL_GPU";
|
|
case VK_PHYSICAL_DEVICE_TYPE_OTHER:
|
|
return "OTHER";
|
|
default:
|
|
return "UNKNOWN";
|
|
}
|
|
};
|
|
|
|
PhysicalDevice newDevice;
|
|
newDevice.handle = newVkDevice;
|
|
|
|
vkGetPhysicalDeviceProperties(newVkDevice, &newDevice.properties);
|
|
const auto& deviceProperties = newDevice.properties;
|
|
auto apiVersion = deviceProperties.apiVersion;
|
|
MGLOG_I(" %s (Vulkan %d.%d.%d, %s)", deviceProperties.deviceName, VK_VERSION_MAJOR(apiVersion),
|
|
VK_VERSION_MINOR(apiVersion), VK_VERSION_PATCH(apiVersion),
|
|
deviceTypeToStr(deviceProperties.deviceType));
|
|
|
|
// Check device extensions (including swapchain extension)
|
|
Bool deviceExtSupported = IsNecessaryDeviceExtensionSupported(newVkDevice);
|
|
if (!deviceExtSupported) {
|
|
outBetterDevice = otherDevice;
|
|
MGLOG_I(" Ignored physical device. (Reason: Some of the required device extension not supported on this "
|
|
"device)");
|
|
return false;
|
|
}
|
|
|
|
// Check swapchain capabilities
|
|
auto swapchainCapabilities = SwapchainObject::GetSwapchainCapabilities(newVkDevice, surface);
|
|
if (!swapchainCapabilities.IsComplete()) {
|
|
outBetterDevice = otherDevice;
|
|
MGLOG_I(" Ignored physical device. (Reason: Swapchain capabilities not met)");
|
|
return false;
|
|
}
|
|
|
|
// Check queue families
|
|
Vector<VkQueueFamilyProperties> queueFamilies = GetQueueFamilyFromPhysicalDevice(newVkDevice);
|
|
newDevice.queueFamilies.graphicsFamily = GetQueueFamilyIndex(queueFamilies, VK_QUEUE_GRAPHICS_BIT);
|
|
if (newDevice.queueFamilies.graphicsFamily == -1) {
|
|
outBetterDevice = otherDevice;
|
|
MGLOG_I(" Ignored physical device. (Reason: No graphics queue family)");
|
|
return false;
|
|
}
|
|
|
|
newDevice.queueFamilies.presentFamily =
|
|
GetPresentQueueFamilyIndex(newDevice, surface, queueFamilies, newDevice.queueFamilies.graphicsFamily);
|
|
if (newDevice.queueFamilies.presentFamily == -1) {
|
|
outBetterDevice = otherDevice;
|
|
MGLOG_I(" Ignored physical device. (Reason: No present queue family)");
|
|
return false;
|
|
}
|
|
|
|
// Accept software/virtual/other devices when no discrete or integrated GPU
|
|
// has been selected yet. This is important for Linux headless CI using lavapipe.
|
|
if (!otherDevice.IsComplete()) {
|
|
outBetterDevice = newDevice;
|
|
MGLOG_I(" Picked physical device. (Reason: First suitable device)");
|
|
return true;
|
|
}
|
|
|
|
// Pick discrete GPU
|
|
if (newDevice.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU &&
|
|
otherDevice.properties.deviceType != VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
|
|
outBetterDevice = newDevice;
|
|
MGLOG_I(" Picked physical device. (Reason: Discrete GPU)");
|
|
return true;
|
|
}
|
|
|
|
// Pick integrated GPU if no discrete GPU
|
|
if (newDevice.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU &&
|
|
otherDevice.properties.deviceType != VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
|
|
outBetterDevice = newDevice;
|
|
MGLOG_I(" Picked physical device. (Reason: Integrated GPU and no discrete one found yet)");
|
|
return true;
|
|
}
|
|
|
|
// Ignore other GPU when discrete GPU found
|
|
if (newDevice.properties.deviceType != VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU &&
|
|
otherDevice.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
|
|
outBetterDevice = otherDevice;
|
|
MGLOG_I(" Ignored physical device. (Reason: Already picked discrete GPU)");
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
Bool VulkanRenderer::IsNecessaryDeviceExtensionSupported(VkPhysicalDevice device) {
|
|
const Vector<VkExtensionProperties> availableExtensions = EnumerateDeviceExtensions(device);
|
|
|
|
MGLOG_I("Got %u Vulkan device extensions: ", static_cast<Uint32>(availableExtensions.size()));
|
|
for (auto& extension : availableExtensions) {
|
|
MGLOG_I(" %s (r.%u)", extension.extensionName, extension.specVersion);
|
|
}
|
|
|
|
for (SizeT i = 0; i < std::size(s_deviceExtensionNames); ++i) {
|
|
if (!IsExtensionSupported(availableExtensions, s_deviceExtensionNames[i])) {
|
|
MGLOG_I("Required extension not found: %s", s_deviceExtensionNames[i]);
|
|
return false;
|
|
}
|
|
MGLOG_I("Required extension found: %s", s_deviceExtensionNames[i]);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void VulkanRenderer::CreateLogicalDeviceAndQueues() {
|
|
Float queuePriority = 1.0f;
|
|
|
|
Vector<VkDeviceQueueCreateInfo> queueCreateInfos;
|
|
MOBILEGL_ASSERT(m_physicalDevice.queueFamilies.graphicsFamily != -1, "Graphics queue family not found.");
|
|
VkDeviceQueueCreateInfo& gfxQueueCreateInfo = queueCreateInfos.emplace_back();
|
|
gfxQueueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
|
|
gfxQueueCreateInfo.queueFamilyIndex = m_physicalDevice.queueFamilies.graphicsFamily;
|
|
gfxQueueCreateInfo.queueCount = 1;
|
|
gfxQueueCreateInfo.pQueuePriorities = &queuePriority;
|
|
|
|
if (m_physicalDevice.queueFamilies.graphicsFamily != m_physicalDevice.queueFamilies.presentFamily) {
|
|
MOBILEGL_ASSERT(m_physicalDevice.queueFamilies.presentFamily != -1, "Present queue family not found.");
|
|
VkDeviceQueueCreateInfo& presentQueueCreateInfo = queueCreateInfos.emplace_back();
|
|
presentQueueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
|
|
presentQueueCreateInfo.queueFamilyIndex = m_physicalDevice.queueFamilies.presentFamily;
|
|
presentQueueCreateInfo.queueCount = 1;
|
|
presentQueueCreateInfo.pQueuePriorities = &queuePriority;
|
|
}
|
|
|
|
VkPhysicalDeviceFeatures supportedDeviceFeatures{};
|
|
vkGetPhysicalDeviceFeatures(m_physicalDevice.handle, &supportedDeviceFeatures);
|
|
|
|
VkPhysicalDeviceFeatures deviceFeatures{};
|
|
// Match GL's robust buffer-fetch behavior where the Vulkan device supports it. This covers
|
|
// out-of-range fetches; arbitrary GL vertex strides/offsets still need the explicit tight
|
|
// repack in VertexInputStateFactory when they violate Vulkan's address-alignment rules.
|
|
// MOBILEGL_MAGMA_DISABLE_ROBUST_BUFFER_ACCESS leaves it off to measure or dodge its GPU cost.
|
|
deviceFeatures.robustBufferAccess =
|
|
MG_Config::Features.MagmaDisableRobustBufferAccess ? VK_FALSE : supportedDeviceFeatures.robustBufferAccess;
|
|
deviceFeatures.geometryShader = supportedDeviceFeatures.geometryShader;
|
|
deviceFeatures.tessellationShader = supportedDeviceFeatures.tessellationShader;
|
|
// gl_PointSize is an ORDINARY per-vertex output in desktop GL - a tessellation
|
|
// evaluation or geometry shader may write it, and a program may capture it by name -
|
|
// but in Vulkan the PointSize built-in is only usable from those two stages when this
|
|
// feature is on (VUID-RuntimeSpirv-PointSize-06439; SPIR-V spells the requirement as
|
|
// the TessellationPointSize / GeometryPointSize capabilities, which glslang emits from
|
|
// any such write). Left off, every one of those programs is invalid usage that a lenient
|
|
// driver silently gives an undefined point size and a strict one faults on. Nothing here
|
|
// asks for it speculatively: the feature is taken only where the device advertises it.
|
|
deviceFeatures.shaderTessellationAndGeometryPointSize =
|
|
supportedDeviceFeatures.shaderTessellationAndGeometryPointSize;
|
|
m_tessellationAndGeometryPointSizeFeatureEnabled =
|
|
deviceFeatures.shaderTessellationAndGeometryPointSize == VK_TRUE;
|
|
// Sampled-read barriers may only name the shader stages whose device feature is
|
|
// actually enabled (VUID-vkCmdPipelineBarrier-srcStageMask-04090/-04091), so the
|
|
// mask is assembled here, next to the feature decision, and handed to consumers.
|
|
m_sampledReadStageMask = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
|
|
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
|
|
if (deviceFeatures.geometryShader == VK_TRUE) {
|
|
m_sampledReadStageMask |= VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT;
|
|
}
|
|
if (deviceFeatures.tessellationShader == VK_TRUE) {
|
|
m_sampledReadStageMask |= VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT |
|
|
VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT;
|
|
}
|
|
deviceFeatures.independentBlend = supportedDeviceFeatures.independentBlend;
|
|
m_independentBlendFeatureEnabled = deviceFeatures.independentBlend == VK_TRUE;
|
|
deviceFeatures.fillModeNonSolid = supportedDeviceFeatures.fillModeNonSolid;
|
|
m_fillModeNonSolidFeatureEnabled = deviceFeatures.fillModeNonSolid == VK_TRUE;
|
|
deviceFeatures.dualSrcBlend = supportedDeviceFeatures.dualSrcBlend;
|
|
// GL_POLYGON_OFFSET_CLAMP (glPolygonOffsetClamp) maps onto vkCmdSetDepthBias's clamp
|
|
// argument, which must stay 0 unless this feature is enabled.
|
|
deviceFeatures.depthBiasClamp = supportedDeviceFeatures.depthBiasClamp;
|
|
m_depthBiasClampFeatureEnabled = deviceFeatures.depthBiasClamp == VK_TRUE;
|
|
m_dualSrcBlendFeatureEnabled = deviceFeatures.dualSrcBlend == VK_TRUE;
|
|
// ARB_sample_shading. Without this feature a pipeline may not set sampleShadingEnable
|
|
// (VUID-VkPipelineMultisampleStateCreateInfo-sampleShadingEnable-00784), so the GL enable
|
|
// has to be dropped rather than forwarded - which is what the flag below records.
|
|
deviceFeatures.sampleRateShading = supportedDeviceFeatures.sampleRateShading;
|
|
m_sampleRateShadingFeatureEnabled = deviceFeatures.sampleRateShading == VK_TRUE;
|
|
// ARB_viewport_array rasterization. Without multiViewport a pipeline may declare exactly
|
|
// one viewport (VUID-VkPipelineViewportStateCreateInfo-viewportCount-01216), so a shader's
|
|
// gl_ViewportIndex can only ever select viewport 0 and the other fifteen rectangles are
|
|
// state with nowhere to go. The GL state stays 16 wide either way - GL 4.3 core requires
|
|
// MAX_VIEWPORTS >= 16 and that is a frontend promise, not a device one; this gate decides
|
|
// only whether a DRAW can rasterize into more than one of them.
|
|
deviceFeatures.multiViewport = supportedDeviceFeatures.multiViewport;
|
|
m_multiViewportFeatureEnabled = deviceFeatures.multiViewport == VK_TRUE;
|
|
m_maxRasterizableViewports =
|
|
m_multiViewportFeatureEnabled
|
|
? std::min<Uint32>(RenderStateParameters::MAX_VIEWPORTS,
|
|
std::max<Uint32>(m_physicalDevice.properties.limits.maxViewports, 1u))
|
|
: 1u;
|
|
MGLOG_I("Vulkan: multiViewport %s; rasterizable viewports=%u (device limit %u, GL state width %u)",
|
|
m_multiViewportFeatureEnabled ? "enabled" : "UNAVAILABLE", m_maxRasterizableViewports,
|
|
m_physicalDevice.properties.limits.maxViewports,
|
|
static_cast<Uint32>(RenderStateParameters::MAX_VIEWPORTS));
|
|
if (!m_multiViewportFeatureEnabled) {
|
|
MGLOG_W("Vulkan: the device does not support the multiViewport feature; gl_ViewportIndex will always "
|
|
"select viewport 0 and per-viewport scissor/depth-range state past index 0 cannot be "
|
|
"rasterized (the state itself is still stored and queryable)");
|
|
}
|
|
deviceFeatures.logicOp = supportedDeviceFeatures.logicOp;
|
|
deviceFeatures.shaderClipDistance = supportedDeviceFeatures.shaderClipDistance;
|
|
deviceFeatures.shaderCullDistance = supportedDeviceFeatures.shaderCullDistance;
|
|
deviceFeatures.wideLines = supportedDeviceFeatures.wideLines;
|
|
m_logicOpFeatureEnabled = deviceFeatures.logicOp == VK_TRUE;
|
|
deviceFeatures.shaderInt64 = supportedDeviceFeatures.shaderInt64;
|
|
// Required for any module that declares OpCapability Float64 - which is every shader with a
|
|
// double in it, including the 64-bit vertex attribute path (the attribute itself arrives as
|
|
// uint32 words, but the bitcast result and everything computed from it is Float64). Without
|
|
// it vkCreateShaderModule is invalid usage (VUID-VkShaderModuleCreateInfo-pCode-08740),
|
|
// which is why SupportsFloat64VertexAttributes gates the entry point on the same feature.
|
|
deviceFeatures.shaderFloat64 = supportedDeviceFeatures.shaderFloat64;
|
|
// Required before a VK_IMAGE_VIEW_TYPE_CUBE_ARRAY view may be created
|
|
// (VUID-VkImageViewCreateInfo-viewType-01004). Without it a cube map array texture cannot
|
|
// get its sampled or full view, so SyncTextureResource fails and the texture stays unbacked.
|
|
deviceFeatures.imageCubeArray = supportedDeviceFeatures.imageCubeArray;
|
|
// Required for desktop GL image load/store semantics. iterationRP writes storage
|
|
// images from vertex and fragment stages and uses formats outside Vulkan's small
|
|
// mandatory storage-image set.
|
|
deviceFeatures.vertexPipelineStoresAndAtomics = supportedDeviceFeatures.vertexPipelineStoresAndAtomics;
|
|
deviceFeatures.fragmentStoresAndAtomics = supportedDeviceFeatures.fragmentStoresAndAtomics;
|
|
deviceFeatures.shaderStorageImageExtendedFormats = supportedDeviceFeatures.shaderStorageImageExtendedFormats;
|
|
// The formatless float-storage compatibility path must be all-or-nothing: transformed
|
|
// modules declare both capabilities and image bindings may be read, written, or both.
|
|
m_unformattedFloatStorageImagesEnabled =
|
|
supportedDeviceFeatures.shaderStorageImageReadWithoutFormat == VK_TRUE &&
|
|
supportedDeviceFeatures.shaderStorageImageWriteWithoutFormat == VK_TRUE;
|
|
if (m_unformattedFloatStorageImagesEnabled) {
|
|
deviceFeatures.shaderStorageImageReadWithoutFormat = VK_TRUE;
|
|
deviceFeatures.shaderStorageImageWriteWithoutFormat = VK_TRUE;
|
|
} else {
|
|
// Surface the degradation instead of failing silently: shader packs that bind a
|
|
// float storage image with a format different from its declaration (e.g.
|
|
// iterationRP) will render incorrectly on this device.
|
|
MGLOG_W("CreateLogicalDeviceAndQueues: shaderStorageImage*WithoutFormat unavailable "
|
|
"(read=%d write=%d); float storage-image format reinterpretation is disabled "
|
|
"and packs relying on it may misrender",
|
|
supportedDeviceFeatures.shaderStorageImageReadWithoutFormat,
|
|
supportedDeviceFeatures.shaderStorageImageWriteWithoutFormat);
|
|
}
|
|
deviceFeatures.drawIndirectFirstInstance = supportedDeviceFeatures.drawIndirectFirstInstance;
|
|
m_drawIndirectFirstInstanceFeatureEnabled = deviceFeatures.drawIndirectFirstInstance == VK_TRUE;
|
|
deviceFeatures.multiDrawIndirect = supportedDeviceFeatures.multiDrawIndirect;
|
|
m_multiDrawIndirectFeatureEnabled = deviceFeatures.multiDrawIndirect == VK_TRUE;
|
|
m_logicOpFeatureEnabled = deviceFeatures.logicOp == VK_TRUE;
|
|
// Backs GL_TEXTURE_MAX_ANISOTROPY_EXT; optional in Vulkan, so the sampler manager falls back
|
|
// to isotropic filtering (and the extension goes unadvertised) when the device lacks it.
|
|
deviceFeatures.samplerAnisotropy = supportedDeviceFeatures.samplerAnisotropy;
|
|
m_samplerAnisotropyFeatureEnabled = deviceFeatures.samplerAnisotropy == VK_TRUE;
|
|
// GL_SAMPLES_PASSED needs exact sample counts; without the feature the boolean
|
|
// occlusion result still satisfies any-samples-style consumers.
|
|
deviceFeatures.occlusionQueryPrecise = supportedDeviceFeatures.occlusionQueryPrecise;
|
|
m_occlusionQueryPreciseEnabled = deviceFeatures.occlusionQueryPrecise == VK_TRUE;
|
|
m_tessellationShaderFeatureEnabled = deviceFeatures.tessellationShader == VK_TRUE;
|
|
// Backs the GL_PRIMITIVES_GENERATED reroute's statistics tier (see the
|
|
// m_primGenReroute* members): a VK_QUERY_TYPE_PIPELINE_STATISTICS pool may only
|
|
// be created with this feature enabled. Enabled wherever the device has it - the
|
|
// feature alone costs nothing; pools exist only where the reroute is armed.
|
|
deviceFeatures.pipelineStatisticsQuery = supportedDeviceFeatures.pipelineStatisticsQuery;
|
|
m_pipelineStatisticsQueryFeatureEnabled = deviceFeatures.pipelineStatisticsQuery == VK_TRUE;
|
|
|
|
VkDeviceCreateInfo deviceCreateInfo{};
|
|
deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
|
|
deviceCreateInfo.pQueueCreateInfos = queueCreateInfos.data();
|
|
deviceCreateInfo.queueCreateInfoCount = queueCreateInfos.size();
|
|
deviceCreateInfo.pEnabledFeatures = &deviceFeatures;
|
|
if (m_validationLayersEnabled) {
|
|
deviceCreateInfo.enabledLayerCount = static_cast<uint32_t>(std::size(s_validationLayerNames));
|
|
deviceCreateInfo.ppEnabledLayerNames = s_validationLayerNames;
|
|
} else {
|
|
deviceCreateInfo.enabledLayerCount = 0;
|
|
}
|
|
|
|
Vector<const char*> enabledDeviceExtensions;
|
|
enabledDeviceExtensions.reserve(std::size(s_deviceExtensionNames) + 2);
|
|
for (const char* extensionName : s_deviceExtensionNames) {
|
|
enabledDeviceExtensions.push_back(extensionName);
|
|
}
|
|
|
|
const Vector<VkExtensionProperties> availableExtensions = EnumerateDeviceExtensions(m_physicalDevice.handle);
|
|
ResolveOptionalDeviceExtensions(availableExtensions, enabledDeviceExtensions);
|
|
|
|
// VK_KHR_image_format_list lets a MUTABLE_FORMAT image declare exactly which formats it
|
|
// may be viewed as. Adreno drops UBWC bandwidth compression on a blindly-mutable image
|
|
// (measured: 65 -> 80 fps in MC 26.2 once mutability is not requested); an explicit,
|
|
// compression-compatible format list is the portable way to keep both.
|
|
m_imageFormatListExtensionEnabled =
|
|
IsExtensionSupported(availableExtensions, VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME);
|
|
if (m_imageFormatListExtensionEnabled) {
|
|
enabledDeviceExtensions.push_back(VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME);
|
|
}
|
|
MGLOG_I("VK_KHR_image_format_list enabled: %s", m_imageFormatListExtensionEnabled ? "true" : "false");
|
|
MGLOG_I("VK_KHR_draw_indirect_count enabled: %s", m_drawIndirectCountExtensionEnabled ? "true" : "false");
|
|
|
|
m_indexTypeUint8ExtensionEnabled = false;
|
|
const char* indexTypeUint8ExtensionName = nullptr;
|
|
if (IsExtensionSupported(availableExtensions, VK_KHR_INDEX_TYPE_UINT8_EXTENSION_NAME)) {
|
|
indexTypeUint8ExtensionName = VK_KHR_INDEX_TYPE_UINT8_EXTENSION_NAME;
|
|
} else if (IsExtensionSupported(availableExtensions, VK_EXT_INDEX_TYPE_UINT8_EXTENSION_NAME)) {
|
|
indexTypeUint8ExtensionName = VK_EXT_INDEX_TYPE_UINT8_EXTENSION_NAME;
|
|
}
|
|
|
|
auto getPhysicalDeviceFeatures2 = reinterpret_cast<PFN_vkGetPhysicalDeviceFeatures2>(
|
|
vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceFeatures2"));
|
|
if (getPhysicalDeviceFeatures2 == nullptr) {
|
|
getPhysicalDeviceFeatures2 = reinterpret_cast<PFN_vkGetPhysicalDeviceFeatures2>(
|
|
vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceFeatures2KHR"));
|
|
}
|
|
|
|
m_updateAfterBindLimits = {};
|
|
VkPhysicalDeviceDescriptorIndexingFeatures descriptorIndexingFeatures{};
|
|
descriptorIndexingFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES;
|
|
VkPhysicalDeviceDescriptorIndexingProperties descriptorIndexingProperties{};
|
|
descriptorIndexingProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_PROPERTIES;
|
|
const Bool descriptorIndexingCore =
|
|
#if !defined(VK_USE_PLATFORM_WIN32_KHR)
|
|
// CreateInstance requests Vulkan 1.1 on these platforms. A 1.2+ GPU
|
|
// does not promote descriptor indexing into that application's core
|
|
// API, so the record arm enables VK_EXT_descriptor_indexing instead (P13 W6:
|
|
// every library, not only the transport build - the old monolith-only `true`
|
|
// here treated the 1.2 device as core under a 1.1 instance).
|
|
false &&
|
|
#endif
|
|
m_physicalDevice.properties.apiVersion >= VK_API_VERSION_1_2;
|
|
const Bool descriptorIndexingExtension =
|
|
IsExtensionSupported(availableExtensions, VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME);
|
|
auto getPhysicalDeviceProperties2 = reinterpret_cast<PFN_vkGetPhysicalDeviceProperties2>(
|
|
vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceProperties2"));
|
|
if (getPhysicalDeviceProperties2 == nullptr) {
|
|
getPhysicalDeviceProperties2 = reinterpret_cast<PFN_vkGetPhysicalDeviceProperties2>(
|
|
vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceProperties2KHR"));
|
|
}
|
|
// Linux/Android request Vulkan 1.1: a 1.2 physical device alone does
|
|
// not expose the promoted renderpass2/depth-resolve API to this app.
|
|
#ifdef VK_USE_PLATFORM_WIN32_KHR
|
|
const Bool wireDepthResolveCore = m_physicalDevice.properties.apiVersion >= VK_API_VERSION_1_2;
|
|
#else
|
|
const Bool wireDepthResolveCore = false;
|
|
#endif
|
|
const Bool wireDepthResolveExtensions =
|
|
m_physicalDevice.properties.apiVersion >= VK_API_VERSION_1_1 &&
|
|
IsExtensionSupported(availableExtensions, VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME) &&
|
|
IsExtensionSupported(availableExtensions, VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME);
|
|
const Bool wireDepthResolveEnabled =
|
|
getPhysicalDeviceProperties2 && (wireDepthResolveCore || wireDepthResolveExtensions);
|
|
m_wireCreateRenderPass2 = nullptr;
|
|
m_wireDepthResolveModes = m_wireStencilResolveModes = 0;
|
|
if (wireDepthResolveEnabled) {
|
|
if (!wireDepthResolveCore) {
|
|
EnableOptionalDeviceExtension(availableExtensions, enabledDeviceExtensions,
|
|
VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME);
|
|
EnableOptionalDeviceExtension(availableExtensions, enabledDeviceExtensions,
|
|
VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME);
|
|
}
|
|
VkPhysicalDeviceDepthStencilResolveProperties resolveProperties{
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_STENCIL_RESOLVE_PROPERTIES};
|
|
VkPhysicalDeviceProperties2 properties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2};
|
|
properties.pNext = &resolveProperties;
|
|
getPhysicalDeviceProperties2(m_physicalDevice.handle, &properties);
|
|
m_wireDepthResolveModes = resolveProperties.supportedDepthResolveModes;
|
|
m_wireStencilResolveModes = resolveProperties.supportedStencilResolveModes;
|
|
}
|
|
// P7 wave 2-B2, CONTRACT-P7 §3.2 (`multisample-blit-aspect`): the shader resolve's
|
|
// stencil half needs SPV_EXT_shader_stencil_export, because a fragment shader cannot
|
|
// write the stencil aspect without it. Enabled on the same terms as the resolve
|
|
// extensions above - wire arms only - and the arm declines stencil where it is absent
|
|
// rather than producing an undefined aspect.
|
|
m_wireShaderStencilExport =
|
|
IsExtensionSupported(availableExtensions, VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME);
|
|
if (m_wireShaderStencilExport)
|
|
EnableOptionalDeviceExtension(availableExtensions, enabledDeviceExtensions,
|
|
VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME);
|
|
// P7 gate 5 (g5-msprobe): which multisample depth/stencil resolve arm goes first is measured
|
|
// once the device exists (ArmWireDepthResolveOrder, below), not keyed on the vendor.
|
|
m_wirePreferShaderDepthResolve = false;
|
|
// P11 B2 (T0): a client's AHardwareBuffer imported as a wire buffer store. Wire arms, on
|
|
// Android, whenever the device advertises the extension - not per session: the device is
|
|
// created once per server and a later session asking T0 must find it able. Enabling it
|
|
// changes no T2 behavior; only an import uses it. The 1.0-era dependencies are core at the
|
|
// 1.1 this device is created at, and are enabled too when advertised (the spike's set).
|
|
m_wireAhbImport = false;
|
|
m_wireGetAhbProperties = nullptr;
|
|
m_sharedImageSyncFd = false;
|
|
#if defined(__ANDROID__)
|
|
if (m_physicalDevice.properties.apiVersion >= VK_API_VERSION_1_1 &&
|
|
IsExtensionSupported(availableExtensions,
|
|
VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME) &&
|
|
IsExtensionSupported(availableExtensions, VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME)) {
|
|
m_wireAhbImport = true;
|
|
for (const char* name :
|
|
{VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME,
|
|
VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME, VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME,
|
|
VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME, VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME,
|
|
VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME, VK_KHR_BIND_MEMORY_2_EXTENSION_NAME,
|
|
VK_KHR_MAINTENANCE_1_EXTENSION_NAME}) {
|
|
if (IsExtensionSupported(availableExtensions, name))
|
|
EnableOptionalDeviceExtension(availableExtensions, enabledDeviceExtensions, name);
|
|
}
|
|
// Shared-image fences (WireSharedImage.cpp) cross sessions as sync_files, which this
|
|
// device waits on and signals through SYNC_FD binary semaphores - imported for a wait,
|
|
// exported for a signal. Both directions, or the CPU fallback.
|
|
if (IsExtensionSupported(availableExtensions, VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME)) {
|
|
auto getExternalSemaphoreProperties =
|
|
reinterpret_cast<PFN_vkGetPhysicalDeviceExternalSemaphoreProperties>(
|
|
vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceExternalSemaphoreProperties"));
|
|
if (getExternalSemaphoreProperties == nullptr) {
|
|
getExternalSemaphoreProperties =
|
|
reinterpret_cast<PFN_vkGetPhysicalDeviceExternalSemaphoreProperties>(
|
|
vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceExternalSemaphorePropertiesKHR"));
|
|
}
|
|
VkExternalSemaphoreProperties properties{VK_STRUCTURE_TYPE_EXTERNAL_SEMAPHORE_PROPERTIES};
|
|
if (getExternalSemaphoreProperties != nullptr) {
|
|
VkPhysicalDeviceExternalSemaphoreInfo query{
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_SEMAPHORE_INFO};
|
|
query.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
|
|
getExternalSemaphoreProperties(m_physicalDevice.handle, &query, &properties);
|
|
}
|
|
constexpr VkExternalSemaphoreFeatureFlags both =
|
|
VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT | VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT;
|
|
if ((properties.externalSemaphoreFeatures & both) == both) {
|
|
m_sharedImageSyncFd = true;
|
|
for (const char* name :
|
|
{VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME, VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME}) {
|
|
if (IsExtensionSupported(availableExtensions, name))
|
|
EnableOptionalDeviceExtension(availableExtensions, enabledDeviceExtensions, name);
|
|
}
|
|
}
|
|
}
|
|
MGLOG_I("DirectVulkan: shared-image fences %s",
|
|
m_sharedImageSyncFd ? "wait and signal on the GPU (SYNC_FD semaphores)"
|
|
: "are waited for on the CPU (no SYNC_FD semaphore import and export)");
|
|
}
|
|
#endif
|
|
if ((descriptorIndexingCore || descriptorIndexingExtension) && getPhysicalDeviceFeatures2 != nullptr &&
|
|
getPhysicalDeviceProperties2 != nullptr) {
|
|
VkPhysicalDeviceFeatures2 featureQuery{};
|
|
featureQuery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
|
|
featureQuery.pNext = &descriptorIndexingFeatures;
|
|
getPhysicalDeviceFeatures2(m_physicalDevice.handle, &featureQuery);
|
|
VkPhysicalDeviceProperties2 propertyQuery{};
|
|
propertyQuery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
|
|
propertyQuery.pNext = &descriptorIndexingProperties;
|
|
getPhysicalDeviceProperties2(m_physicalDevice.handle, &propertyQuery);
|
|
|
|
// This renderer emits every descriptor category listed below, including
|
|
// dynamic UBOs and combined image samplers. Do not enable a partial
|
|
// descriptor-indexing contract: it would make a later reflected program
|
|
// fail in the driver instead of choosing its ordinary descriptor layout.
|
|
const Bool allUpdateAfterBindFeatures =
|
|
descriptorIndexingFeatures.descriptorBindingUniformBufferUpdateAfterBind == VK_TRUE &&
|
|
descriptorIndexingFeatures.descriptorBindingSampledImageUpdateAfterBind == VK_TRUE &&
|
|
descriptorIndexingFeatures.descriptorBindingStorageImageUpdateAfterBind == VK_TRUE &&
|
|
descriptorIndexingFeatures.descriptorBindingStorageBufferUpdateAfterBind == VK_TRUE &&
|
|
descriptorIndexingFeatures.descriptorBindingUniformTexelBufferUpdateAfterBind == VK_TRUE &&
|
|
descriptorIndexingFeatures.descriptorBindingStorageTexelBufferUpdateAfterBind == VK_TRUE &&
|
|
(!deviceFeatures.robustBufferAccess || descriptorIndexingProperties.robustBufferAccessUpdateAfterBind);
|
|
if (allUpdateAfterBindFeatures) {
|
|
if (!descriptorIndexingCore &&
|
|
!IsExtensionAlreadyEnabled(enabledDeviceExtensions, VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME)) {
|
|
enabledDeviceExtensions.push_back(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME);
|
|
}
|
|
descriptorIndexingFeatures.pNext = const_cast<void*>(deviceCreateInfo.pNext);
|
|
deviceCreateInfo.pNext = &descriptorIndexingFeatures;
|
|
m_updateAfterBindLimits = {
|
|
true,
|
|
descriptorIndexingProperties.maxPerStageDescriptorUpdateAfterBindSamplers,
|
|
descriptorIndexingProperties.maxPerStageDescriptorUpdateAfterBindUniformBuffers,
|
|
descriptorIndexingProperties.maxPerStageDescriptorUpdateAfterBindStorageBuffers,
|
|
descriptorIndexingProperties.maxPerStageDescriptorUpdateAfterBindSampledImages,
|
|
descriptorIndexingProperties.maxPerStageDescriptorUpdateAfterBindStorageImages,
|
|
descriptorIndexingProperties.maxPerStageUpdateAfterBindResources,
|
|
descriptorIndexingProperties.maxDescriptorSetUpdateAfterBindSamplers,
|
|
descriptorIndexingProperties.maxDescriptorSetUpdateAfterBindUniformBuffers,
|
|
descriptorIndexingProperties.maxDescriptorSetUpdateAfterBindUniformBuffersDynamic,
|
|
descriptorIndexingProperties.maxDescriptorSetUpdateAfterBindStorageBuffers,
|
|
descriptorIndexingProperties.maxDescriptorSetUpdateAfterBindStorageBuffersDynamic,
|
|
descriptorIndexingProperties.maxDescriptorSetUpdateAfterBindSampledImages,
|
|
descriptorIndexingProperties.maxDescriptorSetUpdateAfterBindStorageImages};
|
|
MGLOG_I("Vulkan: update-after-bind descriptor layouts enabled");
|
|
} else {
|
|
MGLOG_I("Vulkan: descriptor indexing is present but lacks the complete update-after-bind feature set; "
|
|
"using ordinary descriptor layouts");
|
|
}
|
|
} else {
|
|
MGLOG_I("Vulkan: descriptor indexing unavailable; using ordinary descriptor layouts");
|
|
}
|
|
|
|
VkPhysicalDeviceIndexTypeUint8Features indexTypeUint8Features{};
|
|
indexTypeUint8Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INDEX_TYPE_UINT8_FEATURES;
|
|
if (indexTypeUint8ExtensionName != nullptr) {
|
|
VkPhysicalDeviceFeatures2 featureQuery{};
|
|
featureQuery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
|
|
featureQuery.pNext = &indexTypeUint8Features;
|
|
MOBILEGL_ASSERT(getPhysicalDeviceFeatures2 != nullptr,
|
|
"CreateLogicalDeviceAndQueues: vkGetPhysicalDeviceFeatures2 is unavailable");
|
|
getPhysicalDeviceFeatures2(m_physicalDevice.handle, &featureQuery);
|
|
if (indexTypeUint8Features.indexTypeUint8 == VK_TRUE) {
|
|
if (!IsExtensionAlreadyEnabled(enabledDeviceExtensions, indexTypeUint8ExtensionName)) {
|
|
enabledDeviceExtensions.push_back(indexTypeUint8ExtensionName);
|
|
}
|
|
m_indexTypeUint8ExtensionEnabled = true;
|
|
indexTypeUint8Features.pNext = const_cast<void*>(deviceCreateInfo.pNext);
|
|
deviceCreateInfo.pNext = &indexTypeUint8Features;
|
|
MGLOG_I("Enabled optional device extension: %s", indexTypeUint8ExtensionName);
|
|
} else {
|
|
MGLOG_W("%s is advertised, but indexTypeUint8 feature is unavailable; uint8 index buffers will stay "
|
|
"disabled",
|
|
indexTypeUint8ExtensionName);
|
|
}
|
|
} else {
|
|
MGLOG_W("VK_KHR_index_type_uint8 / VK_EXT_index_type_uint8 not supported; uint8 index buffers will stay "
|
|
"disabled");
|
|
}
|
|
|
|
m_shaderDrawParametersFeatureEnabled = false;
|
|
VkPhysicalDeviceShaderDrawParametersFeatures shaderDrawParametersFeatures{};
|
|
shaderDrawParametersFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETERS_FEATURES;
|
|
if (m_physicalDevice.properties.apiVersion >= VK_API_VERSION_1_1 && getPhysicalDeviceFeatures2 != nullptr) {
|
|
VkPhysicalDeviceFeatures2 featureQuery{};
|
|
featureQuery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
|
|
featureQuery.pNext = &shaderDrawParametersFeatures;
|
|
getPhysicalDeviceFeatures2(m_physicalDevice.handle, &featureQuery);
|
|
if (shaderDrawParametersFeatures.shaderDrawParameters == VK_TRUE) {
|
|
shaderDrawParametersFeatures.pNext = const_cast<void*>(deviceCreateInfo.pNext);
|
|
deviceCreateInfo.pNext = &shaderDrawParametersFeatures;
|
|
m_shaderDrawParametersFeatureEnabled = true;
|
|
}
|
|
} else if (m_shaderDrawParametersExtensionEnabled) {
|
|
// Vulkan 1.0 device: enabling VK_KHR_shader_draw_parameters alone exposes the SPIR-V
|
|
// DrawParameters capability; the shaderDrawParameters feature struct only exists from 1.1.
|
|
m_shaderDrawParametersFeatureEnabled = true;
|
|
}
|
|
if (!m_shaderDrawParametersFeatureEnabled) {
|
|
MGLOG_W("shaderDrawParameters is unavailable; shaders using gl_DrawID/gl_BaseInstance will not work");
|
|
}
|
|
|
|
// primitiveTopologyListRestart lets primitive restart work on *list* topologies (strip/fan
|
|
// restart needs no feature). Optional; enabled via VK_EXT_primitive_topology_list_restart.
|
|
m_primitiveTopologyListRestartFeatureEnabled = false;
|
|
VkPhysicalDevicePrimitiveTopologyListRestartFeaturesEXT listRestartFeatures{};
|
|
listRestartFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVE_TOPOLOGY_LIST_RESTART_FEATURES_EXT;
|
|
if (IsExtensionSupported(availableExtensions, VK_EXT_PRIMITIVE_TOPOLOGY_LIST_RESTART_EXTENSION_NAME) &&
|
|
getPhysicalDeviceFeatures2 != nullptr) {
|
|
VkPhysicalDeviceFeatures2 featureQuery{};
|
|
featureQuery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
|
|
featureQuery.pNext = &listRestartFeatures;
|
|
getPhysicalDeviceFeatures2(m_physicalDevice.handle, &featureQuery);
|
|
if (listRestartFeatures.primitiveTopologyListRestart == VK_TRUE) {
|
|
if (!IsExtensionAlreadyEnabled(enabledDeviceExtensions,
|
|
VK_EXT_PRIMITIVE_TOPOLOGY_LIST_RESTART_EXTENSION_NAME)) {
|
|
enabledDeviceExtensions.push_back(VK_EXT_PRIMITIVE_TOPOLOGY_LIST_RESTART_EXTENSION_NAME);
|
|
}
|
|
listRestartFeatures.pNext = const_cast<void*>(deviceCreateInfo.pNext);
|
|
deviceCreateInfo.pNext = &listRestartFeatures;
|
|
m_primitiveTopologyListRestartFeatureEnabled = true;
|
|
MGLOG_I("Enabled optional device extension: %s", VK_EXT_PRIMITIVE_TOPOLOGY_LIST_RESTART_EXTENSION_NAME);
|
|
}
|
|
}
|
|
|
|
// VK_EXT_custom_border_color: an arbitrary GL_TEXTURE_BORDER_COLOR, in float or integer form,
|
|
// instead of the four predefined VkBorderColor values. Without it a border outside
|
|
// transparent black / opaque black / opaque white has to be snapped, which is what made every
|
|
// border texel of a GL_RGBA8 texture with border (255,255,255,255) sample as 0 and what made
|
|
// an integer border of -1 come back as 0.
|
|
//
|
|
// customBorderColorWithoutFormat is required alongside customBorderColors, not merely
|
|
// preferred: a GL sampler object carries a border color with no idea which texture it will
|
|
// be paired with, so the VkSamplerCustomBorderColorCreateInfoEXT this backend builds has to
|
|
// leave `format` VK_FORMAT_UNDEFINED.
|
|
m_customBorderColorFeatureEnabled = false;
|
|
m_maxCustomBorderColorSamplers = 0;
|
|
VkPhysicalDeviceCustomBorderColorFeaturesEXT customBorderColorFeatures{};
|
|
customBorderColorFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_FEATURES_EXT;
|
|
if (IsExtensionSupported(availableExtensions, VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME) &&
|
|
getPhysicalDeviceFeatures2 != nullptr) {
|
|
VkPhysicalDeviceFeatures2 featureQuery{};
|
|
featureQuery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
|
|
featureQuery.pNext = &customBorderColorFeatures;
|
|
getPhysicalDeviceFeatures2(m_physicalDevice.handle, &featureQuery);
|
|
if (customBorderColorFeatures.customBorderColors == VK_TRUE &&
|
|
customBorderColorFeatures.customBorderColorWithoutFormat == VK_TRUE) {
|
|
if (!IsExtensionAlreadyEnabled(enabledDeviceExtensions, VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME)) {
|
|
enabledDeviceExtensions.push_back(VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME);
|
|
}
|
|
customBorderColorFeatures.pNext = const_cast<void*>(deviceCreateInfo.pNext);
|
|
deviceCreateInfo.pNext = &customBorderColorFeatures;
|
|
m_customBorderColorFeatureEnabled = true;
|
|
|
|
if (getPhysicalDeviceProperties2 != nullptr) {
|
|
VkPhysicalDeviceCustomBorderColorPropertiesEXT customBorderColorProperties{};
|
|
customBorderColorProperties.sType =
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_PROPERTIES_EXT;
|
|
VkPhysicalDeviceProperties2 propertyQuery{};
|
|
propertyQuery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
|
|
propertyQuery.pNext = &customBorderColorProperties;
|
|
getPhysicalDeviceProperties2(m_physicalDevice.handle, &propertyQuery);
|
|
m_maxCustomBorderColorSamplers = customBorderColorProperties.maxCustomBorderColorSamplers;
|
|
}
|
|
MGLOG_I("Enabled optional device extension: %s (maxCustomBorderColorSamplers=%u)",
|
|
VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME, m_maxCustomBorderColorSamplers);
|
|
}
|
|
}
|
|
if (!m_customBorderColorFeatureEnabled) {
|
|
MGLOG_I("%s unavailable; GL_TEXTURE_BORDER_COLOR snaps to the nearest predefined VkBorderColor",
|
|
VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME);
|
|
}
|
|
|
|
// Native subgroup topology, and VK_EXT_subgroup_size_control's
|
|
// computeFullSubgroups feature. REQUIRE_FULL_SUBGROUPS on a compute stage is what
|
|
// turns the derived gl_NumSubgroups (DeriveNumSubgroupsPass) from
|
|
// encouraged-but-unspecified driver behavior into a spec guarantee: with the bit
|
|
// set and local_size_x a multiple of the subgroup size, every subgroup launches
|
|
// full, so the subgroup count is exactly invocations / size ("Full Subgroups",
|
|
// VUID-VkPipelineShaderStageCreateInfo-flags-02759/-02785).
|
|
m_nativeSubgroupSize = 0;
|
|
m_nativeSubgroupSupported = false;
|
|
m_computeFullSubgroupsFeatureEnabled = false;
|
|
if (getPhysicalDeviceProperties2 != nullptr) {
|
|
VkPhysicalDeviceSubgroupProperties subgroupProperties{};
|
|
subgroupProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES;
|
|
VkPhysicalDeviceProperties2 subgroupPropertyQuery{};
|
|
subgroupPropertyQuery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
|
|
subgroupPropertyQuery.pNext = &subgroupProperties;
|
|
getPhysicalDeviceProperties2(m_physicalDevice.handle, &subgroupPropertyQuery);
|
|
// Mirrors the loader's HasUsableShaderSubgroupSupport gate, so the module lowerings
|
|
// can never disagree with the advertised capabilities.
|
|
const Bool usableSubgroups = subgroupProperties.subgroupSize > 0 &&
|
|
(subgroupProperties.supportedStages & VK_SHADER_STAGE_COMPUTE_BIT) != 0 &&
|
|
(subgroupProperties.supportedOperations & VK_SUBGROUP_FEATURE_BASIC_BIT) != 0;
|
|
if (usableSubgroups) {
|
|
m_nativeSubgroupSize = subgroupProperties.subgroupSize;
|
|
m_nativeSubgroupSupported = true;
|
|
}
|
|
}
|
|
VkPhysicalDeviceSubgroupSizeControlFeaturesEXT subgroupSizeControlFeatures{};
|
|
subgroupSizeControlFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_SIZE_CONTROL_FEATURES_EXT;
|
|
m_maxComputeWorkgroupSubgroups = 0;
|
|
if (m_nativeSubgroupSupported &&
|
|
IsExtensionSupported(availableExtensions, VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME) &&
|
|
getPhysicalDeviceFeatures2 != nullptr) {
|
|
VkPhysicalDeviceFeatures2 featureQuery{};
|
|
featureQuery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
|
|
featureQuery.pNext = &subgroupSizeControlFeatures;
|
|
getPhysicalDeviceFeatures2(m_physicalDevice.handle, &featureQuery);
|
|
if (getPhysicalDeviceProperties2 != nullptr) {
|
|
VkPhysicalDeviceSubgroupSizeControlPropertiesEXT subgroupSizeControlProperties{};
|
|
subgroupSizeControlProperties.sType =
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_SIZE_CONTROL_PROPERTIES_EXT;
|
|
VkPhysicalDeviceProperties2 propertyQuery{};
|
|
propertyQuery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
|
|
propertyQuery.pNext = &subgroupSizeControlProperties;
|
|
getPhysicalDeviceProperties2(m_physicalDevice.handle, &propertyQuery);
|
|
m_maxComputeWorkgroupSubgroups = subgroupSizeControlProperties.maxComputeWorkgroupSubgroups;
|
|
}
|
|
if (subgroupSizeControlFeatures.computeFullSubgroups == VK_TRUE) {
|
|
if (!IsExtensionAlreadyEnabled(enabledDeviceExtensions, VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME)) {
|
|
enabledDeviceExtensions.push_back(VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME);
|
|
}
|
|
// Only the full-subgroups guarantee is wanted; required/varying subgroup
|
|
// sizes stay unrequested.
|
|
subgroupSizeControlFeatures.subgroupSizeControl = VK_FALSE;
|
|
subgroupSizeControlFeatures.pNext = const_cast<void*>(deviceCreateInfo.pNext);
|
|
deviceCreateInfo.pNext = &subgroupSizeControlFeatures;
|
|
m_computeFullSubgroupsFeatureEnabled = true;
|
|
MGLOG_I("Enabled optional device extension: %s (computeFullSubgroups)",
|
|
VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME);
|
|
}
|
|
}
|
|
|
|
// VK_EXT_transform_feedback backs GL transform feedback capture.
|
|
m_transformFeedbackFeatureEnabled = false;
|
|
VkPhysicalDeviceTransformFeedbackFeaturesEXT transformFeedbackFeatures{};
|
|
transformFeedbackFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_FEATURES_EXT;
|
|
if (IsExtensionSupported(availableExtensions, VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME) &&
|
|
getPhysicalDeviceFeatures2 != nullptr) {
|
|
VkPhysicalDeviceFeatures2 featureQuery{};
|
|
featureQuery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
|
|
featureQuery.pNext = &transformFeedbackFeatures;
|
|
getPhysicalDeviceFeatures2(m_physicalDevice.handle, &featureQuery);
|
|
if (transformFeedbackFeatures.transformFeedback == VK_TRUE) {
|
|
if (!IsExtensionAlreadyEnabled(enabledDeviceExtensions, VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME)) {
|
|
enabledDeviceExtensions.push_back(VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME);
|
|
}
|
|
transformFeedbackFeatures.geometryStreams = VK_FALSE;
|
|
transformFeedbackFeatures.pNext = const_cast<void*>(deviceCreateInfo.pNext);
|
|
deviceCreateInfo.pNext = &transformFeedbackFeatures;
|
|
m_transformFeedbackFeatureEnabled = true;
|
|
MGLOG_I("Enabled optional device extension: %s", VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME);
|
|
}
|
|
}
|
|
// VK_EXT_primitives_generated_query - the query Vulkan defines for GL's
|
|
// GL_PRIMITIVES_GENERATED precisely because the stream query above needs no
|
|
// capture by spec but drivers disagree. Taken with BOTH the base feature and the
|
|
// rasterizer-discard feature or not at all: without the latter, a discarding draw
|
|
// inside the query is invalid usage, and GL applications toggle discard freely.
|
|
// Only the PRIMITIVES_GENERATED reroute consumes it (see ArmPrimGenReroute).
|
|
m_primitivesGeneratedQueryFeatureEnabled = false;
|
|
m_primitivesGeneratedQueryDiscardFeatureEnabled = false;
|
|
VkPhysicalDevicePrimitivesGeneratedQueryFeaturesEXT primitivesGeneratedQueryFeatures{};
|
|
primitivesGeneratedQueryFeatures.sType =
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVES_GENERATED_QUERY_FEATURES_EXT;
|
|
if (IsExtensionSupported(availableExtensions, VK_EXT_PRIMITIVES_GENERATED_QUERY_EXTENSION_NAME) &&
|
|
getPhysicalDeviceFeatures2 != nullptr) {
|
|
VkPhysicalDeviceFeatures2 featureQuery{};
|
|
featureQuery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
|
|
featureQuery.pNext = &primitivesGeneratedQueryFeatures;
|
|
getPhysicalDeviceFeatures2(m_physicalDevice.handle, &featureQuery);
|
|
if (primitivesGeneratedQueryFeatures.primitivesGeneratedQuery == VK_TRUE &&
|
|
primitivesGeneratedQueryFeatures.primitivesGeneratedQueryWithRasterizerDiscard == VK_TRUE) {
|
|
if (!IsExtensionAlreadyEnabled(enabledDeviceExtensions,
|
|
VK_EXT_PRIMITIVES_GENERATED_QUERY_EXTENSION_NAME)) {
|
|
enabledDeviceExtensions.push_back(VK_EXT_PRIMITIVES_GENERATED_QUERY_EXTENSION_NAME);
|
|
}
|
|
primitivesGeneratedQueryFeatures.primitivesGeneratedQueryWithNonZeroStreams = VK_FALSE;
|
|
primitivesGeneratedQueryFeatures.pNext = const_cast<void*>(deviceCreateInfo.pNext);
|
|
deviceCreateInfo.pNext = &primitivesGeneratedQueryFeatures;
|
|
m_primitivesGeneratedQueryFeatureEnabled = true;
|
|
m_primitivesGeneratedQueryDiscardFeatureEnabled = true;
|
|
MGLOG_I("Enabled optional device extension: %s", VK_EXT_PRIMITIVES_GENERATED_QUERY_EXTENSION_NAME);
|
|
}
|
|
}
|
|
// VK_EXT_provoking_vertex. Two independent features live behind one extension:
|
|
// provokingVertexLast -> flat varyings, gl_Layer/gl_ViewportIndex and
|
|
// the input-assembler capture order.
|
|
// transformFeedbackPreservesProvokingVertex -> spec-level guarantee for the capture order;
|
|
// only legal when the transformFeedback
|
|
// feature is also enabled, which is why this
|
|
// block sits after the one above.
|
|
// They are enabled independently on purpose: gating the first on the second would leave flat
|
|
// shading GL-wrong on any device without VK_EXT_transform_feedback, for no legality reason.
|
|
m_provokingVertexLastEnabled = false;
|
|
m_provokingVertexXfbPreserveEnabled = false;
|
|
m_provokingVertexModePerPipeline = false;
|
|
m_provokingVertexFanPreserved = false;
|
|
VkPhysicalDeviceProvokingVertexFeaturesEXT provokingVertexFeatures{};
|
|
provokingVertexFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_FEATURES_EXT;
|
|
if (IsExtensionSupported(availableExtensions, VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME) &&
|
|
getPhysicalDeviceFeatures2 != nullptr) {
|
|
VkPhysicalDeviceFeatures2 featureQuery{};
|
|
featureQuery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
|
|
featureQuery.pNext = &provokingVertexFeatures;
|
|
getPhysicalDeviceFeatures2(m_physicalDevice.handle, &featureQuery);
|
|
|
|
VkPhysicalDeviceProvokingVertexPropertiesEXT provokingVertexProperties{};
|
|
provokingVertexProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_PROPERTIES_EXT;
|
|
auto getPhysicalDeviceProperties2 = reinterpret_cast<PFN_vkGetPhysicalDeviceProperties2>(
|
|
vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceProperties2"));
|
|
if (getPhysicalDeviceProperties2 == nullptr) {
|
|
getPhysicalDeviceProperties2 = reinterpret_cast<PFN_vkGetPhysicalDeviceProperties2>(
|
|
vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceProperties2KHR"));
|
|
}
|
|
if (getPhysicalDeviceProperties2 != nullptr) {
|
|
VkPhysicalDeviceProperties2 propertyQuery{};
|
|
propertyQuery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
|
|
propertyQuery.pNext = &provokingVertexProperties;
|
|
getPhysicalDeviceProperties2(m_physicalDevice.handle, &propertyQuery);
|
|
}
|
|
m_provokingVertexModePerPipeline = provokingVertexProperties.provokingVertexModePerPipeline == VK_TRUE;
|
|
m_provokingVertexFanPreserved =
|
|
provokingVertexProperties.transformFeedbackPreservesTriangleFanProvokingVertex == VK_TRUE;
|
|
|
|
if (provokingVertexFeatures.provokingVertexLast == VK_TRUE) {
|
|
// transformFeedbackPreservesProvokingVertex is deliberately NOT requested. Measured:
|
|
// asking for it regresses transform_feedback.geometry on GL33 through GL45. A
|
|
// geometry shader emits its triangles already in GL's vertex order, and the pipeline
|
|
// that captures them runs on FIRST (see SelectProvokingVertexMode); without the
|
|
// guarantee the driver leaves that stream alone, but with it the capture is forced to
|
|
// follow the pipeline's FIRST convention and comes back rotated. The guarantee buys
|
|
// nothing here either - the input-assembler capture order that
|
|
// direct_state_access.queries_functional needs comes from provokingVertexLast alone,
|
|
// which was confirmed by measurement. Leaving it off also keeps VU 04884 disarmed, so
|
|
// a TRIANGLE_FAN pipeline may take LAST on any device.
|
|
const Bool wantXfbPreserve = false;
|
|
|
|
if (!IsExtensionAlreadyEnabled(enabledDeviceExtensions, VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME)) {
|
|
enabledDeviceExtensions.push_back(VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME);
|
|
}
|
|
provokingVertexFeatures.provokingVertexLast = VK_TRUE;
|
|
provokingVertexFeatures.transformFeedbackPreservesProvokingVertex =
|
|
wantXfbPreserve ? VK_TRUE : VK_FALSE;
|
|
provokingVertexFeatures.pNext = const_cast<void*>(deviceCreateInfo.pNext);
|
|
deviceCreateInfo.pNext = &provokingVertexFeatures;
|
|
m_provokingVertexLastEnabled = true;
|
|
m_provokingVertexXfbPreserveEnabled = wantXfbPreserve;
|
|
MGLOG_I("Enabled optional device extension: %s (transformFeedbackPreservesProvokingVertex=%s)",
|
|
VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME, wantXfbPreserve ? "true" : "false");
|
|
}
|
|
}
|
|
if (!m_provokingVertexLastEnabled) {
|
|
MGLOG_W("VK_EXT_provoking_vertex is unavailable; flat-shaded varyings take a primitive's first "
|
|
"vertex instead of GL's last, and transform feedback records TRIANGLE_STRIP/TRIANGLE_FAN "
|
|
"triangles rotated (0,1,2 / 1,3,2 instead of 0,1,2 / 2,1,3)");
|
|
}
|
|
|
|
if (!m_transformFeedbackFeatureEnabled) {
|
|
MGLOG_W("VK_EXT_transform_feedback is unavailable; transform feedback capture will not work");
|
|
}
|
|
|
|
// VK_EXT_vertex_attribute_divisor. Vulkan's instance input rate advances an attribute
|
|
// once per instance and nothing else, so without this every glVertexAttribDivisor value
|
|
// collapses to 1 and an attribute meant to change every N instances changes every one.
|
|
m_vertexAttributeDivisorEnabled = false;
|
|
VkPhysicalDeviceVertexAttributeDivisorFeaturesEXT vertexAttributeDivisorFeatures{};
|
|
vertexAttributeDivisorFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_ATTRIBUTE_DIVISOR_FEATURES_EXT;
|
|
if (IsExtensionSupported(availableExtensions, VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME) &&
|
|
getPhysicalDeviceFeatures2 != nullptr) {
|
|
VkPhysicalDeviceFeatures2 featureQuery{};
|
|
featureQuery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
|
|
featureQuery.pNext = &vertexAttributeDivisorFeatures;
|
|
getPhysicalDeviceFeatures2(m_physicalDevice.handle, &featureQuery);
|
|
if (vertexAttributeDivisorFeatures.vertexAttributeInstanceRateDivisor == VK_TRUE) {
|
|
if (!IsExtensionAlreadyEnabled(enabledDeviceExtensions,
|
|
VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME)) {
|
|
enabledDeviceExtensions.push_back(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME);
|
|
}
|
|
vertexAttributeDivisorFeatures.vertexAttributeInstanceRateZeroDivisor = VK_FALSE;
|
|
vertexAttributeDivisorFeatures.pNext = const_cast<void*>(deviceCreateInfo.pNext);
|
|
deviceCreateInfo.pNext = &vertexAttributeDivisorFeatures;
|
|
m_vertexAttributeDivisorEnabled = true;
|
|
MGLOG_I("Enabled optional device extension: %s", VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME);
|
|
}
|
|
}
|
|
if (!m_vertexAttributeDivisorEnabled) {
|
|
MGLOG_W("VK_EXT_vertex_attribute_divisor is unavailable; a glVertexAttribDivisor other "
|
|
"than 1 will advance its attribute once per instance");
|
|
}
|
|
|
|
// Host query reset lets the occlusion-query ring recycle slots without a
|
|
// command-buffer round trip.
|
|
m_hostQueryResetEnabled = false;
|
|
VkPhysicalDeviceHostQueryResetFeatures hostQueryResetFeatures{};
|
|
hostQueryResetFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_QUERY_RESET_FEATURES;
|
|
if (IsExtensionSupported(availableExtensions, VK_EXT_HOST_QUERY_RESET_EXTENSION_NAME) &&
|
|
getPhysicalDeviceFeatures2 != nullptr) {
|
|
VkPhysicalDeviceFeatures2 featureQuery{};
|
|
featureQuery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
|
|
featureQuery.pNext = &hostQueryResetFeatures;
|
|
getPhysicalDeviceFeatures2(m_physicalDevice.handle, &featureQuery);
|
|
if (hostQueryResetFeatures.hostQueryReset == VK_TRUE) {
|
|
if (!IsExtensionAlreadyEnabled(enabledDeviceExtensions, VK_EXT_HOST_QUERY_RESET_EXTENSION_NAME)) {
|
|
enabledDeviceExtensions.push_back(VK_EXT_HOST_QUERY_RESET_EXTENSION_NAME);
|
|
}
|
|
hostQueryResetFeatures.pNext = const_cast<void*>(deviceCreateInfo.pNext);
|
|
deviceCreateInfo.pNext = &hostQueryResetFeatures;
|
|
m_hostQueryResetEnabled = true;
|
|
}
|
|
}
|
|
|
|
// YUV shared images (WireYuvImage.cpp) are sampled through a VkSamplerYcbcrConversion, a
|
|
// feature of its own; asked only where AHardwareBuffers are imported at all.
|
|
m_samplerYcbcrConversion = false;
|
|
VkPhysicalDeviceSamplerYcbcrConversionFeatures ycbcrFeatures{};
|
|
ycbcrFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES;
|
|
if (m_wireAhbImport && getPhysicalDeviceFeatures2 != nullptr) {
|
|
VkPhysicalDeviceFeatures2 featureQuery{};
|
|
featureQuery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
|
|
featureQuery.pNext = &ycbcrFeatures;
|
|
getPhysicalDeviceFeatures2(m_physicalDevice.handle, &featureQuery);
|
|
if (ycbcrFeatures.samplerYcbcrConversion == VK_TRUE) {
|
|
ycbcrFeatures.pNext = const_cast<void*>(deviceCreateInfo.pNext);
|
|
deviceCreateInfo.pNext = &ycbcrFeatures;
|
|
m_samplerYcbcrConversion = true;
|
|
}
|
|
}
|
|
|
|
// VK_EXT_multi_draw: tier 1 of the multi-draw dispatch - one vkCmdDrawMulti(Indexed)EXT
|
|
// for a whole glMultiDraw* batch (VkMultiDrawIndexedInfoEXT carries per-draw
|
|
// firstIndex/indexCount/vertexOffset, so glMultiDrawElementsBaseVertex fits natively).
|
|
// Requested only when both the extension and its multiDraw feature are present;
|
|
// absent it, the dispatch falls to the multiDrawIndirect tier or the unrolled loop.
|
|
m_multiDrawExtensionEnabled = false;
|
|
m_maxMultiDrawCount = 0;
|
|
VkPhysicalDeviceMultiDrawFeaturesEXT multiDrawFeatures{};
|
|
multiDrawFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTI_DRAW_FEATURES_EXT;
|
|
if (IsExtensionSupported(availableExtensions, VK_EXT_MULTI_DRAW_EXTENSION_NAME) &&
|
|
getPhysicalDeviceFeatures2 != nullptr) {
|
|
VkPhysicalDeviceFeatures2 featureQuery{};
|
|
featureQuery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
|
|
featureQuery.pNext = &multiDrawFeatures;
|
|
getPhysicalDeviceFeatures2(m_physicalDevice.handle, &featureQuery);
|
|
if (multiDrawFeatures.multiDraw == VK_TRUE) {
|
|
if (!IsExtensionAlreadyEnabled(enabledDeviceExtensions, VK_EXT_MULTI_DRAW_EXTENSION_NAME)) {
|
|
enabledDeviceExtensions.push_back(VK_EXT_MULTI_DRAW_EXTENSION_NAME);
|
|
}
|
|
multiDrawFeatures.pNext = const_cast<void*>(deviceCreateInfo.pNext);
|
|
deviceCreateInfo.pNext = &multiDrawFeatures;
|
|
m_multiDrawExtensionEnabled = true;
|
|
|
|
VkPhysicalDeviceMultiDrawPropertiesEXT multiDrawProperties{};
|
|
multiDrawProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTI_DRAW_PROPERTIES_EXT;
|
|
auto getPhysicalDeviceProperties2 = reinterpret_cast<PFN_vkGetPhysicalDeviceProperties2>(
|
|
vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceProperties2"));
|
|
if (getPhysicalDeviceProperties2 == nullptr) {
|
|
getPhysicalDeviceProperties2 = reinterpret_cast<PFN_vkGetPhysicalDeviceProperties2>(
|
|
vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceProperties2KHR"));
|
|
}
|
|
if (getPhysicalDeviceProperties2 != nullptr) {
|
|
VkPhysicalDeviceProperties2 propertyQuery{};
|
|
propertyQuery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
|
|
propertyQuery.pNext = &multiDrawProperties;
|
|
getPhysicalDeviceProperties2(m_physicalDevice.handle, &propertyQuery);
|
|
}
|
|
// Spec minimum is 1024; a driver reporting 0 through a failed query must not
|
|
// zero out every batch, so fall back to the spec minimum.
|
|
m_maxMultiDrawCount =
|
|
multiDrawProperties.maxMultiDrawCount != 0 ? multiDrawProperties.maxMultiDrawCount : 1024;
|
|
MGLOG_I("Enabled optional device extension: %s (maxMultiDrawCount=%u)",
|
|
VK_EXT_MULTI_DRAW_EXTENSION_NAME, m_maxMultiDrawCount);
|
|
} else {
|
|
MGLOG_I("VK_EXT_multi_draw is advertised but its multiDraw feature is unavailable; "
|
|
"multi-draw batches use the indirect or unrolled tier");
|
|
}
|
|
}
|
|
|
|
// VK_KHR_fragment_shading_rate, for GL_EXT_fragment_shading_rate. pipelineFragmentShadingRate
|
|
// is the floor (glShadingRateEXT, read back through gl_ShadingRateEXT); the primitive rate
|
|
// (gl_PrimitiveShadingRateEXT, combiner op 0) rides along when the device has it. The
|
|
// attachment rate is left off: no GL framebuffer carries a rate attachment to this backend
|
|
// yet, and enabling the feature would only change what the frontend may promise.
|
|
// The extension builds on VK_KHR_create_renderpass2, which this 1.1 device needs by name.
|
|
m_fragmentShadingRate = {};
|
|
VkPhysicalDeviceFragmentShadingRateFeaturesKHR fragmentShadingRateFeatures{
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_FEATURES_KHR};
|
|
if (IsExtensionSupported(availableExtensions, VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME) &&
|
|
(m_physicalDevice.properties.apiVersion >= VK_API_VERSION_1_2 ||
|
|
IsExtensionSupported(availableExtensions, VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME)) &&
|
|
getPhysicalDeviceFeatures2 != nullptr && getPhysicalDeviceProperties2 != nullptr) {
|
|
VkPhysicalDeviceFeatures2 featureQuery{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2};
|
|
featureQuery.pNext = &fragmentShadingRateFeatures;
|
|
getPhysicalDeviceFeatures2(m_physicalDevice.handle, &featureQuery);
|
|
auto getFragmentShadingRates = reinterpret_cast<PFN_vkGetPhysicalDeviceFragmentShadingRatesKHR>(
|
|
vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceFragmentShadingRatesKHR"));
|
|
if (fragmentShadingRateFeatures.pipelineFragmentShadingRate == VK_TRUE &&
|
|
getFragmentShadingRates != nullptr) {
|
|
EnableOptionalDeviceExtension(availableExtensions, enabledDeviceExtensions,
|
|
VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME);
|
|
EnableOptionalDeviceExtension(availableExtensions, enabledDeviceExtensions,
|
|
VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME);
|
|
fragmentShadingRateFeatures.attachmentFragmentShadingRate = VK_FALSE;
|
|
fragmentShadingRateFeatures.pNext = const_cast<void*>(deviceCreateInfo.pNext);
|
|
deviceCreateInfo.pNext = &fragmentShadingRateFeatures;
|
|
|
|
VkPhysicalDeviceFragmentShadingRatePropertiesKHR fragmentShadingRateProperties{
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_PROPERTIES_KHR};
|
|
VkPhysicalDeviceProperties2 propertyQuery{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2};
|
|
propertyQuery.pNext = &fragmentShadingRateProperties;
|
|
getPhysicalDeviceProperties2(m_physicalDevice.handle, &propertyQuery);
|
|
|
|
auto& support = m_fragmentShadingRate;
|
|
support.Pipeline = true;
|
|
support.Primitive = fragmentShadingRateFeatures.primitiveFragmentShadingRate == VK_TRUE;
|
|
support.NonTrivialCombiners = fragmentShadingRateProperties.fragmentShadingRateNonTrivialCombinerOps;
|
|
support.WithShaderDepthStencilWrites =
|
|
fragmentShadingRateProperties.fragmentShadingRateWithShaderDepthStencilWrites;
|
|
support.WithSampleMask = fragmentShadingRateProperties.fragmentShadingRateWithSampleMask;
|
|
support.PrimitiveWithMultipleViewports =
|
|
fragmentShadingRateProperties.primitiveFragmentShadingRateWithMultipleViewports;
|
|
support.LayeredAttachments = fragmentShadingRateProperties.layeredShadingRateAttachments;
|
|
support.MinAttachmentTexelWidth =
|
|
fragmentShadingRateProperties.minFragmentShadingRateAttachmentTexelSize.width;
|
|
support.MinAttachmentTexelHeight =
|
|
fragmentShadingRateProperties.minFragmentShadingRateAttachmentTexelSize.height;
|
|
support.MaxAttachmentTexelWidth =
|
|
fragmentShadingRateProperties.maxFragmentShadingRateAttachmentTexelSize.width;
|
|
support.MaxAttachmentTexelHeight =
|
|
fragmentShadingRateProperties.maxFragmentShadingRateAttachmentTexelSize.height;
|
|
support.MaxAttachmentTexelAspectRatio =
|
|
fragmentShadingRateProperties.maxFragmentShadingRateAttachmentTexelSizeAspectRatio;
|
|
|
|
Uint32 rateCount = 0;
|
|
getFragmentShadingRates(m_physicalDevice.handle, &rateCount, nullptr);
|
|
Vector<VkPhysicalDeviceFragmentShadingRateKHR> rates(
|
|
rateCount, VkPhysicalDeviceFragmentShadingRateKHR{
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_KHR});
|
|
getFragmentShadingRates(m_physicalDevice.handle, &rateCount, rates.data());
|
|
const auto log2Of = [](Uint32 size) -> Int {
|
|
return size == 1 ? 0 : size == 2 ? 1 : size == 4 ? 2 : -1;
|
|
};
|
|
for (Uint32 r = 0; r < rateCount; ++r) {
|
|
const Int w = log2Of(rates[r].fragmentSize.width);
|
|
const Int h = log2Of(rates[r].fragmentSize.height);
|
|
if (w < 0 || h < 0) continue; // larger than GL's 4x4 can name
|
|
const Uint16 bit = static_cast<Uint16>(1u << ((w << 2) | h));
|
|
for (Uint32 i = 0; i < std::size(support.RateMasks); ++i) {
|
|
if ((rates[r].sampleCounts & (1u << i)) != 0) support.RateMasks[i] |= bit;
|
|
}
|
|
}
|
|
MGLOG_I("%s: primitive=%d nonTrivialCombiners=%d "
|
|
"rates(1x)=0x%04x rates(4x)=0x%04x",
|
|
VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME, support.Primitive, support.NonTrivialCombiners,
|
|
support.RateMasks[0], support.RateMasks[2]);
|
|
}
|
|
}
|
|
|
|
// Codex closeout finding 2 (cf-magma): GL 4.6 core 8.26 makes an access through an
|
|
// invalid image unit load zero and DISCARD stores and atomics. A null storage-image
|
|
// descriptor is that rule exactly, so the wire arm binds one for such a unit
|
|
// (UniformManager::SetWireInvalidStorageImageArm). ONLY nullDescriptor is requested:
|
|
// robustBufferAccess2 / robustImageAccess2 would put a bounds check on every access of
|
|
// every draw. Wire arms only, on the same terms as the depth-resolve extensions above -
|
|
// the monolith arm resolves image units elsewhere and keeps its device as it was.
|
|
m_wireNullDescriptor = false;
|
|
VkPhysicalDeviceRobustness2FeaturesEXT robustness2Features{};
|
|
robustness2Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT;
|
|
const char* robustness2Name = IsExtensionSupported(availableExtensions, VK_EXT_ROBUSTNESS_2_EXTENSION_NAME)
|
|
? VK_EXT_ROBUSTNESS_2_EXTENSION_NAME
|
|
: IsExtensionSupported(availableExtensions, VK_KHR_ROBUSTNESS_2_EXTENSION_NAME)
|
|
? VK_KHR_ROBUSTNESS_2_EXTENSION_NAME
|
|
: nullptr;
|
|
if (robustness2Name != nullptr && getPhysicalDeviceFeatures2 != nullptr) {
|
|
VkPhysicalDeviceFeatures2 featureQuery{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2};
|
|
featureQuery.pNext = &robustness2Features;
|
|
getPhysicalDeviceFeatures2(m_physicalDevice.handle, &featureQuery);
|
|
if (robustness2Features.nullDescriptor == VK_TRUE) {
|
|
EnableOptionalDeviceExtension(availableExtensions, enabledDeviceExtensions, robustness2Name);
|
|
robustness2Features.robustBufferAccess2 = VK_FALSE;
|
|
robustness2Features.robustImageAccess2 = VK_FALSE;
|
|
robustness2Features.pNext = const_cast<void*>(deviceCreateInfo.pNext);
|
|
deviceCreateInfo.pNext = &robustness2Features;
|
|
m_wireNullDescriptor = true;
|
|
}
|
|
}
|
|
MGLOG_I("Magma wire: robustness2 nullDescriptor %s (%s)", m_wireNullDescriptor ? "enabled" : "unavailable",
|
|
robustness2Name != nullptr ? robustness2Name : "no robustness2 extension");
|
|
|
|
deviceCreateInfo.enabledExtensionCount = static_cast<Uint32>(enabledDeviceExtensions.size());
|
|
deviceCreateInfo.ppEnabledExtensionNames = enabledDeviceExtensions.data();
|
|
MGLOG_I("Device feature support: robustBufferAccess=%s geometryShader=%s independentBlend=%s logicOp=%s "
|
|
"shaderClipDistance=%s "
|
|
"shaderCullDistance=%s wideLines=%s shaderInt64=%s vertexStoresAtomics=%s "
|
|
"fragmentStoresAtomics=%s storageImageExtendedFormats=%s storageImageReadWithoutFormat=%s "
|
|
"storageImageWriteWithoutFormat=%s drawIndirectFirstInstance=%s "
|
|
"multiDrawIndirect=%s",
|
|
supportedDeviceFeatures.robustBufferAccess ? "true" : "false",
|
|
supportedDeviceFeatures.geometryShader ? "true" : "false",
|
|
supportedDeviceFeatures.independentBlend ? "true" : "false",
|
|
supportedDeviceFeatures.logicOp ? "true" : "false",
|
|
supportedDeviceFeatures.shaderClipDistance ? "true" : "false",
|
|
supportedDeviceFeatures.shaderCullDistance ? "true" : "false",
|
|
supportedDeviceFeatures.wideLines ? "true" : "false",
|
|
supportedDeviceFeatures.shaderInt64 ? "true" : "false",
|
|
supportedDeviceFeatures.vertexPipelineStoresAndAtomics ? "true" : "false",
|
|
supportedDeviceFeatures.fragmentStoresAndAtomics ? "true" : "false",
|
|
supportedDeviceFeatures.shaderStorageImageExtendedFormats ? "true" : "false",
|
|
supportedDeviceFeatures.shaderStorageImageReadWithoutFormat ? "true" : "false",
|
|
supportedDeviceFeatures.shaderStorageImageWriteWithoutFormat ? "true" : "false",
|
|
supportedDeviceFeatures.drawIndirectFirstInstance ? "true" : "false",
|
|
supportedDeviceFeatures.multiDrawIndirect ? "true" : "false");
|
|
MGLOG_I("Device feature enabled: robustBufferAccess=%s geometryShader=%s independentBlend=%s logicOp=%s "
|
|
"shaderClipDistance=%s "
|
|
"shaderCullDistance=%s wideLines=%s shaderInt64=%s vertexStoresAtomics=%s "
|
|
"fragmentStoresAtomics=%s storageImageExtendedFormats=%s storageImageReadWithoutFormat=%s "
|
|
"storageImageWriteWithoutFormat=%s drawIndirectFirstInstance=%s "
|
|
"multiDrawIndirect=%s shaderDrawParameters=%s",
|
|
deviceFeatures.robustBufferAccess ? "true" : "false", deviceFeatures.geometryShader ? "true" : "false",
|
|
deviceFeatures.independentBlend ? "true" : "false", deviceFeatures.logicOp ? "true" : "false",
|
|
deviceFeatures.shaderClipDistance ? "true" : "false",
|
|
deviceFeatures.shaderCullDistance ? "true" : "false", deviceFeatures.wideLines ? "true" : "false",
|
|
deviceFeatures.shaderInt64 ? "true" : "false",
|
|
deviceFeatures.vertexPipelineStoresAndAtomics ? "true" : "false",
|
|
deviceFeatures.fragmentStoresAndAtomics ? "true" : "false",
|
|
deviceFeatures.shaderStorageImageExtendedFormats ? "true" : "false",
|
|
deviceFeatures.shaderStorageImageReadWithoutFormat ? "true" : "false",
|
|
deviceFeatures.shaderStorageImageWriteWithoutFormat ? "true" : "false",
|
|
deviceFeatures.drawIndirectFirstInstance ? "true" : "false",
|
|
deviceFeatures.multiDrawIndirect ? "true" : "false",
|
|
m_shaderDrawParametersFeatureEnabled ? "true" : "false");
|
|
VK_VERIFY(vkCreateDevice(m_physicalDevice.handle, &deviceCreateInfo, nullptr, &m_device), "vkCreateDevice");
|
|
|
|
if (wireDepthResolveEnabled) {
|
|
m_wireCreateRenderPass2 = reinterpret_cast<PFN_vkCreateRenderPass2>(
|
|
vkGetDeviceProcAddr(m_device, wireDepthResolveCore ? "vkCreateRenderPass2" : "vkCreateRenderPass2KHR"));
|
|
}
|
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
|
if (m_wireAhbImport) {
|
|
m_wireGetAhbProperties =
|
|
reinterpret_cast<void*>(vkGetDeviceProcAddr(m_device, "vkGetAndroidHardwareBufferPropertiesANDROID"));
|
|
if (m_wireGetAhbProperties == nullptr) m_wireAhbImport = false;
|
|
}
|
|
if (m_sharedImageSyncFd) {
|
|
m_importSemaphoreFd =
|
|
reinterpret_cast<PFN_vkImportSemaphoreFdKHR>(vkGetDeviceProcAddr(m_device, "vkImportSemaphoreFdKHR"));
|
|
m_getSemaphoreFd =
|
|
reinterpret_cast<PFN_vkGetSemaphoreFdKHR>(vkGetDeviceProcAddr(m_device, "vkGetSemaphoreFdKHR"));
|
|
if (m_importSemaphoreFd == nullptr || m_getSemaphoreFd == nullptr) {
|
|
MGLOG_W("DirectVulkan: VK_KHR_external_semaphore_fd is missing an entry point; shared-image fences "
|
|
"are waited for on the CPU");
|
|
m_sharedImageSyncFd = false;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
s_vkCmdDrawIndexedIndirectCount = reinterpret_cast<PFNDrawIndexedIndirectCountFunc>(
|
|
vkGetDeviceProcAddr(m_device, "vkCmdDrawIndexedIndirectCountKHR"));
|
|
if (s_vkCmdDrawIndexedIndirectCount == nullptr) {
|
|
s_vkCmdDrawIndexedIndirectCount = reinterpret_cast<PFNDrawIndexedIndirectCountFunc>(
|
|
vkGetDeviceProcAddr(m_device, "vkCmdDrawIndexedIndirectCount"));
|
|
}
|
|
if (m_drawIndirectCountExtensionEnabled && s_vkCmdDrawIndexedIndirectCount == nullptr) {
|
|
MGLOG_W("VK_KHR_draw_indirect_count enabled but vkCmdDrawIndexedIndirectCount entry point is missing, will "
|
|
"continue as if VK_KHR_draw_indirect_count is not supported!");
|
|
m_drawIndirectCountExtensionEnabled = false;
|
|
}
|
|
// P8-D: the wire arm's glMultiDrawArraysIndirectCount. Null = that verb reads its count on
|
|
// the CPU (WireDraw.cpp's DrawWireIndirectNative answers false for it).
|
|
s_vkCmdWireDrawIndirectCount = reinterpret_cast<PFNDrawIndexedIndirectCountFunc>(
|
|
vkGetDeviceProcAddr(m_device, "vkCmdDrawIndirectCountKHR"));
|
|
if (s_vkCmdWireDrawIndirectCount == nullptr) {
|
|
s_vkCmdWireDrawIndirectCount = reinterpret_cast<PFNDrawIndexedIndirectCountFunc>(
|
|
vkGetDeviceProcAddr(m_device, "vkCmdDrawIndirectCount"));
|
|
}
|
|
|
|
s_vkCmdDrawMultiEXT = nullptr;
|
|
s_vkCmdDrawMultiIndexedEXT = nullptr;
|
|
if (m_multiDrawExtensionEnabled) {
|
|
s_vkCmdDrawMultiEXT =
|
|
reinterpret_cast<PFN_vkCmdDrawMultiEXT>(vkGetDeviceProcAddr(m_device, "vkCmdDrawMultiEXT"));
|
|
s_vkCmdDrawMultiIndexedEXT = reinterpret_cast<PFN_vkCmdDrawMultiIndexedEXT>(
|
|
vkGetDeviceProcAddr(m_device, "vkCmdDrawMultiIndexedEXT"));
|
|
if (s_vkCmdDrawMultiEXT == nullptr || s_vkCmdDrawMultiIndexedEXT == nullptr) {
|
|
MGLOG_W("VK_EXT_multi_draw enabled but its entry points are missing, will continue as if "
|
|
"VK_EXT_multi_draw is not supported!");
|
|
s_vkCmdDrawMultiEXT = nullptr;
|
|
s_vkCmdDrawMultiIndexedEXT = nullptr;
|
|
m_multiDrawExtensionEnabled = false;
|
|
}
|
|
}
|
|
|
|
s_vkCmdSetFragmentShadingRateKHR = nullptr;
|
|
if (m_fragmentShadingRate.Pipeline) {
|
|
s_vkCmdSetFragmentShadingRateKHR = reinterpret_cast<PFN_vkCmdSetFragmentShadingRateKHR>(
|
|
vkGetDeviceProcAddr(m_device, "vkCmdSetFragmentShadingRateKHR"));
|
|
if (s_vkCmdSetFragmentShadingRateKHR == nullptr) {
|
|
MGLOG_W("VK_KHR_fragment_shading_rate enabled but vkCmdSetFragmentShadingRateKHR is missing, "
|
|
"will continue without fragment shading rates!");
|
|
m_fragmentShadingRate = {};
|
|
}
|
|
}
|
|
|
|
// Resolve the multi-draw dispatch tiers once: device support clamped by the
|
|
// MOBILEGL_MAGMA_MULTIDRAW_MODE preference. Requesting an unavailable tier is
|
|
// never an error - the dispatch falls down the chain ext -> indirect -> unroll.
|
|
{
|
|
using MG_Config::MultiDrawMode;
|
|
const MultiDrawMode mode = MG_Config::Features.MagmaMultiDrawMode;
|
|
m_multiDrawAllowExt =
|
|
m_multiDrawExtensionEnabled && (mode == MultiDrawMode::Auto || mode == MultiDrawMode::Ext);
|
|
m_multiDrawAllowIndirect = m_multiDrawIndirectFeatureEnabled && mode != MultiDrawMode::Unroll;
|
|
m_multiDrawForceUnrollIndirect = mode == MultiDrawMode::Unroll;
|
|
if (mode == MultiDrawMode::Ext && !m_multiDrawExtensionEnabled) {
|
|
MGLOG_I("MOBILEGL_MAGMA_MULTIDRAW_MODE=ext requested but VK_EXT_multi_draw is unavailable; "
|
|
"falling back to the %s tier",
|
|
m_multiDrawAllowIndirect ? "indirect" : "unroll");
|
|
}
|
|
if (mode == MultiDrawMode::Indirect && !m_multiDrawIndirectFeatureEnabled) {
|
|
MGLOG_I("MOBILEGL_MAGMA_MULTIDRAW_MODE=indirect requested but the multiDrawIndirect device "
|
|
"feature is unavailable; falling back to the unroll tier");
|
|
}
|
|
MGLOG_I("Multi-draw dispatch tier: %s (VK_EXT_multi_draw=%s, multiDrawIndirect=%s, mode=%s)",
|
|
m_multiDrawAllowExt ? "ext" : (m_multiDrawAllowIndirect ? "indirect" : "unroll"),
|
|
m_multiDrawExtensionEnabled ? "true" : "false",
|
|
m_multiDrawIndirectFeatureEnabled ? "true" : "false",
|
|
mode == MultiDrawMode::Auto ? "auto"
|
|
: mode == MultiDrawMode::Ext ? "ext"
|
|
: mode == MultiDrawMode::Indirect ? "indirect"
|
|
: "unroll");
|
|
}
|
|
|
|
if (m_transformFeedbackFeatureEnabled) {
|
|
s_vkCmdBindTransformFeedbackBuffersEXT = reinterpret_cast<PFN_vkCmdBindTransformFeedbackBuffersEXT>(
|
|
vkGetDeviceProcAddr(m_device, "vkCmdBindTransformFeedbackBuffersEXT"));
|
|
s_vkCmdBeginTransformFeedbackEXT = reinterpret_cast<PFN_vkCmdBeginTransformFeedbackEXT>(
|
|
vkGetDeviceProcAddr(m_device, "vkCmdBeginTransformFeedbackEXT"));
|
|
s_vkCmdEndTransformFeedbackEXT = reinterpret_cast<PFN_vkCmdEndTransformFeedbackEXT>(
|
|
vkGetDeviceProcAddr(m_device, "vkCmdEndTransformFeedbackEXT"));
|
|
if (s_vkCmdBindTransformFeedbackBuffersEXT == nullptr || s_vkCmdBeginTransformFeedbackEXT == nullptr ||
|
|
s_vkCmdEndTransformFeedbackEXT == nullptr) {
|
|
MGLOG_W("VK_EXT_transform_feedback entry points missing; transform feedback capture disabled");
|
|
m_transformFeedbackFeatureEnabled = false;
|
|
}
|
|
}
|
|
if (m_hostQueryResetEnabled) {
|
|
s_vkResetQueryPool =
|
|
reinterpret_cast<PFN_vkResetQueryPool>(vkGetDeviceProcAddr(m_device, "vkResetQueryPool"));
|
|
if (s_vkResetQueryPool == nullptr) {
|
|
s_vkResetQueryPool =
|
|
reinterpret_cast<PFN_vkResetQueryPool>(vkGetDeviceProcAddr(m_device, "vkResetQueryPoolEXT"));
|
|
}
|
|
if (s_vkResetQueryPool == nullptr) {
|
|
m_hostQueryResetEnabled = false;
|
|
}
|
|
}
|
|
if (m_transformFeedbackFeatureEnabled) {
|
|
s_vkCmdBeginQueryIndexedEXT = reinterpret_cast<PFN_vkCmdBeginQueryIndexedEXT>(
|
|
vkGetDeviceProcAddr(m_device, "vkCmdBeginQueryIndexedEXT"));
|
|
s_vkCmdEndQueryIndexedEXT =
|
|
reinterpret_cast<PFN_vkCmdEndQueryIndexedEXT>(vkGetDeviceProcAddr(m_device, "vkCmdEndQueryIndexedEXT"));
|
|
VkPhysicalDeviceTransformFeedbackPropertiesEXT xfbProperties{};
|
|
xfbProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_PROPERTIES_EXT;
|
|
VkPhysicalDeviceProperties2 properties2{};
|
|
properties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
|
|
properties2.pNext = &xfbProperties;
|
|
// Resolved via proc addr: vkGetPhysicalDeviceProperties2 is Vulkan 1.1, and Android's
|
|
// libvulkan.so only exports it from API 28 while minSdk is 26.
|
|
auto getPhysicalDeviceProperties2 = reinterpret_cast<PFN_vkGetPhysicalDeviceProperties2>(
|
|
vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceProperties2"));
|
|
if (getPhysicalDeviceProperties2 == nullptr) {
|
|
getPhysicalDeviceProperties2 = reinterpret_cast<PFN_vkGetPhysicalDeviceProperties2>(
|
|
vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceProperties2KHR"));
|
|
}
|
|
if (getPhysicalDeviceProperties2 != nullptr) {
|
|
getPhysicalDeviceProperties2(m_physicalDevice.handle, &properties2);
|
|
}
|
|
m_xfbQueriesSupported = xfbProperties.transformFeedbackQueries == VK_TRUE &&
|
|
s_vkCmdBeginQueryIndexedEXT != nullptr && s_vkCmdEndQueryIndexedEXT != nullptr;
|
|
}
|
|
MGLOG_I("index type uint8 enabled: %s", m_indexTypeUint8ExtensionEnabled ? "true" : "false");
|
|
MGLOG_I("Logical device created.");
|
|
|
|
// Queues
|
|
vkGetDeviceQueue(m_device, m_physicalDevice.queueFamilies.graphicsFamily, 0, &m_graphicsQueue);
|
|
vkGetDeviceQueue(m_device, m_physicalDevice.queueFamilies.presentFamily, 0, &m_presentQueue);
|
|
MGLOG_I("Queues got successfully.");
|
|
|
|
// Timestamp (timer query) support: re-enumerate the graphics queue
|
|
// family's properties for its timestampValidBits (0 means the queue
|
|
// cannot write timestamps) and take timestampPeriod (ns per tick) from
|
|
// the device limits.
|
|
const auto timestampQueueFamilies = GetQueueFamilyFromPhysicalDevice(m_physicalDevice.handle);
|
|
m_timestampValidBits = 0;
|
|
const Int32 graphicsFamilyIndex = m_physicalDevice.queueFamilies.graphicsFamily;
|
|
if (graphicsFamilyIndex >= 0 && static_cast<SizeT>(graphicsFamilyIndex) < timestampQueueFamilies.size()) {
|
|
m_timestampValidBits = timestampQueueFamilies[graphicsFamilyIndex].timestampValidBits;
|
|
}
|
|
m_timestampPeriodNs = m_physicalDevice.properties.limits.timestampPeriod;
|
|
m_timerQuerySupported = m_timestampValidBits > 0 && m_timestampPeriodNs > 0.0f;
|
|
MGLOG_I("Timer queries %s (timestampValidBits=%u, timestampPeriod=%f ns/tick)",
|
|
m_timerQuerySupported ? "supported" : "not supported", m_timestampValidBits, m_timestampPeriodNs);
|
|
|
|
// Last, because it records on m_graphicsQueue: decide the PRIMITIVES_GENERATED
|
|
// reroute for XFB-inactive draws. Nothing else has touched the queue yet.
|
|
ArmPrimGenReroute();
|
|
// Same terms: it records on m_graphicsQueue, which nothing but the probe above has used.
|
|
ArmWireDepthResolveOrder();
|
|
// Same terms again.
|
|
ArmWireE5RawReads();
|
|
ArmWireCopySliceSplit();
|
|
ArmWireDiscardStateReemit();
|
|
}
|
|
|
|
// P7 gate 5 (g5-msprobe): THE ARM ORDER OF THE MULTISAMPLE DEPTH/STENCIL RESOLVE IS A MEASURED
|
|
// PROPERTY OF THIS DEVICE. WireDepthResolveArm.h says why (the Adreno 830's no-draw
|
|
// VK_KHR_depth_stencil_resolve pass writes nothing), WireDepthResolveProbe.h what is run. The
|
|
// answer is a device property, so it is memoized per DEVICE IDENTITY (WireDepthResolveArmCache:
|
|
// vendor, device, driver version, pipeline-cache UUID - codex closeout finding 7), and logged once
|
|
// per identity with every reading - the line the device evidence quotes.
|
|
//
|
|
// THE MEASUREMENT CAN BE REPLACED, AND ONLY HERE: MGITEST_MAGMA_DEPTH_RESOLVE_PROBE=bug|clean
|
|
// hands ChooseWireDepthResolveArm a canned measurement instead of running the probe - no host
|
|
// lane runs on a device with the defect - and the DirectVulkan.{Split,Spawn}.MsResolveBug./
|
|
// MsFlipBug. entries force `bug` and read ResolveWireDepthStencil's arm line back from the
|
|
// server log. The canned measurement is evaluated like a real one, so those entries go red
|
|
// when the evaluation stops detecting the defect. MGITEST_MAGMA_DEPTH_RESOLVE_PROBE=elide-subject
|
|
// runs the REAL probe with its render-pass resolve left unrecorded (the sentinel survives, the
|
|
// shader control still resolves), so the DirectVulkan.{Split,Spawn}.MsResolveElide. entries go
|
|
// red when the real recording, readback or tally stops detecting it too.
|
|
void VulkanRenderer::ArmWireDepthResolveOrder() {
|
|
m_wirePreferShaderDepthResolve = false;
|
|
// ResolveWireDepthStencil's own test for the render-pass arm, less its per-format half
|
|
// (the probe asks that per format): without it the shader pass is the only arm.
|
|
const Bool renderPassArmAvailable = !MagmaWireForcedShaderDepthResolve() && m_wireCreateRenderPass2 &&
|
|
(m_wireDepthResolveModes & VK_RESOLVE_MODE_SAMPLE_ZERO_BIT) != 0;
|
|
const char* knobValue = std::getenv("MGITEST_MAGMA_DEPTH_RESOLVE_PROBE");
|
|
const WireDepthResolveProbeKnob knob = ParseWireDepthResolveProbeKnob(knobValue);
|
|
// Memoized per DEVICE IDENTITY, not per process (codex closeout finding 7; see
|
|
// WireDepthResolveArmCache): a second renderer on another device or driver probes for itself.
|
|
const WireDepthResolveDeviceIdentity identity =
|
|
MakeWireDepthResolveDeviceIdentity(m_physicalDevice.properties, renderPassArmAvailable, knob);
|
|
static WireDepthResolveArmCache s_choices;
|
|
const WireDepthResolveArmChoice resolved = s_choices.Resolve(identity, [&]() {
|
|
if (knob == WireDepthResolveProbeKnob::Unrecognised)
|
|
MGLOG_W("DirectVulkan: MGITEST_MAGMA_DEPTH_RESOLVE_PROBE=%s is not `bug`, `clean` or "
|
|
"`elide-subject`; the resolve probe runs as if it were unset",
|
|
knobValue);
|
|
WireDepthResolveArmChoice choice = ChooseWireDepthResolveArm(knob, renderPassArmAvailable, [&]() {
|
|
WireDepthResolveProbeContext context;
|
|
context.physicalDevice = m_physicalDevice.handle;
|
|
context.device = m_device;
|
|
context.queue = m_graphicsQueue;
|
|
context.queueFamilyIndex = static_cast<Uint32>(m_physicalDevice.queueFamilies.graphicsFamily);
|
|
context.createRenderPass2 = m_wireCreateRenderPass2;
|
|
context.depthResolveModes = m_wireDepthResolveModes;
|
|
context.stencilResolveModes = m_wireStencilResolveModes;
|
|
context.shaderStencilExport = m_wireShaderStencilExport;
|
|
context.elideSubject = knob == WireDepthResolveProbeKnob::ElideSubject;
|
|
return RunWireDepthResolveProbe(context);
|
|
});
|
|
if (choice.measurement.fenceWaitTimedOut) {
|
|
// Its objects are leaked on purpose (see the PRIMITIVES_GENERATED probe above for
|
|
// why this device must not be idle-waited on their account).
|
|
MGLOG_W("DirectVulkan: the depth/stencil resolve probe timed out waiting on its own "
|
|
"submission; its Vulkan objects are deliberately leaked and the render-pass arm "
|
|
"keeps its place");
|
|
} else if (choice.verdict == WireDepthResolveProbeVerdict::Inconclusive) {
|
|
MGLOG_W("DirectVulkan: depth/stencil resolve probe verdict=inconclusive - the shader "
|
|
"control did not resolve the probe's inputs either, so nothing is concluded about "
|
|
"the render pass and the default arm order stands");
|
|
}
|
|
MGLOG_I("DirectVulkan: depth/stencil resolve probe (%s) verdict=%s: %s%s%s",
|
|
choice.measurement.fromKnob ? "forced by MGITEST_MAGMA_DEPTH_RESOLVE_PROBE"
|
|
: choice.measurement.subjectElided
|
|
? "measured on this device with its render-pass resolve elided by "
|
|
"MGITEST_MAGMA_DEPTH_RESOLVE_PROBE=elide-subject"
|
|
: renderPassArmAvailable ? "measured on this device"
|
|
: "not run: no render-pass arm",
|
|
WireDepthResolveProbeVerdictName(choice.verdict),
|
|
choice.preferShader ? "the shader pass resolves first, the no-draw render pass is the fallback"
|
|
: (renderPassArmAvailable || choice.measurement.fromKnob)
|
|
? "the VK_KHR_depth_stencil_resolve render pass resolves first"
|
|
: "the shader pass is the only arm",
|
|
choice.measurement.failureReason.empty() ? "" : "; ", choice.measurement.failureReason.c_str());
|
|
for (const WireDepthResolveFormatReading& reading : choice.measurement.formats)
|
|
MGLOG_I("DirectVulkan: depth/stencil resolve probe %s",
|
|
DescribeWireDepthResolveFormat(reading).c_str());
|
|
return choice;
|
|
});
|
|
m_wirePreferShaderDepthResolve = resolved.preferShader;
|
|
}
|
|
|
|
// THE RAW E5B9G9R9 READ ROUTE IS ARMED BY MEASUREMENT (WireE5RawCopy.h): Auto runs
|
|
// RunWireE5TransferProbe once per device identity and arms the route only where a plain transfer
|
|
// read changed words a shader read back exactly; MOBILEGL_MAGMA_E5_RAW_READS=1/0 forces it.
|
|
void VulkanRenderer::ArmWireE5RawReads() {
|
|
m_wireE5RawReads = false;
|
|
const MG_Config::QuirkOverride setting = MG_Config::Features.MagmaE5RawReads;
|
|
if (setting == MG_Config::QuirkOverride::ForceOff) {
|
|
MGLOG_I("DirectVulkan: E5B9G9R9 raw reads off (MOBILEGL_MAGMA_E5_RAW_READS)");
|
|
return;
|
|
}
|
|
if (!WireE5RawCopySupported(m_physicalDevice.handle)) {
|
|
MGLOG_I("DirectVulkan: E5B9G9R9 raw reads unavailable (R32_UINT is not sampled + storage here)");
|
|
return;
|
|
}
|
|
Bool armed = setting == MG_Config::QuirkOverride::ForceOn;
|
|
if (setting == MG_Config::QuirkOverride::Auto) {
|
|
struct Entry {
|
|
WireDepthResolveDeviceIdentity identity;
|
|
WireE5TransferProbeVerdict verdict;
|
|
};
|
|
static std::mutex s_mutex;
|
|
static Vector<Entry> s_entries;
|
|
const WireDepthResolveDeviceIdentity identity = MakeWireDepthResolveDeviceIdentity(
|
|
m_physicalDevice.properties, false, WireDepthResolveProbeKnob::Measure);
|
|
std::lock_guard<std::mutex> lock(s_mutex);
|
|
const Entry* found = nullptr;
|
|
for (const Entry& entry : s_entries)
|
|
if (entry.identity == identity) found = &entry;
|
|
if (found == nullptr) {
|
|
WireE5TransferProbeContext context;
|
|
context.physicalDevice = m_physicalDevice.handle;
|
|
context.device = m_device;
|
|
context.queue = m_graphicsQueue;
|
|
context.queueFamilyIndex = static_cast<Uint32>(m_physicalDevice.queueFamilies.graphicsFamily);
|
|
const WireE5TransferProbeMeasurement measurement = RunWireE5TransferProbe(context);
|
|
const WireE5TransferProbeVerdict verdict = EvaluateWireE5TransferProbe(measurement);
|
|
if (measurement.fenceWaitTimedOut)
|
|
MGLOG_W("DirectVulkan: the E5B9G9R9 transfer probe timed out waiting on its own submission; "
|
|
"its Vulkan objects are deliberately leaked");
|
|
MGLOG_I("DirectVulkan: E5B9G9R9 transfer probe verdict=%s: %s", WireE5TransferProbeVerdictName(verdict),
|
|
DescribeWireE5TransferProbe(measurement).c_str());
|
|
s_entries.push_back({identity, verdict});
|
|
found = &s_entries.back();
|
|
}
|
|
armed = found->verdict == WireE5TransferProbeVerdict::TransferReadNormalizes;
|
|
}
|
|
if (armed) {
|
|
if (const VkResult result = CreateWireE5RawCopyKit(m_device, m_wireE5RawCopyKit); result != VK_SUCCESS) {
|
|
MGLOG_W("DirectVulkan: E5B9G9R9 raw route kit creation failed (%d); transfer reads stay plain",
|
|
static_cast<Int>(result));
|
|
DestroyWireE5RawCopyKit(m_device, m_wireE5RawCopyKit);
|
|
armed = false;
|
|
}
|
|
}
|
|
m_wireE5RawReads = armed;
|
|
MGLOG_I("DirectVulkan: E5B9G9R9 reads %s", armed ? "go through the raw shader route" : "use plain transfers");
|
|
}
|
|
|
|
// THE PER-SLICE SPLIT IS ARMED BY MEASUREMENT (WireCopySliceProbe.h), once per device identity,
|
|
// or forced by MOBILEGL_MAGMA_SPLIT_SLICE_COPIES.
|
|
void VulkanRenderer::ArmWireCopySliceSplit() {
|
|
const MG_Config::QuirkOverride setting = MG_Config::Features.MagmaSplitSliceCopies;
|
|
Bool armed = setting == MG_Config::QuirkOverride::ForceOn;
|
|
if (setting == MG_Config::QuirkOverride::Auto) {
|
|
struct Entry {
|
|
WireDepthResolveDeviceIdentity identity;
|
|
WireCopySliceProbeVerdict verdict;
|
|
};
|
|
static std::mutex s_mutex;
|
|
static Vector<Entry> s_entries;
|
|
const WireDepthResolveDeviceIdentity identity = MakeWireDepthResolveDeviceIdentity(
|
|
m_physicalDevice.properties, false, WireDepthResolveProbeKnob::Measure);
|
|
std::lock_guard<std::mutex> lock(s_mutex);
|
|
const Entry* found = nullptr;
|
|
for (const Entry& entry : s_entries)
|
|
if (entry.identity == identity) found = &entry;
|
|
if (found == nullptr) {
|
|
WireCopySliceProbeContext context;
|
|
context.physicalDevice = m_physicalDevice.handle;
|
|
context.device = m_device;
|
|
context.queue = m_graphicsQueue;
|
|
context.queueFamilyIndex = static_cast<Uint32>(m_physicalDevice.queueFamilies.graphicsFamily);
|
|
const WireCopySliceProbeMeasurement measurement = RunWireCopySliceProbe(context);
|
|
const WireCopySliceProbeVerdict verdict = EvaluateWireCopySliceProbe(measurement);
|
|
if (measurement.fenceWaitTimedOut)
|
|
MGLOG_W("DirectVulkan: the slice copy probe timed out waiting on its own submission; its Vulkan "
|
|
"objects are deliberately leaked");
|
|
MGLOG_I("DirectVulkan: slice copy probe verdict=%s: %s", WireCopySliceProbeVerdictName(verdict),
|
|
DescribeWireCopySliceProbe(measurement).c_str());
|
|
s_entries.push_back({identity, verdict});
|
|
found = &s_entries.back();
|
|
}
|
|
armed = found->verdict == WireCopySliceProbeVerdict::CrossFormatSlicesDropped;
|
|
}
|
|
m_wireSplitCrossFormatSliceCopies = armed;
|
|
MGLOG_I("DirectVulkan: cross-format layered <-> 3D copies %s",
|
|
armed ? "are split one slice per region" : "go as one region");
|
|
}
|
|
|
|
// THE RE-EMIT AFTER A DISCARD DRAW IS ARMED BY MEASUREMENT (WireDiscardStateProbe.h), once per
|
|
// device identity, or forced by MOBILEGL_MAGMA_REEMIT_STATE_AFTER_DISCARD.
|
|
void VulkanRenderer::ArmWireDiscardStateReemit() {
|
|
const MG_Config::QuirkOverride setting = MG_Config::Features.MagmaReemitStateAfterDiscard;
|
|
Bool armed = setting == MG_Config::QuirkOverride::ForceOn;
|
|
if (setting == MG_Config::QuirkOverride::Auto) {
|
|
struct Entry {
|
|
WireDepthResolveDeviceIdentity identity;
|
|
WireDiscardStateProbeVerdict verdict;
|
|
};
|
|
static std::mutex s_mutex;
|
|
static Vector<Entry> s_entries;
|
|
const WireDepthResolveDeviceIdentity identity = MakeWireDepthResolveDeviceIdentity(
|
|
m_physicalDevice.properties, false, WireDepthResolveProbeKnob::Measure);
|
|
std::lock_guard<std::mutex> lock(s_mutex);
|
|
const Entry* found = nullptr;
|
|
for (const Entry& entry : s_entries)
|
|
if (entry.identity == identity) found = &entry;
|
|
if (found == nullptr) {
|
|
WireDiscardStateProbeContext context;
|
|
context.physicalDevice = m_physicalDevice.handle;
|
|
context.device = m_device;
|
|
context.queue = m_graphicsQueue;
|
|
context.queueFamilyIndex = static_cast<Uint32>(m_physicalDevice.queueFamilies.graphicsFamily);
|
|
const WireDiscardStateProbeMeasurement measurement = RunWireDiscardStateProbe(context);
|
|
const WireDiscardStateProbeVerdict verdict = EvaluateWireDiscardStateProbe(measurement);
|
|
if (measurement.fenceWaitTimedOut)
|
|
MGLOG_W("DirectVulkan: the discard state probe timed out waiting on its own submission; its "
|
|
"Vulkan objects are deliberately leaked");
|
|
MGLOG_I("DirectVulkan: discard state probe verdict=%s: %s", WireDiscardStateProbeVerdictName(verdict),
|
|
DescribeWireDiscardStateProbe(measurement).c_str());
|
|
s_entries.push_back({identity, verdict});
|
|
found = &s_entries.back();
|
|
}
|
|
armed = found->verdict == WireDiscardStateProbeVerdict::DiscardDrawDropsState;
|
|
}
|
|
m_wireReemitStateAfterDiscard = armed;
|
|
MGLOG_I("DirectVulkan: dynamic state %s",
|
|
armed ? "is set again after every rasterizer-discard draw" : "carries across discard draws");
|
|
}
|
|
|
|
void VulkanRenderer::ArmPrimGenReroute() {
|
|
using namespace MG_Util::SelfTest;
|
|
m_primGenRerouteKind = PrimGenRerouteKind::None;
|
|
const MG_Config::QuirkOverride overrideSetting = MG_Config::Features.MagmaPrimGenQueryReroute;
|
|
// Without stream queries the GENERATED path never opens a slot at all, so
|
|
// there is nothing to reroute - whatever the override says.
|
|
if (!m_xfbQueriesSupported || !m_hostQueryResetEnabled) {
|
|
return;
|
|
}
|
|
const Bool primitivesGeneratedQueryUsable =
|
|
m_primitivesGeneratedQueryFeatureEnabled && m_primitivesGeneratedQueryDiscardFeatureEnabled;
|
|
PrimitivesGeneratedNoXfbVerdict verdict = PrimitivesGeneratedNoXfbVerdict::Inconclusive;
|
|
// The probe only matters under Auto (ForceOn bypasses the verdict, ForceOff
|
|
// never asks), and the answer is a device property - so it is memoized per
|
|
// process rather than re-paid on every renderer recreation.
|
|
if (overrideSetting == MG_Config::QuirkOverride::Auto) {
|
|
static const PrimitivesGeneratedNoXfbMeasurement s_measurement = [&]() {
|
|
PrimitivesGeneratedNoXfbProbeContext probeContext;
|
|
probeContext.device = m_device;
|
|
probeContext.queue = m_graphicsQueue;
|
|
probeContext.queueFamilyIndex = static_cast<Uint32>(m_physicalDevice.queueFamilies.graphicsFamily);
|
|
probeContext.transformFeedbackQueriesUsable = m_xfbQueriesSupported;
|
|
probeContext.primitivesGeneratedQueryUsable = primitivesGeneratedQueryUsable;
|
|
probeContext.pipelineStatisticsEnabled = m_pipelineStatisticsQueryFeatureEnabled;
|
|
probeContext.tessellationEnabled = m_tessellationShaderFeatureEnabled;
|
|
auto& fns = probeContext.fns;
|
|
fns.vkCreateCommandPool = vkCreateCommandPool;
|
|
fns.vkDestroyCommandPool = vkDestroyCommandPool;
|
|
fns.vkAllocateCommandBuffers = vkAllocateCommandBuffers;
|
|
fns.vkBeginCommandBuffer = vkBeginCommandBuffer;
|
|
fns.vkEndCommandBuffer = vkEndCommandBuffer;
|
|
fns.vkCreateQueryPool = vkCreateQueryPool;
|
|
fns.vkDestroyQueryPool = vkDestroyQueryPool;
|
|
fns.vkCmdResetQueryPool = vkCmdResetQueryPool;
|
|
fns.vkCmdBeginQuery = vkCmdBeginQuery;
|
|
fns.vkCmdEndQuery = vkCmdEndQuery;
|
|
fns.vkCmdBeginQueryIndexedEXT = s_vkCmdBeginQueryIndexedEXT;
|
|
fns.vkCmdEndQueryIndexedEXT = s_vkCmdEndQueryIndexedEXT;
|
|
fns.vkCreateRenderPass = vkCreateRenderPass;
|
|
fns.vkDestroyRenderPass = vkDestroyRenderPass;
|
|
fns.vkCreateFramebuffer = vkCreateFramebuffer;
|
|
fns.vkDestroyFramebuffer = vkDestroyFramebuffer;
|
|
fns.vkCmdBeginRenderPass = vkCmdBeginRenderPass;
|
|
fns.vkCmdEndRenderPass = vkCmdEndRenderPass;
|
|
fns.vkCreateShaderModule = vkCreateShaderModule;
|
|
fns.vkDestroyShaderModule = vkDestroyShaderModule;
|
|
fns.vkCreatePipelineLayout = vkCreatePipelineLayout;
|
|
fns.vkDestroyPipelineLayout = vkDestroyPipelineLayout;
|
|
fns.vkCreateGraphicsPipelines = vkCreateGraphicsPipelines;
|
|
fns.vkDestroyPipeline = vkDestroyPipeline;
|
|
fns.vkCmdBindPipeline = vkCmdBindPipeline;
|
|
fns.vkCmdDraw = vkCmdDraw;
|
|
fns.vkCreateFence = vkCreateFence;
|
|
fns.vkDestroyFence = vkDestroyFence;
|
|
fns.vkQueueSubmit = vkQueueSubmit;
|
|
fns.vkWaitForFences = vkWaitForFences;
|
|
fns.vkGetQueryPoolResults = vkGetQueryPoolResults;
|
|
fns.vkDeviceWaitIdle = vkDeviceWaitIdle;
|
|
return RunPrimitivesGeneratedNoXfbProbe(probeContext);
|
|
}();
|
|
verdict = EvaluatePrimitivesGeneratedNoXfbVerdict(s_measurement);
|
|
if (s_measurement.fenceWaitTimedOut) {
|
|
// The probe's submission never signaled within its bound, so it left its
|
|
// command pool, query pools, render pass, framebuffer, shader modules,
|
|
// pipeline layout, pipelines and fence alive on purpose. This device is the
|
|
// renderer's own and outlives them, so nothing here may destroy them or
|
|
// wait the device idle - the queue may still be executing that submission,
|
|
// and an idle wait is the hang the bound exists to prevent. They leak for
|
|
// the process's life; a device this sick has bigger problems.
|
|
MGLOG_W("PRIMITIVES_GENERATED probe timed out waiting on its own submission (%s); its "
|
|
"Vulkan objects are deliberately leaked and XFB-inactive draws keep the stream "
|
|
"query",
|
|
s_measurement.failureReason.c_str());
|
|
} else if (!s_measurement.ran) {
|
|
MGLOG_W("PRIMITIVES_GENERATED probe did not run (%s); XFB-inactive draws keep the "
|
|
"stream query",
|
|
s_measurement.failureReason.c_str());
|
|
} else {
|
|
const auto logShape = [](const char* name,
|
|
const MG_Util::SelfTest::PrimitivesGeneratedNoXfbShapeMeasurement& shape) {
|
|
MGLOG_I("PRIMITIVES_GENERATED probe %s: drawn=%d stream=%llu/%llu pgq=%llu(%d) "
|
|
"stat=%llu(%d)",
|
|
name, shape.drawn ? 1 : 0, static_cast<unsigned long long>(shape.streamGenerated),
|
|
static_cast<unsigned long long>(shape.expectedPrimitives),
|
|
static_cast<unsigned long long>(shape.primitivesGeneratedExt),
|
|
shape.primitivesGeneratedExtMeasured ? 1 : 0,
|
|
static_cast<unsigned long long>(shape.statisticsClippingInput),
|
|
shape.statisticsMeasured ? 1 : 0);
|
|
};
|
|
logShape("triangles", s_measurement.trianglesPlain);
|
|
logShape("triangles+discard", s_measurement.trianglesDiscard);
|
|
logShape("patches+discard", s_measurement.patchesDiscard);
|
|
}
|
|
}
|
|
// A driver whose stream query counts capture-less draws counts a PAUSED span's
|
|
// draws through the stream slot they take, so that span's result must not have
|
|
// the frontend's CPU paused counter added on top of it either (the pre-reroute
|
|
// accounting did exactly that, double counting every paused draw the CPU could
|
|
// price). Measured, not assumed: the forced arms never ask the probe and leave
|
|
// this false.
|
|
m_primGenStreamCountsXfbInactiveDraws = verdict == PrimitivesGeneratedNoXfbVerdict::StreamCounts;
|
|
m_primGenRerouteKind = ChoosePrimitivesGeneratedReroute(
|
|
overrideSetting, verdict, primitivesGeneratedQueryUsable, m_pipelineStatisticsQueryFeatureEnabled);
|
|
if (m_primGenRerouteKind != PrimGenRerouteKind::None) {
|
|
MGLOG_I("PRIMITIVES_GENERATED for XFB-inactive draws will accumulate through a %s pool%s",
|
|
m_primGenRerouteKind == PrimGenRerouteKind::PrimitivesGeneratedExt
|
|
? "VK_QUERY_TYPE_PRIMITIVES_GENERATED_EXT"
|
|
: "clipping-invocations pipeline-statistics",
|
|
overrideSetting == MG_Config::QuirkOverride::ForceOn ? " (forced on)" : "");
|
|
}
|
|
}
|
|
|
|
void VulkanRenderer::CreateAllocator() {
|
|
MOBILEGL_ASSERT(m_instance != VK_NULL_HANDLE, "CreateAllocator requires valid VkInstance");
|
|
MOBILEGL_ASSERT(m_physicalDevice.handle != VK_NULL_HANDLE, "CreateAllocator requires valid physical device");
|
|
MOBILEGL_ASSERT(m_device != VK_NULL_HANDLE, "CreateAllocator requires valid VkDevice");
|
|
|
|
if (m_allocator != nullptr) {
|
|
return;
|
|
}
|
|
|
|
VmaAllocatorCreateInfo allocatorInfo{};
|
|
VmaVulkanFunctions vulkanFunctions{};
|
|
vulkanFunctions.vkGetInstanceProcAddr = vkGetInstanceProcAddr;
|
|
vulkanFunctions.vkGetDeviceProcAddr = vkGetDeviceProcAddr;
|
|
allocatorInfo.instance = m_instance;
|
|
allocatorInfo.physicalDevice = m_physicalDevice.handle;
|
|
allocatorInfo.device = m_device;
|
|
allocatorInfo.pVulkanFunctions = &vulkanFunctions;
|
|
allocatorInfo.vulkanApiVersion = VK_API_VERSION_1_0;
|
|
|
|
VK_VERIFY(vmaCreateAllocator(&allocatorInfo, &m_allocator), "vmaCreateAllocator");
|
|
}
|
|
|
|
void VulkanRenderer::DestroyAllocator() {
|
|
if (m_allocator != nullptr) {
|
|
vmaDestroyAllocator(m_allocator);
|
|
m_allocator = nullptr;
|
|
}
|
|
}
|
|
|
|
void VulkanRenderer::CreateSwapchain() {
|
|
const VkExtent2D desiredExtent = {
|
|
std::max<Uint32>(m_config.SurfaceWidth, 1),
|
|
std::max<Uint32>(m_config.SurfaceHeight, 1),
|
|
};
|
|
m_swapchainObject.Create(m_device, m_physicalDevice.handle, m_surface,
|
|
static_cast<Uint32>(m_physicalDevice.queueFamilies.graphicsFamily),
|
|
static_cast<Uint32>(m_physicalDevice.queueFamilies.presentFamily),
|
|
m_config.MaxFramesInFlight, MG_Config::Features.MagmaExtraSwapchainImages,
|
|
desiredExtent, m_config.SwapInterval);
|
|
if (m_config.SwapInterval && m_config.SwapInterval != m_swapchainSwapInterval) {
|
|
MGLOG_D("DirectVulkan: swap interval %d -> %s", *m_config.SwapInterval,
|
|
SwapchainObject::GetPresentModeName(m_swapchainObject.GetPresentMode()));
|
|
}
|
|
m_swapchainSwapInterval = m_config.SwapInterval;
|
|
// The FragCoordYFlip variants bake this height in; it is the only input to a shader
|
|
// module that lives outside the GL program, so the factory has to learn it here (and on
|
|
// every recreation, which is the only way it can change).
|
|
if (m_programFactory) {
|
|
m_programFactory->SetDefaultFramebufferHeight(m_swapchainObject.GetExtent().height);
|
|
}
|
|
}
|
|
|
|
void VulkanRenderer::CreateCommandPool() {
|
|
VkCommandPoolCreateInfo createInfo{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO};
|
|
createInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
|
createInfo.queueFamilyIndex = m_physicalDevice.queueFamilies.graphicsFamily;
|
|
VK_VERIFY(vkCreateCommandPool(m_device, &createInfo, nullptr, &m_commandPool));
|
|
MGLOG_I("Command pool created");
|
|
}
|
|
|
|
Bool VulkanRenderer::CreateSurface() {
|
|
if (!m_window) {
|
|
#if defined VK_USE_PLATFORM_METAL_EXT
|
|
m_window = reinterpret_cast<NativeWindowType>(
|
|
CreateInternalMetalLayer(m_config.SurfaceWidth, m_config.SurfaceHeight, &m_platformDisplay));
|
|
m_platformLibrary = reinterpret_cast<void*>(m_window);
|
|
#elif defined VK_USE_PLATFORM_ANDROID_KHR
|
|
// EVERY ENTRY POINT HERE IS OPTIONAL, and each refusal is a `return false`, not
|
|
// MOBILEGL_ASSERT: that macro is compiled out at the INFO level every shipping build uses,
|
|
// and a renderer created for a WINDOW never enabled the headless extension - asking its
|
|
// instance for vkCreateHeadlessSurfaceEXT answers null, and calling that crashed the server
|
|
// the first time such a renderer needed an offscreen target.
|
|
auto* createHeadlessSurface = m_headlessSurfaceSupported
|
|
? reinterpret_cast<PFN_vkCreateHeadlessSurfaceEXT>(
|
|
vkGetInstanceProcAddr(m_instance, "vkCreateHeadlessSurfaceEXT"))
|
|
: nullptr;
|
|
const OffscreenSurfaceRoute route = ChooseOffscreenSurfaceRoute(
|
|
m_headlessSurfaceSupported, createHeadlessSurface != nullptr, m_androidSurfaceEnabled);
|
|
if (route == OffscreenSurfaceRoute::Headless) {
|
|
VkHeadlessSurfaceCreateInfoEXT sci{VK_STRUCTURE_TYPE_HEADLESS_SURFACE_CREATE_INFO_EXT};
|
|
const VkResult result = createHeadlessSurface(m_instance, &sci, nullptr, &m_surface);
|
|
if (result != VK_SUCCESS) {
|
|
MGLOG_E("DirectVulkan: vkCreateHeadlessSurfaceEXT failed (%d) for an offscreen target",
|
|
static_cast<int>(result));
|
|
m_surface = VK_NULL_HANDLE;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
if (route == OffscreenSurfaceRoute::Unavailable) {
|
|
MGLOG_E("DirectVulkan: no offscreen surface route on this instance (neither %s nor %s enabled)",
|
|
VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME, VK_KHR_ANDROID_SURFACE_EXTENSION_NAME);
|
|
return false;
|
|
}
|
|
// Windowless context on a driver without VK_EXT_headless_surface: give
|
|
// the WSI an AImageReader's ANativeWindow. It is a real, valid producer
|
|
// surface that is attached to no display and whose images this code never
|
|
// acquires, which is exactly the "drawable nobody sees" the Xlib fallback
|
|
// below builds out of an unmapped window. libmediandk is dlopen'd rather
|
|
// than linked so a device without it degrades to a refusal instead of
|
|
// failing to load the library at all.
|
|
{
|
|
void* mediaLib = dlopen("libmediandk.so", RTLD_NOW | RTLD_LOCAL);
|
|
if (mediaLib == nullptr) {
|
|
MGLOG_E("DirectVulkan: libmediandk.so could not be loaded for an offscreen target");
|
|
return false;
|
|
}
|
|
using AImageReaderNewFn = int (*)(int32_t, int32_t, int32_t, int32_t, void**);
|
|
using AImageReaderGetWindowFn = int (*)(void*, void**);
|
|
using AImageReaderDeleteFn = void (*)(void*);
|
|
auto* imageReaderNew = reinterpret_cast<AImageReaderNewFn>(dlsym(mediaLib, "AImageReader_new"));
|
|
auto* imageReaderGetWindow =
|
|
reinterpret_cast<AImageReaderGetWindowFn>(dlsym(mediaLib, "AImageReader_getWindow"));
|
|
auto* imageReaderDelete =
|
|
reinterpret_cast<AImageReaderDeleteFn>(dlsym(mediaLib, "AImageReader_delete"));
|
|
if (imageReaderNew == nullptr || imageReaderGetWindow == nullptr || imageReaderDelete == nullptr) {
|
|
MGLOG_E("DirectVulkan: libmediandk.so is missing AImageReader_new/getWindow/delete");
|
|
dlclose(mediaLib);
|
|
return false;
|
|
}
|
|
|
|
constexpr int32_t kAndroidFormatRgba8888 = 0x1; // AIMAGE_FORMAT_RGBA_8888
|
|
const int32_t width = static_cast<int32_t>(std::max<Uint32>(m_config.SurfaceWidth, 1));
|
|
const int32_t height = static_cast<int32_t>(std::max<Uint32>(m_config.SurfaceHeight, 1));
|
|
void* reader = nullptr;
|
|
// maxImages must cover the swapchain's images; the reader never
|
|
// acquires any, so this only sizes its buffer queue.
|
|
const int status = imageReaderNew(width, height, kAndroidFormatRgba8888, 8, &reader);
|
|
void* nativeWindow = nullptr;
|
|
const int windowStatus =
|
|
(status == 0 && reader != nullptr) ? imageReaderGetWindow(reader, &nativeWindow) : -1;
|
|
if (windowStatus != 0 || nativeWindow == nullptr) {
|
|
MGLOG_E("DirectVulkan: no AImageReader window for an offscreen target (new %d, getWindow %d)",
|
|
status, windowStatus);
|
|
if (reader != nullptr) imageReaderDelete(reader);
|
|
dlclose(mediaLib);
|
|
return false;
|
|
}
|
|
m_fallbackImageReader = reader;
|
|
m_platformLibrary = mediaLib;
|
|
m_window = reinterpret_cast<NativeWindowType>(nativeWindow);
|
|
}
|
|
#elif defined VK_USE_PLATFORM_XLIB_KHR
|
|
// No fall-through to Xlib: an offscreen surface never touches a window
|
|
// system. CreateInstance() has already refused the bring-up if the loader
|
|
// lacks the extension, so reaching here without it is a broken invariant
|
|
// rather than a platform limitation - report it and fail, do not continue.
|
|
auto* createHeadlessSurface = reinterpret_cast<PFN_vkCreateHeadlessSurfaceEXT>(
|
|
vkGetInstanceProcAddr(m_instance, "vkCreateHeadlessSurfaceEXT"));
|
|
if (!m_headlessSurfaceSupported || createHeadlessSurface == nullptr) {
|
|
MGLOG_F("vkCreateHeadlessSurfaceEXT is unavailable (%s reported as %s) while creating an "
|
|
"offscreen DirectVulkan surface",
|
|
VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME,
|
|
m_headlessSurfaceSupported ? "supported" : "unsupported");
|
|
throw RuntimeError("vkCreateHeadlessSurfaceEXT is unavailable for an offscreen DirectVulkan surface");
|
|
}
|
|
VkHeadlessSurfaceCreateInfoEXT sci{VK_STRUCTURE_TYPE_HEADLESS_SURFACE_CREATE_INFO_EXT};
|
|
VK_VERIFY(createHeadlessSurface(m_instance, &sci, nullptr, &m_surface),
|
|
"vkCreateHeadlessSurfaceEXT failed");
|
|
return true;
|
|
#else
|
|
auto* createHeadlessSurface = reinterpret_cast<PFN_vkCreateHeadlessSurfaceEXT>(
|
|
vkGetInstanceProcAddr(m_instance, "vkCreateHeadlessSurfaceEXT"));
|
|
if (createHeadlessSurface == nullptr) {
|
|
// Same class as the Xlib branch above: a null entry point behind
|
|
// MOBILEGL_ASSERT is a segv on the next line in every INFO-level build.
|
|
MGLOG_F("vkCreateHeadlessSurfaceEXT is unavailable while creating an offscreen DirectVulkan "
|
|
"surface (%s missing from this loader)",
|
|
VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME);
|
|
throw RuntimeError("vkCreateHeadlessSurfaceEXT is unavailable for an offscreen DirectVulkan surface");
|
|
}
|
|
VkHeadlessSurfaceCreateInfoEXT sci{VK_STRUCTURE_TYPE_HEADLESS_SURFACE_CREATE_INFO_EXT};
|
|
VK_VERIFY(createHeadlessSurface(m_instance, &sci, nullptr, &m_surface),
|
|
"vkCreateHeadlessSurfaceEXT failed");
|
|
return true;
|
|
#endif
|
|
}
|
|
#if defined VK_USE_PLATFORM_ANDROID_KHR
|
|
auto* nativeWindow = static_cast<ANativeWindow*>(m_window);
|
|
if (!nativeWindow) throw RuntimeError("ANativeWindowType is null");
|
|
|
|
VkAndroidSurfaceCreateInfoKHR sci{VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR};
|
|
sci.window = nativeWindow;
|
|
const VkResult androidSurfaceResult = vkCreateAndroidSurfaceKHR(m_instance, &sci, nullptr, &m_surface);
|
|
if (androidSurfaceResult != VK_SUCCESS) {
|
|
MGLOG_E("DirectVulkan: vkCreateAndroidSurfaceKHR failed (%d)", static_cast<int>(androidSurfaceResult));
|
|
m_surface = VK_NULL_HANDLE;
|
|
return false;
|
|
}
|
|
#elif defined VK_USE_PLATFORM_WIN32_KHR
|
|
auto hwnd = static_cast<HWND>(m_window);
|
|
MOBILEGL_ASSERT(hwnd, "HWND is null");
|
|
|
|
VkWin32SurfaceCreateInfoKHR sci{VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR};
|
|
sci.hinstance = GetModuleHandleW(nullptr);
|
|
sci.hwnd = hwnd;
|
|
VK_VERIFY(vkCreateWin32SurfaceKHR(m_instance, &sci, nullptr, &m_surface), "vkCreateWin32SurfaceKHR failed");
|
|
#elif defined VK_USE_PLATFORM_METAL_EXT
|
|
MOBILEGL_ASSERT(m_window, "CAMetalLayer is null");
|
|
|
|
VkMetalSurfaceCreateInfoEXT sci{VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT};
|
|
sci.pLayer = reinterpret_cast<const void*>(m_window);
|
|
VK_VERIFY(vkCreateMetalSurfaceEXT(m_instance, &sci, nullptr, &m_surface), "vkCreateMetalSurfaceEXT failed");
|
|
#elif defined VK_USE_PLATFORM_XLIB_KHR
|
|
// Reached only for a REAL on-screen window surface (a windowed desktop app,
|
|
// retrace in window mode). Presentation to a window legitimately needs a
|
|
// window system; offscreen requests returned above and never come here, so
|
|
// there is no longer any path that opens a display on a caller's behalf.
|
|
//
|
|
// Every failure below is a real error return, not MOBILEGL_ASSERT: that macro
|
|
// is compiled out at the INFO log level every shipping and CI build uses, so
|
|
// asserting here meant a null Display sailed straight into the next Xlib call
|
|
// and segfaulted - which is exactly how this presented in CI.
|
|
if (!m_window) {
|
|
MGLOG_F("CreateSurface: a window surface was requested with no native window");
|
|
throw RuntimeError("CreateSurface: no native window for the Vulkan Xlib surface");
|
|
}
|
|
|
|
void* x11Lib = dlopen("libX11.so.6", RTLD_LOCAL | RTLD_NOW);
|
|
if (!x11Lib) {
|
|
x11Lib = dlopen("libX11.so", RTLD_LOCAL | RTLD_NOW);
|
|
}
|
|
if (x11Lib == nullptr) {
|
|
MGLOG_F("Failed to open libX11 (.so.6 and .so) while creating a Vulkan Xlib window surface: %s", dlerror());
|
|
throw RuntimeError("libX11 is unavailable for the Vulkan Xlib window surface");
|
|
}
|
|
using XOpenDisplayFn = Display* (*)(const char*);
|
|
using XCloseDisplayFn = int (*)(Display*);
|
|
auto* xOpenDisplay = reinterpret_cast<XOpenDisplayFn>(dlsym(x11Lib, "XOpenDisplay"));
|
|
auto* xCloseDisplay = reinterpret_cast<XCloseDisplayFn>(dlsym(x11Lib, "XCloseDisplay"));
|
|
if (xOpenDisplay == nullptr || xCloseDisplay == nullptr) {
|
|
MGLOG_F("Failed to resolve XOpenDisplay/XCloseDisplay while creating a Vulkan Xlib window surface");
|
|
dlclose(x11Lib);
|
|
throw RuntimeError("libX11 is missing XOpenDisplay/XCloseDisplay");
|
|
}
|
|
|
|
const char* displayName = std::getenv("DISPLAY");
|
|
// Never the display this process serves (MG_Util/X11/DisplayGuard.h).
|
|
auto* display = displayName != nullptr && MG_Util::X11::MayDialX11Display(displayName)
|
|
? xOpenDisplay(displayName)
|
|
: nullptr;
|
|
if (display == nullptr) {
|
|
MGLOG_F("XOpenDisplay(%s) failed while creating a Vulkan Xlib window surface; there is no usable X "
|
|
"display for the requested window surface",
|
|
displayName != nullptr ? displayName : "<DISPLAY unset>");
|
|
dlclose(x11Lib);
|
|
throw RuntimeError("XOpenDisplay failed for the Vulkan Xlib window surface");
|
|
}
|
|
m_platformDisplay = display;
|
|
m_platformLibrary = x11Lib;
|
|
m_platformCloseDisplay = reinterpret_cast<void*>(xCloseDisplay);
|
|
|
|
VkXlibSurfaceCreateInfoKHR sci{VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR};
|
|
sci.dpy = display;
|
|
sci.window = static_cast<Window>(m_window);
|
|
VK_VERIFY(vkCreateXlibSurfaceKHR(m_instance, &sci, nullptr, &m_surface), "vkCreateXlibSurfaceKHR failed");
|
|
#else
|
|
// #warning "VulkanRenderer::Initialize called on a platform which is not supported yet"
|
|
MGLOG_W("VulkanRenderer::Initialize called on a platform which is not supported yet"); // TODO: support more
|
|
// platforms
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
Vector<VkQueueFamilyProperties> VulkanRenderer::GetQueueFamilyFromPhysicalDevice(VkPhysicalDevice device) {
|
|
Uint32 queueFamilyCount = 0;
|
|
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);
|
|
|
|
Vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
|
|
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());
|
|
return queueFamilies;
|
|
}
|
|
|
|
Int VulkanRenderer::GetQueueFamilyIndex(const Vector<VkQueueFamilyProperties>& queueFamilies,
|
|
VkQueueFlagBits flag) {
|
|
for (Uint32 i = 0; i < queueFamilies.size(); i++) {
|
|
if (queueFamilies[i].queueFlags & flag) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
Int VulkanRenderer::GetPresentQueueFamilyIndex(const PhysicalDevice& physicalDevice, VkSurfaceKHR surface,
|
|
const Vector<VkQueueFamilyProperties>& queueFamilies,
|
|
Int preferredFamilyIndex) {
|
|
if (preferredFamilyIndex != -1) {
|
|
VkBool32 supportsPresent = false;
|
|
vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice.handle, preferredFamilyIndex, surface,
|
|
&supportsPresent);
|
|
if (supportsPresent) return preferredFamilyIndex;
|
|
}
|
|
|
|
for (Uint32 i = 0; i < queueFamilies.size(); i++) {
|
|
VkBool32 supportsPresent = false;
|
|
vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice.handle, i, surface, &supportsPresent);
|
|
if (supportsPresent) return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
Vector<VkExtensionProperties> VulkanRenderer::EnumerateInstanceExtensions() {
|
|
// The two-call idiom has a race the spec explicitly allows for: the loader
|
|
// re-scans ICDs, so the property count can GROW between the sizing call and
|
|
// the fill call, and the fill then returns VK_INCOMPLETE having written only
|
|
// as many entries as the caller asked for. The result is a silently TRUNCATED
|
|
// extension list - and which extensions fall off the end is exactly as stable
|
|
// as the loader's scan order, i.e. not at all. That is how a headless CI
|
|
// runner could decide VK_EXT_headless_surface did not exist on one run and
|
|
// did on the next, sending the pbuffer path into the Xlib fallback with no
|
|
// X server to open. The sibling EnumerateDeviceExtensions below already
|
|
// checked its second call; this one dropped the result on the floor.
|
|
// Loop until a fill call agrees with its own sizing call.
|
|
Vector<VkExtensionProperties> extensions;
|
|
for (Uint32 attempt = 0; attempt < 8; ++attempt) {
|
|
Uint32 extensionCount = 0;
|
|
VK_VERIFY(vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr));
|
|
extensions.resize(extensionCount);
|
|
if (extensionCount == 0) {
|
|
return extensions;
|
|
}
|
|
const VkResult result = vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data());
|
|
if (result == VK_SUCCESS) {
|
|
extensions.resize(extensionCount);
|
|
return extensions;
|
|
}
|
|
if (result != VK_INCOMPLETE) {
|
|
VK_VERIFY(result, "vkEnumerateInstanceExtensionProperties failed");
|
|
return extensions;
|
|
}
|
|
MGLOG_I("vkEnumerateInstanceExtensionProperties returned VK_INCOMPLETE (the loader's list grew "
|
|
"mid-enumeration); re-enumerating");
|
|
}
|
|
MGLOG_F("vkEnumerateInstanceExtensionProperties never settled; the instance extension list may be "
|
|
"truncated and surface-extension selection is about to be made on incomplete information");
|
|
return extensions;
|
|
}
|
|
|
|
Vector<VkExtensionProperties> VulkanRenderer::EnumerateDeviceExtensions(VkPhysicalDevice device) {
|
|
Uint32 extensionCount = 0;
|
|
VK_VERIFY(vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr));
|
|
Vector<VkExtensionProperties> extensions(extensionCount);
|
|
VK_VERIFY(vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, extensions.data()));
|
|
return extensions;
|
|
}
|
|
|
|
Bool VulkanRenderer::IsExtensionSupported(const Vector<VkExtensionProperties>& availableExtensions,
|
|
const char* extensionName) {
|
|
for (const auto& extension : availableExtensions) {
|
|
if (strcmp(extension.extensionName, extensionName) == 0) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Bool VulkanRenderer::IsExtensionAlreadyEnabled(const Vector<const char*>& enabledExtensions,
|
|
const char* extensionName) {
|
|
return std::any_of(enabledExtensions.begin(), enabledExtensions.end(),
|
|
[&extensionName](const String& name) { return name == extensionName; });
|
|
}
|
|
|
|
Bool VulkanRenderer::EnableOptionalDeviceExtension(const Vector<VkExtensionProperties>& availableExtensions,
|
|
Vector<const char*>& inOutEnabledExtensions,
|
|
const char* extensionName) {
|
|
if (!IsExtensionSupported(availableExtensions, extensionName)) {
|
|
MGLOG_I("Optional device extension not supported: %s", extensionName);
|
|
return false;
|
|
}
|
|
|
|
if (!IsExtensionAlreadyEnabled(inOutEnabledExtensions, extensionName)) {
|
|
inOutEnabledExtensions.push_back(extensionName);
|
|
}
|
|
MGLOG_I("Enabled optional device extension: %s", extensionName);
|
|
return true;
|
|
}
|
|
|
|
void VulkanRenderer::ResolveOptionalDeviceExtensions(const Vector<VkExtensionProperties>& availableExtensions,
|
|
Vector<const char*>& inOutEnabledExtensions) {
|
|
m_drawIndirectCountExtensionEnabled = EnableOptionalDeviceExtension(availableExtensions, inOutEnabledExtensions,
|
|
VK_KHR_DRAW_INDIRECT_COUNT_EXTENSION_NAME);
|
|
m_shaderDrawParametersExtensionEnabled = EnableOptionalDeviceExtension(
|
|
availableExtensions, inOutEnabledExtensions, VK_KHR_SHADER_DRAW_PARAMETERS_EXTENSION_NAME);
|
|
#ifdef VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME
|
|
EnableOptionalDeviceExtension(availableExtensions, inOutEnabledExtensions,
|
|
VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME);
|
|
#endif
|
|
}
|
|
|
|
Bool VulkanRenderer::CheckValidationLayerSupport() {
|
|
Uint32 layerCount = 0;
|
|
VK_VERIFY(vkEnumerateInstanceLayerProperties(&layerCount, nullptr));
|
|
|
|
Vector<VkLayerProperties> layers(layerCount);
|
|
VK_VERIFY(vkEnumerateInstanceLayerProperties(&layerCount, layers.data()));
|
|
|
|
for (const char* layerName : s_validationLayerNames) {
|
|
for (const auto& layerProperties : layers) {
|
|
if (strcmp(layerName, layerProperties.layerName) == 0) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|