Skip to content

Commit

Permalink
Merge pull request #442 from Agrael1/master
Browse files Browse the repository at this point in the history
[WIP] Win32 Handle extension
  • Loading branch information
adam-sawicki-a authored Aug 28, 2024
2 parents b8e5747 + e962c8c commit 0d55cf5
Show file tree
Hide file tree
Showing 5 changed files with 282 additions and 5 deletions.
1 change: 1 addition & 0 deletions Doxyfile
Original file line number Diff line number Diff line change
Expand Up @@ -2466,6 +2466,7 @@ PREDEFINED = VMA_CALL_PRE= \
VMA_MEMORY_PRIORITY=1 \
VMA_KHR_MAINTENANCE4=1 \
VMA_KHR_MAINTENANCE5=1 \
VMA_EXTERNAL_MEMORY_WIN32=1 \
VMA_EXTERNAL_MEMORY=1 \
VMA_EXTENDS_VK_STRUCT= \
VMA_STATS_STRING_ENABLED=1
Expand Down
199 changes: 195 additions & 4 deletions include/vk_mem_alloc.h
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,15 @@ extern "C" {
#endif
#endif

// Defined to 1 when VK_KHR_external_memory_win32 device extension is defined in Vulkan headers.
#if !defined(VMA_EXTERNAL_MEMORY_WIN32)
#if VK_KHR_external_memory_win32
#define VMA_EXTERNAL_MEMORY_WIN32 1
#else
#define VMA_EXTERNAL_MEMORY_WIN32 0
#endif
#endif

// Define these macros to decorate all public functions with additional code,
// before and after returned type, appropriately. This may be useful for
// exporting the functions when compiling VMA as a separate library. Example:
Expand Down Expand Up @@ -461,6 +470,14 @@ typedef enum VmaAllocatorCreateFlagBits
*/
VMA_ALLOCATOR_CREATE_KHR_MAINTENANCE5_BIT = 0x00000100,

/**
Enables usage of VK_KHR_external_memory_win32 extension in the library.

You should set this flag if you found available and enabled this device extension,
while creating Vulkan device passed as VmaAllocatorCreateInfo::device.
*/
VMA_ALLOCATOR_CREATE_KHR_EXTERNAL_MEMORY_WIN32_BIT = 0x00000200,

VMA_ALLOCATOR_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
} VmaAllocatorCreateFlagBits;
/// See #VmaAllocatorCreateFlagBits.
Expand Down Expand Up @@ -1035,6 +1052,11 @@ typedef struct VmaVulkanFunctions
/// Fetch from "vkGetDeviceImageMemoryRequirements" on Vulkan >= 1.3, but you can also fetch it from "vkGetDeviceImageMemoryRequirementsKHR" if you enabled extension VK_KHR_maintenance4.
PFN_vkGetDeviceImageMemoryRequirementsKHR VMA_NULLABLE vkGetDeviceImageMemoryRequirements;
#endif
#ifdef VMA_EXTERNAL_MEMORY_WIN32

This comment has been minimized.

Copy link
@Mrkol

Mrkol Aug 28, 2024

This is wrong and it breaks all linux builds (including my CI 💢), should be #if VMA_EXTERNAL_MEMORY_WIN32, because it's defined as 0 for systems that don't have it, not undefined completely!!!

This comment has been minimized.

Copy link
@Mrkol

Mrkol Aug 28, 2024

Summoning @adam-sawicki-a for a fix and an explanation as to why this broken commit passes all of VMA's CI

This comment has been minimized.

Copy link
@adam-sawicki-a

adam-sawicki-a Aug 28, 2024

Author Contributor

Thank you for reporting this bug. I fixed it.

Answering your question: CI didn't catch this bug because CI builds on Linux, as setup on TravisCI, stopped working more than a year ago. If you know how to revive it e.g. using GitHub Actions, any help is appreciated.

This comment has been minimized.

Copy link
@Mrkol

Mrkol Aug 28, 2024

I can do a PR with a github workflow for Linux builds later this week, sure. Thanks for the quick fix!

This comment has been minimized.

Copy link
@adam-sawicki-a

adam-sawicki-a Aug 28, 2024

Author Contributor

That would be awesome, thanks! No need to launch any GPU workload, just to check if a basic project compiles, pulling VMA implementation code. Preferably on both Clang and GCC.

Please note the project is not supposed to be free of any compiler warnings. Some of them may need to be disabled explicitly if you enable treating warnings as errors.

This comment has been minimized.

Copy link
@Mrkol

Mrkol Aug 28, 2024

Yeah, the warnings have been bothering me too, actually. Is it not supposed to be warning-free because of some fundamental reason, or because of lack of time and manpower to deal with them?

This comment has been minimized.

Copy link
@adam-sawicki-a

adam-sawicki-a Aug 28, 2024

Author Contributor

This is my decision that I keep repeating again and again by quoting part of the documentation, section "Features not supported":

  1. Code free of any compiler warnings. Maintaining the library to compile and work correctly on so many different platforms is hard enough. Being free of any warnings, on any version of any compiler, is simply not feasible. There are many preprocessor macros that make some variables unused, function parameters unreferenced, or conditional expressions constant in some configurations. The code of this library should not be bigger or more complicated just to silence these warnings. It is recommended to disable such warnings instead.

You can enable treating warnings as errors and just disable specific types of warnings if you want. If this helps finding some warnings that I consider important, like an implicit conversion between int and float, or from larger to smaller int, I will fix it. However, those mentioned in the quote above I am not going to fix, especially by adding more characters or lines of code to the source.

PFN_vkGetMemoryWin32HandleKHR VMA_NULLABLE vkGetMemoryWin32HandleKHR;
#else
void* VMA_NULLABLE vkGetMemoryWin32HandleKHR;
#endif
} VmaVulkanFunctions;

/// Description of a Allocator to be created.
Expand Down Expand Up @@ -2052,6 +2074,21 @@ VMA_CALL_PRE void VMA_CALL_POST vmaGetAllocationMemoryProperties(
VmaAllocation VMA_NOT_NULL allocation,
VkMemoryPropertyFlags* VMA_NOT_NULL pFlags);


#if VMA_EXTERNAL_MEMORY_WIN32
/**
\brief Given an allocation, returns Win32 Handle, that may be imported by other processes or APIs.

`hTargetProcess` must be a valid handle to target process or NULL. If it's `NULL`, the function returns
handle for the current process.

If the allocation was created with `VMA_ALLOCATION_CREATE_EXPORT_WIN32_HANDLE_BIT` flag,
the function fills `pHandle` with handle that can be used in target process.
*/
VMA_CALL_PRE VkResult VMA_CALL_POST vmaGetMemoryWin32HandleKHR(VmaAllocator VMA_NOT_NULL allocator,
VmaAllocation VMA_NOT_NULL allocation, HANDLE hTargetProcess, HANDLE* VMA_NOT_NULL pHandle);
#endif // VMA_EXTERNAL_MEMORY_WIN32

/** \brief Maps memory represented by given allocation and returns pointer to it.

Maps memory represented by given allocation to make it accessible to CPU code.
Expand Down Expand Up @@ -6069,6 +6106,84 @@ class VmaMappingHysteresis

#endif // _VMA_MAPPING_HYSTERESIS

#if VMA_EXTERNAL_MEMORY_WIN32
class VmaWin32Handle
{
public:
VmaWin32Handle() noexcept : m_hHandle(VMA_NULL) { }
explicit VmaWin32Handle(HANDLE hHandle) noexcept : m_hHandle(hHandle) { }
~VmaWin32Handle() noexcept { if (m_hHandle != VMA_NULL) { ::CloseHandle(m_hHandle); } }
VMA_CLASS_NO_COPY_NO_MOVE(VmaWin32Handle)

public:
// Strengthened
VkResult GetHandle(VkDevice device, VkDeviceMemory memory, decltype(&vkGetMemoryWin32HandleKHR) pvkGetMemoryWin32HandleKHR, HANDLE hTargetProcess, bool useMutex, HANDLE* pHandle) noexcept
{
*pHandle = VMA_NULL;
// Try to get handle first.
if (m_hHandle != VMA_NULL)
{
*pHandle = Duplicate(hTargetProcess);
return VK_SUCCESS;
}

VkResult res = VK_SUCCESS;
// If failed, try to create it.
{
VmaMutexLockWrite lock(m_Mutex, useMutex);
if (m_hHandle == VMA_NULL)
{
res = Create(device, memory, pvkGetMemoryWin32HandleKHR, &m_hHandle);
}
}

*pHandle = Duplicate(hTargetProcess);
return res;
}

operator bool() const noexcept { return m_hHandle != VMA_NULL; }
private:
// Not atomic
static VkResult Create(VkDevice device, VkDeviceMemory memory, decltype(&vkGetMemoryWin32HandleKHR) pvkGetMemoryWin32HandleKHR, HANDLE* pHandle) noexcept
{
VkResult res = VK_ERROR_FEATURE_NOT_PRESENT;
if (pvkGetMemoryWin32HandleKHR != VMA_NULL)
{
VkMemoryGetWin32HandleInfoKHR handleInfo{ };
handleInfo.sType = VK_STRUCTURE_TYPE_MEMORY_GET_WIN32_HANDLE_INFO_KHR;
handleInfo.memory = memory;
handleInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT_KHR;
res = pvkGetMemoryWin32HandleKHR(device, &handleInfo, pHandle);
}
return res;
}
HANDLE Duplicate(HANDLE hTargetProcess = VMA_NULL) const noexcept
{
if (!m_hHandle)
return m_hHandle;

HANDLE hCurrentProcess = ::GetCurrentProcess();
HANDLE hDupHandle = VMA_NULL;
if (!::DuplicateHandle(hCurrentProcess, m_hHandle, hTargetProcess ? hTargetProcess : hCurrentProcess, &hDupHandle, 0, FALSE, DUPLICATE_SAME_ACCESS))
{
VMA_ASSERT(0 && "Failed to duplicate handle.");
}
return hDupHandle;
}
private:
HANDLE m_hHandle;
VMA_RW_MUTEX m_Mutex; // Protects access m_Handle
};
#else
class VmaWin32Handle
{
// ABI compatibility
void* placeholder = VMA_NULL;
VMA_RW_MUTEX placeholder2;
};
#endif // VMA_EXTERNAL_MEMORY_WIN32


#ifndef _VMA_DEVICE_MEMORY_BLOCK
/*
Represents a single block of device memory (`VkDeviceMemory`) with all the
Expand Down Expand Up @@ -6135,7 +6250,13 @@ class VmaDeviceMemoryBlock
VkDeviceSize allocationLocalOffset,
VkImage hImage,
const void* pNext);

#if VMA_EXTERNAL_MEMORY_WIN32
VkResult CreateWin32Handle(
const VmaAllocator hAllocator,
decltype(&vkGetMemoryWin32HandleKHR) pvkGetMemoryWin32HandleKHR,
HANDLE hTargetProcess,
HANDLE* pHandle)noexcept;
#endif // VMA_EXTERNAL_MEMORY_WIN32
private:
VmaPool m_hParentPool; // VK_NULL_HANDLE if not belongs to custom pool.
uint32_t m_MemoryTypeIndex;
Expand All @@ -6151,6 +6272,8 @@ class VmaDeviceMemoryBlock
VmaMappingHysteresis m_MappingHysteresis;
uint32_t m_MapCount;
void* m_pMappedData;

VmaWin32Handle m_Handle; // Win32 handle
};
#endif // _VMA_DEVICE_MEMORY_BLOCK

Expand Down Expand Up @@ -6236,6 +6359,10 @@ struct VmaAllocation_T
void PrintParameters(class VmaJsonWriter& json) const;
#endif

#if VMA_EXTERNAL_MEMORY_WIN32
VkResult GetWin32Handle(VmaAllocator hAllocator, HANDLE hTargetProcess, HANDLE* hHandle) noexcept;
#endif // VMA_EXTERNAL_MEMORY_WIN32

private:
// Allocation out of VmaDeviceMemoryBlock.
struct BlockAllocation
Expand All @@ -6251,6 +6378,7 @@ struct VmaAllocation_T
void* m_pMappedData; // Not null means memory is mapped.
VmaAllocation_T* m_Prev;
VmaAllocation_T* m_Next;
VmaWin32Handle m_Handle; // Win32 handle
};
union
{
Expand Down Expand Up @@ -10071,6 +10199,7 @@ struct VmaAllocator_T
bool m_UseExtMemoryPriority;
bool m_UseKhrMaintenance4;
bool m_UseKhrMaintenance5;
bool m_UseKhrExternalMemoryWin32;
const VkDevice m_hDevice;
const VkInstance m_hInstance;
const bool m_AllocationCallbacksSpecified;
Expand Down Expand Up @@ -10434,7 +10563,8 @@ VmaDeviceMemoryBlock::VmaDeviceMemoryBlock(VmaAllocator hAllocator)
m_Id(0),
m_hMemory(VK_NULL_HANDLE),
m_MapCount(0),
m_pMappedData(VMA_NULL) {}
m_pMappedData(VMA_NULL),
m_Handle(VMA_NULL) {}

VmaDeviceMemoryBlock::~VmaDeviceMemoryBlock()
{
Expand Down Expand Up @@ -10677,6 +10807,14 @@ VkResult VmaDeviceMemoryBlock::BindImageMemory(
VmaMutexLock lock(m_MapAndBindMutex, hAllocator->m_UseMutex);
return hAllocator->BindVulkanImage(m_hMemory, memoryOffset, hImage, pNext);
}

#if VMA_EXTERNAL_MEMORY_WIN32
VkResult VmaDeviceMemoryBlock::CreateWin32Handle(const VmaAllocator hAllocator, decltype(&vkGetMemoryWin32HandleKHR) pvkGetMemoryWin32HandleKHR, HANDLE hTargetProcess, HANDLE* pHandle) noexcept
{
VMA_ASSERT(pHandle);
return m_Handle.GetHandle(hAllocator->m_hDevice, m_hMemory, pvkGetMemoryWin32HandleKHR, hTargetProcess, hAllocator->m_UseMutex, pHandle);
}
#endif // VMA_EXTERNAL_MEMORY_WIN32
#endif // _VMA_DEVICE_MEMORY_BLOCK_FUNCTIONS

#ifndef _VMA_ALLOCATION_T_FUNCTIONS
Expand Down Expand Up @@ -10977,6 +11115,23 @@ void VmaAllocation_T::PrintParameters(class VmaJsonWriter& json) const
json.WriteString(m_pName);
}
}
#if VMA_EXTERNAL_MEMORY_WIN32
VkResult VmaAllocation_T::GetWin32Handle(VmaAllocator hAllocator, HANDLE hTargetProcess, HANDLE* pHandle) noexcept
{
// Where do we get this function from?
auto pvkGetMemoryWin32HandleKHR = hAllocator->GetVulkanFunctions().vkGetMemoryWin32HandleKHR;
switch (m_Type)
{
case ALLOCATION_TYPE_BLOCK:
return m_BlockAllocation.m_Block->CreateWin32Handle(hAllocator, pvkGetMemoryWin32HandleKHR, hTargetProcess, pHandle);
case ALLOCATION_TYPE_DEDICATED:
return m_DedicatedAllocation.m_Handle.GetHandle(hAllocator->m_hDevice, m_DedicatedAllocation.m_hMemory, pvkGetMemoryWin32HandleKHR, hTargetProcess, hAllocator->m_UseMutex, pHandle);
default:
VMA_ASSERT(0);
return VK_ERROR_FEATURE_NOT_PRESENT;
}
}
#endif // VMA_EXTERNAL_MEMORY_WIN32
#endif // VMA_STATS_STRING_ENABLED

void VmaAllocation_T::FreeName(VmaAllocator hAllocator)
Expand Down Expand Up @@ -12707,6 +12862,7 @@ VmaAllocator_T::VmaAllocator_T(const VmaAllocatorCreateInfo* pCreateInfo) :
m_UseExtMemoryPriority((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_EXT_MEMORY_PRIORITY_BIT) != 0),
m_UseKhrMaintenance4((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_KHR_MAINTENANCE4_BIT) != 0),
m_UseKhrMaintenance5((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_KHR_MAINTENANCE5_BIT) != 0),
m_UseKhrExternalMemoryWin32((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_KHR_EXTERNAL_MEMORY_WIN32_BIT) != 0),
m_hDevice(pCreateInfo->device),
m_hInstance(pCreateInfo->instance),
m_AllocationCallbacksSpecified(pCreateInfo->pAllocationCallbacks != VMA_NULL),
Expand Down Expand Up @@ -12798,6 +12954,19 @@ VmaAllocator_T::VmaAllocator_T(const VmaAllocatorCreateInfo* pCreateInfo) :
VMA_ASSERT(0 && "VMA_ALLOCATOR_CREATE_KHR_MAINTENANCE5_BIT is set but required extension is not available in your Vulkan header or its support in VMA has been disabled by a preprocessor macro.");
}
#endif
#if !(VMA_KHR_MAINTENANCE5)
if(m_UseKhrMaintenance5)
{
VMA_ASSERT(0 && "VMA_ALLOCATOR_CREATE_KHR_MAINTENANCE5_BIT is set but required extension is not available in your Vulkan header or its support in VMA has been disabled by a preprocessor macro.");
}
#endif

#if !(VMA_EXTERNAL_MEMORY_WIN32)
if(m_UseKhrExternalMemoryWin32)
{
VMA_ASSERT(0 && "VMA_ALLOCATOR_CREATE_KHR_EXTERNAL_MEMORY_WIN32_BIT is set but required extension is not available in your Vulkan header or its support in VMA has been disabled by a preprocessor macro.");
}
#endif

memset(&m_DeviceMemoryCallbacks, 0 ,sizeof(m_DeviceMemoryCallbacks));
memset(&m_PhysicalDeviceProperties, 0, sizeof(m_PhysicalDeviceProperties));
Expand Down Expand Up @@ -13022,7 +13191,9 @@ void VmaAllocator_T::ImportVulkanFunctions_Custom(const VmaVulkanFunctions* pVul
VMA_COPY_IF_NOT_NULL(vkGetDeviceBufferMemoryRequirements);
VMA_COPY_IF_NOT_NULL(vkGetDeviceImageMemoryRequirements);
#endif

#if VMA_EXTERNAL_MEMORY_WIN32
VMA_COPY_IF_NOT_NULL(vkGetMemoryWin32HandleKHR);
#endif
#undef VMA_COPY_IF_NOT_NULL
}

Expand Down Expand Up @@ -13124,7 +13295,12 @@ void VmaAllocator_T::ImportVulkanFunctions_Dynamic()
VMA_FETCH_DEVICE_FUNC(vkGetDeviceImageMemoryRequirements, PFN_vkGetDeviceImageMemoryRequirementsKHR, "vkGetDeviceImageMemoryRequirementsKHR");
}
#endif

#if VMA_EXTERNAL_MEMORY_WIN32
if (m_UseKhrExternalMemoryWin32)
{
VMA_FETCH_DEVICE_FUNC(vkGetMemoryWin32HandleKHR, PFN_vkGetMemoryWin32HandleKHR, "vkGetMemoryWin32HandleKHR");
}
#endif
#undef VMA_FETCH_DEVICE_FUNC
#undef VMA_FETCH_INSTANCE_FUNC
}
Expand Down Expand Up @@ -13173,6 +13349,12 @@ void VmaAllocator_T::ValidateVulkanFunctions()
VMA_ASSERT(m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties2KHR != VMA_NULL);
}
#endif
#if VMA_EXTERNAL_MEMORY_WIN32
if (m_UseKhrExternalMemoryWin32)
{
VMA_ASSERT(m_VulkanFunctions.vkGetMemoryWin32HandleKHR != VMA_NULL);
}
#endif

// Not validating these due to suspected driver bugs with these function
// pointers being null despite correct extension or Vulkan version is enabled.
Expand Down Expand Up @@ -16429,6 +16611,15 @@ VMA_CALL_PRE void VMA_CALL_POST vmaFreeVirtualBlockStatsString(VmaVirtualBlock V
VmaFreeString(virtualBlock->GetAllocationCallbacks(), pStatsString);
}
}
#if VMA_EXTERNAL_MEMORY_WIN32
VMA_CALL_PRE VkResult VMA_CALL_POST vmaGetMemoryWin32HandleKHR(VmaAllocator VMA_NOT_NULL allocator,
VmaAllocation VMA_NOT_NULL allocation, HANDLE hTargetProcess, HANDLE* VMA_NOT_NULL pHandle)
{
VMA_ASSERT(allocator && allocation);
VMA_DEBUG_GLOBAL_MUTEX_LOCK;
return allocation->GetWin32Handle(allocator, hTargetProcess, pHandle);
}
#endif // VMA_EXTERNAL_MEMORY_WIN32
#endif // VMA_STATS_STRING_ENABLED
#endif // _VMA_PUBLIC_INTERFACE
#endif // VMA_IMPLEMENTATION
Expand Down
8 changes: 8 additions & 0 deletions src/Shaders/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,22 @@ foreach(SHADER ${SHADERS})
get_filename_component(FILE_NAME ${SHADER} NAME)

# Put the .spv files into the bin folder
set(SPIRV_BIN ${CMAKE_CURRENT_BINARY_DIR}/${FILE_NAME}.spv)
set(SPIRV ${PROJECT_SOURCE_DIR}/bin/${FILE_NAME}.spv)


add_custom_command(
OUTPUT ${SPIRV}
# Use the same file name and append .spv to the compiled shader
COMMAND ${GLSL_VALIDATOR} -V ${CMAKE_CURRENT_SOURCE_DIR}/${SHADER} -o ${SPIRV}
DEPENDS ${SHADER}
)
add_custom_command(
OUTPUT ${SPIRV_BIN}
# Use the same file name and append .spv to the compiled shader
COMMAND ${GLSL_VALIDATOR} -V ${CMAKE_CURRENT_SOURCE_DIR}/${SHADER} -o ${SPIRV_BIN}
DEPENDS ${SHADER}
)

list(APPEND SPIRV_FILES ${SPIRV})
endforeach()
Expand Down
Loading

0 comments on commit 0d55cf5

Please sign in to comment.