光線跟蹤的 GPU 程序解讀

  CUDA by example 中的第六章講解了在 GPU 上實現光線跟蹤的一個例子,旨在介紹常量內存constant memory)和事件,下面給出這個例子的詳細解讀(http://code2.us/2012/02/cuda_learning_11-constant_memory_and_events/)。

 
#include <stdio.h>
#include "common/cpu_bitmap.h"
 
//是否使用__constan__常量內存的開關
#define CONSTANT
 
#define INF 2e10f
#define rnd(x) (x*rand()/RAND_MAX)
#define SPHERES 200
#define DIM 800
 
//求結構體
struct Sphere {
float r, g, b; //球的顏色
float radius; //球的半徑
float x, y, z; //球心座標
 
//判斷從像素點(ox, oy)射出的射線是否與該球相交,並返回交點的z座標
__device__ float hit(float ox, float oy, float *n)
{
float dx = ox - x;
float dy = oy - y;
if (dx*dx + dy*dy < radius*radius)
{
float dz = sqrtf(radius*radius - dx*dx - dy*dy);
*n = dz/sqrtf(radius*radius);
return dz + z;
}
return -INF;
}
};
#ifdef CONSTANT
//__constant__ 常量內存
__constant__ Sphere s[SPHERES];
#else
Sphere *s;
#endif
 
#ifdef CONSTANT
//使用constant常量內存時,不能將其當作參數傳到global函數
__global__ void kernel(unsigned char * ptr)
#else
//普通全局變量必須用傳參的形式傳遞到global函數
__global__ void kernel(unsigned char * ptr, Sphere *s)
#endif
{
int x = threadIdx.x + blockIdx.x * blockDim.x;
int y = threadIdx.y + blockIdx.y * blockDim.y;
 
int offset = x + y * blockDim.x * gridDim.x;
float ox = (x - DIM/2);
float oy = (y - DIM/2);
 
float r=0,g=0,b=0;
//獲得最近的交點
float maxz = -INF;
for (int i=0; i<SPHERES; i++)
{
float n;
float t = s[i].hit(ox, oy, &n);
if (t>maxz)
{
float fscale = n;
r = s[i].r * fscale;
g = s[i].g * fscale;
b = s[i].b * fscale;
maxz = t;
}
}
 
ptr[offset*4 + 0] = (int)(r*255);
ptr[offset*4 + 1] = (int)(g*255);
ptr[offset*4 + 2] = (int)(b*255);
ptr[offset*4 + 3] = 255;
}
 
int main(void)
{
//使用cuda事件來測試性能
cudaEvent_t start, stop;
cudaEventCreate(&start);
cudaEventCreate(&stop);
cudaEventRecord(start, 0);
 
CPUBitmap bitmap(DIM, DIM);
unsigned char * dev_bitmap;
 
cudaMalloc((void**) &dev_bitmap, bitmap.image_size());
 
#ifdef CONSTANT
// __constant__常量內存不需要動態分配內存
#else
// 在GPU設備上分配內存給球數組
cudaMalloc((void**) &s, sizeof(Sphere) * SPHERES);
#endif
 
// 在CPU上生成求數據數據
Sphere *temp_s = (Sphere *)malloc( sizeof(Sphere) * SPHERES);
for (int i=0; i<SPHERES; i++)
{
temp_s[i].r = rnd(1.0f);
temp_s[i].g = rnd(1.0f);
temp_s[i].b = rnd(1.0f);
 
temp_s[i].x = rnd(1000.f) - 500;
temp_s[i].y = rnd(1000.f) - 500;
temp_s[i].z = rnd(1000.f) - 500;
 
temp_s[i].radius = rnd(100.f) + 20;
}
 
#ifdef CONSTANT
//從CPU拷貝到__constant__常量內存
cudaMemcpyToSymbol(s, temp_s, sizeof(Sphere) * SPHERES);
#else
//從CPU拷貝到GPU
cudaMemcpy(s, temp_s, sizeof(Sphere) * SPHERES, cudaMemcpyHostToDevice);
#endif
 
free(temp_s);
 
dim3 grids(DIM/16, DIM/16);
dim3 threads(16, 16);
 
#ifdef CONSTANT
kernel<<<grids, threads>>>(dev_bitmap);
#else
kernel<<<grids, threads>>>(dev_bitmap, s);
#endif
 
cudaMemcpy(bitmap.get_ptr(), dev_bitmap, bitmap.image_size(), cudaMemcpyDeviceToHost);
 
cudaEventRecord(stop, 0);
//事件同步
cudaEventSynchronize(stop);
 
float elapseTime;
cudaEventElapsedTime(&elapseTime, start, stop);
printf("Time to generate: %3.1f ms\n", elapseTime);
cudaEventDestroy(start);
cudaEventDestroy(stop);
 
bitmap.display_and_exit();
 
cudaFree(dev_bitmap);
#ifdef CONSTANT
// __constant__ 常量內存不需要free
#else
cudaFree(s);
#endif
 
return 1;
}

與標準的全局常量內存相比,常量內存存在着一些限制,但在某些情況中,實用常量內存將提升應用程序的性能。
特別是,當線程束中的所有線程都訪問相同的只讀數據時,將獲得額外的性能提升。在這種數據訪問模式中實用常量
內存可以節約內存帶寬,不僅是因爲這種模式可以將讀取操作在半線程束中廣播,而且還因爲在芯片上包含了常量內存
緩存。在許多算法中,內存帶寬都是一種瓶頸,因此採用一些機制來改善這種情況是非常有用的。

知識點:
1. 使用__constant__修飾符來聲明變量爲常量內存;
2. 常量內存爲靜態分配空間,所以不需要調用 cudaMalloc(), cudaFree();
3. CUDA 中的時間本質上是一個 GPU 時間戳,這個時間戳是在用戶指定的時間點上記錄的。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章