- 1. Overview and Problem Formulation
- 2. System Architecture
- 3. CUDA Kernel Implementations and Algorithmic Strategies
- 4. CUDA-OpenGL Interoperability
- 5. Verification and Validation Engine
- 6. Compilation, Dependencies, and Configuration
- 7. Visualization Demo
- 8. Authors
- 9. License
The classical
This repository implements an
- A tiled 1D parallel formulation leveraging on-chip shared memory to minimize global memory transactions.
- A 2D computational grid decomposition that maps pairwise interactions across an
$N \times N$ matrix, paired with intra-block tree reductions and inter-block global accumulation passes. - An arithmetic optimization employing First Add During Load (FADL) to halve thread requirements during reduction stages.
- An empirical evaluation of memory alignment constraints comparing 128-bit
float4vector types against unpadded 96-bitfloat3structures. - A zero-copy visualization pipeline coupling CUDA compute kernels directly to an OpenGL rendering context via CUDA-OpenGL Graphics Interoperability.
Given a system of
where
As the spatial separation EPS2 in constants.h) is introduced into the denominator:
The implementation in kernel.cu computes this interaction within device functions bodyInteractions and bodyInteractions_float3:
__device__ float4 bodyInteractions(float4 bi, float4 bj, float4 ai) {
float3 r_ij;
r_ij.x = bj.x - bi.x;
r_ij.y = bj.y - bi.y;
r_ij.z = bj.z - bi.z;
float distSqrt = r_ij.x * r_ij.x + r_ij.y * r_ij.y + r_ij.z * r_ij.z + EPS2;
float invDenom = 1.0f / sqrtf(distSqrt * distSqrt * distSqrt);
float factor = bj.w * invDenom;
ai.x += r_ij.x * factor;
ai.y += r_ij.y * factor;
ai.z += r_ij.z * factor;
return ai;
}Self-interaction (
Numerical updates are propagated across discrete time increments DT in constants.h) using a staggered leapfrog integration scheme:
- Velocity half-step increment:
- Position full-step update:
In each iteration, the net gravitational acceleration
The application architecture isolates numerical kernels, state storage, visual rendering, and verification logic into dedicated modular units:
+-----------------------------------------------------------------------------+
| main.cpp |
| Host Orchestration, CLI Setup, Memory Allocation |
+----------------------+------------------------------+-----------------------+
| |
+----------------v---------------+ +-----------v------------+
| Compute & Kernel Subsystem | | Validation Engine |
| kernel.cu / kernel.cuh | | validation.cpp / .h |
| - Tiled All-Pairs Kernel | | - OpenMP CPU Baseline |
| - 2D Reduction Kernel | | - Error Diagnostics |
| - FADL Optimization Variants | +------------------------+
| - Inter-Block Reductions |
+----------------+---------------+
|
+----------------v-----------------------------+
| CUDA-OpenGL Interoperability Bridge |
| simulation_visualization.cpp / .h |
| - Direct VBO Memory Sharing |
| - GLSL Mass-Gradient Shaders |
+----------------------------------------------+
-
main.cpp: Entry point managing command-line input parsing, pinned host memory allocation viacudaMallocHost, device memory allocations viacudaMalloc, hardware property queries viaprint_device_prop, and simulation loop routing. -
kernel.cu/kernel.cuh: Device kernel implementations covering tiled shared-memory simulation, 2D matrix reduction, FADL optimizations, and second-pass inter-block global accumulation. -
simulation_visualization.cpp/simulation_visualization.h: OpenGL Core Profile initialization via GLFW and GLEW, dynamic shader compilation, buffer management, andcudaGraphicsResourceregistration. -
validation.cpp/validation.h: OpenMP multi-threaded host baseline calculating reference$N$ -body trajectories and evaluating numerical divergence. -
utils.cpp/utils.h: Memory initialization routines, uniform random state generation across physical domain bounds, and device query utilities. -
constants.h: Global compile-time constants defining body counts, spatial volume boundaries, mass ranges, integration step parameters, and feature toggles.
Particle spatial coordinates and masses are packed into single-precision 4-component vectors (float4):
-
float4.x,float4.y,float4.z: Spatial position coordinates$\mathbf{x} = (x, y, z)$ . -
float4.w: Particle mass$m$ .
Vector alignment characteristics:
- 128-bit Memory Transactions: CUDA devices service global memory requests in 32-byte, 64-byte, or 128-byte aligned transactions. The CUDA compiler enforces 16-byte alignment (
__align__(16)) forfloat4. Each load offloat4retrieves a particle's full position and mass in a single 128-bit instruction, achieving coalesced memory access across warps. float3Alternative: A separate kernel variant utilizesfloat3(12 bytes) for accelerations, velocities, and reduction matrices to eliminate the 4-byte padding overhead per particle. This introduces non-power-of-two memory access patterns that trade memory bus utilization against memory transaction alignment.
[Host Startup: main.cpp]
|
+--> Allocate Pinned Memory (cudaMallocHost for bodies, vel, accel)
+--> Initialize Coordinates & Mass in Domain [-MAX, MAX] (utils.cpp)
+--> Query Device Capabilities (warpSize, sharedMemPerBlock)
|
v
[Interactive CLI Configuration]
|
+---> askForKernelType(): Basic All-Pairs (0) vs. Reduction Kernel (1)
+---> askForVisualization(): Enable OpenGL Viewport (y/n)
+---> askForStepsNumber(): Total Discrete Simulation Steps
|
v
[Execution Branch Dispatch]
|
+---> Headless GPU Mode (enableVisualization = false)
| - Allocate d_bodies on Device
| - Copy Initial Host State to Device (cudaMemcpy)
| - Dispatch simulationLoopNoVisual -> simulate (kernel.cu)
| - Free Allocated Device Buffers
|
+---> Visual Interop Mode (enableVisualization = true)
- Initialize GLFW Window & OpenGL 3.3 Context
- Allocate and Register VBO (cudaGraphicsGLRegisterBuffer)
- Enter Render Loop:
* Map VBO (cudaGraphicsMapResources)
* Retrieve d_bodies Device Pointer
* Dispatch Kernel (Update VBO Coordinates Directly)
* Synchronize Device (cudaDeviceSynchronize)
* Unmap VBO (cudaGraphicsUnmapResources)
* Render Body Points (renderBodies via GL_POINTS)
* Swap Buffers & Poll GLFW Events
The tiled algorithm implemented in kernel decomposes the all-pairs computation into discrete tiles loaded into on-chip shared memory, decoupling global memory latency from arithmetic execution.
For
- Grid dimension:
blocksPerGrid = (N + threadsPerBlock - 1) / threadsPerBlock. - Block dimension:
threadsPerBlock = 32. - Tile width:
$W = \text{blockDim.x} \cdot \text{tileWidthFactor}$ . - Total tiles:
$K = \lceil N / W \rceil$ .
Each thread
Instead of streaming extern __shared__ __align__(16) float4 shBodies[].
__global__ void kernel(float4* globalX, float4* globalA, float4* globalV, int N, int tileWidthFactor) {
extern __shared__ __align__(16) float4 shBodies[];
float4 myBody, myNewBody;
float4 myNewVel;
float4 myNewAccel = { 0.0f, 0.0f, 0.0f, 0.0f };
int tileWidth, globalIdx, sharedIdx;
int tid = blockIdx.x * blockDim.x + threadIdx.x;
if (tid >= N) return;
myNewBody = globalX[tid];
myNewVel = globalV[tid];
tileWidth = blockDim.x * tileWidthFactor;
for (int i = 0, int tile = 0; i < N; i += tileWidth, tile++) {
for (int j = 0; j < tileWidthFactor; j++) {
sharedIdx = blockDim.x * j + threadIdx.x;
globalIdx = tile * tileWidth + blockDim.x * j + threadIdx.x;
if (globalIdx < N) {
shBodies[sharedIdx] = globalX[globalIdx];
}
}
__syncthreads();
myNewAccel = tile_interaction(myNewBody, myNewAccel, tile * tileWidth, tileWidthFactor, N);
__syncthreads();
}
myNewVel.x += 0.5f * DT * myNewAccel.x;
myNewVel.y += 0.5f * DT * myNewAccel.y;
myNewVel.z += 0.5f * DT * myNewAccel.z;
myNewBody.x += DT * myNewVel.x;
myNewBody.y += DT * myNewVel.y;
myNewBody.z += DT * myNewVel.z;
globalX[tid] = myNewBody;
globalV[tid] = myNewVel;
globalA[tid] = myNewAccel;
}Two __syncthreads() barriers are enforced per tile:
- The first barrier ensures all threads in the block have completed copying their respective particle data from global memory into
shBodiesbefore any force calculations commence. - The second barrier prevents faster threads from looping back and overwriting
shBodieswith data from the subsequent tile while other threads are still computing interactions from the current tile.
Device function tile_interaction applies #pragma unroll to unroll the inner interaction loop:
__device__ float4 tile_interaction(float4 myBody, float4 accel, int tileFirstIdx, int tileWidthFactor, int N) {
extern __shared__ __align__(16) float4 shBodies[];
#pragma unroll
for (int i = 0; i < blockDim.x * tileWidthFactor && i < N - tileFirstIdx; i++) {
accel = bodyInteractions(myBody, shBodies[i], accel);
}
return accel;
}Unrolling the inner loop reduces branch overhead, enables the instruction scheduler to hide floating-point pipeline latencies, and maximizes register reuse for target particle coordinates.
To exploit fine-grained concurrency across both dimensions of the kernel_reduction maps each pairwise interaction directly to a thread within a two-dimensional grid:
- Thread coordinates:
-
tidX = blockIdx.x * blockDim.x + threadIdx.x: Target interacting body$j$ . -
tidY = blockIdx.y * blockDim.y + threadIdx.y: Base body$i$ accumulating acceleration.
-
- Grid configuration:
-
dim3 blockDim(threadsPerBlock, threadsPerBlock)($32 \times 32$ , block size$B = 32$ ). -
dim3 gridDim(blocksPerGrid, blocksPerGrid)($\lceil N / B \rceil \times \lceil N / B \rceil$ ).
-
Within each block
and stages the result into shared memory index sid = threadIdx.y * blockDim.x + threadIdx.x.
The block performs an in-place parallel tree reduction along the horizontal dimension (
The implementation unrolls the power-of-two reduction tree:
// Explicit unrolled reduction for 32-thread block width
if (tid < 16) {
shMem[sid].x += shMem[sid + 16].x;
shMem[sid].y += shMem[sid + 16].y;
shMem[sid].z += shMem[sid + 16].z;
}
__syncthreads();
if (tid < 8) {
shMem[sid].x += shMem[sid + 8].x;
shMem[sid].y += shMem[sid + 8].y;
shMem[sid].z += shMem[sid + 8].z;
}
__syncthreads();
if (tid < 4) {
shMem[sid].x += shMem[sid + 4].x;
shMem[sid].y += shMem[sid + 4].y;
shMem[sid].z += shMem[sid + 4].z;
}
__syncthreads();
if (tid < 2) {
shMem[sid].x += shMem[sid + 2].x;
shMem[sid].y += shMem[sid + 2].y;
shMem[sid].z += shMem[sid + 2].z;
}
__syncthreads();
if (tid < 1) {
shMem[sid].x += shMem[sid + 1].x;
shMem[sid].y += shMem[sid + 1].y;
shMem[sid].z += shMem[sid + 1].z;
}
__syncthreads();When reduction completes, thread threadIdx.x == 0 writes
if (threadIdx.x == 0) {
reduceMatrix[tidY * gridDim.x + blockIdx.x] = shMem[sid];
}The 2D reduction kernel decomposes summation across thread blocks in the inter_block_reduction is dispatched as a second-pass 1D kernel:
- Grid configuration:
blocksPerGrid = (N + threadsPerBlock - 1) / threadsPerBlock,threadsPerBlock = 32. - Each thread processes one body
$i$ (tidY), summing over all block contributions$B_x \in {0, 1, \dots, M - 1}$ where$M = \text{gridDim.x}$ :
__global__ void inter_block_reduction(float4* globalX, float4* globalA, float4* globalV, float4* reduceMatrix, int N, int numBlocks) {
int tidY = blockIdx.x * blockDim.x + threadIdx.x;
if (tidY >= N) return;
float4 myNewAccel = { 0.0f, 0.0f, 0.0f, 0.0f };
float4 myNewVel = globalV[tidY];
float4 baseBody = globalX[tidY];
for (int block = 0; block < numBlocks; block++) {
int idx = tidY * numBlocks + block;
myNewAccel.x += reduceMatrix[idx].x;
myNewAccel.y += reduceMatrix[idx].y;
myNewAccel.z += reduceMatrix[idx].z;
}
myNewVel.x += 0.5f * DT * myNewAccel.x;
myNewVel.y += 0.5f * DT * myNewAccel.y;
myNewVel.z += 0.5f * DT * myNewAccel.z;
baseBody.x += DT * myNewVel.x;
baseBody.y += DT * myNewVel.y;
baseBody.z += DT * myNewVel.z;
globalX[tidY] = baseBody;
globalV[tidY] = myNewVel;
globalA[tidY] = myNewAccel;
}This two-stage reduction eliminates atomic operations, preserving deterministic floating-point accumulation order for a given grid configuration.
In standard tree reductions, half of the active threads become idle during the first reduction step (tid < 16), resulting in reduced warp execution efficiency. The FADL kernel (kernel_reduction_fadl) resolves this inefficiency by halving the thread block dimension along
- The block dimension is configured as
dim3 blockDim(threadsPerBlock / 2, threadsPerBlock). - Thread
$(x, y)$ computes interactions for two separate body offsets along the horizontal axis:- Target 1:
$j_1 = B_x \cdot (2 \cdot \text{blockDim.x}) + x$ - Target 2:
$j_2 = j_1 + \text{blockDim.x}$
- Target 1:
- The thread accumulates both forces into registers before storing the pre-summed value into shared memory:
myNewAccel1 = bodyInteractions(globalX[tidY], globalX[tidX], myNewAccel1);
myNewAccel2 = bodyInteractions(globalX[tidY], globalX[tidX + blockDim.x], myNewAccel2);
myNewAccel1x += myNewAccel2.x;
myNewAccel1y += myNewAccel2.y;
myNewAccel1z += myNewAccel2.z;
shMem[sid] = myNewAccel1;
__syncthreads();- The reduction tree bypasses the
tid < 16stage entirely and commences directly attid < 8:
if (tid < 8) {
myNewAccel1x += shMem[sid + 8].x;
myNewAccel1y += shMem[sid + 8].y;
myNewAccel1z += shMem[sid + 8].z;
shMem[sid].x = myNewAccel1x;
shMem[sid].y = myNewAccel1y;
shMem[sid].z = myNewAccel1z;
}
__syncthreads();- Halved Shared Memory Allocation: The allocated shared memory per block decreases from
sizeof(float4) * 32 * 32(16 KB) tosizeof(float4) * 16 * 32(8 KB), increasing theoretical occupancy per Streaming Multiprocessor. - Reduced Global Intermediate Buffer: The width of
reduceMatrixis halved (blocksPerGrid / 2), cutting global memory traffic in the subsequentinter_block_reductionkernel by 50%. fadl4Extension: FunctionsimulateVisual_embParallel_fadl4extends this optimization by assigning 4 interaction evaluations per thread, further reducing block width tothreadsPerBlock / 4and intermediate matrix storage toblocksPerGrid / 4.
The architecture includes dedicated vector implementations (kernel_reduction_float3, inter_block_reduction_float3, and simulateVisual_embParallel_float3) to evaluate the trade-offs between memory bus alignment and data packing.
| Metric / Parameter | float4 Implementation |
float3 Implementation |
|---|---|---|
| Vector Storage Size | 16 bytes (128 bits) | 12 bytes (96 bits) |
| Memory Alignment | 16-byte aligned (__align__(16)) |
4-byte aligned |
| Padding Overhead | 25% unused data in velocity/acceleration (.w) |
0% unused data |
| Memory Access Efficiency | Single 128-bit coalesced transaction | Unaligned access splits into 64-bit + 32-bit reads |
| Intermediate Matrix Size | sizeof(float4) * N * numBlocks |
sizeof(float3) * N * numBlocks |
| Register Pressure | Higher (4 floats tracked per accumulator) | Lower (3 floats tracked per accumulator) |
The float4 kernel maintains coalesced alignment across 128-bit hardware cache line segments, whereas the float3 kernel minimizes total byte allocation at the expense of non-coalesced memory transactions when accessing global arrays.
Standard decoupled GPU visualization pipelines transfer simulated particle states from device memory to host memory over the PCIe bus via cudaMemcpy(..., cudaMemcpyDeviceToHost), followed by a second upload from host memory to an OpenGL Vertex Buffer Object (VBO) via glBufferSubData. This introduces PCIe bandwidth overhead that limits simulation frame rates.
This project implements direct CUDA-OpenGL Graphics Interoperability using the CUDA Driver/Runtime Graphics API (cuda_gl_interop.h). The OpenGL VBO holding particle vertex attributes resides in GPU memory and is registered directly with the CUDA runtime. During execution, CUDA maps the VBO as native device memory, updates coordinates in place, and unmaps the buffer. No simulation data is transferred over the PCIe bus during rendering:
[Standard Decoupled Pipeline]
GPU VRAM (CUDA d_bodies) ---> PCIe Bus ---> Host RAM ---> PCIe Bus ---> GPU VRAM (OpenGL VBO)
(Dual PCIe Host-Device Transfers)
[Integrated Zero-Copy Pipeline]
GPU VRAM (OpenGL VBO) <=== Mapped Direct Device Pointer ===> CUDA Simulation Kernel
(Zero PCIe Transfers, Direct Device Memory Access)
The zero-copy pipeline operates through the following lifecycle managed in simulation_visualization.cpp and main.cpp:
-
VBO Initialization: An OpenGL buffer is allocated to hold
$N$ float4structures:glGenBuffers(1, &VBO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(float4) * numBodies, bodies, GL_DYNAMIC_DRAW);
-
CUDA Registration: The VBO handle is registered with CUDA as a graphics resource:
cudaGraphicsGLRegisterBuffer(graphicResource, *VBO, cudaGraphicsRegisterFlagsNone);
-
Per-Frame Simulation Cycle:
-
Resource Mapping: CUDA takes ownership of the buffer:
cudaGraphicsMapResources(1, &graphic_res, 0);
-
Device Pointer Retrieval: CUDA obtains a typed
float4*pointer addressing the underlying VBO storage:cudaGraphicsResourceGetMappedPointer((void**)&d_bodies, &size4, graphic_res);
-
Kernel Execution: Compute kernels (
kernel,kernel_reduction, orkernel_reduction_fadl) update particle coordinates directly insided_bodies. -
Resource Unmapping: CUDA releases ownership back to OpenGL:
cudaGraphicsUnmapResources(1, &graphic_res, 0);
-
Resource Mapping: CUDA takes ownership of the buffer:
-
Draw Dispatch: OpenGL executes a point-primitive draw call binding the updated VBO:
renderBodies(*VBO, numBodies);
Rendering uses OpenGL Core Profile programmable shaders. Particles are rendered as individual point primitives (GL_POINTS).
The vertex shader accepts vertex attributes from layout position 0 (aBody), which maps directly to the float4 coordinate vector
#version 330 core
layout (location = 0) in vec4 aBody;
uniform float minWeight;
uniform float maxWeight;
uniform float maxX;
uniform float maxY;
uniform float maxZ;
out float weight;
void main() {
weight = (aBody.w - minWeight) / (maxWeight - minWeight);
vec3 pos;
pos.x = 2.0 * (aBody.x - (-maxX)) / (maxX - (-maxX)) - 1.0;
pos.y = 2.0 * (aBody.y - (-maxY)) / (maxY - (-maxY)) - 1.0;
pos.z = 2.0 * (aBody.z - (-maxZ)) / (maxZ - (-maxZ)) - 1.0;
gl_Position = vec4(pos, 1.0);
}The shader executes two affine transformations:
-
Spatial Normalization: Linearly maps physical simulation coordinates
$(x, y, z) \in [-X_{\max}, X_{\max}] \times [-Y_{\max}, Y_{\max}] \times [-Z_{\max}, Z_{\max}]$ into Normalized Device Coordinates:
-
Mass Scalar Mapping: Normalizes particle mass
$m \in [m_{\min}, m_{\max}]$ to scalar$w \in [0.0, 1.0]$ :
The fragment shader evaluates linear interpolation (mix) across a two-color gradient:
where:
#version 330 core
in float weight;
out vec4 FragColor;
void main() {
vec3 blue = vec3(0.0, 1.0, 0.0); // Low-mass color (green channel)
vec3 red = vec3(1.0, 0.0, 0.0); // High-mass color (red channel)
vec3 gradientColor = mix(blue, red, weight);
FragColor = vec4(gradientColor, 1.0);
}Particles with lower mass render as green, transitioning linearly toward red as particle mass approaches MAX_W.
To verify the numerical correctness of the GPU kernels, validation.cpp implements a reference solver on the CPU. The routine CPU_compute parallelizes outer loop iterations across host CPU cores via OpenMP:
void CPU_compute(float4* gX, float4* gA, float4* gV, int N) {
#pragma omp parallel for
for (int i = 0; i < N; ++i) {
float4 body = gX[i];
float4 acceleration = { 0.0f, 0.0f, 0.0f, 0.0f };
for (int j = 0; j < N; ++j) {
if (i != j) {
acceleration = bodyInteractions_CPU(body, gX[j], acceleration);
}
}
gA[i] = acceleration;
gV[i].x += 0.5f * DT * acceleration.x;
gV[i].y += 0.5f * DT * acceleration.y;
gV[i].z += 0.5f * DT * acceleration.z;
gX[i].x += DT * gV[i].x;
gX[i].y += DT * gV[i].y;
gX[i].z += DT * gV[i].z;
}
}The mathematical computation matches bodyInteractions in device code, ensuring identity of softening parameters, acceleration denominators, and time-integration updates.
The verification function compareHostToDevice executes concurrent simulation runs on both device and host across 1000 discrete timesteps:
- Allocates pinned host memory buffers (
cudaMallocHost) for device-to-host transfers:dToH_bodies,dToH_velocity, anddToH_accelerations. - Advances the device state by invoking
simulate1000 times. - Advances the host reference state by invoking
CPU_compute1000 times. - Synchronizes the device via
cudaDeviceSynchronize(). - Transfers device state back to the host via
cudaMemcpy(..., cudaMemcpyDeviceToHost). - Compares the resulting positions, velocities, and accelerations across all
$N$ bodies.
Floating-point operations on CUDA GPUs utilize fused multiply-add (FMA) instructions and differ in accumulation ordering compared to CPU x86-64 execution. To verify numerical consistency between host and device without masking algorithmic errors, validation routines in validation.cpp compare device output state vectors
verify_still_bodies: Evaluates an adaptive relative tolerance threshold scaled to 1% of particle magnitude across each spatial dimension:
A diagnostic message is logged to standard output if:
-
verify_equality4/verify_equality3: Enforces an absolute tolerance threshold of$\delta = 0.01$ across all vector components:
All simulation parameters, algorithm configurations, and hardware directives are defined in constants.h:
| Macro / Constant | Default Value | Description |
|---|---|---|
REDUCTION |
1 |
Enables 2D grid reduction kernel path over basic tiled all-pairs |
FLOAT_3 |
0 |
Selects between float3 (1) and float4 (0) data layouts |
FADL |
1 |
Enables First Add During Load optimization in reduction kernels |
BLOCK_64 |
0 |
Configures shared memory bank size (cudaSharedMemBankSizeEightByte) |
THREADS_PER_BLOCK |
32 |
Number of threads per block dimension (Warp aligned) |
TILE_WIDTH_FACTOR |
1 |
Multiplier for shared memory tile width in tiled kernel |
N_BODIES |
8192 |
Total number of bodies simulated in the system |
MAX_X, MAX_Y, MAX_Z
|
100000.0f |
Spatial initialization boundary dimensions |
MIN_W, MAX_W
|
1e5f, 1e9f
|
Mass range for random particle generation |
MAX_VIEW[X,Y,Z] |
200000.0f |
OpenGL viewport normalization bounds |
EPS2 |
0.002f |
Plummer softening factor squared ( |
DT |
0.1f |
Simulation timestep duration ( |
The project is configured for 64-bit Windows environments targeting NVIDIA GPU architectures:
- Platform: Microsoft Windows 10 / 11 (x64)
- Compiler: Microsoft Visual C++ Compiler (MSVC v143, Visual Studio 2022)
- CUDA Toolkit: CUDA 11.8 (Compatible with CUDA 11.x / 12.x)
- GPU Target Architecture:
compute_86,sm_86(NVIDIA Ampere Architecture, e.g., RTX 30-series; configurable in project settings forsm_75,sm_89,sm_90) - Third-Party Libraries:
- GLEW (2.1.0): OpenGL Extension Wrangler library
- GLFW (3.3.8): Window management and context creation
- GLM: OpenGL Mathematics header-only template library
- OpenMP: Multi-core host CPU parallelization directives (
#pragma omp parallel forinvalidation.cpp, requires/openmpcompiler flag)
- Build Solution:
Open
TestProject.slninside Visual Studio 2022. Select configurationReleaseand platformx64. Build the solution to produceTestProject.exe. - Library Dependencies:
Ensure
glew32.dllresides in the executable output directory or in systemPATH. - Interactive CLI Prompt:
Launch the binary via terminal or Visual Studio debugger. The application prompts for operational modes:
What kernel do you wish to launch? 0. Basic all-pairs kernel 1. Reduction kernel Selection: 1 Do you want to visualize the simulation? (y/n): y How many steps do you wish to execute?: 5000
Interactive rendering of the
nbody_visual_demo.mp4
This project is licensed under the terms of the MIT License. Refer to the LICENSE file for complete details.
This repository incorporates third-party libraries which remain subject to their respective licensing agreements.