/*
-----------------------------------------------------------------------
Copyright: 2010-2021, imec Vision Lab, University of Antwerp
2014-2021, CWI, Amsterdam
Contact: astra@astra-toolbox.com
Website: http://www.astra-toolbox.com/
This file is part of the ASTRA Toolbox.
The ASTRA Toolbox is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
The ASTRA Toolbox is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with the ASTRA Toolbox. If not, see .
-----------------------------------------------------------------------
*/
#include "astra/cuda/2d/util.h"
#include "astra/Logging.h"
#include
#include
namespace astraCUDA {
bool copyVolumeToDevice(const float* in_data, unsigned int in_pitch,
const SDimensions& dims,
float* outD_data, unsigned int out_pitch)
{
size_t width = dims.iVolWidth;
size_t height = dims.iVolHeight;
// TODO: memory order
cudaError_t err;
err = cudaMemcpy2D(outD_data, sizeof(float)*out_pitch, in_data, sizeof(float)*in_pitch, sizeof(float)*width, height, cudaMemcpyHostToDevice);
ASTRA_CUDA_ASSERT(err);
assert(err == cudaSuccess);
return true;
}
bool copyVolumeFromDevice(float* out_data, unsigned int out_pitch,
const SDimensions& dims,
float* inD_data, unsigned int in_pitch)
{
size_t width = dims.iVolWidth;
size_t height = dims.iVolHeight;
// TODO: memory order
cudaError_t err = cudaMemcpy2D(out_data, sizeof(float)*out_pitch, inD_data, sizeof(float)*in_pitch, sizeof(float)*width, height, cudaMemcpyDeviceToHost);
ASTRA_CUDA_ASSERT(err);
return true;
}
bool copySinogramFromDevice(float* out_data, unsigned int out_pitch,
const SDimensions& dims,
float* inD_data, unsigned int in_pitch)
{
size_t width = dims.iProjDets;
size_t height = dims.iProjAngles;
// TODO: memory order
cudaError_t err = cudaMemcpy2D(out_data, sizeof(float)*out_pitch, inD_data, sizeof(float)*in_pitch, sizeof(float)*width, height, cudaMemcpyDeviceToHost);
ASTRA_CUDA_ASSERT(err);
return true;
}
bool copySinogramToDevice(const float* in_data, unsigned int in_pitch,
const SDimensions& dims,
float* outD_data, unsigned int out_pitch)
{
size_t width = dims.iProjDets;
size_t height = dims.iProjAngles;
// TODO: memory order
cudaError_t err;
err = cudaMemcpy2D(outD_data, sizeof(float)*out_pitch, in_data, sizeof(float)*in_pitch, sizeof(float)*width, height, cudaMemcpyHostToDevice);
ASTRA_CUDA_ASSERT(err);
return true;
}
bool allocateVolume(float*& ptr, unsigned int width, unsigned int height, unsigned int& pitch)
{
size_t p;
cudaError_t ret = cudaMallocPitch((void**)&ptr, &p, sizeof(float)*width, height);
if (ret != cudaSuccess) {
reportCudaError(ret);
ASTRA_ERROR("Failed to allocate %dx%d GPU buffer", width, height);
return false;
}
assert(p % sizeof(float) == 0);
pitch = p / sizeof(float);
return true;
}
void zeroVolume(float* data, unsigned int pitch, unsigned int width, unsigned int height)
{
cudaError_t err;
err = cudaMemset2D(data, sizeof(float)*pitch, 0, sizeof(float)*width, height);
ASTRA_CUDA_ASSERT(err);
}
bool allocateVolumeData(float*& D_ptr, unsigned int& pitch, const SDimensions& dims)
{
return allocateVolume(D_ptr, dims.iVolWidth, dims.iVolHeight, pitch);
}
bool allocateProjectionData(float*& D_ptr, unsigned int& pitch, const SDimensions& dims)
{
return allocateVolume(D_ptr, dims.iProjDets, dims.iProjAngles, pitch);
}
void zeroVolumeData(float* D_ptr, unsigned int pitch, const SDimensions& dims)
{
zeroVolume(D_ptr, pitch, dims.iVolWidth, dims.iVolHeight);
}
void zeroProjectionData(float* D_ptr, unsigned int pitch, const SDimensions& dims)
{
zeroVolume(D_ptr, pitch, dims.iProjDets, dims.iProjAngles);
}
void duplicateVolumeData(float* D_dst, float* D_src, unsigned int pitch, const SDimensions& dims)
{
cudaMemcpy2D(D_dst, sizeof(float)*pitch, D_src, sizeof(float)*pitch, sizeof(float)*dims.iVolWidth, dims.iVolHeight, cudaMemcpyDeviceToDevice);
}
void duplicateProjectionData(float* D_dst, float* D_src, unsigned int pitch, const SDimensions& dims)
{
cudaMemcpy2D(D_dst, sizeof(float)*pitch, D_src, sizeof(float)*pitch, sizeof(float)*dims.iProjDets, dims.iProjAngles, cudaMemcpyDeviceToDevice);
}
template
__global__ void reduce1D(float *g_idata, float *g_odata, unsigned int n)
{
extern __shared__ float sdata[];
unsigned int tid = threadIdx.x;
unsigned int i = blockIdx.x*(blockSize*2) + tid;
unsigned int gridSize = blockSize*gridDim.x;
sdata[tid] = 0;
while (i < n) { sdata[tid] += g_idata[i]; i += gridSize; }
__syncthreads();
if (blockSize >= 512) { if (tid < 256) { sdata[tid] += sdata[tid + 256]; } __syncthreads(); }
if (blockSize >= 256) { if (tid < 128) { sdata[tid] += sdata[tid + 128]; } __syncthreads(); }
if (blockSize >= 128) { if (tid < 64) { sdata[tid] += sdata[tid + 64]; } __syncthreads(); }
if (tid < 32) {
volatile float* smem = sdata;
if (blockSize >= 64) smem[tid] += smem[tid + 32];
if (blockSize >= 32) smem[tid] += smem[tid + 16];
if (blockSize >= 16) smem[tid] += smem[tid + 8];
if (blockSize >= 8) smem[tid] += smem[tid + 4];
if (blockSize >= 4) smem[tid] += smem[tid + 2];
if (blockSize >= 2) smem[tid] += smem[tid + 1];
}
if (tid == 0) g_odata[blockIdx.x] = sdata[0];
}
__global__ void reduce2D(float *g_idata, float *g_odata,
unsigned int pitch,
unsigned int nx, unsigned int ny)
{
extern __shared__ float sdata[];
const unsigned int tidx = threadIdx.x;
const unsigned int tidy = threadIdx.y;
const unsigned int tid = tidy * 16 + tidx;
unsigned int x = blockIdx.x*16 + tidx;
unsigned int y = blockIdx.y*16 + tidy;
sdata[tid] = 0;
if (x < nx) {
while (y < ny) {
sdata[tid] += (g_idata[pitch*y+x] * g_idata[pitch*y+x]);
y += 16 * gridDim.y;
}
}
__syncthreads();
if (tid < 128)
sdata[tid] += sdata[tid + 128];
__syncthreads();
if (tid < 64)
sdata[tid] += sdata[tid + 64];
__syncthreads();
if (tid < 32) { // 32 is warp size
volatile float* smem = sdata;
smem[tid] += smem[tid + 32];
smem[tid] += smem[tid + 16];
smem[tid] += smem[tid + 8];
smem[tid] += smem[tid + 4];
smem[tid] += smem[tid + 2];
smem[tid] += smem[tid + 1];
}
if (tid == 0)
g_odata[blockIdx.y * gridDim.x + blockIdx.x] = sdata[0];
}
float dotProduct2D(float* D_data, unsigned int pitch,
unsigned int width, unsigned int height)
{
unsigned int bx = (width + 15) / 16;
unsigned int by = (height + 127) / 128;
unsigned int shared_mem2 = sizeof(float) * 16 * 16;
dim3 dimBlock2(16, 16);
dim3 dimGrid2(bx, by);
float* D_buf;
cudaMalloc(&D_buf, sizeof(float) * (bx * by + 1) );
float* D_res = D_buf + (bx*by);
// Step 1: reduce 2D from image to a single vector, taking sum of squares
reduce2D<<< dimGrid2, dimBlock2, shared_mem2>>>(D_data, D_buf, pitch, width, height);
cudaTextForceKernelsCompletion();
// Step 2: reduce 1D: add up elements in vector
if (bx * by > 512)
reduce1D<512><<< 1, 512, sizeof(float)*512>>>(D_buf, D_res, bx*by);
else if (bx * by > 128)
reduce1D<128><<< 1, 128, sizeof(float)*128>>>(D_buf, D_res, bx*by);
else if (bx * by > 32)
reduce1D<32><<< 1, 32, sizeof(float)*32*2>>>(D_buf, D_res, bx*by);
else if (bx * by > 8)
reduce1D<8><<< 1, 8, sizeof(float)*8*2>>>(D_buf, D_res, bx*by);
else
reduce1D<1><<< 1, 1, sizeof(float)*1*2>>>(D_buf, D_res, bx*by);
float x;
cudaMemcpy(&x, D_res, 4, cudaMemcpyDeviceToHost);
cudaTextForceKernelsCompletion();
cudaFree(D_buf);
return x;
}
bool cudaTextForceKernelsCompletion()
{
cudaError_t returnedCudaError = cudaThreadSynchronize();
if(returnedCudaError != cudaSuccess) {
ASTRA_ERROR("Failed to force completion of cuda kernels: %d: %s.", returnedCudaError, cudaGetErrorString(returnedCudaError));
return false;
}
return true;
}
void reportCudaError(cudaError_t err)
{
if(err != cudaSuccess)
ASTRA_ERROR("CUDA error %d: %s.", err, cudaGetErrorString(err));
}
}