Identify each unsupported device feature flag that the app attempts to be enable.

- Make MVKDevice::enableFeatures() functions into templates to pass struct type.
- Add mvkGetAddressOfFirstMember() to retrieve the address of the first member of
  a struct, taking into consideration whether the struct has a Vulkan pNext member.
- Add mvk::getTypeName() and mvk::getOrdinalSuffix() string functions.
This commit is contained in:
Bill Hollings 2023-05-15 14:32:22 -04:00
parent 7d4f5745f7
commit 4893f78b29
6 changed files with 133 additions and 47 deletions

View File

@ -21,6 +21,8 @@
#include <string> #include <string>
#include <streambuf> #include <streambuf>
#include <vector>
#include <cxxabi.h>
namespace mvk { namespace mvk {
@ -59,6 +61,26 @@ namespace mvk {
return varName; return varName;
} }
/** Returns a string containing the ordinal suffix for a numeric value.*/
inline const char* getOrdinalSuffix(int64_t val) {
static const char* suffixes[] = {"th", "st", "nd", "rd"};
auto ord = val % 100;
if (ord > 10 && ord < 20) { return suffixes[0]; } // All teens end in th.
ord = ord % 10;
if (ord > 3) { return suffixes[0]; } // 4-9 end in th.
return suffixes[ord];
}
/** Returns the name of a C++ type. */
template<typename T>
inline std::string getTypeName(const T* pObj) {
int status;
char* demangledName = abi::__cxa_demangle(typeid(*pObj).name(), 0, 0, &status);
std::string tName = demangledName;
free(demangledName);
return tName;
}
#pragma mark - #pragma mark -
#pragma mark Streams #pragma mark Streams

View File

@ -30,6 +30,7 @@ Released TBD
- Add `MVK_ENABLE_EXPLICIT_LOD_WORKAROUND` environment variable to selectively - Add `MVK_ENABLE_EXPLICIT_LOD_WORKAROUND` environment variable to selectively
disable recent fixes to handling LOD for arrayed depth images in shaders, disable recent fixes to handling LOD for arrayed depth images in shaders,
on Apple Silicon, when those fixes cause regression in rendering behavior. on Apple Silicon, when those fixes cause regression in rendering behavior.
- Identify each unsupported device feature flag that the app attempts to be enable.
- For correctness, set `VkPhysicalDeviceLimits::lineWidthGranularity` to `1`. - For correctness, set `VkPhysicalDeviceLimits::lineWidthGranularity` to `1`.
- Improve GitHub CI production of binary artifacts on submission and release. - Improve GitHub CI production of binary artifacts on submission and release.

View File

@ -886,8 +886,8 @@ protected:
void initQueues(const VkDeviceCreateInfo* pCreateInfo); void initQueues(const VkDeviceCreateInfo* pCreateInfo);
void reservePrivateData(const VkDeviceCreateInfo* pCreateInfo); void reservePrivateData(const VkDeviceCreateInfo* pCreateInfo);
void enableFeatures(const VkDeviceCreateInfo* pCreateInfo); void enableFeatures(const VkDeviceCreateInfo* pCreateInfo);
void enableFeatures(VkBaseInStructure* pEnabled, const VkBaseInStructure* pRequested, const VkBaseInStructure* pAvailable, uint32_t count); template<typename S> void enableFeatures(S* pEnabled, const S* pRequested, const S* pAvailable, uint32_t count);
void enableFeatures(VkBool32* pEnabledBools, const VkBool32* pRequestedBools, const VkBool32* pAvailableBools, uint32_t count); template<typename S> void enableFeatures(S* pRequested, VkBool32* pEnabledBools, const VkBool32* pRequestedBools, const VkBool32* pAvailableBools, uint32_t count);
void enableExtensions(const VkDeviceCreateInfo* pCreateInfo); void enableExtensions(const VkDeviceCreateInfo* pCreateInfo);
const char* getActivityPerformanceDescription(MVKPerformanceTracker& activity, MVKPerformanceStatistics& perfStats); const char* getActivityPerformanceDescription(MVKPerformanceTracker& activity, MVKPerformanceStatistics& perfStats);
void logActivityPerformance(MVKPerformanceTracker& activity, MVKPerformanceStatistics& perfStats, bool isInline = false); void logActivityPerformance(MVKPerformanceTracker& activity, MVKPerformanceStatistics& perfStats, bool isInline = false);

View File

@ -32,6 +32,7 @@
#include "MVKCommandPool.h" #include "MVKCommandPool.h"
#include "MVKFoundation.h" #include "MVKFoundation.h"
#include "MVKCodec.h" #include "MVKCodec.h"
#include "MVKStrings.h"
#include <MoltenVKShaderConverter/SPIRVToMSLConverter.h> #include <MoltenVKShaderConverter/SPIRVToMSLConverter.h>
#import "CAMetalLayer+MoltenVK.h" #import "CAMetalLayer+MoltenVK.h"
@ -4590,7 +4591,8 @@ void MVKDevice::enableFeatures(const VkDeviceCreateInfo* pCreateInfo) {
//Enable device features based on requested and available features, //Enable device features based on requested and available features,
// including extended features that are requested in the pNext chain. // including extended features that are requested in the pNext chain.
if (pCreateInfo->pEnabledFeatures) { if (pCreateInfo->pEnabledFeatures) {
enableFeatures(&_enabledFeatures.robustBufferAccess, enableFeatures(pCreateInfo->pEnabledFeatures,
&_enabledFeatures.robustBufferAccess,
&pCreateInfo->pEnabledFeatures->robustBufferAccess, &pCreateInfo->pEnabledFeatures->robustBufferAccess,
&pdFeats2.features.robustBufferAccess, 55); &pdFeats2.features.robustBufferAccess, 55);
} }
@ -4599,29 +4601,36 @@ void MVKDevice::enableFeatures(const VkDeviceCreateInfo* pCreateInfo) {
switch ((uint32_t)next->sType) { switch ((uint32_t)next->sType) {
case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2: { case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2: {
auto* requestedFeatures = (VkPhysicalDeviceFeatures2*)next; auto* requestedFeatures = (VkPhysicalDeviceFeatures2*)next;
enableFeatures(&_enabledFeatures.robustBufferAccess, enableFeatures(requestedFeatures,
&_enabledFeatures.robustBufferAccess,
&requestedFeatures->features.robustBufferAccess, &requestedFeatures->features.robustBufferAccess,
&pdFeats2.features.robustBufferAccess, 55); &pdFeats2.features.robustBufferAccess, 55);
break; break;
} }
case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES: { case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES: {
auto* requestedFeatures = (VkPhysicalDeviceVulkan11Features*)next; auto* requestedFeatures = (VkPhysicalDeviceVulkan11Features*)next;
enableFeatures(&_enabled16BitStorageFeatures.storageBuffer16BitAccess, enableFeatures(requestedFeatures,
&_enabled16BitStorageFeatures.storageBuffer16BitAccess,
&requestedFeatures->storageBuffer16BitAccess, &requestedFeatures->storageBuffer16BitAccess,
&pd16BitStorageFeatures.storageBuffer16BitAccess, 4); &pd16BitStorageFeatures.storageBuffer16BitAccess, 4);
enableFeatures(&_enabledMultiviewFeatures.multiview, enableFeatures(requestedFeatures,
&_enabledMultiviewFeatures.multiview,
&requestedFeatures->multiview, &requestedFeatures->multiview,
&pdMultiviewFeatures.multiview, 3); &pdMultiviewFeatures.multiview, 3);
enableFeatures(&_enabledVariablePointerFeatures.variablePointersStorageBuffer, enableFeatures(requestedFeatures,
&_enabledVariablePointerFeatures.variablePointersStorageBuffer,
&requestedFeatures->variablePointersStorageBuffer, &requestedFeatures->variablePointersStorageBuffer,
&pdVariablePointerFeatures.variablePointersStorageBuffer, 2); &pdVariablePointerFeatures.variablePointersStorageBuffer, 2);
enableFeatures(&_enabledProtectedMemoryFeatures.protectedMemory, enableFeatures(requestedFeatures,
&_enabledProtectedMemoryFeatures.protectedMemory,
&requestedFeatures->protectedMemory, &requestedFeatures->protectedMemory,
&pdProtectedMemoryFeatures.protectedMemory, 1); &pdProtectedMemoryFeatures.protectedMemory, 1);
enableFeatures(&_enabledSamplerYcbcrConversionFeatures.samplerYcbcrConversion, enableFeatures(requestedFeatures,
&_enabledSamplerYcbcrConversionFeatures.samplerYcbcrConversion,
&requestedFeatures->samplerYcbcrConversion, &requestedFeatures->samplerYcbcrConversion,
&pdSamplerYcbcrConversionFeatures.samplerYcbcrConversion, 1); &pdSamplerYcbcrConversionFeatures.samplerYcbcrConversion, 1);
enableFeatures(&_enabledShaderDrawParametersFeatures.shaderDrawParameters, enableFeatures(requestedFeatures,
&_enabledShaderDrawParametersFeatures.shaderDrawParameters,
&requestedFeatures->shaderDrawParameters, &requestedFeatures->shaderDrawParameters,
&pdShaderDrawParametersFeatures.shaderDrawParameters, 1); &pdShaderDrawParametersFeatures.shaderDrawParameters, 1);
break; break;
@ -4629,55 +4638,72 @@ void MVKDevice::enableFeatures(const VkDeviceCreateInfo* pCreateInfo) {
case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES: { case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES: {
auto& pdvulkan12FeaturesNoExt = _physicalDevice->_vulkan12FeaturesNoExt; auto& pdvulkan12FeaturesNoExt = _physicalDevice->_vulkan12FeaturesNoExt;
auto* requestedFeatures = (VkPhysicalDeviceVulkan12Features*)next; auto* requestedFeatures = (VkPhysicalDeviceVulkan12Features*)next;
enableFeatures(&_enabledVulkan12FeaturesNoExt.samplerMirrorClampToEdge, enableFeatures(requestedFeatures,
&_enabledVulkan12FeaturesNoExt.samplerMirrorClampToEdge,
&requestedFeatures->samplerMirrorClampToEdge, &requestedFeatures->samplerMirrorClampToEdge,
&pdvulkan12FeaturesNoExt.samplerMirrorClampToEdge, 2); &pdvulkan12FeaturesNoExt.samplerMirrorClampToEdge, 2);
enableFeatures(&_enabled8BitStorageFeatures.storageBuffer8BitAccess, enableFeatures(requestedFeatures,
&_enabled8BitStorageFeatures.storageBuffer8BitAccess,
&requestedFeatures->storageBuffer8BitAccess, &requestedFeatures->storageBuffer8BitAccess,
&pd8BitStorageFeatures.storageBuffer8BitAccess, 3); &pd8BitStorageFeatures.storageBuffer8BitAccess, 3);
enableFeatures(&_enabledShaderAtomicInt64Features.shaderBufferInt64Atomics, enableFeatures(requestedFeatures,
&_enabledShaderAtomicInt64Features.shaderBufferInt64Atomics,
&requestedFeatures->shaderBufferInt64Atomics, &requestedFeatures->shaderBufferInt64Atomics,
&pdShaderAtomicInt64Features.shaderBufferInt64Atomics, 2); &pdShaderAtomicInt64Features.shaderBufferInt64Atomics, 2);
enableFeatures(&_enabledShaderFloat16Int8Features.shaderFloat16, enableFeatures(requestedFeatures,
&_enabledShaderFloat16Int8Features.shaderFloat16,
&requestedFeatures->shaderFloat16, &requestedFeatures->shaderFloat16,
&pdShaderFloat16Int8Features.shaderFloat16, 2); &pdShaderFloat16Int8Features.shaderFloat16, 2);
enableFeatures(&_enabledVulkan12FeaturesNoExt.descriptorIndexing, enableFeatures(requestedFeatures,
&_enabledVulkan12FeaturesNoExt.descriptorIndexing,
&requestedFeatures->descriptorIndexing, &requestedFeatures->descriptorIndexing,
&pdvulkan12FeaturesNoExt.descriptorIndexing, 1); &pdvulkan12FeaturesNoExt.descriptorIndexing, 1);
enableFeatures(&_enabledDescriptorIndexingFeatures.shaderInputAttachmentArrayDynamicIndexing, enableFeatures(requestedFeatures,
&_enabledDescriptorIndexingFeatures.shaderInputAttachmentArrayDynamicIndexing,
&requestedFeatures->shaderInputAttachmentArrayDynamicIndexing, &requestedFeatures->shaderInputAttachmentArrayDynamicIndexing,
&pdDescriptorIndexingFeatures.shaderInputAttachmentArrayDynamicIndexing, 20); &pdDescriptorIndexingFeatures.shaderInputAttachmentArrayDynamicIndexing, 20);
enableFeatures(&_enabledVulkan12FeaturesNoExt.samplerFilterMinmax, enableFeatures(requestedFeatures,
&_enabledVulkan12FeaturesNoExt.samplerFilterMinmax,
&requestedFeatures->samplerFilterMinmax, &requestedFeatures->samplerFilterMinmax,
&pdvulkan12FeaturesNoExt.samplerFilterMinmax, 1); &pdvulkan12FeaturesNoExt.samplerFilterMinmax, 1);
enableFeatures(&_enabledScalarBlockLayoutFeatures.scalarBlockLayout, enableFeatures(requestedFeatures,
&_enabledScalarBlockLayoutFeatures.scalarBlockLayout,
&requestedFeatures->scalarBlockLayout, &requestedFeatures->scalarBlockLayout,
&pdScalarBlockLayoutFeatures.scalarBlockLayout, 1); &pdScalarBlockLayoutFeatures.scalarBlockLayout, 1);
enableFeatures(&_enabledImagelessFramebufferFeatures.imagelessFramebuffer, enableFeatures(requestedFeatures,
&_enabledImagelessFramebufferFeatures.imagelessFramebuffer,
&requestedFeatures->imagelessFramebuffer, &requestedFeatures->imagelessFramebuffer,
&pdImagelessFramebufferFeatures.imagelessFramebuffer, 1); &pdImagelessFramebufferFeatures.imagelessFramebuffer, 1);
enableFeatures(&_enabledUniformBufferStandardLayoutFeatures.uniformBufferStandardLayout, enableFeatures(requestedFeatures,
&_enabledUniformBufferStandardLayoutFeatures.uniformBufferStandardLayout,
&requestedFeatures->uniformBufferStandardLayout, &requestedFeatures->uniformBufferStandardLayout,
&pdUniformBufferStandardLayoutFeatures.uniformBufferStandardLayout, 1); &pdUniformBufferStandardLayoutFeatures.uniformBufferStandardLayout, 1);
enableFeatures(&_enabledShaderSubgroupExtendedTypesFeatures.shaderSubgroupExtendedTypes, enableFeatures(requestedFeatures,
&_enabledShaderSubgroupExtendedTypesFeatures.shaderSubgroupExtendedTypes,
&requestedFeatures->shaderSubgroupExtendedTypes, &requestedFeatures->shaderSubgroupExtendedTypes,
&pdShaderSubgroupExtendedTypesFeatures.shaderSubgroupExtendedTypes, 1); &pdShaderSubgroupExtendedTypesFeatures.shaderSubgroupExtendedTypes, 1);
enableFeatures(&_enabledSeparateDepthStencilLayoutsFeatures.separateDepthStencilLayouts, enableFeatures(requestedFeatures,
&_enabledSeparateDepthStencilLayoutsFeatures.separateDepthStencilLayouts,
&requestedFeatures->separateDepthStencilLayouts, &requestedFeatures->separateDepthStencilLayouts,
&pdSeparateDepthStencilLayoutsFeatures.separateDepthStencilLayouts, 1); &pdSeparateDepthStencilLayoutsFeatures.separateDepthStencilLayouts, 1);
enableFeatures(&_enabledHostQueryResetFeatures.hostQueryReset, enableFeatures(requestedFeatures,
&_enabledHostQueryResetFeatures.hostQueryReset,
&requestedFeatures->hostQueryReset, &requestedFeatures->hostQueryReset,
&pdHostQueryResetFeatures.hostQueryReset, 1); &pdHostQueryResetFeatures.hostQueryReset, 1);
enableFeatures(&_enabledTimelineSemaphoreFeatures.timelineSemaphore, enableFeatures(requestedFeatures,
&_enabledTimelineSemaphoreFeatures.timelineSemaphore,
&requestedFeatures->timelineSemaphore, &requestedFeatures->timelineSemaphore,
&pdTimelineSemaphoreFeatures.timelineSemaphore, 1); &pdTimelineSemaphoreFeatures.timelineSemaphore, 1);
enableFeatures(&_enabledBufferDeviceAddressFeatures.bufferDeviceAddress, enableFeatures(requestedFeatures,
&_enabledBufferDeviceAddressFeatures.bufferDeviceAddress,
&requestedFeatures->bufferDeviceAddress, &requestedFeatures->bufferDeviceAddress,
&pdBufferDeviceAddressFeatures.bufferDeviceAddress, 3); &pdBufferDeviceAddressFeatures.bufferDeviceAddress, 3);
enableFeatures(&_enabledVulkanMemoryModelFeatures.vulkanMemoryModel, enableFeatures(requestedFeatures,
&_enabledVulkanMemoryModelFeatures.vulkanMemoryModel,
&requestedFeatures->vulkanMemoryModel, &requestedFeatures->vulkanMemoryModel,
&pdVulkanMemoryModelFeatures.vulkanMemoryModel, 3); &pdVulkanMemoryModelFeatures.vulkanMemoryModel, 3);
enableFeatures(&_enabledVulkan12FeaturesNoExt.shaderOutputViewportIndex, enableFeatures(requestedFeatures,
&_enabledVulkan12FeaturesNoExt.shaderOutputViewportIndex,
&requestedFeatures->shaderOutputViewportIndex, &requestedFeatures->shaderOutputViewportIndex,
&pdvulkan12FeaturesNoExt.shaderOutputViewportIndex, 3); &pdvulkan12FeaturesNoExt.shaderOutputViewportIndex, 3);
break; break;
@ -4685,17 +4711,17 @@ void MVKDevice::enableFeatures(const VkDeviceCreateInfo* pCreateInfo) {
#define MVK_DEVICE_FEATURE(structName, enumName, flagCount) \ #define MVK_DEVICE_FEATURE(structName, enumName, flagCount) \
case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_##enumName##_FEATURES: { \ case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_##enumName##_FEATURES: { \
enableFeatures((VkBaseInStructure*)&_enabled##structName##Features, \ enableFeatures(&_enabled##structName##Features, \
next, \ (VkPhysicalDevice##structName##Features*)next, \
(VkBaseInStructure*)&pd##structName##Features, \ &pd##structName##Features, \
flagCount); \ flagCount); \
break; \ break; \
} }
#define MVK_DEVICE_FEATURE_EXTN(structName, enumName, extnSfx, flagCount) \ #define MVK_DEVICE_FEATURE_EXTN(structName, enumName, extnSfx, flagCount) \
case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_##enumName##_FEATURES_##extnSfx: { \ case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_##enumName##_FEATURES_##extnSfx: { \
enableFeatures((VkBaseInStructure*)&_enabled##structName##Features, \ enableFeatures(&_enabled##structName##Features, \
next, \ (VkPhysicalDevice##structName##Features##extnSfx*)next, \
(VkBaseInStructure*)&pd##structName##Features, \ &pd##structName##Features, \
flagCount); \ flagCount); \
break; \ break; \
} }
@ -4707,18 +4733,23 @@ void MVKDevice::enableFeatures(const VkDeviceCreateInfo* pCreateInfo) {
} }
} }
void MVKDevice::enableFeatures(VkBaseInStructure* pEnabled, const VkBaseInStructure* pRequested, const VkBaseInStructure* pAvailable, uint32_t count) { template<typename S>
enableFeatures((VkBool32*)(&(pEnabled->pNext) + 1), void MVKDevice::enableFeatures(S* pEnabled, const S* pRequested, const S* pAvailable, uint32_t count) {
(VkBool32*)(&(pRequested->pNext) + 1), enableFeatures(pRequested,
(VkBool32*)(&(pAvailable->pNext) + 1), (VkBool32*)mvkGetAddressOfFirstMember(pEnabled),
(VkBool32*)mvkGetAddressOfFirstMember(pRequested),
(VkBool32*)mvkGetAddressOfFirstMember(pAvailable),
count); count);
} }
void MVKDevice::enableFeatures(VkBool32* pEnabledBools, const VkBool32* pRequestedBools, const VkBool32* pAvailableBools, uint32_t count) { template<typename S>
void MVKDevice::enableFeatures(S* pRequested, VkBool32* pEnabledBools, const VkBool32* pRequestedBools, const VkBool32* pAvailableBools, uint32_t count) {
for (uint32_t i = 0; i < count; i++) { for (uint32_t i = 0; i < count; i++) {
pEnabledBools[i] = pRequestedBools[i] && pAvailableBools[i]; pEnabledBools[i] = pRequestedBools[i] && pAvailableBools[i];
if (pRequestedBools[i] && !pAvailableBools[i]) { if (pRequestedBools[i] && !pAvailableBools[i]) {
setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateDevice(): Requested feature is not available on this device.")); uintptr_t mbrOffset = (uintptr_t)&pRequestedBools[i] - (uintptr_t)mvkGetAddressOfFirstMember(pRequested);
size_t mbrIdxOrd = (mbrOffset / sizeof(VkBool32)) + 1;
setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateDevice(): Requested physical device feature specified by the %zu%s flag in %s is not available on this device.", mbrIdxOrd, mvk::getOrdinalSuffix(mbrIdxOrd), mvk::getTypeName(pRequested).c_str()));
} }
} }
} }

View File

@ -21,7 +21,7 @@
#include "MVKInstance.h" #include "MVKInstance.h"
#include "MVKFoundation.h" #include "MVKFoundation.h"
#include "MVKOSExtensions.h" #include "MVKOSExtensions.h"
#include <cxxabi.h> #include "MVKStrings.h"
using namespace std; using namespace std;
@ -44,13 +44,7 @@ static const char* getReportingLevelString(MVKConfigLogLevel logLevel) {
#pragma mark - #pragma mark -
#pragma mark MVKBaseObject #pragma mark MVKBaseObject
string MVKBaseObject::getClassName() { string MVKBaseObject::getClassName() { return mvk::getTypeName(this); }
int status;
char* demangled = abi::__cxa_demangle(typeid(*this).name(), 0, 0, &status);
string clzName = demangled;
free(demangled);
return clzName;
}
void MVKBaseObject::reportMessage(MVKConfigLogLevel logLevel, const char* format, ...) { void MVKBaseObject::reportMessage(MVKConfigLogLevel logLevel, const char* format, ...) {
va_list args; va_list args;

View File

@ -217,6 +217,44 @@ static constexpr uint64_t mvkAlignByteCount(uint64_t byteCount, uint64_t byteAli
return mvkAlignByteRef(byteCount, byteAlignment, alignDown); return mvkAlignByteRef(byteCount, byteAlignment, alignDown);
} }
/**
* Compile time indication if the struct contains a specific member.
*
* If S::mbr is well-formed because the struct contains that member, the decltype() and
* comma operator together trigger a true_type, otherwise it falls back to a false_type.
*
* Credit to: https://fekir.info/post/detect-member-variables/
*/
#define mvk_define_has_member(mbr) \
template <typename T, typename = void> struct mvk_has_##mbr : std::false_type {}; \
template <typename T> struct mvk_has_##mbr<T, decltype((void)T::mbr, void())> : std::true_type {};
mvk_define_has_member(pNext); // Defines the mvk_has_pNext() function.
/** Returns the address of the first member of a structure, which is just the address of the structure. */
template <typename S>
void* mvkGetAddressOfFirstMember(const S* pStruct, std::false_type){
return (void*)pStruct;
}
/**
* Returns the address of the first member of a Vulkan structure containing a pNext member.
* The first member is the one after the pNext member.
*/
template <class S>
void* mvkGetAddressOfFirstMember(const S* pStruct, std::true_type){
return (void*)(&(pStruct->pNext) + 1);
}
/**
* Returns the address of the first member of a structure. If the structure is a Vulkan
* structure containing a pNext member, the first member is the one after the pNext member.
*/
template <class S>
void* mvkGetAddressOfFirstMember(const S* pStruct){
return mvkGetAddressOfFirstMember(pStruct, mvk_has_pNext<S>{});
}
/** /**
* Reverses the order of the rows in the specified data block. * Reverses the order of the rows in the specified data block.
* The transformation is performed in-place. * The transformation is performed in-place.