Nabla
Nabla copied to clipboard
Enabling Features in LogicalDevice, DeviceSelection, Format Reporting Structure
Summary
LogicalDevice creation enabled features shouldn't necessarily equal the ones it reports as enabled (superset)
Basically what I'd imagine the usage of the API to be like.
RARE: Creating a physical device with all advertised features/extensions:
auto features = physicalDevice->getFeatures();
ILogicalDevice::SCreationParams params = {};
params.queueParamsCount = ; // set queue stuff
params.queueParams = ; // set queue stuff
params.enabledFeatures = features;
auto device = physicalDevice->createLogicalDevice(params);
FREQUENT: Choosing a physical device with the features
IPhysicalDevice::SRequiredProperties props = {}; // default initializes to apiVersion=1.1, deviceType = ET_UNKNOWN, pipelineCacheUUID = '\0', device UUID=`\0`, driverUUID=`\0`, deviceLUID=`\0`, deviceNodeMask= ~0u, driverID=UNKNOWN
// example of particular config
props.apiVersion = 1.2;
props.deviceTypeMask = ~IPhysicalDevice::ET_CPU; // would be good to turn the enum into a mask
props.driverIDMask = ~(EDI_AMD_PROPRIETARY|EDI_INTEL_PROPRIETARY_WINDOWS); // would be goot to turn the enum into a mask
props.conformanceVersion = 1.2;
SDeviceFeatures requiredFeatures = {};
requiredFeatures.rayQuery = true;
SDeviceLimits minimumLimits = {}; // would default initialize to worst possible values (small values for maximum sizes, large values for alignments, etc.)
// TODO: later add some stuff for requiring queue families, formats and minimum memory heap sizes
auto physicalDeviceCandidates = api->getCompatiblePhysicalDevices(props,requiredFeatures,minimumLimits,numSwapchains,supportedSwapchains,/*optional: would enforce tighter checks to actually accept compatibility, like formats, present modes and surface caps*/swapchainSupportDecider);
if (physicalDeviceCandidates.empty())
{
logError();
exit();
}
// TODO: later iterate through candidate devices (fullfilling all the required criteria) to find the "best" one
// std::sort(physicalDeviceCandidates.begin(),physicalDeviceCandidates.end(),SDefaultPhysicalDeviceOrder());
auto physicalDevice = physicalDeviceCandidates.begin();
assert(requiredFeatures < physicalDevice->getFeatures());
assert(minimumLimits < physicalDevice->getLimits());
ILogicalDevice::SCreationParams params = {};
params.queueParamsCount = ; // set queue stuff
params.queueParams = ; // set queue stuff
params.enabledFeatures = requiredFeatures;
auto device = physicalDevice->createLogicalDevice(params);
// this would be wrong, because during device creation we would enable additional features either due to:
// - dependencies (like buffer address for raytracing)
// - backend force-enabling them (like in OpenGL, where extensions are just enabled, you have no choice)
// assert(requiredFeatures != device->getEnabledFeatures());
assert(requiredFeatures < device->getEnabledFeatures());
SDeviceFeatures and SDeviceLimits should have a operator<
Basically to let us establish if features or limits are a superset of the requested.
If you need a != operator then define it as
inline bool operator!=(const & other) const
{
return *this<other || other<*this;
}
We should probably change the SFormat....Usage structs to contain the [E_FORMAT::COUNT] array inside themselves
Would probably need a Capabilities nested struct def for the array type, and also add default initializers to the structs.
This would make it easier to implement the requiredFormats for getCompatiblePhysicalDevices()
Our current API, returns the format usages "one by one"
virtual const SFormatBufferUsage& getBufferFormatUsages(const asset::E_FORMAT format) = 0;
virtual const SFormatImageUsage& getImageFormatUsagesLinear(const asset::E_FORMAT format) = 0;
virtual const SFormatImageUsage& getImageFormatUsagesOptimal(const asset::E_FORMAT format) = 0;
this makes it hard to extend it in the future to "reuse" the same struct (sanely, without fancy containers, iterators, templates, heap memory allocations) to express the "required" formats and their usages to the device selector.
However if we had
struct SFormatBufferUsages
{
struct SUsage
{
// what SFormatBufferUsage is right now, plus a default constructor that sets all usage bits to false
};
// whether `this` is a subset of `other`
inline bool operator<(const SFormatBufferUsages& other) const;
SUsage m_formatUsages[asset::EF_COUNT] = {};
};
virtual const SFormatBufferUsages& getBufferFormatUsages() = 0;
struct SFormatImageUsages
{
struct SUsage
{
// what SFormatImageUsage is right now, plus a default constructor that sets all usage bits to false
};
// whether `this` is a subset of `other`
inline bool operator<(const SFormatImageUsages& other) const;
SUsage m_formatUsages[asset::EF_COUNT] = {};
};
virtual const SFormatImageUsages& getImageFormatUsagesLinear() = 0;
virtual const SFormatImageUsages& getImageFormatUsagesOptimal() = 0;
It would get a lot easier to "request" a format, we'd simply start with a default initialized struct (no usages for any format), and then just fill out the formats and their usages we require.
Then verifying that a physical device can be a candidate would be as easy as
candidate = candidate && requiredBufferFormatUsages<physicalDevice->getBufferFormatUsages() && requiredImageFormatUsagesLinear<physicalDevice->getImageFormatUsagesLinear() && requiredImageFormatUsagesOptimal<physicalDevice->getImageFormatUsagesOptimal();
P.S. You can nuke IVideoCapabilityReporter and IVideoDriver now, we've covered all their features.