Exemple d’utilisation de Cuda depuis Gimp
Je pense qu’il n’est plus besoin de présenter The Gimp : LE logiciel de dessin bitmap du monde libre.
Mais connaissez-vous Cuda ?
Cuda est, au moment où j’écris ces lignes, encore un inconnu pour bon nombre de personnes. Cuda est une technologie développée par nVidia et qui permet au développeur d’utiliser le processeur de la carte vidéo (le GPU) pour effectuer toute sorte de calcul ou de traitement.
Il faut savoir que nos chères cartes graphiques sont plus que dopées par rapport à nos Intel et autres Amd. Pour exemple, un Quad Core contient 4 cœurs (pour simplifier, cela correspond à 4 processeurs), tandis que le GPU d’une GTX280 de nVidia en contient 240… C’est ce que l’on appelle une architecture massivement parallèle.
Cuda met à disposition du développeur un compilateur (nvcc) qui lui permet de compiler du code "cu". En gros : cela rajoute quelques fonctionnalités au compilateur C/C++ traditionnel qui permet d’écrire dans le même fichier du code exécutable par le processeur central et du code exécutable par le GPU. Les fonctions GPU ainsi écrites peuvent être appelées par des fonctions classiques de manière quasi transparente.
Et bien on peut par exemple soulager le processeur central de certains traitements graphiques. Pourquoi ne pas écrire un plug-in pour Gimp dans ce cas ?
Le plug-in fourni plus bas inverse la valeur de chaque pixel d’une image RGB. Il est évident qu’il ne s’agit pas d’une opération nécessitant une grande puissance de calcul.
Le plug-in est divisé en 2 fichiers sources :
gimp_pixel_rgn_get_rect
) dans ce tampon, appelle kernel_render pour effectuer le traitement et met à jour l’image (gimp_pixel_rgn_set_rect
)Ce plug-in a été testé et développé dans les conditions suivantes :
La ligne ci-dessous permet de compiler le plug-in cudatest pour Gimp :
nvcc -o cudatest --host-compilation C `pkg-config --cflags --libs gimp-2.0` gimpcuda.cu gimpcuda_kernel.cu
Descriptions des options :
Une fois le fichier cudatest généré, il ne vous reste plus qu’à l’installer dans votre répertoire de plug-ins (généralement ~/.gimp-2.4/plug-ins
) et à lancer The Gimp.
Lorsque vous développez des plug-ins, il est nécessaire d’arrêter et de relancer The Gimp à chaque modification pour qu’il prenne en compte la nouvelle version du plug-in.
Voici quelques liens utiles pour le développement de plug-in sous Gimp et/ou avec Cuda :
Ici devrait se trouver tout le bla-bla habituel sur le code fourni : je ne garantis rien, il n’est pas une référence en matière de commentaires ni en matière de programmation. Son but est uniquement de montrer le principe permettant d’utiliser Cuda dans un plug-in Gimp.
#include <libgimp/gimp.h>
void kernel_render(unsigned char *buffer,int width,int height,int channels);
static void query(void) {
static GimpParamDef args[]={
{GIMP_PDB_INT32 ,"run-mode","Run mode" },
{GIMP_PDB_IMAGE ,"image" ,"Input image" },
{GIMP_PDB_DRAWABLE,"drawable","Input drawable"}
};
gimp_install_procedure(
"plug-in-cuda-test",
"Plug-in test cuda de Fred",
"Teste l'intégration de Cuda dans un plug-in Gimp",
"Frédéric BISSON",
"Copyright Frédéric BISSON",
"2008",
"_Cuda test...",
"RGB",
GIMP_PLUGIN,
G_N_ELEMENTS (args), 0,
args, NULL
);
gimp_plugin_menu_register("plug-in-cuda-test","<Image>/Filters/Misc");
}
static void render(GimpDrawable *drawable) {
gint channels;
gint x1,y1,x2,y2;
GimpPixelRgn src,dst;
guchar *buffer;
gint width,height;
gimp_drawable_mask_bounds(drawable->drawable_id,&x1,&y1,&x2,&y2);
channels=gimp_drawable_bpp(drawable->drawable_id);
width =x2-x1;
height=y2-y1;
gimp_pixel_rgn_init(&src,drawable,x1,y1,width,height,FALSE,FALSE);
gimp_pixel_rgn_init(&dst,drawable,x1,y1,width,height,TRUE ,TRUE );
buffer=g_new(guchar,channels*width*height);
gimp_pixel_rgn_get_rect(&src,buffer,x1,y1,width,height);
kernel_render(buffer,width,height,channels);
gimp_pixel_rgn_set_rect(&dst,buffer,x1,y1,width,height);
g_free(buffer);
gimp_drawable_flush(drawable);
gimp_drawable_merge_shadow(drawable->drawable_id,TRUE);
gimp_drawable_update(drawable->drawable_id,x1,y1,width,height);
}
static void run(const gchar *name,gint nparams,const GimpParam *param,gint *nreturn_vals,GimpParam **return_vals) {
static GimpParam values[1];
GimpPDBStatusType status = GIMP_PDB_SUCCESS;
GimpRunMode run_mode;
GimpDrawable *drawable;
// Setting mandatory output values
*nreturn_vals=1;
*return_vals =values;
values[0].type =GIMP_PDB_STATUS;
values[0].data.d_status=status;
// Getting run_mode - we won't display a dialog if we are in NONINTERACTIVE mode
run_mode=(GimpRunMode)param[0].data.d_int32;
if(run_mode!=GIMP_RUN_NONINTERACTIVE) {
g_message("Cuda test in progress...\n");
}
// Get specified drawable
drawable=gimp_drawable_get(param[2].data.d_drawable);
render(drawable);
gimp_displays_flush();
gimp_drawable_detach(drawable);
}
GimpPlugInInfo PLUG_IN_INFO={
NULL,
NULL,
query,
run
};
MAIN()
#include <stdio.h>
__global__ void doProcessPicture(unsigned char *src,unsigned char *dst,int width,int height,int channels) {
int i,j;
int base=threadIdx.x*width*channels*gridDim.x;
int offset;
for(j=0;j<gridDim.x;j++) {
for(i=0;i<width*channels;i++) {
offset=base+i+j*width*channels;
dst[offset]=255-src[offset];
}
}
}
void kernel_render(unsigned char *buffer,int width,int height,int channels) {
unsigned char *devSrc;
unsigned char *devDst;
int bufferSize;
int nbIter;
int nbThread;
bufferSize=width*height*channels;
nbThread=192;
nbIter=height/nbThread;
// Allocate two buffers on the GPU
cudaMalloc((void**)&devSrc,bufferSize);
cudaMalloc((void**)&devDst,bufferSize);
// Copy our buffer into the GPU input buffer
cudaMemcpy(devSrc,buffer,bufferSize,cudaMemcpyHostToDevice);
// Run the GPU routine
doProcessPicture<<<nbIter,nbThread,0>>>(devSrc,devDst,width,height,channels);
// Retrieve the GPU output buffer into our buffer
cudaMemcpy(buffer,devDst,bufferSize,cudaMemcpyDeviceToHost);
// Free allocated GPU buffers
cudaFree(devSrc);
cudaFree(devDst);
}