// interp3_gpu.m - fast texture based GPU based interpolation method for 3D deformation // // RECOMPILE: mexcuda -output interp3_gpu_mex interp3_gpu_ker.cu interp3_gpu_mex.cpp // // %*-----------------------------------------------------------------------* // %|                                                                       | // %|  Except where otherwise noted, this work is licensed under a          | // %|  Creative Commons Attribution-NonCommercial-ShareAlike 4.0            | // %|  International (CC BY-NC-SA 4.0) license.                             | // %|                                                                       | // %|  Copyright (c) 2018 by Paul Scherrer Institute (http://www.psi.ch)    | // %|                                                                       | // %|      Author: CXS group, PSI  | // %*-----------------------------------------------------------------------* // % You may use this code with the following provisions: // % // % If the code is fully or partially redistributed, or rewritten in another // % computing language this notice should be included in the redistribution. // % // % If this code, or subfunctions or parts of it, is used for research in a // % publication or if it is fully or partially rewritten for another // % computing language the authors and institution should be acknowledged // % in written form in the publication: “Data processing was carried out // % using the “cSAXS matlab package” developed by the CXS group, // % Paul Scherrer Institut, Switzerland.” // % Variations on the latter text can be incorporated upon discussion with // % the CXS group if needed to more specifically reflect the use of the package // % for the published work. // % // % A publication that focuses on describing features, or parameters, that // % are already existing in the code should be first discussed with the // % authors. // % // % This code and subroutines are part of a continuous development, they // % are provided “as they are” without guarantees or liability on part // % of PSI or the authors. It is the user responsibility to ensure its // % proper use and the correctness of the results. #include #include #include "interp3_gpu.hpp" #include #include #include #include "mex.h" #include "gpu/mxGPUArray.h" #define MAX(x,y) (x>y?x:y); #define MIN(x,y) (x0?x:-x); #define INF (1023); typedef const unsigned int cuint; typedef const int cint; typedef texture texture3D; static texture3D ImgTexture, X_tex, Y_tex, Z_tex; // splitting volume on smaller blocks to prevent GPU crashes static cuint g_blockX = 256; static cuint g_blockY = 256; static cuint g_blockZ = 256; cudaArray* allocateVolumeArray( cuint X, cuint Y, cuint Z) { cudaChannelFormatDesc channelDesc = cudaCreateChannelDesc(); cudaArray* cuArray; cudaExtent extent; extent.width = X; extent.height = Y; extent.depth = Z; cudaError err = cudaMalloc3DArray(&cuArray, &channelDesc, extent); if (err != cudaSuccess) { mexPrintf ("Failed to allocate %dx%dx%d GPU array\n",X,Y,Z); return 0; } return cuArray; } static bool bindVolumeDataTexture(const cudaArray* array, texture3D & Texture, bool normalized) { cudaChannelFormatDesc channelDesc = cudaCreateChannelDesc(); Texture.addressMode[0] = cudaAddressModeClamp; Texture.addressMode[1] = cudaAddressModeClamp; Texture.addressMode[2] = cudaAddressModeClamp; Texture.filterMode = cudaFilterModeLinear; //cudaFilterModePoint Texture.normalized = normalized; cudaError err = cudaBindTextureToArray(Texture, array, channelDesc); checkLastError("cudaBindTextureToArray "); return true; } bool transferVolumeToArray(const mxGPUArray * m_img, cudaArray *& array) { mwSize const * dimensions = mxGPUGetDimensions(m_img); mwSize Ndim = mxGPUGetNumberOfDimensions(m_img); int M = (int)dimensions[0]; int N = (int)dimensions[1]; int O = Ndim > 2 ? (int)dimensions[2] : 1; // get the values into float array const float * img =(const float *)mxGPUGetDataReadOnly(m_img); array = allocateVolumeArray(M,N,O); if (array == 0) return false; if (M * sizeof(float) > 2048) { mexPrintf("Volume is too large to be transfered to GPU array"); return false; } /* make volume array (no copying) */ cudaPitchedPtr volume; volume.ptr = (float *)img; volume.pitch = M * sizeof(float); volume.xsize = M; volume.ysize = N; cudaExtent extent; extent.width = M; extent.height = N; extent.depth = O; cudaMemcpy3DParms p; cudaPos zp = { 0, 0, 0 }; p.srcArray = 0; p.srcPos = zp; p.srcPtr = volume; p.dstArray = array; p.dstPtr.ptr = 0; p.dstPtr.pitch = 0; p.dstPtr.xsize = 0; p.dstPtr.ysize = 0; p.dstPos = zp; p.extent = extent; p.kind = cudaMemcpyDeviceToDevice; cudaError err = cudaMemcpy3D(&p); if (!checkLastError("transferVolumeToArray cudaMemcpy3D")) return false; return true; } int checkLastError(char * msg) { cudaError_t cudaStatus = cudaGetLastError(); if (cudaStatus != cudaSuccess) { char err[512]; sprintf(err, "interp3 variation failed \n %s: %s. \n", msg, cudaGetErrorString(cudaStatus)); mexErrMsgTxt(err); return 0; } return 1; } bool cudaTextForceKernelsCompletion() { cudaError_t returnedCudaError = cudaThreadSynchronize(); if (returnedCudaError != cudaSuccess) { fprintf(stderr, "Failed to force completion of cuda kernels: %d: %s. \n ", returnedCudaError, cudaGetErrorString(returnedCudaError)); return false; } return true; } /** * TEXTURE TRILINEAR INTERPOLATION **/ __global__ void kernel_interp3(float * p, cuint N, cuint M, cuint O, cuint Xstart, cuint Ystart, cuint Zstart) { // Location in a 3D matrix mwSize m = Xstart+ blockIdx.x * blockDim.x + threadIdx.x; mwSize n = Ystart+ blockIdx.y * blockDim.y + threadIdx.y; mwSize o = Zstart+ blockIdx.z * blockDim.z + threadIdx.z; if (m < M & n < N & o < O) { float xs, ys, zs; // shifted coordinates float mn, nn, on; // normalized coordinates mn = (float)(m)/M; nn = (float)(n)/N; on = (float)(o)/O; // mn = (m+0.5f); // nn = (n+0.5f); // on = (o+0.5f); // load deformed coordinates xs = m+0.5f - tex3D(X_tex,mn, nn, on); ys = n+0.5f - tex3D(Y_tex,mn, nn, on); zs = o+0.5f - tex3D(Z_tex,mn, nn, on); // get trilinear interplation bool outsiders = (xs > 0) & (ys > 0) & (zs > 0) & (xs < M) & (ys < N) & (zs < O); float p_val = (outsiders ? tex3D(ImgTexture,xs,ys,zs) : 0); // write the interpolation to the output p[(n)*N+(m)+(o)*M*N] = p_val; } } /** * Host function called by MEX gateway. */ void interp3_init( float * p, const mxGPUArray * p0, const mxGPUArray *m_X, const mxGPUArray *m_Y, const mxGPUArray *m_Z, cuint M, cuint N, cuint O) { if (M*N*O*4 > 1024e6) { mexPrintf("Image size exceeded 1024MB, textures in interp3 will fail\n"); return; } /* move image to the texture array */ cudaArray* cuArray, *cuArrayX, *cuArrayY, *cuArrayZ; checkLastError("after allocateVolumeArray"); transferVolumeToArray(p0, cuArray); checkLastError("after transferVolumeToArray\n \n "); bindVolumeDataTexture(cuArray, ImgTexture, false); /* move X deformation to the texture array */ checkLastError("after allocateVolumeArray"); transferVolumeToArray(m_X, cuArrayX); checkLastError("after transferVolumeToArray\n \n "); bindVolumeDataTexture(cuArrayX, X_tex, true); /* move Y deformation to the texture array */ checkLastError("after allocateVolumeArray"); transferVolumeToArray(m_Y, cuArrayY); checkLastError("after transferVolumeToArray\n \n "); bindVolumeDataTexture(cuArrayY, Y_tex, true); /* move Z deformation to the texture array */ checkLastError("after allocateVolumeArray"); transferVolumeToArray(m_Z, cuArrayZ); checkLastError("after transferVolumeToArray\n \n "); bindVolumeDataTexture(cuArrayZ, Z_tex, true); // *************** 3-dim case *************** // Choose a reasonably sized number of threads in each dimension for the block. int const threadsPerBlockEachDim = 10; // MAX THREAD is 1024 ~ 10*10*10 for 3D dim3 const dimThread(threadsPerBlockEachDim, threadsPerBlockEachDim, threadsPerBlockEachDim); //mexPrintf("Thread %i %i %i \n ", dimThread.x, dimThread.y, dimThread.z); // Compute the thread block and grid sizes based on the board dimensions. int const blocksPerGrid_M = (g_blockX + threadsPerBlockEachDim - 1) / threadsPerBlockEachDim; int const blocksPerGrid_N = (g_blockY + threadsPerBlockEachDim - 1) / threadsPerBlockEachDim; int const blocksPerGrid_O = (g_blockZ + threadsPerBlockEachDim - 1) / threadsPerBlockEachDim; dim3 dimBlock(blocksPerGrid_M, blocksPerGrid_N, blocksPerGrid_O); //mexPrintf("Block %i %i %i \n ", blocksPerGrid_M, blocksPerGrid_N, blocksPerGrid_O); std::list streams; for ( int blockXstart=0; blockXstart < M; blockXstart += g_blockX) for ( int blockYstart=0; blockYstart < N; blockYstart += g_blockY) for ( int blockZstart=0; blockZstart < O; blockZstart += g_blockZ) { cudaStream_t stream; cudaStreamCreate(&stream); streams.push_back(stream); kernel_interp3<<>> (p,M,N,O, blockXstart,blockYstart,blockZstart); } checkLastError("after kernel"); cudaThreadSynchronize(); for (std::list::iterator iter = streams.begin(); iter != streams.end(); ++iter) cudaStreamDestroy(*iter); streams.clear(); // clear memory , unbind textures cudaTextForceKernelsCompletion(); cudaFreeArray(cuArray); cudaFreeArray(cuArrayX); cudaFreeArray(cuArrayY); cudaFreeArray(cuArrayZ); checkLastError("after cudaFreeArray"); cudaUnbindTexture(ImgTexture); cudaUnbindTexture(X_tex); cudaUnbindTexture(Y_tex); cudaUnbindTexture(Z_tex); checkLastError("cudaUnbindTexture"); }