Apply consistent code formatting across the repo. Add clang-format and pre-commit hooks.

This commit is contained in:
Rob Armstrong
2025-03-27 10:30:07 -07:00
parent 2cd58fbc9a
commit ceab6e8bcc
782 changed files with 107230 additions and 106548 deletions

View File

@@ -21,7 +21,7 @@ For Linux:
For Linux aarch64(L4T):
-- Install GLFW3 library using "sudo apt-get install libglfw3-dev" this will provide glfw3
-- Install GLFW3 library using "sudo apt-get install libglfw3-dev" this will provide glfw3
-- install above will also provide libvulkan-dev as dependencies
-- Add Vulkan and GLFW3 libraries directories to LD_LIBRARY_PATH
-- Pass path to vulkan sdk while building 'make VULKAN_SDK_PATH=<PATH_TO_VULKAN_SDK>', VULKAN_SDK_PATH in this scenario is typically "/usr"

View File

@@ -29,8 +29,9 @@
* See: https://www.piday.org/million/
*/
#include "MonteCarloPi.h"
#include <algorithm>
#include "MonteCarloPi.h"
#define CUDA_DRIVER_API
#include <helper_cuda.h>
#include <iostream>
@@ -45,257 +46,245 @@
// ipcHandleTypeFlag variable is a convenience variable and is passed by value
// to individual requests.
#if defined(__linux__)
CUmemAllocationHandleType ipcHandleTypeFlag =
CU_MEM_HANDLE_TYPE_POSIX_FILE_DESCRIPTOR;
CUmemAllocationHandleType ipcHandleTypeFlag = CU_MEM_HANDLE_TYPE_POSIX_FILE_DESCRIPTOR;
#else
CUmemAllocationHandleType ipcHandleTypeFlag = CU_MEM_HANDLE_TYPE_WIN32;
#endif
// Windows-specific LPSECURITYATTRIBUTES
void getDefaultSecurityDescriptor(CUmemAllocationProp *prop) {
void getDefaultSecurityDescriptor(CUmemAllocationProp *prop)
{
#if defined(__linux__)
return;
return;
#elif defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64)
static const char sddl[] = "D:P(OA;;GARCSDWDWOCCDCLCSWLODTWPRPCRFA;;;WD)";
static OBJECT_ATTRIBUTES objAttributes;
static bool objAttributesConfigured = false;
static const char sddl[] = "D:P(OA;;GARCSDWDWOCCDCLCSWLODTWPRPCRFA;;;WD)";
static OBJECT_ATTRIBUTES objAttributes;
static bool objAttributesConfigured = false;
if (!objAttributesConfigured) {
PSECURITY_DESCRIPTOR secDesc;
BOOL result = ConvertStringSecurityDescriptorToSecurityDescriptorA(
sddl, SDDL_REVISION_1, &secDesc, NULL);
if (result == 0) {
printf("IPC failure: getDefaultSecurityDescriptor Failed! (%d)\n",
GetLastError());
if (!objAttributesConfigured) {
PSECURITY_DESCRIPTOR secDesc;
BOOL result = ConvertStringSecurityDescriptorToSecurityDescriptorA(sddl, SDDL_REVISION_1, &secDesc, NULL);
if (result == 0) {
printf("IPC failure: getDefaultSecurityDescriptor Failed! (%d)\n", GetLastError());
}
InitializeObjectAttributes(&objAttributes, NULL, 0, NULL, secDesc);
objAttributesConfigured = true;
}
InitializeObjectAttributes(&objAttributes, NULL, 0, NULL, secDesc);
objAttributesConfigured = true;
}
prop->win32HandleMetaData = &objAttributes;
return;
prop->win32HandleMetaData = &objAttributes;
return;
#endif
}
__global__ void monte_carlo_kernel(vec2 *xyVector, float *pointsInsideCircle,
float *numPointsInCircle,
unsigned int numPoints, float time) {
const size_t stride = gridDim.x * blockDim.x;
size_t tid = blockIdx.x * blockDim.x + threadIdx.x;
float count = 0.0f;
__global__ void monte_carlo_kernel(vec2 *xyVector,
float *pointsInsideCircle,
float *numPointsInCircle,
unsigned int numPoints,
float time)
{
const size_t stride = gridDim.x * blockDim.x;
size_t tid = blockIdx.x * blockDim.x + threadIdx.x;
float count = 0.0f;
curandState rgnState;
curand_init((unsigned long long)time, tid, 0, &rgnState);
curandState rgnState;
curand_init((unsigned long long)time, tid, 0, &rgnState);
for (; tid < numPoints; tid += stride) {
float x = curand_uniform(&rgnState);
float y = curand_uniform(&rgnState);
x = (2.0f * x) - 1.0f;
y = (2.0f * y) - 1.0f;
xyVector[tid][0] = x;
xyVector[tid][1] = y;
for (; tid < numPoints; tid += stride) {
float x = curand_uniform(&rgnState);
float y = curand_uniform(&rgnState);
x = (2.0f * x) - 1.0f;
y = (2.0f * y) - 1.0f;
xyVector[tid][0] = x;
xyVector[tid][1] = y;
// Compute the distance of this point form the center(0, 0)
float dist = sqrtf((x * x) + (y * y));
// Compute the distance of this point form the center(0, 0)
float dist = sqrtf((x * x) + (y * y));
// If distance is less than the radius of the unit circle, the point lies in
// the circle.
pointsInsideCircle[tid] = (dist <= 1.0f);
count += (dist <= 1.0f);
}
atomicAdd(numPointsInCircle, count);
// If distance is less than the radius of the unit circle, the point lies in
// the circle.
pointsInsideCircle[tid] = (dist <= 1.0f);
count += (dist <= 1.0f);
}
atomicAdd(numPointsInCircle, count);
}
MonteCarloPiSimulation::MonteCarloPiSimulation(size_t num_points)
: m_xyVector(nullptr),
m_pointsInsideCircle(nullptr),
m_totalPointsInsideCircle(0),
m_totalPointsSimulated(0),
m_numPoints(num_points) {}
MonteCarloPiSimulation::~MonteCarloPiSimulation() {
if (m_numPointsInCircle) {
checkCudaErrors(cudaFree(m_numPointsInCircle));
m_numPointsInCircle = nullptr;
}
if (m_hostNumPointsInCircle) {
checkCudaErrors(cudaFreeHost(m_hostNumPointsInCircle));
m_hostNumPointsInCircle = nullptr;
}
cleanupSimulationAllocations();
: m_xyVector(nullptr)
, m_pointsInsideCircle(nullptr)
, m_totalPointsInsideCircle(0)
, m_totalPointsSimulated(0)
, m_numPoints(num_points)
{
}
void MonteCarloPiSimulation::initSimulation(int cudaDevice,
cudaStream_t stream) {
m_cudaDevice = cudaDevice;
getIdealExecutionConfiguration();
MonteCarloPiSimulation::~MonteCarloPiSimulation()
{
if (m_numPointsInCircle) {
checkCudaErrors(cudaFree(m_numPointsInCircle));
m_numPointsInCircle = nullptr;
}
if (m_hostNumPointsInCircle) {
checkCudaErrors(cudaFreeHost(m_hostNumPointsInCircle));
m_hostNumPointsInCircle = nullptr;
}
// Allocate a position buffer that contains random location of the points in
// XY cartesian plane.
// Allocate a bitmap buffer which holds information of whether a point in the
// position buffer is inside the unit circle or not.
setupSimulationAllocations();
checkCudaErrors(
cudaMalloc((float **)&m_numPointsInCircle, sizeof(*m_numPointsInCircle)));
checkCudaErrors(cudaMallocHost((float **)&m_hostNumPointsInCircle,
sizeof(*m_hostNumPointsInCircle)));
cleanupSimulationAllocations();
}
void MonteCarloPiSimulation::stepSimulation(float time, cudaStream_t stream) {
checkCudaErrors(cudaMemsetAsync(m_numPointsInCircle, 0,
sizeof(*m_numPointsInCircle), stream));
void MonteCarloPiSimulation::initSimulation(int cudaDevice, cudaStream_t stream)
{
m_cudaDevice = cudaDevice;
getIdealExecutionConfiguration();
monte_carlo_kernel<<<m_blocks, m_threads, 0, stream>>>(
m_xyVector, m_pointsInsideCircle, m_numPointsInCircle, m_numPoints, time);
getLastCudaError("Failed to launch CUDA simulation");
// Allocate a position buffer that contains random location of the points in
// XY cartesian plane.
// Allocate a bitmap buffer which holds information of whether a point in the
// position buffer is inside the unit circle or not.
setupSimulationAllocations();
checkCudaErrors(cudaMemcpyAsync(m_hostNumPointsInCircle, m_numPointsInCircle,
sizeof(*m_numPointsInCircle),
cudaMemcpyDeviceToHost, stream));
// Queue up a stream callback to compute and print the PI value.
checkCudaErrors(
cudaLaunchHostFunc(stream, this->computePiCallback, (void *)this));
checkCudaErrors(cudaMalloc((float **)&m_numPointsInCircle, sizeof(*m_numPointsInCircle)));
checkCudaErrors(cudaMallocHost((float **)&m_hostNumPointsInCircle, sizeof(*m_hostNumPointsInCircle)));
}
void MonteCarloPiSimulation::computePiCallback(void *args) {
MonteCarloPiSimulation *cbData = (MonteCarloPiSimulation *)args;
cbData->m_totalPointsInsideCircle += *(cbData->m_hostNumPointsInCircle);
cbData->m_totalPointsSimulated += cbData->m_numPoints;
double piValue = 4.0 * ((double)cbData->m_totalPointsInsideCircle /
(double)cbData->m_totalPointsSimulated);
printf("Approximate Pi value for %zd data points: %lf \n",
cbData->m_totalPointsSimulated, piValue);
void MonteCarloPiSimulation::stepSimulation(float time, cudaStream_t stream)
{
checkCudaErrors(cudaMemsetAsync(m_numPointsInCircle, 0, sizeof(*m_numPointsInCircle), stream));
monte_carlo_kernel<<<m_blocks, m_threads, 0, stream>>>(
m_xyVector, m_pointsInsideCircle, m_numPointsInCircle, m_numPoints, time);
getLastCudaError("Failed to launch CUDA simulation");
checkCudaErrors(cudaMemcpyAsync(
m_hostNumPointsInCircle, m_numPointsInCircle, sizeof(*m_numPointsInCircle), cudaMemcpyDeviceToHost, stream));
// Queue up a stream callback to compute and print the PI value.
checkCudaErrors(cudaLaunchHostFunc(stream, this->computePiCallback, (void *)this));
}
void MonteCarloPiSimulation::getIdealExecutionConfiguration() {
int warpSize = 0;
int multiProcessorCount = 0;
checkCudaErrors(cudaSetDevice(m_cudaDevice));
checkCudaErrors(
cudaDeviceGetAttribute(&warpSize, cudaDevAttrWarpSize, m_cudaDevice));
// We don't need large block sizes, since there's not much inter-thread
// communication
m_threads = warpSize;
// Use the occupancy calculator and fill the gpu as best as we can
checkCudaErrors(cudaOccupancyMaxActiveBlocksPerMultiprocessor(
&m_blocks, monte_carlo_kernel, warpSize, 0));
checkCudaErrors(cudaDeviceGetAttribute(
&multiProcessorCount, cudaDevAttrMultiProcessorCount, m_cudaDevice));
m_blocks *= multiProcessorCount;
// Go ahead and the clamp the blocks to the minimum needed for this
// height/width
m_blocks =
std::min(m_blocks, (int)((m_numPoints + m_threads - 1) / m_threads));
void MonteCarloPiSimulation::computePiCallback(void *args)
{
MonteCarloPiSimulation *cbData = (MonteCarloPiSimulation *)args;
cbData->m_totalPointsInsideCircle += *(cbData->m_hostNumPointsInCircle);
cbData->m_totalPointsSimulated += cbData->m_numPoints;
double piValue = 4.0 * ((double)cbData->m_totalPointsInsideCircle / (double)cbData->m_totalPointsSimulated);
printf("Approximate Pi value for %zd data points: %lf \n", cbData->m_totalPointsSimulated, piValue);
}
void MonteCarloPiSimulation::setupSimulationAllocations() {
CUdeviceptr d_ptr = 0U;
size_t granularity = 0;
CUmemGenericAllocationHandle cudaPositionHandle, cudaInCircleHandle;
void MonteCarloPiSimulation::getIdealExecutionConfiguration()
{
int warpSize = 0;
int multiProcessorCount = 0;
CUmemAllocationProp allocProp = {};
allocProp.type = CU_MEM_ALLOCATION_TYPE_PINNED;
allocProp.location.type = CU_MEM_LOCATION_TYPE_DEVICE;
allocProp.location.id = m_cudaDevice;
allocProp.win32HandleMetaData = NULL;
allocProp.requestedHandleTypes = ipcHandleTypeFlag;
checkCudaErrors(cudaSetDevice(m_cudaDevice));
checkCudaErrors(cudaDeviceGetAttribute(&warpSize, cudaDevAttrWarpSize, m_cudaDevice));
// Windows-specific LPSECURITYATTRIBUTES is required when
// CU_MEM_HANDLE_TYPE_WIN32 is used. The security attribute defines the scope
// of which exported allocations may be tranferred to other processes. For all
// other handle types, pass NULL.
getDefaultSecurityDescriptor(&allocProp);
// We don't need large block sizes, since there's not much inter-thread
// communication
m_threads = warpSize;
// Get the recommended granularity for m_cudaDevice.
checkCudaErrors(cuMemGetAllocationGranularity(
&granularity, &allocProp, CU_MEM_ALLOC_GRANULARITY_RECOMMENDED));
// Use the occupancy calculator and fill the gpu as best as we can
checkCudaErrors(cudaOccupancyMaxActiveBlocksPerMultiprocessor(&m_blocks, monte_carlo_kernel, warpSize, 0));
size_t xyPositionVecSize = m_numPoints * sizeof(*m_xyVector);
size_t inCircleVecSize = m_numPoints * sizeof(*m_pointsInsideCircle);
checkCudaErrors(cudaDeviceGetAttribute(&multiProcessorCount, cudaDevAttrMultiProcessorCount, m_cudaDevice));
m_blocks *= multiProcessorCount;
size_t xyPositionSize =
ROUND_UP_TO_GRANULARITY(xyPositionVecSize, granularity);
size_t inCircleSize = ROUND_UP_TO_GRANULARITY(inCircleVecSize, granularity);
m_totalAllocationSize = (xyPositionSize + inCircleSize);
// Reserve the required contiguous VA space for the allocations
checkCudaErrors(
cuMemAddressReserve(&d_ptr, m_totalAllocationSize, granularity, 0U, 0));
// Create the allocations as a pinned allocation on this device.
// Create an allocation to store all the positions of points on the xy plane
// and a second allocation which stores information if the corresponding
// position is inside the unit circle or not.
checkCudaErrors(
cuMemCreate(&cudaPositionHandle, xyPositionSize, &allocProp, 0));
checkCudaErrors(
cuMemCreate(&cudaInCircleHandle, inCircleSize, &allocProp, 0));
// Export the allocation to a platform-specific handle. The type of handle
// requested here must match the requestedHandleTypes field in the prop
// structure passed to cuMemCreate. The handle obtained here will be passed to
// vulkan to import the allocation.
checkCudaErrors(cuMemExportToShareableHandle(
(void *)&m_posShareableHandle, cudaPositionHandle, ipcHandleTypeFlag, 0));
checkCudaErrors(
cuMemExportToShareableHandle((void *)&m_inCircleShareableHandle,
cudaInCircleHandle, ipcHandleTypeFlag, 0));
CUdeviceptr va_position = d_ptr;
CUdeviceptr va_InCircle = va_position + xyPositionSize;
m_pointsInsideCircle = (float *)va_InCircle;
m_xyVector = (vec2 *)va_position;
// Assign the chunk to the appropriate VA range
checkCudaErrors(
cuMemMap(va_position, xyPositionSize, 0, cudaPositionHandle, 0));
checkCudaErrors(
cuMemMap(va_InCircle, inCircleSize, 0, cudaInCircleHandle, 0));
// Release the handles for the allocation. Since the allocation is currently
// mapped to a VA range with a previous call to cuMemMap the actual freeing of
// memory allocation will happen on an eventual call to cuMemUnmap. Thus the
// allocation will be kept live until it is unmapped.
checkCudaErrors(cuMemRelease(cudaPositionHandle));
checkCudaErrors(cuMemRelease(cudaInCircleHandle));
CUmemAccessDesc accessDescriptor = {};
accessDescriptor.location.id = m_cudaDevice;
accessDescriptor.location.type = CU_MEM_LOCATION_TYPE_DEVICE;
accessDescriptor.flags = CU_MEM_ACCESS_FLAGS_PROT_READWRITE;
// Apply the access descriptor to the whole VA range. Essentially enables
// Read-Write access to the range.
checkCudaErrors(
cuMemSetAccess(d_ptr, m_totalAllocationSize, &accessDescriptor, 1));
// Go ahead and the clamp the blocks to the minimum needed for this
// height/width
m_blocks = std::min(m_blocks, (int)((m_numPoints + m_threads - 1) / m_threads));
}
void MonteCarloPiSimulation::cleanupSimulationAllocations() {
if (m_xyVector && m_pointsInsideCircle) {
// Unmap the mapped virtual memory region
// Since the handles to the mapped backing stores have already been released
// by cuMemRelease, and these are the only/last mappings referencing them,
// The backing stores will be freed.
checkCudaErrors(cuMemUnmap((CUdeviceptr)m_xyVector, m_totalAllocationSize));
void MonteCarloPiSimulation::setupSimulationAllocations()
{
CUdeviceptr d_ptr = 0U;
size_t granularity = 0;
CUmemGenericAllocationHandle cudaPositionHandle, cudaInCircleHandle;
checkIpcErrors(ipcCloseShareableHandle(m_posShareableHandle));
checkIpcErrors(ipcCloseShareableHandle(m_inCircleShareableHandle));
CUmemAllocationProp allocProp = {};
allocProp.type = CU_MEM_ALLOCATION_TYPE_PINNED;
allocProp.location.type = CU_MEM_LOCATION_TYPE_DEVICE;
allocProp.location.id = m_cudaDevice;
allocProp.win32HandleMetaData = NULL;
allocProp.requestedHandleTypes = ipcHandleTypeFlag;
// Free the virtual address region.
// Windows-specific LPSECURITYATTRIBUTES is required when
// CU_MEM_HANDLE_TYPE_WIN32 is used. The security attribute defines the scope
// of which exported allocations may be tranferred to other processes. For all
// other handle types, pass NULL.
getDefaultSecurityDescriptor(&allocProp);
// Get the recommended granularity for m_cudaDevice.
checkCudaErrors(cuMemGetAllocationGranularity(&granularity, &allocProp, CU_MEM_ALLOC_GRANULARITY_RECOMMENDED));
size_t xyPositionVecSize = m_numPoints * sizeof(*m_xyVector);
size_t inCircleVecSize = m_numPoints * sizeof(*m_pointsInsideCircle);
size_t xyPositionSize = ROUND_UP_TO_GRANULARITY(xyPositionVecSize, granularity);
size_t inCircleSize = ROUND_UP_TO_GRANULARITY(inCircleVecSize, granularity);
m_totalAllocationSize = (xyPositionSize + inCircleSize);
// Reserve the required contiguous VA space for the allocations
checkCudaErrors(cuMemAddressReserve(&d_ptr, m_totalAllocationSize, granularity, 0U, 0));
// Create the allocations as a pinned allocation on this device.
// Create an allocation to store all the positions of points on the xy plane
// and a second allocation which stores information if the corresponding
// position is inside the unit circle or not.
checkCudaErrors(cuMemCreate(&cudaPositionHandle, xyPositionSize, &allocProp, 0));
checkCudaErrors(cuMemCreate(&cudaInCircleHandle, inCircleSize, &allocProp, 0));
// Export the allocation to a platform-specific handle. The type of handle
// requested here must match the requestedHandleTypes field in the prop
// structure passed to cuMemCreate. The handle obtained here will be passed to
// vulkan to import the allocation.
checkCudaErrors(
cuMemAddressFree((CUdeviceptr)m_xyVector, m_totalAllocationSize));
cuMemExportToShareableHandle((void *)&m_posShareableHandle, cudaPositionHandle, ipcHandleTypeFlag, 0));
checkCudaErrors(
cuMemExportToShareableHandle((void *)&m_inCircleShareableHandle, cudaInCircleHandle, ipcHandleTypeFlag, 0));
m_xyVector = nullptr;
m_pointsInsideCircle = nullptr;
}
CUdeviceptr va_position = d_ptr;
CUdeviceptr va_InCircle = va_position + xyPositionSize;
m_pointsInsideCircle = (float *)va_InCircle;
m_xyVector = (vec2 *)va_position;
// Assign the chunk to the appropriate VA range
checkCudaErrors(cuMemMap(va_position, xyPositionSize, 0, cudaPositionHandle, 0));
checkCudaErrors(cuMemMap(va_InCircle, inCircleSize, 0, cudaInCircleHandle, 0));
// Release the handles for the allocation. Since the allocation is currently
// mapped to a VA range with a previous call to cuMemMap the actual freeing of
// memory allocation will happen on an eventual call to cuMemUnmap. Thus the
// allocation will be kept live until it is unmapped.
checkCudaErrors(cuMemRelease(cudaPositionHandle));
checkCudaErrors(cuMemRelease(cudaInCircleHandle));
CUmemAccessDesc accessDescriptor = {};
accessDescriptor.location.id = m_cudaDevice;
accessDescriptor.location.type = CU_MEM_LOCATION_TYPE_DEVICE;
accessDescriptor.flags = CU_MEM_ACCESS_FLAGS_PROT_READWRITE;
// Apply the access descriptor to the whole VA range. Essentially enables
// Read-Write access to the range.
checkCudaErrors(cuMemSetAccess(d_ptr, m_totalAllocationSize, &accessDescriptor, 1));
}
void MonteCarloPiSimulation::cleanupSimulationAllocations()
{
if (m_xyVector && m_pointsInsideCircle) {
// Unmap the mapped virtual memory region
// Since the handles to the mapped backing stores have already been released
// by cuMemRelease, and these are the only/last mappings referencing them,
// The backing stores will be freed.
checkCudaErrors(cuMemUnmap((CUdeviceptr)m_xyVector, m_totalAllocationSize));
checkIpcErrors(ipcCloseShareableHandle(m_posShareableHandle));
checkIpcErrors(ipcCloseShareableHandle(m_inCircleShareableHandle));
// Free the virtual address region.
checkCudaErrors(cuMemAddressFree((CUdeviceptr)m_xyVector, m_totalAllocationSize));
m_xyVector = nullptr;
m_pointsInsideCircle = nullptr;
}
}

View File

@@ -29,67 +29,66 @@
#ifndef __PISIM_H__
#define __PISIM_H__
#include <vector>
#include <cuda.h>
#include <cuda_runtime_api.h>
#include <curand.h>
#include <curand_kernel.h>
#include <cuda.h>
#include <vector>
#include "helper_multiprocess.h"
typedef float vec2[2];
class MonteCarloPiSimulation {
size_t m_numPoints;
class MonteCarloPiSimulation
{
size_t m_numPoints;
// Pointers to Cuda allocated buffers which are imported and used by vulkan as
// vertex buffer
vec2 *m_xyVector;
float *m_pointsInsideCircle;
// Pointers to Cuda allocated buffers which are imported and used by vulkan as
// vertex buffer
vec2 *m_xyVector;
float *m_pointsInsideCircle;
// Pointers to device and host allocated memories storing number of points
// that are inside the unit circle
float *m_numPointsInCircle;
float *m_hostNumPointsInCircle;
// Pointers to device and host allocated memories storing number of points
// that are inside the unit circle
float *m_numPointsInCircle;
float *m_hostNumPointsInCircle;
int m_blocks, m_threads;
int m_blocks, m_threads;
// Total size of allocations created by cuMemMap Apis. This size is the sum of
// sizes of m_xyVector and m_pointsInsideCircle buffers.
size_t m_totalAllocationSize;
// Total size of allocations created by cuMemMap Apis. This size is the sum of
// sizes of m_xyVector and m_pointsInsideCircle buffers.
size_t m_totalAllocationSize;
// Shareable Handles(a file descriptor on Linux and NT Handle on Windows),
// used for sharing cuda
// allocated memory with Vulkan
ShareableHandle m_posShareableHandle, m_inCircleShareableHandle;
// Shareable Handles(a file descriptor on Linux and NT Handle on Windows),
// used for sharing cuda
// allocated memory with Vulkan
ShareableHandle m_posShareableHandle, m_inCircleShareableHandle;
// Cuda Device corresponding to the Vulkan Physical device
int m_cudaDevice;
// Cuda Device corresponding to the Vulkan Physical device
int m_cudaDevice;
// Track and accumulate total points that have been simulated since start of
// the sample. The idea is to get a closer approximation to PI with time.
size_t m_totalPointsInsideCircle;
size_t m_totalPointsSimulated;
// Track and accumulate total points that have been simulated since start of
// the sample. The idea is to get a closer approximation to PI with time.
size_t m_totalPointsInsideCircle;
size_t m_totalPointsSimulated;
void setupSimulationAllocations();
void cleanupSimulationAllocations();
void getIdealExecutionConfiguration();
void setupSimulationAllocations();
void cleanupSimulationAllocations();
void getIdealExecutionConfiguration();
public:
MonteCarloPiSimulation(size_t num_points);
~MonteCarloPiSimulation();
void initSimulation(int cudaDevice, cudaStream_t stream = 0);
void stepSimulation(float time, cudaStream_t stream = 0);
static void computePiCallback(void *args);
public:
MonteCarloPiSimulation(size_t num_points);
~MonteCarloPiSimulation();
void initSimulation(int cudaDevice, cudaStream_t stream = 0);
void stepSimulation(float time, cudaStream_t stream = 0);
static void computePiCallback(void *args);
size_t getNumPoints() const { return m_numPoints; }
size_t getNumPoints() const { return m_numPoints; }
float getNumPointsInCircle() const { return *m_hostNumPointsInCircle; }
float getNumPointsInCircle() const { return *m_hostNumPointsInCircle; }
ShareableHandle &getPositionShareableHandle() { return m_posShareableHandle; }
ShareableHandle &getInCircleShareableHandle() {
return m_inCircleShareableHandle;
}
ShareableHandle &getPositionShareableHandle() { return m_posShareableHandle; }
ShareableHandle &getInCircleShareableHandle() { return m_inCircleShareableHandle; }
};
#endif // __PISIM_H__
#endif // __PISIM_H__

View File

@@ -37,4 +37,3 @@ Download and install the [CUDA Toolkit 12.5](https://developer.nvidia.com/cuda-d
Make sure the dependencies mentioned in [Dependencies]() section above are installed.
## References (for more details)

File diff suppressed because it is too large Load Diff

View File

@@ -34,127 +34,128 @@
#include <vulkan/vulkan.h>
#ifdef _WIN64
#define NOMINMAX
#include <windows.h>
#include <vulkan/vulkan_win32.h>
#include <windows.h>
#endif /* _WIN64 */
struct GLFWwindow;
class VulkanBaseApp {
public:
VulkanBaseApp(const std::string& appName, bool enableValidation = false);
static VkExternalSemaphoreHandleTypeFlagBits getDefaultSemaphoreHandleType();
static VkExternalMemoryHandleTypeFlagBits getDefaultMemHandleType();
virtual ~VulkanBaseApp();
void init();
void* getMemHandle(VkDeviceMemory memory,
VkExternalMemoryHandleTypeFlagBits handleType);
void* getSemaphoreHandle(VkSemaphore semaphore,
VkExternalSemaphoreHandleTypeFlagBits handleType);
bool isVkPhysicalDeviceUuid(void* Uuid);
void createExternalSemaphore(
VkSemaphore& semaphore, VkExternalSemaphoreHandleTypeFlagBits handleType);
void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage,
VkMemoryPropertyFlags properties, VkBuffer& buffer,
VkDeviceMemory& bufferMemory);
void createExternalBuffer(VkDeviceSize size, VkBufferUsageFlags usage,
VkMemoryPropertyFlags properties,
VkExternalMemoryHandleTypeFlagsKHR extMemHandleType,
VkBuffer& buffer, VkDeviceMemory& bufferMemory);
void importExternalBuffer(void* handle,
VkExternalMemoryHandleTypeFlagBits handleType,
size_t size, VkBufferUsageFlags usage,
VkMemoryPropertyFlags properties, VkBuffer& buffer,
VkDeviceMemory& memory);
void copyBuffer(VkBuffer dst, VkBuffer src, VkDeviceSize size);
VkCommandBuffer beginSingleTimeCommands();
void endSingleTimeCommands(VkCommandBuffer commandBuffer);
void mainLoop();
class VulkanBaseApp
{
public:
VulkanBaseApp(const std::string &appName, bool enableValidation = false);
static VkExternalSemaphoreHandleTypeFlagBits getDefaultSemaphoreHandleType();
static VkExternalMemoryHandleTypeFlagBits getDefaultMemHandleType();
virtual ~VulkanBaseApp();
void init();
void *getMemHandle(VkDeviceMemory memory, VkExternalMemoryHandleTypeFlagBits handleType);
void *getSemaphoreHandle(VkSemaphore semaphore, VkExternalSemaphoreHandleTypeFlagBits handleType);
bool isVkPhysicalDeviceUuid(void *Uuid);
void createExternalSemaphore(VkSemaphore &semaphore, VkExternalSemaphoreHandleTypeFlagBits handleType);
void createBuffer(VkDeviceSize size,
VkBufferUsageFlags usage,
VkMemoryPropertyFlags properties,
VkBuffer &buffer,
VkDeviceMemory &bufferMemory);
void createExternalBuffer(VkDeviceSize size,
VkBufferUsageFlags usage,
VkMemoryPropertyFlags properties,
VkExternalMemoryHandleTypeFlagsKHR extMemHandleType,
VkBuffer &buffer,
VkDeviceMemory &bufferMemory);
void importExternalBuffer(void *handle,
VkExternalMemoryHandleTypeFlagBits handleType,
size_t size,
VkBufferUsageFlags usage,
VkMemoryPropertyFlags properties,
VkBuffer &buffer,
VkDeviceMemory &memory);
void copyBuffer(VkBuffer dst, VkBuffer src, VkDeviceSize size);
VkCommandBuffer beginSingleTimeCommands();
void endSingleTimeCommands(VkCommandBuffer commandBuffer);
void mainLoop();
protected:
const std::string m_appName;
const bool m_enableValidation;
VkInstance m_instance;
VkDebugUtilsMessengerEXT m_debugMessenger;
VkSurfaceKHR m_surface;
VkPhysicalDevice m_physicalDevice;
uint8_t m_deviceUUID[VK_UUID_SIZE];
VkDevice m_device;
VkQueue m_graphicsQueue;
VkQueue m_presentQueue;
VkSwapchainKHR m_swapChain;
std::vector<VkImage> m_swapChainImages;
VkFormat m_swapChainFormat;
VkExtent2D m_swapChainExtent;
std::vector<VkImageView> m_swapChainImageViews;
std::vector<std::pair<VkShaderStageFlagBits, std::string> > m_shaderFiles;
VkRenderPass m_renderPass;
VkPipelineLayout m_pipelineLayout;
VkPipeline m_graphicsPipeline;
std::vector<VkFramebuffer> m_swapChainFramebuffers;
VkCommandPool m_commandPool;
std::vector<VkCommandBuffer> m_commandBuffers;
std::vector<VkSemaphore> m_imageAvailableSemaphores;
std::vector<VkSemaphore> m_renderFinishedSemaphores;
std::vector<VkFence> m_inFlightFences;
std::vector<VkBuffer> m_uniformBuffers;
std::vector<VkDeviceMemory> m_uniformMemory;
VkDescriptorSetLayout m_descriptorSetLayout;
VkDescriptorPool m_descriptorPool;
std::vector<VkDescriptorSet> m_descriptorSets;
protected:
const std::string m_appName;
const bool m_enableValidation;
VkInstance m_instance;
VkDebugUtilsMessengerEXT m_debugMessenger;
VkSurfaceKHR m_surface;
VkPhysicalDevice m_physicalDevice;
uint8_t m_deviceUUID[VK_UUID_SIZE];
VkDevice m_device;
VkQueue m_graphicsQueue;
VkQueue m_presentQueue;
VkSwapchainKHR m_swapChain;
std::vector<VkImage> m_swapChainImages;
VkFormat m_swapChainFormat;
VkExtent2D m_swapChainExtent;
std::vector<VkImageView> m_swapChainImageViews;
std::vector<std::pair<VkShaderStageFlagBits, std::string>> m_shaderFiles;
VkRenderPass m_renderPass;
VkPipelineLayout m_pipelineLayout;
VkPipeline m_graphicsPipeline;
std::vector<VkFramebuffer> m_swapChainFramebuffers;
VkCommandPool m_commandPool;
std::vector<VkCommandBuffer> m_commandBuffers;
std::vector<VkSemaphore> m_imageAvailableSemaphores;
std::vector<VkSemaphore> m_renderFinishedSemaphores;
std::vector<VkFence> m_inFlightFences;
std::vector<VkBuffer> m_uniformBuffers;
std::vector<VkDeviceMemory> m_uniformMemory;
VkDescriptorSetLayout m_descriptorSetLayout;
VkDescriptorPool m_descriptorPool;
std::vector<VkDescriptorSet> m_descriptorSets;
VkImage m_depthImage;
VkDeviceMemory m_depthImageMemory;
VkImageView m_depthImageView;
size_t m_currentFrame;
bool m_framebufferResized;
VkImage m_depthImage;
VkDeviceMemory m_depthImageMemory;
VkImageView m_depthImageView;
size_t m_currentFrame;
bool m_framebufferResized;
virtual void initVulkanApp() {}
virtual void fillRenderingCommandBuffer(VkCommandBuffer& buffer) {}
virtual std::vector<const char*> getRequiredExtensions() const;
virtual std::vector<const char*> getRequiredDeviceExtensions() const;
virtual void getVertexDescriptions(
std::vector<VkVertexInputBindingDescription>& bindingDesc,
std::vector<VkVertexInputAttributeDescription>& attribDesc);
virtual void getAssemblyStateInfo(
VkPipelineInputAssemblyStateCreateInfo& info);
virtual void getWaitFrameSemaphores(
std::vector<VkSemaphore>& wait,
std::vector<VkPipelineStageFlags>& waitStages) const;
virtual void getSignalFrameSemaphores(std::vector<VkSemaphore>& signal) const;
virtual VkDeviceSize getUniformSize() const;
virtual void updateUniformBuffer(uint32_t imageIndex, size_t globalFrame);
virtual void drawFrame();
virtual void initVulkanApp() {}
virtual void fillRenderingCommandBuffer(VkCommandBuffer &buffer) {}
virtual std::vector<const char *> getRequiredExtensions() const;
virtual std::vector<const char *> getRequiredDeviceExtensions() const;
virtual void getVertexDescriptions(std::vector<VkVertexInputBindingDescription> &bindingDesc,
std::vector<VkVertexInputAttributeDescription> &attribDesc);
virtual void getAssemblyStateInfo(VkPipelineInputAssemblyStateCreateInfo &info);
virtual void getWaitFrameSemaphores(std::vector<VkSemaphore> &wait,
std::vector<VkPipelineStageFlags> &waitStages) const;
virtual void getSignalFrameSemaphores(std::vector<VkSemaphore> &signal) const;
virtual VkDeviceSize getUniformSize() const;
virtual void updateUniformBuffer(uint32_t imageIndex, size_t globalFrame);
virtual void drawFrame();
private:
GLFWwindow* m_window;
private:
GLFWwindow *m_window;
void initWindow();
void initVulkan();
void createInstance();
void createSurface();
void createDevice();
void createSwapChain();
void createImageViews();
void createRenderPass();
void createDescriptorSetLayout();
void createGraphicsPipeline();
void createFramebuffers();
void createCommandPool();
void createDepthResources();
void createUniformBuffers();
void createDescriptorPool();
void createDescriptorSets();
void createCommandBuffers();
void createSyncObjects();
void initWindow();
void initVulkan();
void createInstance();
void createSurface();
void createDevice();
void createSwapChain();
void createImageViews();
void createRenderPass();
void createDescriptorSetLayout();
void createGraphicsPipeline();
void createFramebuffers();
void createCommandPool();
void createDepthResources();
void createUniformBuffers();
void createDescriptorPool();
void createDescriptorSets();
void createCommandBuffers();
void createSyncObjects();
void cleanupSwapChain();
void recreateSwapChain();
void cleanupSwapChain();
void recreateSwapChain();
bool isSuitableDevice(VkPhysicalDevice dev) const;
static void resizeCallback(GLFWwindow* window, int width, int height);
bool isSuitableDevice(VkPhysicalDevice dev) const;
static void resizeCallback(GLFWwindow *window, int width, int height);
};
void readFile(std::istream& s, std::vector<char>& data);
void readFile(std::istream &s, std::vector<char> &data);
#endif /* __VULKANBASEAPP_H__ */

View File

@@ -30,53 +30,49 @@
#define __VKCUDA_H__
#include <cuda_runtime_api.h>
#include "cuda.h"
#define CUDA_DRIVER_API
#include <helper_cuda.h>
bool isDeviceCompatible(void *Uuid, size_t size) {
int cudaDevice = cudaInvalidDeviceId;
int deviceCount;
checkCudaErrors(cudaGetDeviceCount(&deviceCount));
bool isDeviceCompatible(void *Uuid, size_t size)
{
int cudaDevice = cudaInvalidDeviceId;
int deviceCount;
checkCudaErrors(cudaGetDeviceCount(&deviceCount));
for (int i = 0; i < deviceCount; ++i) {
cudaDeviceProp devProp = {};
checkCudaErrors(cudaGetDeviceProperties(&devProp, i));
if (!memcmp(&devProp.uuid, Uuid, size)) {
cudaDevice = i;
break;
for (int i = 0; i < deviceCount; ++i) {
cudaDeviceProp devProp = {};
checkCudaErrors(cudaGetDeviceProperties(&devProp, i));
if (!memcmp(&devProp.uuid, Uuid, size)) {
cudaDevice = i;
break;
}
}
if (cudaDevice == cudaInvalidDeviceId) {
return false;
}
}
if (cudaDevice == cudaInvalidDeviceId) {
return false;
}
int deviceSupportsHandle = 0;
int attributeVal = 0;
int deviceComputeMode = 0;
int deviceSupportsHandle = 0;
int attributeVal = 0;
int deviceComputeMode = 0;
checkCudaErrors(cuDeviceGetAttribute(
&deviceComputeMode, CU_DEVICE_ATTRIBUTE_COMPUTE_MODE, cudaDevice));
checkCudaErrors(cuDeviceGetAttribute(
&attributeVal, CU_DEVICE_ATTRIBUTE_VIRTUAL_ADDRESS_MANAGEMENT_SUPPORTED,
cudaDevice));
checkCudaErrors(cuDeviceGetAttribute(&deviceComputeMode, CU_DEVICE_ATTRIBUTE_COMPUTE_MODE, cudaDevice));
checkCudaErrors(
cuDeviceGetAttribute(&attributeVal, CU_DEVICE_ATTRIBUTE_VIRTUAL_ADDRESS_MANAGEMENT_SUPPORTED, cudaDevice));
#if defined(__linux__)
checkCudaErrors(cuDeviceGetAttribute(
&deviceSupportsHandle,
CU_DEVICE_ATTRIBUTE_HANDLE_TYPE_POSIX_FILE_DESCRIPTOR_SUPPORTED,
cudaDevice));
checkCudaErrors(cuDeviceGetAttribute(
&deviceSupportsHandle, CU_DEVICE_ATTRIBUTE_HANDLE_TYPE_POSIX_FILE_DESCRIPTOR_SUPPORTED, cudaDevice));
#else
checkCudaErrors(cuDeviceGetAttribute(
&deviceSupportsHandle,
CU_DEVICE_ATTRIBUTE_HANDLE_TYPE_WIN32_HANDLE_SUPPORTED, cudaDevice));
checkCudaErrors(cuDeviceGetAttribute(
&deviceSupportsHandle, CU_DEVICE_ATTRIBUTE_HANDLE_TYPE_WIN32_HANDLE_SUPPORTED, cudaDevice));
#endif
if ((deviceComputeMode != CU_COMPUTEMODE_DEFAULT) || !attributeVal ||
!deviceSupportsHandle) {
return false;
}
return true;
if ((deviceComputeMode != CU_COMPUTEMODE_DEFAULT) || !attributeVal || !deviceSupportsHandle) {
return false;
}
return true;
}
#endif // __VKCUDA_H__
#endif // __VKCUDA_H__

View File

@@ -32,20 +32,18 @@
* Vulkan.
*/
#include "VulkanBaseApp.h"
#include <iostream>
#include <iomanip>
#include <chrono>
#include <algorithm>
#include <chrono>
#include <cuda.h>
#include <iomanip>
#include <iostream>
#include "MonteCarloPi.h"
#include <helper_cuda.h>
#include <cuda.h>
#include "VulkanBaseApp.h"
#include "helper_cuda.h"
#include "helper_multiprocess.h"
//#define DEBUG
// #define DEBUG
#ifndef DEBUG
#define ENABLE_VALIDATION (false)
#else
@@ -56,297 +54,295 @@
std::string execution_path;
class VulkanCudaPi : public VulkanBaseApp {
typedef struct UniformBufferObject_st { float frame; } UniformBufferObject;
class VulkanCudaPi : public VulkanBaseApp
{
typedef struct UniformBufferObject_st
{
float frame;
} UniformBufferObject;
VkBuffer m_inCircleBuffer, m_xyPositionBuffer;
VkDeviceMemory m_inCircleMemory, m_xyPositionMemory;
VkSemaphore m_vkWaitSemaphore, m_vkSignalSemaphore;
MonteCarloPiSimulation m_sim;
UniformBufferObject m_ubo;
cudaStream_t m_stream;
cudaExternalSemaphore_t m_cudaWaitSemaphore, m_cudaSignalSemaphore;
using chrono_tp = std::chrono::time_point<std::chrono::high_resolution_clock>;
chrono_tp m_lastTime;
size_t m_lastFrame;
VkBuffer m_inCircleBuffer, m_xyPositionBuffer;
VkDeviceMemory m_inCircleMemory, m_xyPositionMemory;
VkSemaphore m_vkWaitSemaphore, m_vkSignalSemaphore;
MonteCarloPiSimulation m_sim;
UniformBufferObject m_ubo;
cudaStream_t m_stream;
cudaExternalSemaphore_t m_cudaWaitSemaphore, m_cudaSignalSemaphore;
using chrono_tp = std::chrono::time_point<std::chrono::high_resolution_clock>;
chrono_tp m_lastTime;
size_t m_lastFrame;
public:
VulkanCudaPi(size_t num_points)
: VulkanBaseApp("simpleVulkanMMAP", ENABLE_VALIDATION),
m_inCircleBuffer(VK_NULL_HANDLE),
m_xyPositionBuffer(VK_NULL_HANDLE),
m_inCircleMemory(VK_NULL_HANDLE),
m_xyPositionMemory(VK_NULL_HANDLE),
m_sim(num_points),
m_ubo(),
m_stream(0),
m_vkWaitSemaphore(VK_NULL_HANDLE),
m_vkSignalSemaphore(VK_NULL_HANDLE),
m_cudaWaitSemaphore(),
m_cudaSignalSemaphore(),
m_lastFrame(0) {
// Add our compiled vulkan shader files
char* vertex_shader_path =
sdkFindFilePath("vert.spv", execution_path.c_str());
char* fragment_shader_path =
sdkFindFilePath("frag.spv", execution_path.c_str());
m_shaderFiles.push_back(
std::make_pair(VK_SHADER_STAGE_VERTEX_BIT, vertex_shader_path));
m_shaderFiles.push_back(
std::make_pair(VK_SHADER_STAGE_FRAGMENT_BIT, fragment_shader_path));
}
~VulkanCudaPi() {
if (m_stream) {
// Make sure there's no pending work before we start tearing down
checkCudaErrors(cudaStreamSynchronize(m_stream));
checkCudaErrors(cudaStreamDestroy(m_stream));
public:
VulkanCudaPi(size_t num_points)
: VulkanBaseApp("simpleVulkanMMAP", ENABLE_VALIDATION)
, m_inCircleBuffer(VK_NULL_HANDLE)
, m_xyPositionBuffer(VK_NULL_HANDLE)
, m_inCircleMemory(VK_NULL_HANDLE)
, m_xyPositionMemory(VK_NULL_HANDLE)
, m_sim(num_points)
, m_ubo()
, m_stream(0)
, m_vkWaitSemaphore(VK_NULL_HANDLE)
, m_vkSignalSemaphore(VK_NULL_HANDLE)
, m_cudaWaitSemaphore()
, m_cudaSignalSemaphore()
, m_lastFrame(0)
{
// Add our compiled vulkan shader files
char *vertex_shader_path = sdkFindFilePath("vert.spv", execution_path.c_str());
char *fragment_shader_path = sdkFindFilePath("frag.spv", execution_path.c_str());
m_shaderFiles.push_back(std::make_pair(VK_SHADER_STAGE_VERTEX_BIT, vertex_shader_path));
m_shaderFiles.push_back(std::make_pair(VK_SHADER_STAGE_FRAGMENT_BIT, fragment_shader_path));
}
if (m_vkSignalSemaphore != VK_NULL_HANDLE) {
checkCudaErrors(cudaDestroyExternalSemaphore(m_cudaSignalSemaphore));
vkDestroySemaphore(m_device, m_vkSignalSemaphore, nullptr);
}
if (m_vkWaitSemaphore != VK_NULL_HANDLE) {
checkCudaErrors(cudaDestroyExternalSemaphore(m_cudaWaitSemaphore));
vkDestroySemaphore(m_device, m_vkWaitSemaphore, nullptr);
}
if (m_xyPositionBuffer != VK_NULL_HANDLE) {
vkDestroyBuffer(m_device, m_xyPositionBuffer, nullptr);
}
if (m_xyPositionMemory != VK_NULL_HANDLE) {
vkFreeMemory(m_device, m_xyPositionMemory, nullptr);
}
if (m_inCircleBuffer != VK_NULL_HANDLE) {
vkDestroyBuffer(m_device, m_inCircleBuffer, nullptr);
}
if (m_inCircleMemory != VK_NULL_HANDLE) {
vkFreeMemory(m_device, m_inCircleMemory, nullptr);
}
}
~VulkanCudaPi()
{
if (m_stream) {
// Make sure there's no pending work before we start tearing down
checkCudaErrors(cudaStreamSynchronize(m_stream));
checkCudaErrors(cudaStreamDestroy(m_stream));
}
void fillRenderingCommandBuffer(VkCommandBuffer& commandBuffer) {
VkBuffer vertexBuffers[] = {m_inCircleBuffer, m_xyPositionBuffer};
VkDeviceSize offsets[] = {0, 0};
vkCmdBindVertexBuffers(commandBuffer, 0,
sizeof(vertexBuffers) / sizeof(vertexBuffers[0]),
vertexBuffers, offsets);
vkCmdDraw(commandBuffer, (uint32_t)(m_sim.getNumPoints()), 1, 0, 0);
}
void getVertexDescriptions(
std::vector<VkVertexInputBindingDescription>& bindingDesc,
std::vector<VkVertexInputAttributeDescription>& attribDesc) {
bindingDesc.resize(2);
attribDesc.resize(2);
bindingDesc[0].binding = 0;
bindingDesc[0].stride = sizeof(float);
bindingDesc[0].inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
bindingDesc[1].binding = 1;
bindingDesc[1].stride = sizeof(vec2);
bindingDesc[1].inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
attribDesc[0].binding = 0;
attribDesc[0].location = 0;
attribDesc[0].format = VK_FORMAT_R32_SFLOAT;
attribDesc[0].offset = 0;
attribDesc[1].binding = 1;
attribDesc[1].location = 1;
attribDesc[1].format = VK_FORMAT_R32G32_SFLOAT;
attribDesc[1].offset = 0;
}
void getAssemblyStateInfo(VkPipelineInputAssemblyStateCreateInfo& info) {
info.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
info.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
info.primitiveRestartEnable = VK_FALSE;
}
void getWaitFrameSemaphores(
std::vector<VkSemaphore>& wait,
std::vector<VkPipelineStageFlags>& waitStages) const {
if (m_currentFrame != 0) {
// Have vulkan wait until cuda is done with the vertex buffer before
// rendering
// We don't do this on the first frame, as the wait semaphore hasn't been
// initialized yet
wait.push_back(m_vkWaitSemaphore);
// We want to wait until all the pipeline commands are complete before
// letting cuda work
waitStages.push_back(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT);
}
}
void getSignalFrameSemaphores(std::vector<VkSemaphore>& signal) const {
// Add this semaphore for vulkan to signal once the vertex buffer is ready
// for cuda to modify
signal.push_back(m_vkSignalSemaphore);
}
void initVulkanApp() {
const size_t nVerts = m_sim.getNumPoints();
// Obtain cuda device id for the device corresponding to the Vulkan physical
// device
int deviceCount;
int cudaDevice = cudaInvalidDeviceId;
checkCudaErrors(cudaGetDeviceCount(&deviceCount));
for (int dev = 0; dev < deviceCount; ++dev) {
cudaDeviceProp devProp = {};
checkCudaErrors(cudaGetDeviceProperties(&devProp, dev));
if (isVkPhysicalDeviceUuid(&devProp.uuid)) {
cudaDevice = dev;
break;
}
}
if (cudaDevice == cudaInvalidDeviceId) {
throw std::runtime_error("No Suitable device found!");
if (m_vkSignalSemaphore != VK_NULL_HANDLE) {
checkCudaErrors(cudaDestroyExternalSemaphore(m_cudaSignalSemaphore));
vkDestroySemaphore(m_device, m_vkSignalSemaphore, nullptr);
}
if (m_vkWaitSemaphore != VK_NULL_HANDLE) {
checkCudaErrors(cudaDestroyExternalSemaphore(m_cudaWaitSemaphore));
vkDestroySemaphore(m_device, m_vkWaitSemaphore, nullptr);
}
if (m_xyPositionBuffer != VK_NULL_HANDLE) {
vkDestroyBuffer(m_device, m_xyPositionBuffer, nullptr);
}
if (m_xyPositionMemory != VK_NULL_HANDLE) {
vkFreeMemory(m_device, m_xyPositionMemory, nullptr);
}
if (m_inCircleBuffer != VK_NULL_HANDLE) {
vkDestroyBuffer(m_device, m_inCircleBuffer, nullptr);
}
if (m_inCircleMemory != VK_NULL_HANDLE) {
vkFreeMemory(m_device, m_inCircleMemory, nullptr);
}
}
// On the corresponding cuda device, create the cuda stream we'll using
checkCudaErrors(cudaSetDevice(cudaDevice));
checkCudaErrors(
cudaStreamCreateWithFlags(&m_stream, cudaStreamNonBlocking));
m_sim.initSimulation(cudaDevice, m_stream);
importExternalBuffer(
(void*)(uintptr_t)m_sim.getPositionShareableHandle(),
getDefaultMemHandleType(), nVerts * sizeof(vec2),
VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, m_xyPositionBuffer,
m_xyPositionMemory);
importExternalBuffer(
(void*)(uintptr_t)m_sim.getInCircleShareableHandle(),
getDefaultMemHandleType(), nVerts * sizeof(float),
VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, m_inCircleBuffer,
m_inCircleMemory);
// Create the semaphore vulkan will signal when it's done with the vertex
// buffer
createExternalSemaphore(m_vkSignalSemaphore,
getDefaultSemaphoreHandleType());
// Create the semaphore vulkan will wait for before using the vertex buffer
createExternalSemaphore(m_vkWaitSemaphore, getDefaultSemaphoreHandleType());
// Import the semaphore cuda will use -- vulkan's signal will be cuda's wait
importCudaExternalSemaphore(m_cudaWaitSemaphore, m_vkSignalSemaphore,
getDefaultSemaphoreHandleType());
// Import the semaphore cuda will use -- cuda's signal will be vulkan's wait
importCudaExternalSemaphore(m_cudaSignalSemaphore, m_vkWaitSemaphore,
getDefaultSemaphoreHandleType());
}
void importCudaExternalSemaphore(
cudaExternalSemaphore_t& cudaSem, VkSemaphore& vkSem,
VkExternalSemaphoreHandleTypeFlagBits handleType) {
cudaExternalSemaphoreHandleDesc externalSemaphoreHandleDesc = {};
if (handleType & VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_BIT) {
externalSemaphoreHandleDesc.type =
cudaExternalSemaphoreHandleTypeOpaqueWin32;
} else if (handleType &
VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT) {
externalSemaphoreHandleDesc.type =
cudaExternalSemaphoreHandleTypeOpaqueWin32Kmt;
} else if (handleType & VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT) {
externalSemaphoreHandleDesc.type =
cudaExternalSemaphoreHandleTypeOpaqueFd;
} else {
throw std::runtime_error("Unknown handle type requested!");
void fillRenderingCommandBuffer(VkCommandBuffer &commandBuffer)
{
VkBuffer vertexBuffers[] = {m_inCircleBuffer, m_xyPositionBuffer};
VkDeviceSize offsets[] = {0, 0};
vkCmdBindVertexBuffers(
commandBuffer, 0, sizeof(vertexBuffers) / sizeof(vertexBuffers[0]), vertexBuffers, offsets);
vkCmdDraw(commandBuffer, (uint32_t)(m_sim.getNumPoints()), 1, 0, 0);
}
void getVertexDescriptions(std::vector<VkVertexInputBindingDescription> &bindingDesc,
std::vector<VkVertexInputAttributeDescription> &attribDesc)
{
bindingDesc.resize(2);
attribDesc.resize(2);
bindingDesc[0].binding = 0;
bindingDesc[0].stride = sizeof(float);
bindingDesc[0].inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
bindingDesc[1].binding = 1;
bindingDesc[1].stride = sizeof(vec2);
bindingDesc[1].inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
attribDesc[0].binding = 0;
attribDesc[0].location = 0;
attribDesc[0].format = VK_FORMAT_R32_SFLOAT;
attribDesc[0].offset = 0;
attribDesc[1].binding = 1;
attribDesc[1].location = 1;
attribDesc[1].format = VK_FORMAT_R32G32_SFLOAT;
attribDesc[1].offset = 0;
}
void getAssemblyStateInfo(VkPipelineInputAssemblyStateCreateInfo &info)
{
info.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
info.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
info.primitiveRestartEnable = VK_FALSE;
}
void getWaitFrameSemaphores(std::vector<VkSemaphore> &wait, std::vector<VkPipelineStageFlags> &waitStages) const
{
if (m_currentFrame != 0) {
// Have vulkan wait until cuda is done with the vertex buffer before
// rendering
// We don't do this on the first frame, as the wait semaphore hasn't been
// initialized yet
wait.push_back(m_vkWaitSemaphore);
// We want to wait until all the pipeline commands are complete before
// letting cuda work
waitStages.push_back(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT);
}
}
void getSignalFrameSemaphores(std::vector<VkSemaphore> &signal) const
{
// Add this semaphore for vulkan to signal once the vertex buffer is ready
// for cuda to modify
signal.push_back(m_vkSignalSemaphore);
}
void initVulkanApp()
{
const size_t nVerts = m_sim.getNumPoints();
// Obtain cuda device id for the device corresponding to the Vulkan physical
// device
int deviceCount;
int cudaDevice = cudaInvalidDeviceId;
checkCudaErrors(cudaGetDeviceCount(&deviceCount));
for (int dev = 0; dev < deviceCount; ++dev) {
cudaDeviceProp devProp = {};
checkCudaErrors(cudaGetDeviceProperties(&devProp, dev));
if (isVkPhysicalDeviceUuid(&devProp.uuid)) {
cudaDevice = dev;
break;
}
}
if (cudaDevice == cudaInvalidDeviceId) {
throw std::runtime_error("No Suitable device found!");
}
// On the corresponding cuda device, create the cuda stream we'll using
checkCudaErrors(cudaSetDevice(cudaDevice));
checkCudaErrors(cudaStreamCreateWithFlags(&m_stream, cudaStreamNonBlocking));
m_sim.initSimulation(cudaDevice, m_stream);
importExternalBuffer((void *)(uintptr_t)m_sim.getPositionShareableHandle(),
getDefaultMemHandleType(),
nVerts * sizeof(vec2),
VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
m_xyPositionBuffer,
m_xyPositionMemory);
importExternalBuffer((void *)(uintptr_t)m_sim.getInCircleShareableHandle(),
getDefaultMemHandleType(),
nVerts * sizeof(float),
VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
m_inCircleBuffer,
m_inCircleMemory);
// Create the semaphore vulkan will signal when it's done with the vertex
// buffer
createExternalSemaphore(m_vkSignalSemaphore, getDefaultSemaphoreHandleType());
// Create the semaphore vulkan will wait for before using the vertex buffer
createExternalSemaphore(m_vkWaitSemaphore, getDefaultSemaphoreHandleType());
// Import the semaphore cuda will use -- vulkan's signal will be cuda's wait
importCudaExternalSemaphore(m_cudaWaitSemaphore, m_vkSignalSemaphore, getDefaultSemaphoreHandleType());
// Import the semaphore cuda will use -- cuda's signal will be vulkan's wait
importCudaExternalSemaphore(m_cudaSignalSemaphore, m_vkWaitSemaphore, getDefaultSemaphoreHandleType());
}
void importCudaExternalSemaphore(cudaExternalSemaphore_t &cudaSem,
VkSemaphore &vkSem,
VkExternalSemaphoreHandleTypeFlagBits handleType)
{
cudaExternalSemaphoreHandleDesc externalSemaphoreHandleDesc = {};
if (handleType & VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_BIT) {
externalSemaphoreHandleDesc.type = cudaExternalSemaphoreHandleTypeOpaqueWin32;
}
else if (handleType & VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT) {
externalSemaphoreHandleDesc.type = cudaExternalSemaphoreHandleTypeOpaqueWin32Kmt;
}
else if (handleType & VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT) {
externalSemaphoreHandleDesc.type = cudaExternalSemaphoreHandleTypeOpaqueFd;
}
else {
throw std::runtime_error("Unknown handle type requested!");
}
#ifdef _WIN64
externalSemaphoreHandleDesc.handle.win32.handle =
(HANDLE)getSemaphoreHandle(vkSem, handleType);
externalSemaphoreHandleDesc.handle.win32.handle = (HANDLE)getSemaphoreHandle(vkSem, handleType);
#else
externalSemaphoreHandleDesc.handle.fd =
(int)(uintptr_t)getSemaphoreHandle(vkSem, handleType);
externalSemaphoreHandleDesc.handle.fd = (int)(uintptr_t)getSemaphoreHandle(vkSem, handleType);
#endif
externalSemaphoreHandleDesc.flags = 0;
externalSemaphoreHandleDesc.flags = 0;
checkCudaErrors(
cudaImportExternalSemaphore(&cudaSem, &externalSemaphoreHandleDesc));
}
VkDeviceSize getUniformSize() const { return sizeof(UniformBufferObject); }
void updateUniformBuffer(uint32_t imageIndex, size_t globalFrame) {
m_ubo.frame = (float)globalFrame;
void* data;
vkMapMemory(m_device, m_uniformMemory[imageIndex], 0, getUniformSize(), 0,
&data);
memcpy(data, &m_ubo, sizeof(m_ubo));
vkUnmapMemory(m_device, m_uniformMemory[imageIndex]);
}
std::vector<const char*> getRequiredExtensions() const {
std::vector<const char*> extensions;
extensions.push_back(VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME);
extensions.push_back(VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME);
extensions.push_back(VK_KHR_EXTERNAL_FENCE_CAPABILITIES_EXTENSION_NAME);
extensions.push_back(
VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
return extensions;
}
std::vector<const char*> getRequiredDeviceExtensions() const {
std::vector<const char*> extensions;
extensions.push_back(VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME);
extensions.push_back(VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME);
#ifdef _WIN64
extensions.push_back(VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME);
extensions.push_back(VK_KHR_EXTERNAL_SEMAPHORE_WIN32_EXTENSION_NAME);
#else
extensions.push_back(VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME);
extensions.push_back(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME);
#endif /* _WIN64 */
return extensions;
}
void drawFrame() {
static chrono_tp startTime = std::chrono::high_resolution_clock::now();
chrono_tp currentTime = std::chrono::high_resolution_clock::now();
float time = std::chrono::duration<float, std::chrono::seconds::period>(
currentTime - startTime)
.count();
if (m_currentFrame == 0) {
m_lastTime = startTime;
checkCudaErrors(cudaImportExternalSemaphore(&cudaSem, &externalSemaphoreHandleDesc));
}
cudaExternalSemaphoreWaitParams waitParams = {};
waitParams.flags = 0;
waitParams.params.fence.value = 0;
VkDeviceSize getUniformSize() const { return sizeof(UniformBufferObject); }
cudaExternalSemaphoreSignalParams signalParams = {};
signalParams.flags = 0;
signalParams.params.fence.value = 0;
void updateUniformBuffer(uint32_t imageIndex, size_t globalFrame)
{
m_ubo.frame = (float)globalFrame;
void *data;
vkMapMemory(m_device, m_uniformMemory[imageIndex], 0, getUniformSize(), 0, &data);
memcpy(data, &m_ubo, sizeof(m_ubo));
vkUnmapMemory(m_device, m_uniformMemory[imageIndex]);
}
// Have vulkan draw the current frame...
VulkanBaseApp::drawFrame();
// Wait for vulkan to complete it's work
checkCudaErrors(cudaWaitExternalSemaphoresAsync(&m_cudaWaitSemaphore,
&waitParams, 1, m_stream));
// Now step the simulation
m_sim.stepSimulation(time, m_stream);
std::vector<const char *> getRequiredExtensions() const
{
std::vector<const char *> extensions;
extensions.push_back(VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME);
extensions.push_back(VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME);
extensions.push_back(VK_KHR_EXTERNAL_FENCE_CAPABILITIES_EXTENSION_NAME);
extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
return extensions;
}
// Signal vulkan to continue with the updated buffers
checkCudaErrors(cudaSignalExternalSemaphoresAsync(
&m_cudaSignalSemaphore, &signalParams, 1, m_stream));
}
std::vector<const char *> getRequiredDeviceExtensions() const
{
std::vector<const char *> extensions;
extensions.push_back(VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME);
extensions.push_back(VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME);
#ifdef _WIN64
extensions.push_back(VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME);
extensions.push_back(VK_KHR_EXTERNAL_SEMAPHORE_WIN32_EXTENSION_NAME);
#else
extensions.push_back(VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME);
extensions.push_back(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME);
#endif /* _WIN64 */
return extensions;
}
void drawFrame()
{
static chrono_tp startTime = std::chrono::high_resolution_clock::now();
chrono_tp currentTime = std::chrono::high_resolution_clock::now();
float time = std::chrono::duration<float, std::chrono::seconds::period>(currentTime - startTime).count();
if (m_currentFrame == 0) {
m_lastTime = startTime;
}
cudaExternalSemaphoreWaitParams waitParams = {};
waitParams.flags = 0;
waitParams.params.fence.value = 0;
cudaExternalSemaphoreSignalParams signalParams = {};
signalParams.flags = 0;
signalParams.params.fence.value = 0;
// Have vulkan draw the current frame...
VulkanBaseApp::drawFrame();
// Wait for vulkan to complete it's work
checkCudaErrors(cudaWaitExternalSemaphoresAsync(&m_cudaWaitSemaphore, &waitParams, 1, m_stream));
// Now step the simulation
m_sim.stepSimulation(time, m_stream);
// Signal vulkan to continue with the updated buffers
checkCudaErrors(cudaSignalExternalSemaphoresAsync(&m_cudaSignalSemaphore, &signalParams, 1, m_stream));
}
};
int main(int argc, char** argv) {
execution_path = argv[0];
VulkanCudaPi app(NUM_SIMULATION_POINTS);
app.init();
app.mainLoop();
return 0;
int main(int argc, char **argv)
{
execution_path = argv[0];
VulkanCudaPi app(NUM_SIMULATION_POINTS);
app.init();
app.mainLoop();
return 0;
}