Matrix Convolution CUDA

A high-performance implementation of 2D matrix convolution optimized for NVIDIA GPUs. Designed to handle 32-bit ARGB image data with simultaneous 4-channel processing.


Problem Statement

Standard image processing often handles color channels sequentially, which is inefficient. The challenge is to process packed 32-bit integers (ARGB) efficiently in a single GPU pass while maintaining high occupancy across CUDA cores.

Hardware Requirements: NVIDIA GPU (Compute 3.0+), CUDA Toolkit 12.1+, and Windows 10/11.

Solution Design

The library utilizes a specialized kernel that bit-shifts integer pixels into independent color components. The dimensions handle "Valid" padding, where the output size is:

  • Width: WinKw+1W_{in} - K_w + 1
  • Height: HinKh+1H_{in} - K_h + 1
Output(x,y)=i=0kh1j=0kw1Input(x+j,y+i)Kernel(j,i)Output(x,y) = \sum_{i=0}^{k_h-1} \sum_{j=0}^{k_w-1} Input(x+j, y+i) \cdot Kernel(j,i)

Solution Implementation

Pixel Processing

To maximize throughput, the kernel extracts 8-bit channels using bitwise operations, applies weights, and clamps the final values to the [0,255][0, 255] range.

Blue:

pixelValue>>24pixelValue >> 24

Green:

(pixelValue<<8)>>24(pixelValue << 8) >> 24

Red:

(pixelValue<<16)>>24(pixelValue << 16) >> 24

Alpha:

(pixelValue<<24)>>24(pixelValue << 24) >> 24

Memory Lifecycle

  1. Allocation: GPU buffers via cudaMalloc.
  2. Transfer: HtoD copy of image and kernel data.
  3. Sync: cudaDeviceSynchronize ensures host safety.
  4. Cleanup: cudaFree prevents memory leaks.

Solution Analysis

The primary performance gain in this implementation comes from Global Memory Coalescing. By aligning thread IDs with pixel indices, the hardware can combine multiple memory requests into a single 128-byte transaction.

Throughput & Efficiency

Processing 32-bit ARGB integers allows the GPU to utilize its full bus width. While the current approach uses Bitwise Extraction, future iterations could utilize uchar4 vector types to leverage hardware-level data alignment for even higher bandwidth.

Compute Occupancy

By setting the block size to 16x16 (256 threads), the kernel maintains high occupancy on modern Ampere and Ada Lovelace architectures. This configuration balances the register pressure from the bit-shifting logic against the available warp schedulers.


Conclusion

This CUDA implementation successfully offloads the computationally expensive O(N2K2)O(N^2 \cdot K^2) convolution operation to the GPU. By treating the 32-bit pixel as a single unit and decomposing it within the kernel, we minimize the overhead of multiple memory passes.

The project highlights the effectiveness of GPGPU programming for real-time image manipulation, proving that even simple bitwise-parallelism can outperform highly optimized CPU multi-threading for large-scale matrix operations.


Source Code

The full repository, including the C# frontend and the CUDA C++ kernels, is available on GitHub.