blob: 1fd226c163cec496ab7a4555398a264b5a8e0902 [file] [log] [blame]
/*
* MVKFoundation.h
*
* Copyright (c) 2015-2021 The Brenwill Workshop Ltd. (http://www.brenwill.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "MVKCommonEnvironment.h"
#include "mvk_vulkan.h"
#include <algorithm>
#include <cassert>
#include <limits>
#include <string>
#include <cassert>
#include <simd/simd.h>
#include <type_traits>
#pragma mark Math
// Common scaling multipliers
#define KIBI (1024)
#define MEBI (KIBI * KIBI)
#define GIBI (KIBI * MEBI)
/** Represents a non-existent index. */
static const int kMVKIndexNone = -1;
/** A type definition for 16-bit half-float values. */
typedef uint16_t MVKHalfFloat;
/** A representation of the value of 1.0 as a 16-bit half-float. */
#define kHalfFloat1 0x3C00
#pragma mark -
#pragma mark Vertex content structures
/** 2D vertex position and texcoord content. */
typedef struct {
simd::float2 position;
simd::float3 texCoord;
} MVKVertexPosTex;
#pragma mark -
#pragma mark Vulkan support
/** Tracks the Vulkan command currently being used. */
typedef enum : uint8_t {
kMVKCommandUseNone, /**< No use defined. */
kMVKCommandUseEndCommandBuffer, /**< vkEndCommandBuffer (prefilled VkCommandBuffer). */
kMVKCommandUseQueueSubmit, /**< vkQueueSubmit. */
kMVKCommandUseAcquireNextImage, /**< vkAcquireNextImageKHR. */
kMVKCommandUseQueuePresent, /**< vkQueuePresentKHR. */
kMVKCommandUseQueueWaitIdle, /**< vkQueueWaitIdle. */
kMVKCommandUseDeviceWaitIdle, /**< vkDeviceWaitIdle. */
kMVKCommandUseInvalidateMappedMemoryRanges, /**< vkInvalidateMappedMemoryRanges. */
kMVKCommandUseBeginRenderPass, /**< vkCmdBeginRenderPass. */
kMVKCommandUseNextSubpass, /**< vkCmdNextSubpass. */
kMVKCommandUsePipelineBarrier, /**< vkCmdPipelineBarrier. */
kMVKCommandUseBlitImage, /**< vkCmdBlitImage. */
kMVKCommandUseCopyImage, /**< vkCmdCopyImage. */
kMVKCommandUseResolveImage, /**< vkCmdResolveImage - resolve stage. */
kMVKCommandUseResolveExpandImage, /**< vkCmdResolveImage - expand stage. */
kMVKCommandUseResolveCopyImage, /**< vkCmdResolveImage - copy stage. */
kMVKCommandUseCopyBuffer, /**< vkCmdCopyBuffer. */
kMVKCommandUseCopyBufferToImage, /**< vkCmdCopyBufferToImage. */
kMVKCommandUseCopyImageToBuffer, /**< vkCmdCopyImageToBuffer. */
kMVKCommandUseFillBuffer, /**< vkCmdFillBuffer. */
kMVKCommandUseUpdateBuffer, /**< vkCmdUpdateBuffer. */
kMVKCommandUseClearColorImage, /**< vkCmdClearColorImage. */
kMVKCommandUseClearDepthStencilImage, /**< vkCmdClearDepthStencilImage. */
kMVKCommandUseResetQueryPool, /**< vkCmdResetQueryPool. */
kMVKCommandUseDispatch, /**< vkCmdDispatch. */
kMVKCommandUseTessellationVertexTessCtl, /**< vkCmdDraw* - vertex and tessellation control stages. */
kMVKCommandUseMultiviewInstanceCountAdjust, /**< vkCmdDrawIndirect* - adjust instance count for multiview. */
kMVKCommandUseCopyQueryPoolResults, /**< vkCmdCopyQueryPoolResults. */
kMVKCommandUseAccumOcclusionQuery, /**< Any command terminating a Metal render pass with active visibility buffer. */
kMVKCommandUseRecordGPUCounterSample /**< Any command triggering the recording of a GPU counter sample. */
} MVKCommandUse;
/** Represents a given stage of a graphics pipeline. */
enum MVKGraphicsStage {
kMVKGraphicsStageVertex = 0, /**< The vertex shader stage. */
kMVKGraphicsStageTessControl, /**< The tessellation control shader stage. */
kMVKGraphicsStageRasterization /**< The rest of the pipeline. */
};
/** Returns the name of the result value. */
const char* mvkVkResultName(VkResult vkResult);
/** Returns the name of the component swizzle. */
const char* mvkVkComponentSwizzleName(VkComponentSwizzle swizzle);
/** Returns the Vulkan API version number as a string. */
static inline std::string mvkGetVulkanVersionString(uint32_t vkVersion) {
std::string verStr;
verStr += std::to_string(VK_VERSION_MAJOR(vkVersion));
verStr += ".";
verStr += std::to_string(VK_VERSION_MINOR(vkVersion));
verStr += ".";
verStr += std::to_string(VK_VERSION_PATCH(vkVersion));
return verStr;
}
/** Returns the MoltenVK API version number as a string. */
static inline std::string mvkGetMoltenVKVersionString(uint32_t mvkVersion) {
std::string verStr;
verStr += std::to_string(mvkVersion / 10000);
verStr += ".";
verStr += std::to_string((mvkVersion % 10000) / 100);
verStr += ".";
verStr += std::to_string(mvkVersion % 100);
return verStr;
}
#pragma mark -
#pragma mark Alignment functions
/** Returns whether the specified positive value is a power-of-two. */
template<typename T>
static inline bool mvkIsPowerOfTwo(T value) {
// Test POT: (x != 0) && ((x & (x - 1)) == 0)
return value && ((value & (value - 1)) == 0);
}
/**
* Ensures the specified positive value is a power-of-two. Returns the specified value
* if it is a power-of-two value. If it is not, returns the next power-of-two value
* that is larger than the specified value is returned.
*/
template<typename T>
static inline T mvkEnsurePowerOfTwo(T value) {
if (mvkIsPowerOfTwo(value)) { return value; }
T pot = 1;
while(pot <= value) { pot <<= 1; };
return pot;
}
/**
* Returns the power-of-two exponent of the next power-of-two
* number that is at least as big as the specified value.
*
* This implementation returns zero for both zero and one as inputs.
*/
template<typename T>
static inline T mvkPowerOfTwoExponent(T value) {
T p2Value = mvkEnsurePowerOfTwo(value);
// Count the trailing zeros
p2Value = (p2Value ^ (p2Value - 1)) >> 1; // Set trailing 0s to 1s and zero rest
T potExp = 0;
while (p2Value) {
p2Value >>= 1;
potExp++;
}
return potExp;
}
/**
* Aligns the byte reference to the specified alignment, and returns the aligned value,
* which will be greater than or equal to the reference if alignDown is false, or less
* than or equal to the reference if alignDown is true.
*
* This is a low level utility method. Usually you will use the convenience functions
* mvkAlignAddress() and mvkAlignByteCount() to align addresses and offsets respectively.
*/
static inline uintptr_t mvkAlignByteRef(uintptr_t byteRef, uintptr_t byteAlignment, bool alignDown = false) {
if (byteAlignment == 0) { return byteRef; }
assert(mvkIsPowerOfTwo(byteAlignment));
uintptr_t mask = byteAlignment - 1;
uintptr_t alignedRef = (byteRef + mask) & ~mask;
if (alignDown && (alignedRef > byteRef)) {
alignedRef -= byteAlignment;
}
return alignedRef;
}
/**
* Aligns the memory address to the specified byte alignment, and returns the aligned address,
* which will be greater than or equal to the original address if alignDown is false, or less
* than or equal to the original address if alignDown is true.
*/
static inline void* mvkAlignAddress(void* address, uintptr_t byteAlignment, bool alignDown = false) {
return (void*)mvkAlignByteRef((uintptr_t)address, byteAlignment, alignDown);
}
/**
* Aligns the byte offset to the specified byte alignment, and returns the aligned offset,
* which will be greater than or equal to the original offset if alignDown is false, or less
* than or equal to the original offset if alignDown is true.
*/
static inline uintptr_t mvkAlignByteCount(uintptr_t byteCount, uintptr_t byteAlignment, bool alignDown = false) {
return mvkAlignByteRef(byteCount, byteAlignment, alignDown);
}
/**
* Reverses the order of the rows in the specified data block.
* The transformation is performed in-place.
*
* This function may be used to reverse the order of the rows of any row-major memory
* structure, but is particularly useful for vertically flipping the contents of a texture
* or image, which is a common requirement when converting content data between a Vulkan
* texture orientation and a Metal texture orientation.
*
* The specified data block is assumed to be in row-major order, containing the specified
* number of rows, and with the specified number of bytes in each row. The total number of
* bytes in the data block must be at least (bytesPerRow * rowCount).
*/
void mvkFlipVertically(void* rowMajorData, uint32_t rowCount, size_t bytesPerRow);
/**
* The following constants are used to indicate values that have no defined limit.
* They are ridiculously large numbers, but low enough to be safely used as both
* uint and int values without risking overflowing between positive and negative values.
*/
static int32_t kMVKUndefinedLargePositiveInt32 = mvkEnsurePowerOfTwo(std::numeric_limits<int32_t>::max() / 2);
static int32_t kMVKUndefinedLargeNegativeInt32 = -kMVKUndefinedLargePositiveInt32;
static uint32_t kMVKUndefinedLargeUInt32 = kMVKUndefinedLargePositiveInt32;
static int64_t kMVKUndefinedLargePositiveInt64 = mvkEnsurePowerOfTwo(std::numeric_limits<int64_t>::max() / 2);
static int64_t kMVKUndefinedLargeNegativeInt64 = -kMVKUndefinedLargePositiveInt64;
static uint64_t kMVKUndefinedLargeUInt64 = kMVKUndefinedLargePositiveInt64;
#pragma mark Vulkan structure support functions
/** Returns a VkExtent2D created from the width and height of a VkExtent3D. */
static inline VkExtent2D mvkVkExtent2DFromVkExtent3D(VkExtent3D e) { return {e.width, e.height }; }
/** Returns a VkExtent3D, created from a VkExtent2D, and with depth of 1. */
static inline VkExtent3D mvkVkExtent3DFromVkExtent2D(VkExtent2D e) { return {e.width, e.height, 1U }; }
/** Returns whether the two Vulkan extents are equal by comparing their respective components. */
static inline bool mvkVkExtent2DsAreEqual(VkExtent2D e1, VkExtent2D e2) {
return (e1.width == e2.width) && (e1.height == e2.height);
}
/** Returns whether the two Vulkan extents are equal by comparing their respective components. */
static inline bool mvkVkExtent3DsAreEqual(VkExtent3D e1, VkExtent3D e2) {
return (e1.width == e2.width) && (e1.height == e2.height) && (e1.depth == e2.depth);
}
/** Returns whether the two Vulkan offsets are equal by comparing their respective components. */
static inline bool mvkVkOffset2DsAreEqual(VkOffset2D os1, VkOffset2D os2) {
return (os1.x == os2.x) && (os1.y == os2.y);
}
/** Returns whether the two Vulkan offsets are equal by comparing their respective components. */
static inline bool mvkVkOffset3DsAreEqual(VkOffset3D os1, VkOffset3D os2) {
return (os1.x == os2.x) && (os1.y == os2.y) && (os1.z == os2.z);
}
/**
* Returns the difference between two offsets, by subtracting the subtrahend from the minuend,
* which is accomplished by subtracting each of the corresponding x,y,z components.
*/
static inline VkOffset3D mvkVkOffset3DDifference(VkOffset3D minuend, VkOffset3D subtrahend) {
VkOffset3D rslt;
rslt.x = minuend.x - subtrahend.x;
rslt.y = minuend.y - subtrahend.y;
rslt.z = minuend.z - subtrahend.z;
return rslt;
}
/** Packs the four swizzle components into a single 32-bit word. */
static inline uint32_t mvkPackSwizzle(VkComponentMapping components) {
return ((components.r & 0xFF) << 0) | ((components.g & 0xFF) << 8) |
((components.b & 0xFF) << 16) | ((components.a & 0xFF) << 24);
}
/** Unpacks a single 32-bit word containing four swizzle components. */
static inline VkComponentMapping mvkUnpackSwizzle(uint32_t packed) {
VkComponentMapping components;
components.r = (VkComponentSwizzle)((packed >> 0) & 0xFF);
components.g = (VkComponentSwizzle)((packed >> 8) & 0xFF);
components.b = (VkComponentSwizzle)((packed >> 16) & 0xFF);
components.a = (VkComponentSwizzle)((packed >> 24) & 0xFF);
return components;
}
/**
* Returns whether the two component swizzles, cs1 and cs2 match. Positional identity matches
* and wildcard matches are allowed. The two values match under any of the following conditions:
* 1) cs1 and cs2 are equal to each other.
* 2) Either cs1 or cs2 is equal to VK_COMPONENT_SWIZZLE_IDENTITY and the other value
* is equal to the positional value csPos, which is one of VK_COMPONENT_SWIZZLE_R,
* VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, or VK_COMPONENT_SWIZZLE_A.
* 3) Either cs1 or cs2 is VK_COMPONENT_SWIZZLE_MAX_ENUM, which is considered a wildcard,
* and matches any value.
*/
static inline bool mvkVKComponentSwizzlesMatch(VkComponentSwizzle cs1,
VkComponentSwizzle cs2,
VkComponentSwizzle csPos) {
return ((cs1 == cs2) ||
((cs1 == VK_COMPONENT_SWIZZLE_IDENTITY) && (cs2 == csPos)) ||
((cs2 == VK_COMPONENT_SWIZZLE_IDENTITY) && (cs1 == csPos)) ||
(cs1 == VK_COMPONENT_SWIZZLE_MAX_ENUM) || (cs2 == VK_COMPONENT_SWIZZLE_MAX_ENUM));
}
/**
* Returns whether the two swizzle component mappings match each other, by comparing the
* corresponding elements of the two mappings. A component value of VK_COMPONENT_SWIZZLE_IDENTITY
* on either mapping matches the VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B,
* or VK_COMPONENT_SWIZZLE_A value in the other mapping if it is the correct position.
* A component value of VK_COMPONENT_SWIZZLE_MAX_ENUM is considered a wildcard and matches
* any value in the corresponding component in the other mapping.
*/
static inline bool mvkVkComponentMappingsMatch(VkComponentMapping cm1, VkComponentMapping cm2) {
return (mvkVKComponentSwizzlesMatch(cm1.r, cm2.r, VK_COMPONENT_SWIZZLE_R) &&
mvkVKComponentSwizzlesMatch(cm1.g, cm2.g, VK_COMPONENT_SWIZZLE_G) &&
mvkVKComponentSwizzlesMatch(cm1.b, cm2.b, VK_COMPONENT_SWIZZLE_B) &&
mvkVKComponentSwizzlesMatch(cm1.a, cm2.a, VK_COMPONENT_SWIZZLE_A));
}
/** Print the size of the type. */
#define mvkPrintSizeOf(type) printf("Size of " #type " is %lu.\n", sizeof(type))
#pragma mark -
#pragma mark Template functions
#pragma mark Math
/** Returns whether the value will fit inside the numeric type. */
template<typename T, typename Tval>
const bool mvkFits(const Tval& val) {
return val <= std::numeric_limits<T>::max();
}
/** Clamps the value between the lower and upper bounds, inclusive. */
template<typename T>
const T& mvkClamp(const T& val, const T& lower, const T& upper) {
return std::min(std::max(val, lower), upper);
}
/** Returns the result of a division, rounded up. */
template<typename T, typename U>
constexpr typename std::common_type<T, U>::type mvkCeilingDivide(T numerator, U denominator) {
typedef typename std::common_type<T, U>::type R;
// Short circuit very common usecase of dividing by one.
return (denominator == 1) ? numerator : (R(numerator) + denominator - 1) / denominator;
}
/** Returns the absolute value of a number. */
template<typename R, typename T, bool = std::is_signed<T>::value>
struct MVKAbs;
template<typename R, typename T>
struct MVKAbs<R, T, true> {
static constexpr R eval(T x) noexcept {
return x >= 0 ? x : (x == std::numeric_limits<T>::min() ? -static_cast<R>(x) : -x);
}
};
template<typename R, typename T>
struct MVKAbs<R, T, false> {
static constexpr R eval(T x) noexcept {
return x;
}
};
/** Returns the absolute value of the difference of two numbers. */
template<typename T, typename U>
constexpr typename std::common_type<T, U>::type mvkAbsDiff(T x, U y) {
return x >= y ? x - y : y - x;
}
/** Returns the greatest common divisor of two numbers. */
template<typename T>
constexpr T mvkGreatestCommonDivisorImpl(T a, T b) {
return b == 0 ? a : mvkGreatestCommonDivisorImpl(b, a % b);
}
template<typename T, typename U>
constexpr typename std::common_type<T, U>::type mvkGreatestCommonDivisor(T a, U b) {
typedef typename std::common_type<T, U>::type R;
typedef typename std::make_unsigned<R>::type UI;
return static_cast<R>(mvkGreatestCommonDivisorImpl(static_cast<UI>(MVKAbs<R, T>::eval(a)), static_cast<UI>(MVKAbs<R, U>::eval(b))));
}
/** Returns the least common multiple of two numbers. */
template<typename T, typename U>
constexpr typename std::common_type<T, U>::type mvkLeastCommonMultiple(T a, U b) {
typedef typename std::common_type<T, U>::type R;
return (a == 0 && b == 0) ? 0 : MVKAbs<R, T>::eval(a) / mvkGreatestCommonDivisor(a, b) * MVKAbs<R, U>::eval(b);
}
#pragma mark Hashing
/**
* Returns a hash value calculated from the specified array of numeric elements,
* using the DJB2a algorithm: hash = (hash * 33) ^ value.
*
* For a hash on a single array, leave the seed value unspecified, to use the default
* seed value. To accumulate a single hash value over several arrays, use the hash
* value returned by previous calls as the seed in subsequent calls.
*/
template<class N>
std::size_t mvkHash(const N* pVals, std::size_t count = 1, std::size_t seed = 5381) {
std::size_t hash = seed;
for (std::size_t i = 0; i < count; i++) { hash = ((hash << 5) + hash) ^ pVals[i]; }
return hash;
}
#pragma mark Containers
/**
* Structure to reference an array of typed elements in contiguous memory.
* Allocation and management of the memory is handled externally.
*/
template<typename Type>
struct MVKArrayRef {
Type* data;
const size_t size;
const Type* begin() const { return data; }
const Type* end() const { return &data[size]; }
const Type& operator[]( const size_t i ) const { return data[i]; }
Type& operator[]( const size_t i ) { return data[i]; }
MVKArrayRef() : MVKArrayRef(nullptr, 0) {}
MVKArrayRef(Type* d, size_t s) : data(d), size(s) {}
};
/** Ensures the size of the specified container is at least the specified size. */
template<typename C, typename S>
void mvkEnsureSize(C& container, S size) {
if (size > container.size()) { container.resize(size); }
}
/**
* Iterates through the contents of the specified object pointer container and destroys
* each object, including freeing the object memory, and clearing the container.
*/
template<typename C>
void mvkDestroyContainerContents(C& container) {
for (auto elem : container) { elem->destroy(); }
container.clear();
}
/**
* Iterates through the contents of the specified Objective-C object pointer
* container and releases each object, and clears the container.
*/
#ifdef __OBJC__
template<typename C>
void mvkReleaseContainerContents(C& container) {
for (auto elem : container) { [elem release]; }
container.clear();
}
#endif
/** Returns whether the container contains an item equal to the value. */
template<class C, class T>
bool contains(C& container, const T& val) {
for (const T& cVal : container) { if (cVal == val) { return true; } }
return false;
}
/** Removes the first occurance of the specified value from the specified container. */
template<class C, class T>
void mvkRemoveFirstOccurance(C& container, T val) {
for (auto iter = container.begin(), end = container.end(); iter != end; iter++) {
if( *iter == val ) {
container.erase(iter);
return;
}
}
}
/** Removes all occurances of the specified value from the specified container. */
template<class C, class T>
void mvkRemoveAllOccurances(C& container, T val) {
container.erase(remove(container.begin(), container.end(), val), container.end());
}
#pragma mark Values and structs
/** Selects and returns one of the values, based on the platform OS. */
template<typename T>
const T& mvkSelectPlatformValue(const T& macOSVal, const T& iOSVal) {
#if MVK_IOS_OR_TVOS
return iOSVal;
#endif
#if MVK_MACOS
return macOSVal;
#endif
}
/**
* If pVal is not null, clears the memory occupied by *pVal by writing zeros to all bytes.
* The optional count allows clearing multiple elements in an array.
*/
template<typename T>
void mvkClear(T* pVal, size_t count = 1) { if (pVal) { memset(pVal, 0, sizeof(T) * count); } }
/**
* If pVal is not null, overrides the const declaration, and clears the memory occupied by *pVal
* by writing zeros to all bytes. The optional count allows clearing multiple elements in an array.
*/
template<typename T>
void mvkClear(const T* pVal, size_t count = 1) { mvkClear((T*)pVal, count); }
/**
* If pSrc and pDst are both not null, copies the contents of the source value to the
* destination value. The optional count allows copying of multiple elements in an array.
*/
template<typename T>
void mvkCopy(T* pDst, const T* pSrc, size_t count = 1) {
if (pSrc && pDst) { memcpy(pDst, pSrc, sizeof(T) * count); }
}
/**
* If pV1 and pV2 are both not null, returns whether the contents of the two values are equal,
* otherwise returns false. The optional count allows comparing multiple elements in an array.
*/
template<typename T>
bool mvkAreEqual(const T* pV1, const T* pV2, size_t count = 1) {
return (pV1 && pV2) ? (memcmp(pV1, pV2, sizeof(T) * count) == 0) : false;
}
/**
* Sets the value referenced by the destination pointer with the value referenced by
* the source pointer, and returns whether the value was set.
*
* If both specified pointers are non-NULL, populates the value referenced by the
* destination pointer with the value referenced by the source pointer, and returns true.
*
* If the source pointer is NULL, the value referenced by the destination pointer
* is overwritten with zeros to clear it, and returns false.
*
* If the destination pointer is NULL, does nothing, and returns false.
*/
template<typename T>
bool mvkSetOrClear(T* pDest, const T* pSrc) {
if (pDest && pSrc) {
*pDest = *pSrc;
return true;
}
if (pDest) { mvkClear(pDest); }
return false;
}
#pragma mark Boolean flags
/** Enables the flags (sets bits to 1) within the value parameter specified by the bitMask parameter. */
template<typename Tv, typename Tm>
void mvkEnableFlags(Tv& value, const Tm bitMask) { value = (Tv)(value | bitMask); }
/** Disables the flags (sets bits to 0) within the value parameter specified by the bitMask parameter. */
template<typename Tv, typename Tm>
void mvkDisableFlags(Tv& value, const Tm bitMask) { value = (Tv)(value & ~(Tv)bitMask); }
/** Returns whether the specified value has ANY of the flags specified in bitMask enabled (set to 1). */
template<typename Tv, typename Tm>
bool mvkIsAnyFlagEnabled(Tv value, const Tm bitMask) { return ((value & bitMask) != 0); }
/** Returns whether the specified value has ALL of the flags specified in bitMask enabled (set to 1). */
template<typename Tv, typename Tm>
bool mvkAreAllFlagsEnabled(Tv value, const Tm bitMask) { return ((value & bitMask) == bitMask); }
/** Returns whether the specified value has ONLY one or more of the flags specified in bitMask enabled (set to 1), and none others. */
template<typename Tv, typename Tm>
bool mvkIsOnlyAnyFlagEnabled(Tv value, const Tm bitMask) { return (mvkIsAnyFlagEnabled(value, bitMask) && ((value | bitMask) == bitMask)); }
/** Returns whether the specified value has ONLY ALL of the flags specified in bitMask enabled (set to 1), and none others. */
template<typename Tv, typename Tm>
bool mvkAreOnlyAllFlagsEnabled(Tv value, const Tm bitMask) { return (value == bitMask); }