From 935bdd1c8b99ce5d70b6e4a53ca29f06353e8baa Mon Sep 17 00:00:00 2001 From: perrot Date: Tue, 15 Feb 2011 13:29:51 +0100 Subject: [PATCH] Initial commit Snake gpu for sm20 --- exec/SNAKE2D | Bin 0 -> 481958 bytes lib/lib_alloc.o | Bin 0 -> 10032 bytes lib/lib_contour.o | Bin 0 -> 5408 bytes lib/lib_images.o | Bin 0 -> 9320 bytes lib/lib_math.o | Bin 0 -> 6944 bytes lib/lib_snake_common.o | Bin 0 -> 10984 bytes makefile | 82 +++ src/constantes.h | 52 ++ src/constantes.h~ | 52 ++ src/defines.h | 45 ++ src/defines.h~ | 45 ++ src/lib_alloc.c | 110 ++++ src/lib_alloc.c~ | 96 +++ src/lib_alloc.h | 12 + src/lib_alloc.h~ | 11 + src/lib_contour.c | 254 ++++++++ src/lib_contour.h | 8 + src/lib_gpu.cu | 623 +++++++++++++++++++ src/lib_gpu.cu~ | 634 +++++++++++++++++++ src/lib_gpu.h | 29 + src/lib_gpu.h~ | 20 + src/lib_images.c | 305 ++++++++++ src/lib_images.c~ | 211 +++++++ src/lib_images.h | 11 + src/lib_images.h~ | 8 + src/lib_kernel_snake_2_gpu.cu | 447 ++++++++++++++ src/lib_kernel_snake_2_gpu.cu~ | 447 ++++++++++++++ src/lib_kernels_contribs.cu | 1043 ++++++++++++++++++++++++++++++++ src/lib_kernels_contribs.cu~ | 1043 ++++++++++++++++++++++++++++++++ src/lib_kernels_contribs.h | 33 + src/lib_kernels_cumuls.cu | 233 +++++++ src/lib_kernels_cumuls.cu~ | 233 +++++++ src/lib_kernels_maths.cu | 244 ++++++++ src/lib_kernels_maths.cu~ | 226 +++++++ src/lib_kernels_maths.h | 37 ++ src/lib_kernels_maths.h~ | 231 +++++++ src/lib_kernels_maths_gpu.cu~ | 231 +++++++ src/lib_math.c | 276 +++++++++ src/lib_math.c~ | 257 ++++++++ src/lib_math.h | 31 + src/lib_math.h~ | 30 + src/lib_snake_2_gpu.cu | 141 +++++ src/lib_snake_2_gpu.cu~ | 126 ++++ src/lib_snake_2_gpu.h | 9 + src/lib_snake_2_gpu.h~ | 8 + src/lib_snake_common.c | 723 ++++++++++++++++++++++ src/lib_snake_common.c~ | 718 ++++++++++++++++++++++ src/lib_snake_common.h | 21 + src/lib_snake_common.h~ | 21 + src/lib_test_gpu.cu | 78 +++ src/lib_test_gpu.cu~ | 45 ++ src/lib_test_gpu.h | 9 + src/lib_test_gpu.h~ | 2 + src/snake2D_gpu.cu | 347 +++++++++++ src/snake2D_gpu.cu~ | 362 +++++++++++ src/structures.h | 145 +++++ src/structures.h~ | 129 ++++ 57 files changed, 10534 insertions(+) create mode 100755 exec/SNAKE2D create mode 100644 lib/lib_alloc.o create mode 100644 lib/lib_contour.o create mode 100644 lib/lib_images.o create mode 100644 lib/lib_math.o create mode 100644 lib/lib_snake_common.o create mode 100644 makefile create mode 100644 src/constantes.h create mode 100644 src/constantes.h~ create mode 100644 src/defines.h create mode 100644 src/defines.h~ create mode 100644 src/lib_alloc.c create mode 100644 src/lib_alloc.c~ create mode 100644 src/lib_alloc.h create mode 100644 src/lib_alloc.h~ create mode 100644 src/lib_contour.c create mode 100644 src/lib_contour.h create mode 100644 src/lib_gpu.cu create mode 100644 src/lib_gpu.cu~ create mode 100644 src/lib_gpu.h create mode 100644 src/lib_gpu.h~ create mode 100644 src/lib_images.c create mode 100644 src/lib_images.c~ create mode 100644 src/lib_images.h create mode 100644 src/lib_images.h~ create mode 100644 src/lib_kernel_snake_2_gpu.cu create mode 100644 src/lib_kernel_snake_2_gpu.cu~ create mode 100644 src/lib_kernels_contribs.cu create mode 100644 src/lib_kernels_contribs.cu~ create mode 100644 src/lib_kernels_contribs.h create mode 100644 src/lib_kernels_cumuls.cu create mode 100644 src/lib_kernels_cumuls.cu~ create mode 100644 src/lib_kernels_maths.cu create mode 100644 src/lib_kernels_maths.cu~ create mode 100644 src/lib_kernels_maths.h create mode 100644 src/lib_kernels_maths.h~ create mode 100644 src/lib_kernels_maths_gpu.cu~ create mode 100644 src/lib_math.c create mode 100644 src/lib_math.c~ create mode 100644 src/lib_math.h create mode 100644 src/lib_math.h~ create mode 100644 src/lib_snake_2_gpu.cu create mode 100644 src/lib_snake_2_gpu.cu~ create mode 100644 src/lib_snake_2_gpu.h create mode 100644 src/lib_snake_2_gpu.h~ create mode 100644 src/lib_snake_common.c create mode 100644 src/lib_snake_common.c~ create mode 100644 src/lib_snake_common.h create mode 100644 src/lib_snake_common.h~ create mode 100644 src/lib_test_gpu.cu create mode 100644 src/lib_test_gpu.cu~ create mode 100644 src/lib_test_gpu.h create mode 100644 src/lib_test_gpu.h~ create mode 100644 src/snake2D_gpu.cu create mode 100644 src/snake2D_gpu.cu~ create mode 100644 src/structures.h create mode 100644 src/structures.h~ diff --git a/exec/SNAKE2D b/exec/SNAKE2D new file mode 100755 index 0000000000000000000000000000000000000000..67f3437fa7b489a3d60f6384c850865128d0b87e GIT binary patch literal 481958 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 V`{$v5*cV93V9Mc;RSixi+5a%nFtGpt literal 0 HcmV?d00001 diff --git a/lib/lib_alloc.o b/lib/lib_alloc.o new file mode 100644 index 0000000000000000000000000000000000000000..a7408ec960f72f8d0282f9d7d4b81fbbe66dd437 GIT binary patch literal 10032 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 U>>lp_nbfozsLmOWaQ4pPKkaz>mH+?% literal 0 HcmV?d00001 diff --git a/lib/lib_images.o b/lib/lib_images.o new file mode 100644 index 0000000000000000000000000000000000000000..120abff8013860adab43abcf82d447e8ff4bbfed GIT binary patch literal 9320 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 WoCg{(`WHAgDM!{ZbuL`iG5-f%a!*D8 literal 0 HcmV?d00001 diff --git a/lib/lib_math.o b/lib/lib_math.o new file mode 100644 index 0000000000000000000000000000000000000000..a9ff1100e689d46a04ff53eef553145dcf6e2635 GIT binary patch literal 6944 zcmbtYeQ;FO6@Rw{BFg6?hM*8WV){Gx-Ipv2 z{!#m8_T6)T_ndRjIq%%xd&!1Sq{``VNcQEB9*`zyHY!Q4%$SrbxL6?-NzRpF=8!y6!?COL#*QryoK8T#o9*TRY_iQu(mfX$*C_nud>*5Y~s@(oKdzS5{bmL z7Gm0Il0GNtfTpn6F-YlSkTOR}+Kbgp8q@6Ow92IeCa&ZOg+HN2p zldo?eP^g3C={R}1NS?0nsNpC>E)OGDhwyWi`(d$cDA1Am(wjCa0=OJmmkhBp<=_9yh z$a8uhunetcNiR#QF+9uIH(1m@M7CpO{wW#JBhMh$WB46L-d~ad3Xna-HnH*XaahE5 z1*uAB4B9F_-{oW55xL^ z4D0|bQE?5;mBy1q7r+{;8R|AHWTCK6_m4(QbThP_9({S!>Red zR4xvxsiz&GRB_OBM^cMqC3&hPdK`Wga`!dZr&SExVIzSg}w(}muxtl>WM-*rAUVR!JcN+Y+ zO@e@Cr!z5xgFlAf&`ymdh*gM8a45h^I0vj_hs}3~d3zAwwOf)?SLv+J&gVN8JBWid zbOwE&I7=t*D{A>;m=1jnY3wX+=|i|>KF1cHAF3 zor04z&H;kMDLp~kTeJqj;zzI&u?zO^r~P-+{$TSjiBFLlb|#h@`UKM0Y21kGvL{#v zWhY3Ia~nk;8>J0iByK~McsAq+voYMp#%T2^&U8dmvEu+UCka%_X}*`DI6ydG{0N>9?z_>p3TJsm2+va+k&_uiBzK))mmeD3!ahCs?oAW<6(! zbxnr_c&~CNEI@qKHp4j;S_AcAg>&CED~@Y(5cEej-(e?Cg6~jXFvm|JKecWISL>OK zTVYkhfD~D54K}PEChe)n0DOpwazd;gM%1KONTGuht4FL9yK0Mgo}3l|T2Fzyxq%1b zMsBaNDw`a=oE)D(gJR!N<383kWIRh0$!2Xt_!67dMgthD>}DEQ-!j6U;!>a+SU-EC z8>$u3jnIu`ApEGQl6zC~JjK|l7%$3-vDT{ucNsk)R<##RgF^HNFNcijA*0r#7&V3X z73&LeFEK`IQZ;i*2ZAFo^ecuk6F;Bf&})ogxG9IlJ*5LRsoDj==i)ctaBlTtV}zyB zw`xrZIS6#a!3WAr3JIf+k8o%>5?y2*=mt91Lf4ib^jNyv|Jtt>P%(=cXY(a=C;SO*+?&8hvPACfZmc459mtIy%M@xrG zce8hURrYS5>gdMDu<+lGPmjH9^UXM9$;(q$Pp!Dn$5@*iQCYxFJM~ENvP-|8zBK0IL!(nKp+h&#Tr3-l)REVmO&EhRau|SJ~+VZ3wo-x~8i>+1YhEuh86Dm$pgqt@asah9Yy^f7G zVNJ$n9aS8!nNkIui;5dkm1SLkP&{y11qYR;ys6TVJ7D|<3}4^yYm^3cJx?XV7*17t z!#D^XZzGqGu|E{pqx76~R;3E*^=bVHu@t3IQcK41;wj)|;(de4;qYl}gPSGqf<`+& z*!~0)RF1X%oC{8^lPc-b>!^O!ynba=zgy__Q&_<3x77z-aOj3bUP6ltP)C{;yTi$D z4wcwY$pqxLA^!w4*M{a!K=W;A{sgqZh8A!L8N{1uTN${Aww2vTbA5pqiLG~gA?pP` z$hzAHxw)VmatmZp2=# zuZ4oULdH(kw*H&(@kbhZL&jiD&k-kH77@n>5l3HjO1{-(_)0 ze;#~wqS9y;*_anj_R3~(Vty~5ftf`<#SCx+j^7Z1YvAMt)#HA%zrX$H2a6~F&i=HkL~1{aE4kpoWrTSP{e0bA<}VMM&Hj5LW~09%oZKD;r{N@8c{~<2 z&Ic}78@H!*Z`jB@bBg?lG@jWeNq5G5_xkSCrV?ylx4-mIL3aGh2V1v4y?&}R{9x*f zzr1y^@4W|#g@R+V%{r88VG+?%;$)a2Eu{T@unBD$hZYpq`y zmC71oD_2HaVBQkbqh;9j$8o*B;inYP|9AOdPU*Sx+*F8pE4}J-uDq%A1FYqgb3*gU zTP<BRmw(In>* ztuXPwz6z`nHX}!e#BLG4FAul#x5r1ZPWD!}O)4L;?d=W4l5J;eJ^~@XCbU+5o24;q z4HDJA06*K_u0Nf{ye%vXkx+hK`MS|>v7eD|XnDPEuj#<^pB<^%_6cEU5BdAkJim6N z>$%0UcttGICv_3w7A)EM)APO2{+&YM1EFsZ`TN^={z!VVbZ$H!r@8C8Dp*>s8%9F< zH+sT^^LNN~Tez+}&9;B)fa@=pbj>S)D^R~&&kB-6&x>%oj(m!nRc^PGe{6{#NZ+nE zpNgAH%0FF_G{G9^v9sKfZLmFM*g8u_Jk6#$w zLCUx&j~n@snwwszTUoEKC~J_)A`8puhIdM2;XJzCEv;;7!Sb5ArWQTWI5A7aSHxNg z(4%o3Yns$h-`tRBu3NRHu0Em160LPDv1p=EiZ?B9scUS~w7OWLF1ozFxo%ywHI@V8 zCDzoS187Oa>mb*+EN_lVtS%)=O|(jStU+2H)%B*8(U{g)zgAk+ihwjp zZ)%WMCGd9xZU5+=izpLugSyh{_f1f?f(9K g9&Yz{cOGu{_x?QG?(g9|-0p9$AVfRB+v;Y7A literal 0 HcmV?d00001 diff --git a/lib/lib_snake_common.o b/lib/lib_snake_common.o new file mode 100644 index 0000000000000000000000000000000000000000..a7abed2ef461d8a955933849c850726a52adb319 GIT binary patch literal 10984 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 literal 0 HcmV?d00001 diff --git a/makefile b/makefile new file mode 100644 index 0000000..70d42b5 --- /dev/null +++ b/makefile @@ -0,0 +1,82 @@ +# chemin des librairies + + +PATH_GCC = $(PWD) + +PATH_LIB = $(PATH_GCC)/lib/ +PATH_SRC = $(PATH_GCC)/src/ +PATH_EXEC = $(PATH_GCC)/exec/ + +# repertoires pour les headers +PATH_INCLUDE = $(PATH_GCC)/src/ + +# compilateur +CC = gcc +NVCC = /usr/local/cuda/bin/nvcc +CXX = g++ + +# options de compilation +# -Wall : Warning all +# -g : debug (ex : gdb sas) +# -pg : profile memoire et cpu pour chaque fonctions (ex : gprof name | less) +# -O3 : optimisation code -O0 (rien) ~ -O3 (maxi) +# -fstrict-aliasing : aligne la memoire sur 32 bits + +# gcc +OPTION_CC1 = -Wall -O3 -static #-pg #-g -fprofile-arcs -ftest-coverage +OPTION_CC2 = $(OPTION_CC1) -funroll-all-loops -fstrict-aliasing + +OPTION_CC = $(OPTION_CC2) -I$(PATH_INCLUDE) -I$(PATH_SRC) + + +# librairies pour la compilation +LIB_CC = -lm +LIBSNV = -L/usr/local/cuda/lib64 -lcuda -lcudart + +# sources utiles a la compilation des main +SRCS = lib_alloc.c lib_images.c lib_math.c lib_snake_common.c lib_contour.c + +SRCS_NV = lib_test_gpu.cu lib_snake_2_gpu.cu lib_gpu.cu snake2D_gpu.cu + +OBJS = $(SRCS:%.c=$(PATH_LIB)%.o) $(SRCS_NV:%.cu=$(PATH_LIB)%.o) + +# dependances supplementaires +DEPS = $(PATH_SRC)/constantes.h $(PATH_SRC)/structures.h $(PATH_GCC)/makefile +help : + @echo "cibles : snake2D doc clean" + +all : snake2D doc +clean : + -rm -f $(PATH_LIB)*.o + -rm -rf $(PATH_GCC)/docs/ + + +# regle pour les .o +# --use_fast_math +# --ptxas-options=-v +$(PATH_LIB)%_gpu.o : $(PATH_SRC)%_gpu.cu + $(NVCC) -arch=sm_20 --use_fast_math -c $< -o $@ + +$(PATH_LIB)%.o : $(PATH_SRC)%.c $(DEPS) + $(CC) $(OPTION_CC) -c $< -o $@ + +# regle pour l'exec + +$(PATH_EXEC)SNAKE2D : $(PATH_SRC)snake2D_gpu.cu $(OBJS) $(DEPS) + $(CXX) $(OBJS) $(LIBSNV) \ + -o $(PATH_EXEC)SNAKE2D + +# compilation main + +snake2D : $(PATH_EXEC)SNAKE2D + @echo "" + @echo "***** fin compil snake2D *****" + @echo "" + +# generation des doc + +doc : + doxygen doxygen.conf + @echo "" + @echo "***** fin compil doc *****" + @echo "" diff --git a/src/constantes.h b/src/constantes.h new file mode 100644 index 0000000..38ad9db --- /dev/null +++ b/src/constantes.h @@ -0,0 +1,52 @@ +/** + * \file constantes.h + * \brief Definition des parametres de l'algo snake + * \author NB - PhyTI + * \version x.x + * \date 20 decembre 2009 + * + * + */ + + +#ifndef _CONSTANTES_H + +#define _CONSTANTES_H + + +/** + * \def COEF_DECROI pourcentage de decroissance pour accepter les modifications + * \def INV_COEF_DECROI pourcentage de decroissance pour accepter les modifications + */ +#define COEF_DECROI 0.99999 +#define INV_COEF_DECROI 1.00001 + + +/** + * \def ALIGN_SSE attribut d'alignement memoire pour sse2 (128 bits) + */ +//#define ATT_ALIGN_SSE __attribute__ ((aligned (16))) + + +/** + * \def ALIGN_SSE alignement memoire pour sse2 (128 bits) + */ +//#define ALIGN_SSE 16 + + +/** + * \def NBBIT_SUM1 longueur du champ de bits associe a sum_1 + * \def NBBIT_SUMX longueur du champ de bits associe a sum_x + */ +/*#define NBBIT_SUM1 24 sur 64 */ +/*#define NBBIT_SUMX 40 sur 64 (complement) */ + +/** + * \def SIZE_NAME_FILE longueur maxi associee aux noms de fichiers + * \def SIZE_LINE_TEXT longueur maxi associee a une ligne de texte + */ +#define SIZE_NAME_FILE 256 +#define SIZE_LINE_TEXT 256 + + +#endif //_CONSTANTES_H diff --git a/src/constantes.h~ b/src/constantes.h~ new file mode 100644 index 0000000..38ad9db --- /dev/null +++ b/src/constantes.h~ @@ -0,0 +1,52 @@ +/** + * \file constantes.h + * \brief Definition des parametres de l'algo snake + * \author NB - PhyTI + * \version x.x + * \date 20 decembre 2009 + * + * + */ + + +#ifndef _CONSTANTES_H + +#define _CONSTANTES_H + + +/** + * \def COEF_DECROI pourcentage de decroissance pour accepter les modifications + * \def INV_COEF_DECROI pourcentage de decroissance pour accepter les modifications + */ +#define COEF_DECROI 0.99999 +#define INV_COEF_DECROI 1.00001 + + +/** + * \def ALIGN_SSE attribut d'alignement memoire pour sse2 (128 bits) + */ +//#define ATT_ALIGN_SSE __attribute__ ((aligned (16))) + + +/** + * \def ALIGN_SSE alignement memoire pour sse2 (128 bits) + */ +//#define ALIGN_SSE 16 + + +/** + * \def NBBIT_SUM1 longueur du champ de bits associe a sum_1 + * \def NBBIT_SUMX longueur du champ de bits associe a sum_x + */ +/*#define NBBIT_SUM1 24 sur 64 */ +/*#define NBBIT_SUMX 40 sur 64 (complement) */ + +/** + * \def SIZE_NAME_FILE longueur maxi associee aux noms de fichiers + * \def SIZE_LINE_TEXT longueur maxi associee a une ligne de texte + */ +#define SIZE_NAME_FILE 256 +#define SIZE_LINE_TEXT 256 + + +#endif //_CONSTANTES_H diff --git a/src/defines.h b/src/defines.h new file mode 100644 index 0000000..d6dc963 --- /dev/null +++ b/src/defines.h @@ -0,0 +1,45 @@ +#ifndef __DEFINES_H__ +#define __DEFINES_H__ + + +const unsigned int CORRESPONDANCE_Di_Dj_FREEMAN[3][3] = + {{3,2,1}, + {4,8,0}, + {5,6,7}} ; + + +const int TABLE_CODAGE[8][8] = + {{ 0, 0, 0, 0, 0,-1,-1,-1}, /* 0 */ + { 1, 1, 1, 1, 1, 0, 0, 0}, /* 1 */ + { 1, 1, 1, 1, 1, 0, 0, 0}, /* 2 */ + { 1, 1, 1, 1, 1, 0, 0, 0}, /* 3 */ + { 0, 0, 0, 0, 0,-1,-1,-1}, /* 4 */ + { 0, 0, 0, 0, 0,-1,-1,-1}, /* 5 */ + { 0, 0, 0, 0, 0,-1,-1,-1}, /* 6 */ + { 0, 0, 0, 0, 0,-1,-1,-1}}; /* 7 */ +/* 0 1 2 3 4 5 6 7 */ + +const int TABLE_CODAGE1[8][8] = + {{ 0, 1, 1, 1, 1, 0, 0, 0}, /* 0 */ + { 0, 1, 1, 1, 1, 2, 0, 0}, /* 1 */ + { 0, 1, 1, 1, 1, 2, 2, 0}, /* 2 */ + { 0, 1, 1, 1, 1, 2, 2, 2}, /* 3 */ + {-1, 0, 0, 0, 0,-1,-1,-1}, /* 4 */ + {-1, 2, 0, 0, 0,-1,-1,-1}, /* 5 */ + {-1, 2, 2, 0, 0,-1,-1,-1}, /* 6 */ + {-1, 2, 2, 2, 0,-1,-1,-1}}; /* 7 */ +/* 0 1 2 3 4 5 6 7 */ + +#define BSMAX 128 +#define MAX_PIX 20000 +#define MAX_NODES 1000 +#define MAX_LISTE_PIX 10000000 +#define MAX(x,y) ( ( (x)>=(y) )?(x):(y) ) +#define ABS(x) ( ((x)>0)?(x):-(x)) +#define DEC 4 +#define DEC2 8 +#define CONFLICT_FREE_OFFSET(index) ( ((index) >>(DEC)) + ((index) >>(DEC2) ) ) +#define CFO(index) ( ( (index) >>(DEC) ) + ( (index) >>(DEC2) ) ) +#define CFI(index) ( (index) + (CFO(index)) ) + +#endif diff --git a/src/defines.h~ b/src/defines.h~ new file mode 100644 index 0000000..5bfa254 --- /dev/null +++ b/src/defines.h~ @@ -0,0 +1,45 @@ +#ifndef __DEFINES_H__ +#define __DEFINES_H__ + + +const unsigned int CORRESPONDANCE_Di_Dj_FREEMAN[3][3] = + {{3,2,1}, + {4,8,0}, + {5,6,7}} ; + + +const int TABLE_CODAGE[8][8] = + {{ 0, 0, 0, 0, 0,-1,-1,-1}, /* 0 */ + { 1, 1, 1, 1, 1, 0, 0, 0}, /* 1 */ + { 1, 1, 1, 1, 1, 0, 0, 0}, /* 2 */ + { 1, 1, 1, 1, 1, 0, 0, 0}, /* 3 */ + { 0, 0, 0, 0, 0,-1,-1,-1}, /* 4 */ + { 0, 0, 0, 0, 0,-1,-1,-1}, /* 5 */ + { 0, 0, 0, 0, 0,-1,-1,-1}, /* 6 */ + { 0, 0, 0, 0, 0,-1,-1,-1}}; /* 7 */ +/* 0 1 2 3 4 5 6 7 */ + +const int TABLE_CODAGE1[8][8] = + {{ 0, 1, 1, 1, 1, 0, 0, 0}, /* 0 */ + { 0, 1, 1, 1, 1, 2, 0, 0}, /* 1 */ + { 0, 1, 1, 1, 1, 2, 2, 0}, /* 2 */ + { 0, 1, 1, 1, 1, 2, 2, 2}, /* 3 */ + {-1, 0, 0, 0, 0,-1,-1,-1}, /* 4 */ + {-1, 2, 0, 0, 0,-1,-1,-1}, /* 5 */ + {-1, 2, 2, 0, 0,-1,-1,-1}, /* 6 */ + {-1, 2, 2, 2, 0,-1,-1,-1}}; /* 7 */ +/* 0 1 2 3 4 5 6 7 */ + +#define BS 512 +#define MAX_PIX 20000 +#define MAX_NODES 1000 +#define MAX_LISTE_PIX 10000000 +#define MAX(x,y) ( ( (x)>=(y) )?(x):(y) ) +#define ABS(x) ( ((x)>0)?(x):-(x)) +#define DEC 4 +#define DEC2 8 +#define CONFLICT_FREE_OFFSET(index) ( ((index) >>(DEC)) + ((index) >>(DEC2) ) ) +#define CFO(index) ( ( (index) >>(DEC) ) + ( (index) >>(DEC2) ) ) +#define CFI(index) ( (index) + (CFO(index)) ) + +#endif diff --git a/src/lib_alloc.c b/src/lib_alloc.c new file mode 100644 index 0000000..21bc735 --- /dev/null +++ b/src/lib_alloc.c @@ -0,0 +1,110 @@ +/** + * \file lib_alloc.c + * \brief routines d'allocation des differentes datas du snake2D3D + * \author NB - PhyTI + * \version x.x + * \date 20 decembre 2009 + * + */ + +#include + +#include "lib_alloc.h" + +/** + * \fn int **new_matrix_int(int i_dim, int j_dim) + * \brief allocation d'un tableau 2D (tab[i][j]) avec data en ligne (tab[0][n]) + * + * \param[in] i_dim dimension verticale du tableau + * \param[in] j_dim dimension horizontale du tableau + * + * \return pointeur sur le tableau + * + */ +int **new_matrix_int(int i_dim, int j_dim) +{ + // allocation en ligne + int **matrice ; + int *vecteur ; + int i ; + + vecteur = malloc(sizeof(int)*i_dim*j_dim) ; + matrice = malloc(sizeof(int*)*i_dim) ; + for (i=0;i + +#include "lib_alloc.h" + +/** + * \fn int **new_matrix_int(int i_dim, int j_dim) + * \brief allocation d'un tableau 2D (tab[i][j]) avec data en ligne (tab[0][n]) + * + * \param[in] i_dim dimension verticale du tableau + * \param[in] j_dim dimension horizontale du tableau + * + * \return pointeur sur le tableau + * + */ +int **new_matrix_int(int i_dim, int j_dim) +{ + // allocation en ligne + int **matrice ; + int *vecteur ; + int i ; + + vecteur = malloc(sizeof(int)*i_dim*j_dim) ; + matrice = malloc(sizeof(int*)*i_dim) ; + for (i=0;i P2] + * Utilise l'algo de Bresenham en version int (recupere sur Wikipedia) + * + */ +uint32 calcul_liste_pixel_segment(uint32 y1,uint32 x1,uint32 y2,uint32 x2, uint32 *liste_pixel_segment, int ind_offset) +{ + int dx, dy ; + int e ; + uint32 *liste ; + + liste = liste_pixel_segment + ind_offset ; + + if ( (dx=x2-x1) != 0) + { + if ( dx > 0 ) + { + if ( (dy=y2-y1) != 0 ) + { + if ( dy > 0 ) + { + // vecteur oblique dans le 1er quadran + if ( dx >= dy ) + { + // vecteur diagonal ou oblique proche de l'horizontale, dans le 1er octant + dx = (e=dx) * 2 ; dy = dy * 2 ; // e est positif + while( x1!=x2 ) //boucler sans fin: deplacements horizontaux + { + *liste++=y1; + *liste++=x1; + + x1++; + if ( (e-=dy) < 0 ) + { + y1++ ; // deplacement diagonal + e += dx ; + } + } + } + else + { + // vecteur oblique proche de la verticale, dans le 2nd octant + dy = (e=dy) * 2 ; dx = dx * 2 ; // e est positif + while ( y1 != y2 ) //boucler sans fin: deplacements verticaux + { + *liste++=y1; + *liste++=x1; + y1 += 1 ; + if ( (e -= dx) < 0 ) + { + x1++ ; // deplacement diagonal + e += dy ; + } + } + } + } + else // dy < 0 (et dx > 0) + { + // vecteur oblique dans le 4e cadran + if ( dx >= -dy ) + { + // vecteur diagonal ou oblique proche de l'horizontale, dans le 8e octant + dx = (e=dx) * 2 ; dy = dy * 2 ; // e est positif + while ( x1 != x2 ) //boucler sans fin // deplacements horizontaux + { + *liste++=y1; + *liste++=x1; + x1 ++; + if ( (e += dy) < 0 ) + { + y1--; // deplacement diagonal + e += dx ; + } + } + } + else // vecteur oblique proche de la verticale, dans le 7e octant + { + dy = (e=dy) * 2 ; dx = dx * 2 ; // e est negatif + while ( y1 != y2 ) // boucler sans fin: deplacements verticaux + { + *liste++=y1; + *liste++=x1; + y1--; + if ( (e += dx) > 0 ) + { + x1 ++; // deplacement diagonal + e += dy ; + } + } + } + } + } + else // dy = 0 (et dx > 0) + { + // vecteur horizontal vers la droite + while ( x1 != x2 ) + { + *liste++=y1; + *liste++=x1; + x1++ ; + } + } + } + else + { + // dx < 0 + if ( (dy=y2-y1) != 0 ) + { + if ( dy > 0 ) + { + // vecteur oblique dans le 2nd quadran + if ( -dx >= dy ) + { + // vecteur diagonal ou oblique proche de l'horizontale, dans le 4e octant + dx = (e=dx) * 2 ; dy = dy * 2 ; // e est negatif + while ( x1 != x2 ) //boucler sans fin // deplacements horizontaux + { + *liste++=y1; + *liste++=x1; + x1 -- ; + if ( (e += dy) >= 0 ) + { + y1++ ; // deplacement diagonal + e += dx ; + } + } + } + else + { + // vecteur oblique proche de la verticale, dans le 3e octant + dy = (e=dy) * 2 ; dx = dx * 2 ; // e est positif + while ( y1 != y2 ) //boucler sans fin // deplacements verticaux + { + *liste++=y1; + *liste++=x1; + y1++ ; + if ( (e += dx) <= 0 ) + { + x1--; // depacement diagonal + e += dy ; + } + } + } + } + + else // dy < 0 (et dx < 0) + { + // vecteur oblique dans le 3e cadran + if ( dx <= dy ) + { + // vecteur diagonal ou oblique proche de l'horizontale, dans le 5e octant + dx = (e=dx) * 2 ; dy = dy * 2 ; // e est negatif + while ( x1 != x2 ) //boucler sans fin // deplacements horizontaux + { + *liste++=y1; + *liste++=x1; + x1 -- ; + if ( (e -= dy) >= 0 ) + { + y1-- ; // deplacement diagonal + e += dx ; + } + } + } + else + { + // vecteur oblique proche de la verticale, dans le 6e octant + dy = (e=dy) * 2 ; dx = dx * 2 ; // e est negatif + while ( y1 != y2 ) //boucler sans fin // deplacements verticaux + { + *liste++=y1; + *liste++=x1; + y1-- ; + if ( (e-=dx) >= 0 ) + { + x1 --; // deplacement diagonal + e += dy ; + } + } + } + } + } + else // dy = 0 (et dx < 0) + { + // vecteur horizontal vers la gauche + while ( x1 != x2 ) + { + *liste++=y1; + *liste++=x1; + x1 -- ; + } + } + } + } + else // dx = 0 + { + if ( (dy=y2-y1) != 0 ) + { + if ( dy > 0 ) + { + // vecteur vertical croissant + while ( y1 != y2 ) + { + *liste++=y1; + *liste++=x1; + y1 ++ ; + } + } + else // dy < 0 (et dx = 0) + { + // vecteur vertical décroissant + while ( y1 != y2 ) + { + *liste++=y1; + *liste++=x1; + y1 -- ; + } + } + } + } + + *liste++=y2; + *liste++=x2; + + return (uint32)(liste-liste_pixel_segment)/2; +} diff --git a/src/lib_contour.h b/src/lib_contour.h new file mode 100644 index 0000000..12d2903 --- /dev/null +++ b/src/lib_contour.h @@ -0,0 +1,8 @@ +#ifndef _LIB_CONTOUR_H_ +#define _LIB_CONTOUR_H_ + +#include "structures.h" + +uint32 calcul_liste_pixel_segment(uint32 y1,uint32 x1,uint32 y2,uint32 x2, uint32 *liste_pixel_segment, int ind_offset) ; + +#endif //_LIB_CONTOUR_H_ diff --git a/src/lib_gpu.cu b/src/lib_gpu.cu new file mode 100644 index 0000000..d6df5ec --- /dev/null +++ b/src/lib_gpu.cu @@ -0,0 +1,623 @@ + +#include + + +extern "C"{ +#include "structures.h" +#include "lib_math.h" +#include "defines.h" +#include "lib_gpu.h" +#include "lib_snake_2_gpu.h" +} +#include "lib_test_gpu.h" +#include "lib_kernels_cumuls.cu" +#include "lib_kernel_snake_2_gpu.cu" + +#define DEBUG_IMG_CUMUL 1 +bool DISPLAY_ERR_IMG_CUMUL = 1; +//#define DEBUG_POSITIONS +//#define DEBUG_MOVE +//#define DEBUG_CRST +//#define DEBUG_MV +//#define DEBUG_SOMSOM +//#define DEBUG_SOMBLOCS +//#define DEBUG_LISTES +//#define DEBUG_STATS_REF + + + +void cuda_init_img_cumul(unsigned short ** img_in, int H, int L, int nb_nodes, + unsigned short ** d_img, t_cumul_x ** d_img_x, t_cumul_x2 ** d_img_x2, + int ** d_freemanDiDj, int ** d_codeNoeud, + snake_node_gpu ** d_snake, uint32 ** d_nb_pix_max, + uint4 ** d_positions, uint64 ** d_contribs_segments, uint4 ** d_freemans_centres, + int ** d_codes_segments, int64 ** d_stats_snake, + int64 ** d_stats, int64 ** d_stats_ref, double ** d_vrais, double ** d_vrais_snake, + uint2 ** d_liste_pixels, uint64 ** d_contribs_segments_blocs, + bool ** d_move + ) +{ + unsigned int taille = H*L; + timeval chrono; + + + //allocation cumuls en memoire GPU + tic(&chrono, NULL); + /* + MAX_PIX 20000 + MAX_NODES 10000 + MAX_LISTE_PIX 10000000 + */ + cudaMalloc( (void**) d_snake, MAX_NODES*sizeof(snake_node_gpu) ); + + cudaMalloc( (void**) d_img, taille*sizeof(unsigned short) ); + cudaMalloc( (void**) d_img_x, taille*sizeof(t_cumul_x) ); + cudaMalloc( (void**) d_img_x2, taille*sizeof(t_cumul_x2) ); + + cudaMalloc( (void**) d_freemanDiDj, 9*sizeof(int) ); + cudaMalloc( (void**) d_codeNoeud, 64*sizeof(int) ); + + cudaMalloc( (void**) d_stats_snake, 6*sizeof(int64)) ; + cudaMalloc( (void**) d_positions, 8*MAX_NODES*sizeof(uint4)) ; + cudaMalloc( (void**) d_contribs_segments, 3*16*MAX_NODES*sizeof(uint64)) ; + cudaMalloc( (void**) d_contribs_segments_blocs, (3*MAX_LISTE_PIX/32)*sizeof(uint64)) ; + cudaMalloc( (void**) d_freemans_centres, 16*MAX_NODES*sizeof(uint4)) ; + cudaMalloc( (void**) d_codes_segments, 16*MAX_NODES*sizeof(int)) ; + cudaMalloc( (void**) d_stats, 3*8*MAX_NODES*sizeof(int64)) ; + cudaMalloc( (void**) d_stats_ref, 3*MAX_NODES*sizeof(int64)) ; + cudaMalloc( (void**) d_vrais, 8*MAX_NODES*sizeof(double)) ; + cudaMalloc( (void**) d_move, MAX_NODES*sizeof(bool)) ; + cudaMalloc( (void**) d_nb_pix_max, sizeof(uint32)) ; + cudaMalloc( (void**) d_vrais_snake, sizeof(double)) ; + + cudaMalloc( (void**) d_liste_pixels, 16*5*(MAX_NODES)*sizeof(uint2) ); + + printf("TOTAL MEM = %ld octets\n", + (2*MAX_NODES*(sizeof(snake_node_gpu)+(8+16)*sizeof(uint4)+3*16*8+16*4+24*8+3*8+8*sizeof(double)+sizeof(bool)) + +(MAX_LISTE_PIX)*(sizeof(uint2)+1) + +taille*(8+sizeof(t_cumul_x)+sizeof(t_cumul_x2)) + +9*4+64*4+6*8+4+sizeof(double)) ); + + int64 * h_stats_snake = new int64[6]; + + toc(chrono, "temps alloc mem GPU"); + + /*detection-choix-initialisation de la carte GPU*/ + tic(&chrono, NULL) ; + cudaDeviceProp deviceProp; + deviceProp.major = 2; + deviceProp.minor = 0; + int dev; + cudaChooseDevice(&dev, &deviceProp); + cudaGetDeviceProperties(&deviceProp, dev); + if(deviceProp.major >= 2 ) + { + printf("Using Device %d: \"%s\"\n", dev, deviceProp.name); + cudaSetDevice(dev); + } + toc(chrono, "temps acces GPU") ; + + //copie tables correspondances freeman en mem GPU + tic(&chrono, NULL) ; + cudaMemcpy( *d_freemanDiDj, CORRESPONDANCE_Di_Dj_FREEMAN , 9*sizeof(int), cudaMemcpyHostToDevice); + cudaMemcpy( *d_codeNoeud, TABLE_CODAGE , 64*sizeof(unsigned int), cudaMemcpyHostToDevice); + toc(chrono, "temps transfert tables de codage") ; + + /*transfert image en global mem GPU*/ + tic(&chrono, NULL); + cudaMemcpy( *d_img, img_in[0], taille*sizeof(unsigned short), cudaMemcpyHostToDevice); + toc(chrono, "transfert image vers GPU"); + + //calculs images cumulees sur GPU + int blocs_max = 65536 ; + int bs = 256 ; //arbitraire, d'apres les observations c'est souvent l'optimu + unsigned int base = 0 ; + unsigned int bl_l = (L+bs-1)/bs ; + unsigned int nb_lines = blocs_max / bl_l ; + unsigned int lines ; + unsigned int tranches = ( 1 + H / nb_lines ) ; + nb_lines = (H +tranches -1)/ tranches ; // equilibre la taille des tranches + + dim3 threads(bs,1,1); + int smem = nextPow2(bl_l)*2; //smem pour le prefixscan des sommes de blocs (etape 2) + smem += smem >> DEC; + smem += smem >> DEC; + int smem_size = smem*sizeof(uint64); + uint64 * d_somblocs ; // sommes des cumuls par bloc de calcul + + + if(DEBUG_IMG_CUMUL) + { + printf("--- CALCULS IMAGES CUMULEES+STATS GPU ----\n"); + printf("\t%d threads par bloc -- %u blocs par ligne -- %u tranches -- %u lignes par tranche \n",bs, bl_l, tranches,nb_lines); + printf(" Smem totale pour cumuls : %d\n", CFI(bs)*(sizeof(t_cumul_x)+sizeof(t_cumul_x2)) ); + tic(&chrono, NULL); + } + //calculs cumuls generiques : necessitent 3 etapes / 3 kernels + cudaMalloc( (void**) &d_somblocs, 2*bl_l*nb_lines*sizeof(uint64) ); + cudaFuncSetCacheConfig(calcul_cumuls_gpu, cudaFuncCachePreferShared); + do + { + if ( H-base < nb_lines ) lines = H - base ; else lines = nb_lines ; + printf("base = ligne %d -- traitement de %d lignes \n", base, lines) ; + dim3 grid(bl_l*lines,1,1) ; + calcul_cumuls_gpu<<>>(*d_img, *d_img_x, *d_img_x2, H, L, d_somblocs, bl_l, base, lines) ; + scan_somblocs<<<2*lines, nextPow2(bl_l)/2, smem_size>>>(d_somblocs, bl_l) ; + add_soms_to_cumuls<<>>(*d_img_x, *d_img_x2, H, L, d_somblocs, bl_l, base, lines) ; + base += lines ; + } + while (base < H) ; + cudaFree(d_somblocs) ; + + //calcul des sommes totales N, sigX et sigX2 sur l'image + calcul_stats_image<<<1, 1>>>( *d_img_x, *d_img_x2, H, L, (uint64*)*d_stats_snake); + + + cudaThreadSynchronize() ; + toc(chrono, "\tTemps GPU"); + if(DEBUG_IMG_CUMUL) + { + + //allocation memoire CPU + t_cumul_x * img_x = new t_cumul_x [H*L]; + t_cumul_x2 * img_x2 = new t_cumul_x2 [H*L]; + + /*pour test comparaison*/ + t_cumul_x * img_xb = new t_cumul_x [H*L]; + t_cumul_x2 * img_x2b = new t_cumul_x2 [H*L]; + + cudaMemcpy( img_xb, *d_img_x, taille*sizeof(t_cumul_x), cudaMemcpyDeviceToHost); + cudaMemcpy( img_x2b, *d_img_x2, taille*sizeof(t_cumul_x2), cudaMemcpyDeviceToHost); + + //cumuls : etape 1 CPU + /* + for (int i=0; i>>(*d_snake, 140, H, L) ; + else if (nb_nodes == 40) genere_snake_rectangle_Nnodes_gpu<<< 1, 1>>>(*d_snake, (H+L)/20, H, L) ; + + int nnodes = nb_nodes ; + snake_node_gpu * h_snake = new snake_node_gpu[nnodes]; + snake_node * h_snake_ll = new snake_node[nnodes] ; + uint4 * h_liste_positions = new uint4[nnodes*8]; + double * h_vrais_snake = new double ; + //init les stats du snake + uint2 * d_liste_temp ; + t_sum_x2 * d_sompart ; + int tpb, bps, npixmax ; + + //calcul nb threads par bloc + npixmax = 2*(H+L-4*dist)/(nnodes-1) ; + tpb = nextPow2(npixmax) ; + if (tpb >= 256) tpb = 256 ;// /!\ le kernel <<< calcul_contrib...>>> ne supporte pas un bs>256 a cause de la shared-mem nécessaire + if (tpb < 32 ) tpb = 32 ; + tpb=128 ; + bps = (npixmax+tpb-1)/tpb ; + printf("PARAMS EXEC INIT : %d pix max, %d threads/bloc, %d blocs/seg, %d blocs/grille\n", npixmax, tpb, bps, nnodes*bps); + + //alloc + cudaMalloc((void**) &d_liste_temp, nnodes*bps*tpb*sizeof(uint2)); + cudaMalloc((void**) &d_sompart, 3*nnodes*bps*sizeof(t_sum_x2)); + cudaMalloc((void**) &d_stats_ref, 3*nnodes*sizeof(int64)); + + //DEBUG : pour forcer la mise à zero du tableau intermediaire d_stats_ref + int64 h_stats_ref[3*nnodes] ; + for (int a=0; a<3*nnodes ; a++) h_stats_ref[a] = 0 ; + cudaMemcpy( h_stats_ref, d_stats_ref, sizeof(int64), cudaMemcpyHostToDevice) ; + //fin forçage a 0 + + //DEBUG : pour forcer la mise à zero du tableau intermediaire d_sompart + t_sum_x2 h_sompart[ 3*nnodes*bps ] ; + for (int a=0; a<3*nnodes*bps ; a++) h_sompart[a] = 0 ; + cudaMemcpy( h_sompart, d_sompart, sizeof(t_sum_x2), cudaMemcpyHostToDevice) ; + //fin forçage a 0 + + calcul_contribs_segments_snake<<< nnodes*bps, tpb, (CFI(tpb))*(3*sizeof(t_sum_x2))>>> + (*d_snake, nnodes, + *d_img_x, *d_img_x2, + L, d_liste_temp, d_sompart, *d_freemanDiDj ); + + //TODO + //parametrer pour ne pas appeler qd tpb=1 + //oblige a modifier le kernel <<< calcul_contrib...>>> pour ecrire directement ds d_snake + // au lieu de d_sompart + somsom_snake<<< nnodes , 1 >>>(d_sompart, nnodes, bps, *d_snake); + + + calcul_stats_snake<<< 1 , 1 >>>(*d_snake, nnodes, *d_stats_snake, *d_vrais_snake, + *d_img_x, *d_img_x2, + *d_codeNoeud, L + ); + cudaThreadSynchronize() ; + toc(chrono, "\tTemps") ; + + /* + verif stats initiales du snake + */ + cudaMemcpy( h_vrais_snake, *d_vrais_snake, sizeof(double), cudaMemcpyDeviceToHost) ; + cudaMemcpy( h_stats_snake, *d_stats_snake, 6*sizeof(int64), cudaMemcpyDeviceToHost) ; + + printf("STATS SNAKE log vrais=%lf : c1=%lu - cx=%lu - cx2=%lu - N=%lu - SUMX=%lu - SUMX2=%lu\n", + *h_vrais_snake, + h_stats_snake[0], h_stats_snake[1], h_stats_snake[2], + h_stats_snake[3], h_stats_snake[4], h_stats_snake[5] ); + + /* + verif stats diminuees des contribs des 2 segments associes a chaque noeud + */ +#ifdef DEBUG_STATS_REF + cudaMemcpy( h_stats_ref, d_stats_ref, 3*nnodes*sizeof(int64), cudaMemcpyDeviceToHost) ; + cudaMemcpy( h_snake, *d_snake, nnodes*sizeof(snake_node_gpu), cudaMemcpyDeviceToHost) ; + + + printf("******* STATS DIMINUEES\n"); + for(int n=0; n0) ) + { + // /!\ penser a oter le test de prise en + // compte pour les pix sur la même ligne dans + // le kernel, sinon les comparaisons des + // sommes par colonne seront fausses + i = h_liste_pixels_segment[2*(b*bs + p)] ; + j = h_liste_pixels_segment[2*(b*bs + p) + 1] ; + c1 += img_1[i][j] ; + cx += img_x[i][j] ; + cx2+= img_x2[i][j]; + } + } + if ( ( c1 != h_sombloc[(16*n + s)*nblocs_seg + b ] ) || ( cx != h_sombloc[(16*n + s)*nblocs_seg + b + grid.x] ) + || ( cx2 != h_sombloc[ (16*n + s)*nblocs_seg + b + 2*grid.x] ) ) + printf("seg %d - %d pix : bloc %d -> CPU : %lu - %lu - %lu \t|| GPU : %lu - %lu - %lu \n", s, nb_pix, b, + c1, cx, cx2, h_sombloc[(16*n+s)*nblocs_seg + b], h_sombloc[(16*n+s)*nblocs_seg + b + grid.x], + h_sombloc[(16*n+s)*nblocs_seg + b + 2*grid.x]) ; + } + + } + for(int s=0; s<8; s++) + { + int nb_pix = calcul_liste_pixel_segment( h_liste_positions[8*idx_n+s].x, h_liste_positions[8*idx_n+s].y, + h_snake[idx_nsuiv].posi,h_snake[idx_nsuiv].posj, + h_liste_pixels_segment, 0); + for (int b=0; b0) ) + { + // /!\ penser a oter le test de prise en + // compte pour les pix sur la même ligne dans + // le kernel, sinon les comparaisons des + // sommes par colonne seront fausses + i = h_liste_pixels_segment[2*(b*bs + p)] ; + j = h_liste_pixels_segment[2*(b*bs + p) + 1] ; + c1 += img_1[i][j] ; + cx += img_x[i][j] ; + cx2+= img_x2[i][j]; + } + } + if ( ( c1 != h_sombloc[(16*n + s + 8)*nblocs_seg + b ] ) || ( cx != h_sombloc[(16*n + s + 8)*nblocs_seg + b + grid.x] ) + || ( cx2 != h_sombloc[ (16*n + s + 8)*nblocs_seg + b + 2*grid.x] ) ) + printf("seg %d - %d pix : bloc %d -> CPU : %lu - %lu - %lu \t|| GPU : %lu - %lu - %lu \n", s, nb_pix, b, + c1, cx, cx2, h_sombloc[(16*n+s+8)*nblocs_seg + b], h_sombloc[(16*n+s+8)*nblocs_seg + b + grid.x], + h_sombloc[(16*n+s+8)*nblocs_seg + b + 2*grid.x]) ; + } + + } + + } +#endif //DEBUG_SOMBLOCS + + + /* + + Test du calcul des sommes totales 'somsom' faites par le kernel 'somsom_full' + + */ + +#ifdef DEBUG_SOMSOM + printf("********* SOMMES TOTALES ***********\n"); + printf("bs = %d - grid = %d - intervalles = %d - nblocs_seg = %d - pairs = %d \n", bs, grid.x, n_interval, nblocs_seg, pairs); + for (int n=0; n< n_interval; n++) + { + idx_n = 2*n + !pairs ; + if (idx_n == 0) idx_nprec = nnodes - 1 ; + else idx_nprec = idx_n - 1 ; + if (idx_n == nnodes-1) idx_nsuiv = 0 ; + else idx_nsuiv = idx_n + 1 ; + printf("******** node %d\n", idx_n) ; + for(int s=0; s<8; s++) + { + int nb_pix = calcul_liste_pixel_segment(h_snake[idx_nprec].posi,h_snake[idx_nprec].posj, + h_liste_positions[8*idx_n+s].x, h_liste_positions[8*idx_n+s].y, + h_liste_pixels_segment, 0); + uint64 c1=0, cx=0, cx2=0 ; + for (int b=0; b0) ) + { + // /!\ penser a oter le test de prise en + // compte pour les pix sur la même ligne dans + // le kernel, sinon les comparaisons des + // sommes par colonne seront fausses + i = h_liste_pixels_segment[2*(b*bs + p)] ; + j = h_liste_pixels_segment[2*(b*bs + p) + 1] ; + c1 += img_1[i][j] ; + cx += img_x[i][j] ; + cx2+= img_x2[i][j]; + } + } + } + if ( ( c1 != h_somsom[3*(16*n + s)] ) || ( cx != h_somsom[3*(16*n + s) + 1] ) + || ( cx2 != h_somsom[3*(16*n + s) + 2] ) ) + printf("seg %d - %d pix -> CPU : %lu - %lu - %lu \t|| GPU : %lu - %lu - %lu \n", s, nb_pix, + c1, cx, cx2, h_somsom[3*(16*n + s)], h_somsom[3*(16*n + s) + 1], + h_somsom[3*(16*n + s) + 2]) ; + + } + + for(int s=0; s<8; s++) + { + int nb_pix = calcul_liste_pixel_segment( h_liste_positions[8*idx_n+s].x, h_liste_positions[8*idx_n+s].y, + h_snake[idx_nsuiv].posi,h_snake[idx_nsuiv].posj, + h_liste_pixels_segment, 0); + uint64 c1=0, cx=0, cx2=0 ; + for (int b=0; b0) ) + { + // /!\ penser a oter le test de prise en + // compte pour les pix sur la même ligne dans + // le kernel, sinon les comparaisons des + // sommes par colonne seront fausses + i = h_liste_pixels_segment[2*(b*bs + p)] ; + j = h_liste_pixels_segment[2*(b*bs + p) + 1] ; + c1 += img_1[i][j] ; + cx += img_x[i][j] ; + cx2+= img_x2[i][j]; + } + } + } + if ( ( c1 != h_somsom[3*(16*n + s + 8)] ) || ( cx != h_somsom[3*(16*n + s + 8) + 1] ) + || ( cx2 != h_somsom[3*(16*n + s + 8) + 2] ) ) + printf("seg %d - %d pix -> CPU : %lu - %lu - %lu \t|| GPU : %lu - %lu - %lu \n", s, nb_pix, + c1, cx, cx2, h_somsom[3*(16*n + s + 8)], h_somsom[3*(16*n + s + 8) + 1], + h_somsom[3*(16*n + s + 8) + 2]) ; + + } + + } + +#endif + + +#ifdef DEBUG_MV + printf("**** STATS - REF : %lf \n", *h_vrais_snake); + for(int n=0; n + + +extern "C"{ +#include "structures.h" +#include "lib_math.h" +#include "defines.h" +#include "lib_gpu.h" +#include "lib_snake_2_gpu.h" +} +#include "lib_test_gpu.h" +#include "lib_kernels_cumuls.cu" +#include "lib_kernel_snake_2_gpu.cu" + +#define DEBUG_IMG_CUMUL 1 +bool DISPLAY_ERR_IMG_CUMUL = 1; +//#define DEBUG_POSITIONS +//#define DEBUG_MOVE +//#define DEBUG_CRST +//#define DEBUG_MV +//#define DEBUG_SOMSOM +//#define DEBUG_SOMBLOCS +//#define DEBUG_LISTES +//#define DEBUG_STATS_REF + + +inline unsigned int nextPow2( unsigned int x ) { + --x; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + return ++x; +} + + +void cuda_init_img_cumul(unsigned short ** img_in, int H, int L, int nb_nodes, + unsigned short ** d_img, t_cumul_x ** d_img_x, t_cumul_x2 ** d_img_x2, + int ** d_freemanDiDj, int ** d_codeNoeud, + snake_node_gpu ** d_snake, uint32 ** d_nb_pix_max, + uint4 ** d_positions, uint64 ** d_contribs_segments, uint4 ** d_freemans_centres, + int ** d_codes_segments, int64 ** d_stats_snake, + int64 ** d_stats, int64 ** d_stats_ref, double ** d_vrais, double ** d_vrais_snake, + uint2 ** d_liste_pixels, uint64 ** d_contribs_segments_blocs, + bool ** d_move + ) +{ + unsigned int taille = H*L; + timeval chrono; + + + //allocation cumuls en memoire GPU + tic(&chrono, NULL); + /* + MAX_PIX 20000 + MAX_NODES 10000 + MAX_LISTE_PIX 10000000 + */ + cudaMalloc( (void**) d_snake, MAX_NODES*sizeof(snake_node_gpu) ); + + cudaMalloc( (void**) d_img, taille*sizeof(unsigned short) ); + cudaMalloc( (void**) d_img_x, taille*sizeof(t_cumul_x) ); + cudaMalloc( (void**) d_img_x2, taille*sizeof(t_cumul_x2) ); + + cudaMalloc( (void**) d_freemanDiDj, 9*sizeof(int) ); + cudaMalloc( (void**) d_codeNoeud, 64*sizeof(int) ); + + cudaMalloc( (void**) d_stats_snake, 6*sizeof(int64)) ; + cudaMalloc( (void**) d_positions, 8*MAX_NODES*sizeof(uint4)) ; + cudaMalloc( (void**) d_contribs_segments, 3*16*MAX_NODES*sizeof(uint64)) ; + cudaMalloc( (void**) d_contribs_segments_blocs, (3*MAX_LISTE_PIX/32)*sizeof(uint64)) ; + cudaMalloc( (void**) d_freemans_centres, 16*MAX_NODES*sizeof(uint4)) ; + cudaMalloc( (void**) d_codes_segments, 16*MAX_NODES*sizeof(int)) ; + cudaMalloc( (void**) d_stats, 3*8*MAX_NODES*sizeof(int64)) ; + cudaMalloc( (void**) d_stats_ref, 3*MAX_NODES*sizeof(int64)) ; + cudaMalloc( (void**) d_vrais, 8*MAX_NODES*sizeof(double)) ; + cudaMalloc( (void**) d_move, MAX_NODES*sizeof(bool)) ; + cudaMalloc( (void**) d_nb_pix_max, sizeof(uint32)) ; + cudaMalloc( (void**) d_vrais_snake, sizeof(double)) ; + + cudaMalloc( (void**) d_liste_pixels, 16*5*(MAX_NODES)*sizeof(uint2) ); + + printf("TOTAL MEM = %ld octets\n", + (2*MAX_NODES*(sizeof(snake_node_gpu)+(8+16)*sizeof(uint4)+3*16*8+16*4+24*8+3*8+8*sizeof(double)+sizeof(bool)) + +(MAX_LISTE_PIX)*(sizeof(uint2)+1) + +taille*(8+sizeof(t_cumul_x)+sizeof(t_cumul_x2)) + +9*4+64*4+6*8+4+sizeof(double)) ); + + int64 * h_stats_snake = new int64[6]; + + toc(chrono, "temps alloc mem GPU"); + + /*detection-choix-initialisation de la carte GPU*/ + tic(&chrono, NULL) ; + cudaDeviceProp deviceProp; + deviceProp.major = 1; + deviceProp.minor = 0; + int desiredMinorRevision = 3; + int dev; + cudaChooseDevice(&dev, &deviceProp); + cudaGetDeviceProperties(&deviceProp, dev); + if(deviceProp.major > 1 || deviceProp.minor >= desiredMinorRevision) + { + printf("Using Device %d: \"%s\"\n", dev, deviceProp.name); + cudaSetDevice(dev); + } + toc(chrono, "temps acces GPU") ; + + //copie tables correspondances freeman en mem GPU + tic(&chrono, NULL) ; + cudaMemcpy( *d_freemanDiDj, CORRESPONDANCE_Di_Dj_FREEMAN , 9*sizeof(int), cudaMemcpyHostToDevice); + cudaMemcpy( *d_codeNoeud, TABLE_CODAGE , 64*sizeof(unsigned int), cudaMemcpyHostToDevice); + toc(chrono, "temps transfert tables de codage") ; + + /*transfert image en global mem GPU*/ + tic(&chrono, NULL); + cudaMemcpy( *d_img, img_in[0], taille*sizeof(unsigned short), cudaMemcpyHostToDevice); + toc(chrono, "transfert image vers GPU"); + + //calculs images cumulees sur GPU + int blocs_max = 65536 ; + int bs = 256 ; //arbitraire, d'apres les observations c'est souvent l'optimu + unsigned int base = 0 ; + unsigned int bl_l = (L+bs-1)/bs ; + unsigned int nb_lines = blocs_max / bl_l ; + unsigned int lines ; + unsigned int tranches = ( 1 + H / nb_lines ) ; + nb_lines = (H +tranches -1)/ tranches ; // equilibre la taille des tranches + unsigned blocs = bl_l*nb_lines ; + dim3 threads(bs,1,1); + int smem = nextPow2(bl_l)*2; //smem pour le prefixscan des sommes de blocs (etape 2) + smem += smem >> DEC; + smem += smem >> DEC; + int smem_size = smem*sizeof(uint64); + uint64 * d_somblocs ; // sommes des cumuls par bloc de calcul + + + if(DEBUG_IMG_CUMUL) + { + printf("--- CALCULS IMAGES CUMULEES+STATS GPU ----\n"); + printf("\t%d threads par bloc -- %u blocs par ligne -- %u tranches -- %u lignes par tranche \n",bs, bl_l, tranches,nb_lines); + printf(" Smem totale pour cumuls : %d\n", CFI(bs)*(sizeof(t_cumul_x)+sizeof(t_cumul_x2)) ); + tic(&chrono, NULL); + } + //calculs cumuls generiques : necessitent 3 etapes / 3 kernels + cudaMalloc( (void**) &d_somblocs, 2*bl_l*nb_lines*sizeof(uint64) ); + cudaFuncSetCacheConfig(calcul_cumuls_gpu, cudaFuncCachePreferShared); + do + { + if ( H-base < nb_lines ) lines = H - base ; else lines = nb_lines ; + printf("base = ligne %d -- traitement de %d lignes \n", base, lines) ; + dim3 grid(bl_l*lines,1,1) ; + calcul_cumuls_gpu<<>>(*d_img, *d_img_x, *d_img_x2, H, L, d_somblocs, bl_l, base, lines) ; + scan_somblocs<<<2*lines, nextPow2(bl_l)/2, smem_size>>>(d_somblocs, bl_l) ; + add_soms_to_cumuls<<>>(*d_img_x, *d_img_x2, H, L, d_somblocs, bl_l, base, lines) ; + base += lines ; + } + while (base < H) ; + cudaFree(d_somblocs) ; + + //calcul des sommes totales N, sigX et sigX2 sur l'image + calcul_stats_image<<<1, 1>>>( *d_img_x, *d_img_x2, H, L, (uint64*)*d_stats_snake); + + + cudaThreadSynchronize() ; + toc(chrono, "\tTemps GPU"); + if(DEBUG_IMG_CUMUL) + { + + //allocation memoire CPU + t_cumul_x * img_x = new t_cumul_x [H*L]; + t_cumul_x2 * img_x2 = new t_cumul_x2 [H*L]; + + /*pour test comparaison*/ + t_cumul_x * img_xb = new t_cumul_x [H*L]; + t_cumul_x2 * img_x2b = new t_cumul_x2 [H*L]; + + cudaMemcpy( img_xb, *d_img_x, taille*sizeof(t_cumul_x), cudaMemcpyDeviceToHost); + cudaMemcpy( img_x2b, *d_img_x2, taille*sizeof(t_cumul_x2), cudaMemcpyDeviceToHost); + + //cumuls : etape 1 CPU + /* + for (int i=0; i>>(*d_snake, 140, H, L) ; + else if (nb_nodes == 40) genere_snake_rectangle_Nnodes_gpu<<< 1, 1>>>(*d_snake, (H+L)/20, H, L) ; + + int nnodes = nb_nodes ; + snake_node_gpu * h_snake = new snake_node_gpu[nnodes]; + snake_node * h_snake_ll = new snake_node[nnodes] ; + uint4 * h_liste_positions = new uint4[nnodes*8]; + double * h_vrais_snake = new double ; + //init les stats du snake + uint2 * d_liste_temp ; + t_sum_x2 * d_sompart ; + int tpb, bps, npixmax ; + + //calcul nb threads par bloc + npixmax = 2*(H+L-4*dist)/(nnodes-1) ; + tpb = nextPow2(npixmax) ; + if (tpb >= 256) tpb = 256 ;// /!\ le kernel <<< calcul_contrib...>>> ne supporte pas un bs>256 a cause de la shared-mem nécessaire + if (tpb < 32 ) tpb = 32 ; + tpb=128 ; + bps = (npixmax+tpb-1)/tpb ; + printf("PARAMS EXEC INIT : %d pix max, %d threads/bloc, %d blocs/seg, %d blocs/grille\n", npixmax, tpb, bps, nnodes*bps); + + //alloc + cudaMalloc((void**) &d_liste_temp, nnodes*bps*tpb*sizeof(uint2)); + cudaMalloc((void**) &d_sompart, 3*nnodes*bps*sizeof(t_sum_x2)); + cudaMalloc((void**) &d_stats_ref, 3*nnodes*sizeof(int64)); + + //DEBUG : pour forcer la mise à zero du tableau intermediaire d_stats_ref + int64 h_stats_ref[3*nnodes] ; + for (int a=0; a<3*nnodes ; a++) h_stats_ref[a] = 0 ; + cudaMemcpy( h_stats_ref, d_stats_ref, sizeof(int64), cudaMemcpyHostToDevice) ; + //fin forçage a 0 + + //DEBUG : pour forcer la mise à zero du tableau intermediaire d_sompart + t_sum_x2 h_sompart[ 3*nnodes*bps ] ; + for (int a=0; a<3*nnodes*bps ; a++) h_sompart[a] = 0 ; + cudaMemcpy( h_sompart, d_sompart, sizeof(t_sum_x2), cudaMemcpyHostToDevice) ; + //fin forçage a 0 + + calcul_contribs_segments_snake<<< nnodes*bps, tpb, (CFI(tpb))*(3*sizeof(t_sum_x2))>>> + (*d_snake, nnodes, + *d_img_x, *d_img_x2, + L, d_liste_temp, d_sompart, *d_freemanDiDj ); + + //TODO + //parametrer pour ne pas appeler qd tpb=1 + //oblige a modifier le kernel <<< calcul_contrib...>>> pour ecrire directement ds d_snake + // au lieu de d_sompart + somsom_snake<<< nnodes , 1 >>>(d_sompart, nnodes, bps, *d_snake); + + + calcul_stats_snake<<< 1 , 1 >>>(*d_snake, nnodes, *d_stats_snake, *d_vrais_snake, + *d_img_x, *d_img_x2, + *d_codeNoeud, L + ); + cudaThreadSynchronize() ; + toc(chrono, "\tTemps") ; + + /* + verif stats initiales du snake + */ + cudaMemcpy( h_vrais_snake, *d_vrais_snake, sizeof(double), cudaMemcpyDeviceToHost) ; + cudaMemcpy( h_stats_snake, *d_stats_snake, 6*sizeof(int64), cudaMemcpyDeviceToHost) ; + + printf("STATS SNAKE log vrais=%lf : c1=%lu - cx=%lu - cx2=%lu - N=%lu - SUMX=%lu - SUMX2=%lu\n", + *h_vrais_snake, + h_stats_snake[0], h_stats_snake[1], h_stats_snake[2], + h_stats_snake[3], h_stats_snake[4], h_stats_snake[5] ); + + /* + verif stats diminuees des contribs des 2 segments associes a chaque noeud + */ +#ifdef DEBUG_STATS_REF + cudaMemcpy( h_stats_ref, d_stats_ref, 3*nnodes*sizeof(int64), cudaMemcpyDeviceToHost) ; + cudaMemcpy( h_snake, *d_snake, nnodes*sizeof(snake_node_gpu), cudaMemcpyDeviceToHost) ; + + + printf("******* STATS DIMINUEES\n"); + for(int n=0; n0) ) + { + // /!\ penser a oter le test de prise en + // compte pour les pix sur la même ligne dans + // le kernel, sinon les comparaisons des + // sommes par colonne seront fausses + i = h_liste_pixels_segment[2*(b*bs + p)] ; + j = h_liste_pixels_segment[2*(b*bs + p) + 1] ; + c1 += img_1[i][j] ; + cx += img_x[i][j] ; + cx2+= img_x2[i][j]; + } + } + if ( ( c1 != h_sombloc[(16*n + s)*nblocs_seg + b ] ) || ( cx != h_sombloc[(16*n + s)*nblocs_seg + b + grid.x] ) + || ( cx2 != h_sombloc[ (16*n + s)*nblocs_seg + b + 2*grid.x] ) ) + printf("seg %d - %d pix : bloc %d -> CPU : %lu - %lu - %lu \t|| GPU : %lu - %lu - %lu \n", s, nb_pix, b, + c1, cx, cx2, h_sombloc[(16*n+s)*nblocs_seg + b], h_sombloc[(16*n+s)*nblocs_seg + b + grid.x], + h_sombloc[(16*n+s)*nblocs_seg + b + 2*grid.x]) ; + } + + } + for(int s=0; s<8; s++) + { + int nb_pix = calcul_liste_pixel_segment( h_liste_positions[8*idx_n+s].x, h_liste_positions[8*idx_n+s].y, + h_snake[idx_nsuiv].posi,h_snake[idx_nsuiv].posj, + h_liste_pixels_segment, 0); + for (int b=0; b0) ) + { + // /!\ penser a oter le test de prise en + // compte pour les pix sur la même ligne dans + // le kernel, sinon les comparaisons des + // sommes par colonne seront fausses + i = h_liste_pixels_segment[2*(b*bs + p)] ; + j = h_liste_pixels_segment[2*(b*bs + p) + 1] ; + c1 += img_1[i][j] ; + cx += img_x[i][j] ; + cx2+= img_x2[i][j]; + } + } + if ( ( c1 != h_sombloc[(16*n + s + 8)*nblocs_seg + b ] ) || ( cx != h_sombloc[(16*n + s + 8)*nblocs_seg + b + grid.x] ) + || ( cx2 != h_sombloc[ (16*n + s + 8)*nblocs_seg + b + 2*grid.x] ) ) + printf("seg %d - %d pix : bloc %d -> CPU : %lu - %lu - %lu \t|| GPU : %lu - %lu - %lu \n", s, nb_pix, b, + c1, cx, cx2, h_sombloc[(16*n+s+8)*nblocs_seg + b], h_sombloc[(16*n+s+8)*nblocs_seg + b + grid.x], + h_sombloc[(16*n+s+8)*nblocs_seg + b + 2*grid.x]) ; + } + + } + + } +#endif //DEBUG_SOMBLOCS + + + /* + + Test du calcul des sommes totales 'somsom' faites par le kernel 'somsom_full' + + */ + +#ifdef DEBUG_SOMSOM + printf("********* SOMMES TOTALES ***********\n"); + printf("bs = %d - grid = %d - intervalles = %d - nblocs_seg = %d - pairs = %d \n", bs, grid.x, n_interval, nblocs_seg, pairs); + for (int n=0; n< n_interval; n++) + { + idx_n = 2*n + !pairs ; + if (idx_n == 0) idx_nprec = nnodes - 1 ; + else idx_nprec = idx_n - 1 ; + if (idx_n == nnodes-1) idx_nsuiv = 0 ; + else idx_nsuiv = idx_n + 1 ; + printf("******** node %d\n", idx_n) ; + for(int s=0; s<8; s++) + { + int nb_pix = calcul_liste_pixel_segment(h_snake[idx_nprec].posi,h_snake[idx_nprec].posj, + h_liste_positions[8*idx_n+s].x, h_liste_positions[8*idx_n+s].y, + h_liste_pixels_segment, 0); + uint64 c1=0, cx=0, cx2=0 ; + for (int b=0; b0) ) + { + // /!\ penser a oter le test de prise en + // compte pour les pix sur la même ligne dans + // le kernel, sinon les comparaisons des + // sommes par colonne seront fausses + i = h_liste_pixels_segment[2*(b*bs + p)] ; + j = h_liste_pixels_segment[2*(b*bs + p) + 1] ; + c1 += img_1[i][j] ; + cx += img_x[i][j] ; + cx2+= img_x2[i][j]; + } + } + } + if ( ( c1 != h_somsom[3*(16*n + s)] ) || ( cx != h_somsom[3*(16*n + s) + 1] ) + || ( cx2 != h_somsom[3*(16*n + s) + 2] ) ) + printf("seg %d - %d pix -> CPU : %lu - %lu - %lu \t|| GPU : %lu - %lu - %lu \n", s, nb_pix, + c1, cx, cx2, h_somsom[3*(16*n + s)], h_somsom[3*(16*n + s) + 1], + h_somsom[3*(16*n + s) + 2]) ; + + } + + for(int s=0; s<8; s++) + { + int nb_pix = calcul_liste_pixel_segment( h_liste_positions[8*idx_n+s].x, h_liste_positions[8*idx_n+s].y, + h_snake[idx_nsuiv].posi,h_snake[idx_nsuiv].posj, + h_liste_pixels_segment, 0); + uint64 c1=0, cx=0, cx2=0 ; + for (int b=0; b0) ) + { + // /!\ penser a oter le test de prise en + // compte pour les pix sur la même ligne dans + // le kernel, sinon les comparaisons des + // sommes par colonne seront fausses + i = h_liste_pixels_segment[2*(b*bs + p)] ; + j = h_liste_pixels_segment[2*(b*bs + p) + 1] ; + c1 += img_1[i][j] ; + cx += img_x[i][j] ; + cx2+= img_x2[i][j]; + } + } + } + if ( ( c1 != h_somsom[3*(16*n + s + 8)] ) || ( cx != h_somsom[3*(16*n + s + 8) + 1] ) + || ( cx2 != h_somsom[3*(16*n + s + 8) + 2] ) ) + printf("seg %d - %d pix -> CPU : %lu - %lu - %lu \t|| GPU : %lu - %lu - %lu \n", s, nb_pix, + c1, cx, cx2, h_somsom[3*(16*n + s + 8)], h_somsom[3*(16*n + s + 8) + 1], + h_somsom[3*(16*n + s + 8) + 2]) ; + + } + + } + +#endif + + +#ifdef DEBUG_MV + printf("**** STATS - REF : %lf \n", *h_vrais_snake); + for(int n=0; n> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + return ++x; +} + + +void cuda_init_img_cumul(unsigned short ** img_in, int H, int L, int nb_nodes, + unsigned short ** d_img, t_cumul_x ** d_img_x, t_cumul_x2 ** d_img_x2, + int ** d_freemanDiDj, int ** d_codeNoeud, + snake_node_gpu ** d_snake, uint32 ** d_nb_pix_max, + uint4 ** d_positions, uint64 ** d_contribs_segments, uint4 ** d_freemans_centres, + int ** d_codes_segments, int64 ** d_stats_snake, + int64 ** d_stats, int64 ** d_stats_ref, double ** d_vrais, double ** d_vrais_snake, + uint2 ** d_liste_pixels, uint64 ** d_contribs_segments_blocs, + bool ** d_move + ); + +void affiche_snake_gpu(int **image, snake_node_gpu *snake, int nnodes_, int valseg, int valnoeud, + uint32 *liste_pixel_segment); + +#endif //_LIB_GPU_H_ diff --git a/src/lib_gpu.h~ b/src/lib_gpu.h~ new file mode 100644 index 0000000..9cfb894 --- /dev/null +++ b/src/lib_gpu.h~ @@ -0,0 +1,20 @@ +#ifndef _LIB_GPU_H_ +#define _LIB_GPU_H_ + +inline unsigned int nextPow2( unsigned int x ); + +void cuda_init_img_cumul(unsigned short ** img_in, int H, int L, int nb_nodes, + unsigned short ** d_img, t_cumul_x ** d_img_x, t_cumul_x2 ** d_img_x2, + int ** d_freemanDiDj, int ** d_codeNoeud, + snake_node_gpu ** d_snake, uint32 ** d_nb_pix_max, + uint4 ** d_positions, uint64 ** d_contribs_segments, uint4 ** d_freemans_centres, + int ** d_codes_segments, int64 ** d_stats_snake, + int64 ** d_stats, int64 ** d_stats_ref, double ** d_vrais, double ** d_vrais_snake, + uint2 ** d_liste_pixels, uint64 ** d_contribs_segments_blocs, + bool ** d_move + ); + +void affiche_snake_gpu(int **image, snake_node_gpu *snake, int nnodes_, int valseg, int valnoeud, + uint32 *liste_pixel_segment); + +#endif //_LIB_GPU_H_ diff --git a/src/lib_images.c b/src/lib_images.c new file mode 100644 index 0000000..0aaf81e --- /dev/null +++ b/src/lib_images.c @@ -0,0 +1,305 @@ +/** + * \file lib_images.c + * \brief Librairie de lecture/ecriture d'image ppm/pgm 8/16 bits + * \author NB - PhyTI + * \version x.x + * \date 20 decembre 2009 + * + */ + +#include +#include +#include +#include + +#include "lib_images.h" +#include "lib_math.h" + + +/** + * \fn int type_image_ppm(int *prof, int *i_dim, int *j_dim, int *level, char *file_name) + * \brief Fonction qui renvoie le type de l'image ppm et des caracteristiques + * + * \param[out] prof profondeur de l'image 1 pour pgm 3 pour ppm, 0 sinon + * \param[out] i_dim renvoie la dimension verticale de l'image (si NULL, renvoie que prof) + * \param[out] j_dim renvoie la dimension horizontale de l'image + * \param[out] level renvoie la dynamique de l'image + * \param[in] file_name fichier image + * + * \return 1 si ok O sinon + * + */ +int type_image_ppm(int *prof, uint32 *i_dim, uint32 *j_dim, int *level, char *file_name) +{ + char buffer[SIZE_LINE_TEXT] ; + FILE *file ; + + *prof = 0 ; + + file = fopen(file_name, "rb"); + if (file == NULL) + return 0 ; + + // lecture de la premiere ligne + fgets(buffer, SIZE_LINE_TEXT, file); + + /* pgm */ + if ((buffer[0] == 'P') & (buffer[1] == '5')) + *prof = 1 ; // GGG + /* ppm */ + if ((buffer[0] == 'P') & (buffer[1] == '6')) + *prof = 3 ; // RVBRVBRVB + + /* type non gere */ + if (*prof == 0) return 0 ; + + /* pour une utilisation du type */ + /* ret = type_image_ppm(&prof, NULL, NULL, NULL, file_name) */ + if (i_dim == NULL) + return 1 ; + + /* on saute les lignes de commentaires */ + fgets(buffer, SIZE_LINE_TEXT, file); + while ((buffer[0] == '#')|(buffer[0] == '\n')) + fgets(buffer, SIZE_LINE_TEXT, file); + + /* on lit les dimensions de l'image */ + sscanf(buffer, "%d %d", j_dim, i_dim) ; + fgets(buffer, SIZE_LINE_TEXT, file); + sscanf(buffer, "%d", level) ; + + + fclose(file); + return 1 ; +} + + + + +/** + * \fn void load_pgm2int(int **image, int i_dim, int j_dim, + * int nb_level, char *fichier_image) + * \brief lecture pgm 8 ou 16 bits + * + * \param[out] image + * \param[in] i_dim dimension verticale de l'image + * \param[in] j_dim dimension horizontale de l'image + * \param[in] nb_level dynamique de l'image + * \param[in] fichier_image fichier image + * + * + */ +void load_pgm2int(int **image, int i_dim, int j_dim, + int nb_level, char *fichier_image) +{ + int i, j ; + char buffer[SIZE_LINE_TEXT] ; + unsigned char *ligne; + unsigned short *ligne2; + FILE *file = fopen(fichier_image, "rb"); + + fgets(buffer, SIZE_LINE_TEXT, file); /* P5 */ + /* on saute les lignes de commentaires */ + fgets(buffer, SIZE_LINE_TEXT, file); + while ((buffer[0] == '#')|(buffer[0] == '\n')) + fgets(buffer, SIZE_LINE_TEXT, file); + /* derniere ligne lue : dimensions */ + fgets(buffer, SIZE_LINE_TEXT, file); /* dynamique */ + + /* data */ + + if (nb_level < 256) + { + // fichier en char, on converti au format int + ligne = malloc(sizeof(unsigned char)*j_dim) ; + + for (i=0;i +#include +#include +#include + +#include "lib_images.h" +#include "lib_math.h" + + +/** + * \fn int type_image_ppm(int *prof, int *i_dim, int *j_dim, int *level, char *file_name) + * \brief Fonction qui renvoie le type de l'image ppm et des caracteristiques + * + * \param[out] prof profondeur de l'image 1 pour pgm 3 pour ppm, 0 sinon + * \param[out] i_dim renvoie la dimension verticale de l'image (si NULL, renvoie que prof) + * \param[out] j_dim renvoie la dimension horizontale de l'image + * \param[out] level renvoie la dynamique de l'image + * \param[in] file_name fichier image + * + * \return 1 si ok O sinon + * + */ +int type_image_ppm(int *prof, uint32 *i_dim, uint32 *j_dim, int *level, char *file_name) +{ + char buffer[SIZE_LINE_TEXT] ; + FILE *file ; + + *prof = 0 ; + + file = fopen(file_name, "rb"); + if (file == NULL) + return 0 ; + + // lecture de la premiere ligne + fgets(buffer, SIZE_LINE_TEXT, file); + + /* pgm */ + if ((buffer[0] == 'P') & (buffer[1] == '5')) + *prof = 1 ; // GGG + /* ppm */ + if ((buffer[0] == 'P') & (buffer[1] == '6')) + *prof = 3 ; // RVBRVBRVB + + /* type non gere */ + if (*prof == 0) return 0 ; + + /* pour une utilisation du type */ + /* ret = type_image_ppm(&prof, NULL, NULL, NULL, file_name) */ + if (i_dim == NULL) + return 1 ; + + /* on saute les lignes de commentaires */ + fgets(buffer, SIZE_LINE_TEXT, file); + while ((buffer[0] == '#')|(buffer[0] == '\n')) + fgets(buffer, SIZE_LINE_TEXT, file); + + /* on lit les dimensions de l'image */ + sscanf(buffer, "%d %d", j_dim, i_dim) ; + fgets(buffer, SIZE_LINE_TEXT, file); + sscanf(buffer, "%d", level) ; + + + fclose(file); + return 1 ; +} + + + + +/** + * \fn void load_pgm2int(int **image, int i_dim, int j_dim, + * int nb_level, char *fichier_image) + * \brief lecture pgm 8 ou 16 bits + * + * \param[out] image + * \param[in] i_dim dimension verticale de l'image + * \param[in] j_dim dimension horizontale de l'image + * \param[in] nb_level dynamique de l'image + * \param[in] fichier_image fichier image + * + * + */ +void load_pgm2int(int **image, int i_dim, int j_dim, + int nb_level, char *fichier_image) +{ + int i, j ; + char buffer[SIZE_LINE_TEXT] ; + unsigned char *ligne; + unsigned short *ligne2; + FILE *file = fopen(fichier_image, "rb"); + + fgets(buffer, SIZE_LINE_TEXT, file); /* P5 */ + /* on saute les lignes de commentaires */ + fgets(buffer, SIZE_LINE_TEXT, file); + while ((buffer[0] == '#')|(buffer[0] == '\n')) + fgets(buffer, SIZE_LINE_TEXT, file); + /* derniere ligne lue : dimensions */ + fgets(buffer, SIZE_LINE_TEXT, file); /* dynamique */ + + /* data */ + + if (nb_level < 256) + { + // fichier en char, on converti au format int + ligne = malloc(sizeof(unsigned char)*j_dim) ; + + for (i=0;i limite ) + d_snake[n].posi = d_snake[n-1].posi + inch ; + else + d_snake[n].posi = d_snake[n-1].posi + inch/2 ; + d_snake[n].posj = dist_bords ; + d_snake[n-1].nb_pixels = d_snake[n].posi - d_snake[n-1].posi ; + n++ ; i++ ; + } + /* n1 */ + d_snake[n].posi = i_dim - dist_bords ; + d_snake[n].posj = dist_bords ; + d_snake[n-1].nb_pixels = d_snake[n].posi - d_snake[n-1].posi ; + n++ ; + /*entre S1 et S2*/ + i = 0 ; + while (i< nb_node_seg) + { + if ( (j_dim - dist_bords) - (d_snake[n-1].posj + incl) > limite ) + d_snake[n].posj = d_snake[n-1].posj + incl ; + else + d_snake[n].posj = d_snake[n-1].posj + incl/2 ; + d_snake[n].posi = i_dim - dist_bords ; + d_snake[n-1].nb_pixels = d_snake[n].posj - d_snake[n-1].posj ; + n++ ; i++ ; + } + /* n2 */ + d_snake[n].posi = i_dim - dist_bords ; + d_snake[n].posj = j_dim - dist_bords ; + d_snake[n-1].nb_pixels = d_snake[n].posj - d_snake[n-1].posj ; + n++ ; + /*entre S2 et S3*/ + i = 0 ; + while (i< nb_node_seg) + { + if ( (d_snake[n-1].posi - inch) - dist_bords > limite ) + d_snake[n].posi = d_snake[n-1].posi - inch ; + else + d_snake[n].posi = d_snake[n-1].posi - inch/2 ; + d_snake[n].posj = j_dim - dist_bords ; + d_snake[n-1].nb_pixels = d_snake[n-1].posi - d_snake[n].posi ; + n++ ; i++ ; + } + /* n3 */ + d_snake[n].posi = dist_bords ; + d_snake[n].posj = j_dim - dist_bords ; + d_snake[n-1].nb_pixels = d_snake[n-1].posi - d_snake[n].posi ; + n++ ; + /*entre S3 et S0*/ + i = 0 ; + while (i< nb_node_seg) + { + if ( (d_snake[n-1].posj - incl) - dist_bords > limite) + d_snake[n].posj = d_snake[n-1].posj - incl ; + else + d_snake[n].posj = d_snake[n-1].posj - incl/2 ; + d_snake[n].posi = dist_bords ; + d_snake[n-1].nb_pixels = d_snake[n-1].posj - d_snake[n].posj ; + n++ ; i++ ; + } + d_snake[n-1].nb_pixels = d_snake[n-1].posj - d_snake[0].posj ; + for (i=0; i taille smem dynamique + t_sum_x* scumuls_x = (t_sum_x*) &scumuls_1[CFI(blockDim.x)] ; + t_sum_x2* scumuls_x2 = (t_sum_x2*) &scumuls_x[CFI(blockDim.x)] ; + + //indices des noeuds + uint x1, y1, x2, y2 ; + int n1, n2 ; + + n1 = segment ; + n2 = segment +1 ; + //gestion du bouclage du snake + if (n2 >= nb_nodes) n2 = 0 ; + + //affectation des differentes positions aux différents segments 'blocs de threads' + x1 = d_snake[n1].posj ; + y1 = d_snake[n1].posi ; + x2 = d_snake[n2].posj ; + y2 = d_snake[n2].posi ; + + //params des deplacements + int dx=x2-x1; + int dy=y2-y1; + uint abs_dx = ABS(dx); + uint abs_dy = ABS(dy); + uint nb_pix = abs_dy>abs_dx?(abs_dy+1):(abs_dx+1); // alternative -> lecture ds liste_points[] + int incx=0, incy=0; + uint2 p ; + int xprec, xsuiv ; + + //calcul liste des pixels du segment (x1,y1)-(x2,y2) + if (dy > 0) incy=1; else incy=-1 ; + if (dx > 0) incx=1; else incx=-1 ; + + if (tis < nb_pix){ + if (abs_dy > abs_dx){ + //1 thread par ligne + double k = (double)dx/dy ; + p.x = y1 + incy*tis ; + p.y = x1 + floor((double)incy*k*tis+0.5) ; + //enreg. coords. pixels en global mem pour freemans + + if ((tis < 2)||(tis > nb_pix - 3)||(tis == nb_pix/2)) + { + liste_pix[idx].x = p.x ; + liste_pix[idx].y = p.y ; + } + + } else { + //1 thread par colonne + double k=(double)dy/dx ; + p.x = y1 + floor((double)(incx*k*tis)+0.5) ; + p.y = x1 + incx*tis ; + if ( tis > 0 ){ + xsuiv = y1 + floor((double)(incx*k*(tis+1))+0.5) ; + xprec = y1 + floor((double)(incx*k*(tis-1))+0.5) ; + } + //enreg. coords. pixels en global mem pour freeman + //TODO + //on peut calculer les freemans des segments + //sans stocker l'ensemble des valeurs des pixels + //juste avec les derivees aux extremites a calculer ici + + if ((tis < 2)||(tis > nb_pix - 3)||(tis == nb_pix/2)) + { + liste_pix[idx].x = p.x ; + liste_pix[idx].y = p.y ; + } + + } + } + __syncthreads(); + + //calcul contribs individuelles des pixels + + if ( (tis >0) && (tis < nb_pix-1) + && ( (abs_dy <= abs_dx) + && ( (xprec > p.x) || (xsuiv > p.x)) + || (abs_dy > abs_dx) ) ) + { + int pos = p.x * l + p.y ; + scumuls_1[ CFI(tib)] = 1+p.y; + scumuls_x[ CFI(tib)] = cumul_x[ pos ] ; + scumuls_x2[CFI(tib)] = cumul_x2[ pos ]; + } else { + scumuls_1[ CFI(tib)] = 0; + scumuls_x[ CFI(tib)] = 0; + scumuls_x2[CFI(tib)] = 0; + } + + __syncthreads(); + //somme des contribs individuelles + // unroll des sommes partielles en smem + + if (blockSize >= 512) { + if (tib < 256) { + scumuls_1[ CFI(tib)] += scumuls_1[ CFI(tib + 256) ]; + scumuls_x[ CFI(tib)] += scumuls_x[ CFI(tib + 256) ]; + scumuls_x2[CFI(tib)] += scumuls_x2[CFI(tib + 256) ]; + } + __syncthreads(); + } + + if (blockSize >= 256) { + if (tib < 128) { + scumuls_1[ CFI(tib)] += scumuls_1[ CFI(tib + 128) ]; + scumuls_x[ CFI(tib)] += scumuls_x[ CFI(tib + 128) ]; + scumuls_x2[CFI(tib)] += scumuls_x2[CFI(tib + 128) ]; + } + __syncthreads(); + } + if (blockSize >= 128) { + if (tib < 64) { + scumuls_1[ CFI(tib)] += scumuls_1[ CFI(tib + 64) ]; + scumuls_x[ CFI(tib)] += scumuls_x[ CFI(tib + 64) ]; + scumuls_x2[CFI(tib)] += scumuls_x2[CFI(tib + 64) ]; + } + __syncthreads(); + } + + //32 threads <==> 1 warp + if (tib < 32) + { + { + scumuls_1[ CFI(tib)] += scumuls_1[ CFI(tib + 32) ]; + scumuls_x[ CFI(tib)] += scumuls_x[ CFI(tib + 32) ]; + scumuls_x2[CFI(tib)] += scumuls_x2[CFI(tib + 32) ]; + } + { + scumuls_1[ CFI(tib)] += scumuls_1[ CFI(tib + 16) ]; + scumuls_x[ CFI(tib)] += scumuls_x[ CFI(tib + 16) ]; + scumuls_x2[CFI(tib)] += scumuls_x2[CFI(tib + 16) ]; + } + { + scumuls_1[ CFI(tib)] += scumuls_1[ CFI(tib + 8) ]; + scumuls_x[ CFI(tib)] += scumuls_x[ CFI(tib + 8) ]; + scumuls_x2[CFI(tib)] += scumuls_x2[CFI(tib + 8) ]; + } + scumuls_1[ CFI(tib)] += scumuls_1[ CFI(tib + 4) ]; + scumuls_x[ CFI(tib)] += scumuls_x[ CFI(tib + 4) ]; + scumuls_x2[CFI(tib)] += scumuls_x2[CFI(tib + 4) ]; + + scumuls_1[ CFI(tib)] += scumuls_1[ CFI(tib + 2) ]; + scumuls_x[ CFI(tib)] += scumuls_x[ CFI(tib + 2) ]; + scumuls_x2[CFI(tib)] += scumuls_x2[CFI(tib + 2) ]; + + scumuls_1[ CFI(tib)] += scumuls_1[ CFI(tib + 1) ]; + scumuls_x[ CFI(tib)] += scumuls_x[ CFI(tib + 1) ]; + scumuls_x2[CFI(tib)] += scumuls_x2[CFI(tib + 1) ]; + } + + // resultat sommes partielles en gmem + if (tib == 0) { + gsombloc[ blockIdx.x ] = (t_sum_x2) scumuls_1[0]; + gsombloc[ blockIdx.x + gridDim.x ] = (t_sum_x2) scumuls_x[0]; + gsombloc[ blockIdx.x + 2*gridDim.x ] = (t_sum_x2) scumuls_x2[0]; + } + + //calculs freemans, centre et code segment + //1 uint4 par segment + + int Di, Dj; + if (tis == 0){ + Di = 1 + liste_pix[idx+1].x - liste_pix[idx].x ; + Dj = 1 + liste_pix[idx+1].y - liste_pix[idx].y ; + d_snake[segment].freeman_out = d_table_freeman[3*Di + Dj] ; + //code seg + if (dy > 0 ) d_snake[segment].code_segment = -1 ; + if (dy < 0 ) d_snake[segment].code_segment = 1 ; + if (dy == 0) d_snake[segment].code_segment = 0 ; + } + + if (tis == nb_pix-1){ + Di = 1 + liste_pix[idx].x - liste_pix[idx-1].x ; + Dj = 1 + liste_pix[idx].y - liste_pix[idx-1].y; + d_snake[segment].freeman_in = d_table_freeman[3*Di + Dj] ; + } + + if (tis == (nb_pix/2)){ + d_snake[segment].centre_i = liste_pix[idx].x ; + d_snake[segment].centre_j = liste_pix[idx].y ; + } +} + +/* + sommme des contribs par bloc -> contribs segment, pour le snake + + execution sur : 1bloc / 1 thread par segment + */ + +__global__ void somsom_snake(t_sum_x2 * somblocs, int nb_nodes, unsigned int nb_bl_seg, snake_node_gpu * d_snake){ + + t_sum_x2 sdata[3]; + unsigned int seg = blockIdx.x ; + + //un thread par segment + { + sdata[0] = 0; + sdata[1] = 0; + sdata[2] = 0; + } + + for (int b=0; b < nb_bl_seg ; b++){ + sdata[0] += somblocs[seg*nb_bl_seg + b]; + sdata[1] += somblocs[(seg + nb_nodes)*nb_bl_seg + b]; + sdata[2] += somblocs[(seg + 2*nb_nodes)*nb_bl_seg + b]; + } + + //totaux en gmem + { + d_snake[seg].sum_1 = sdata[0]; + d_snake[seg].sum_x = sdata[1]; + d_snake[seg].sum_x2 = sdata[2]; + } +} + +__device__ double codage_gl_gauss(uint64 stat_sum_1, uint64 stat_sum_x, uint64 stat_sum_x2, + uint64 n_dim, uint64 SUM_X, uint64 SUM_X2){ + uint64 stat_sum_xe ; /* somme des xn region exterieure */ + uint32 ne ; /* nombre de pixel region exterieure */ + double sigi2, sige2; /* variance region interieure et exterieure */ + + /* variance des valeurs des niveaux de gris a l'interieur du snake */ + sigi2 = + ((double)stat_sum_x2/(double)stat_sum_1) - + ((double)stat_sum_x/(uint64)stat_sum_1)*((double)stat_sum_x/(uint64)stat_sum_1) ; + + /* variance des valeurs des niveaux de gris a l'exterieur du snake */ + ne = n_dim-stat_sum_1 ; + stat_sum_xe = SUM_X - stat_sum_x ; + sige2 = + ((double)SUM_X2-stat_sum_x2)/(double)ne - + ((double)stat_sum_xe/(uint64)ne)*((double)stat_sum_xe/(uint64)ne) ; + + if ((sigi2 > 0)|(sige2 > 0)) + return 0.5*((double)stat_sum_1*log(sigi2) + (double)ne*log(sige2)) ; + return -1 ; +} + + +__global__ void calcul_stats_snake(snake_node_gpu * d_snake, int nnodes, int64 * d_stats_snake, double * vrais_min, + t_cumul_x * cumul_x, t_cumul_x2 * cumul_x2, int * TABLE_CODAGE, uint32 l + ) +{ + + int id_nx, id_nprec, id_nprecprec ; + int code_noeud, code_segment, pos ; + __shared__ int64 s_stats_snake[3] ; + + //init stats en shared mem + s_stats_snake[0] = 0 ; + s_stats_snake[1] = 0 ; + s_stats_snake[2] = 0 ; + + + for (id_nx = 0; id_nx < nnodes; id_nx++) + { + if (id_nx == 0) id_nprec = nnodes - 1; + else id_nprec = id_nx - 1; + if (id_nprec == 0) id_nprecprec = nnodes -1 ; + else id_nprecprec = id_nprec - 1 ; + /* gestion des segments partant du noeud */ + /* vers le noeud suivant dans l'ordre trigo */ + code_segment = d_snake[id_nprec].code_segment ; + if (code_segment > 0) + { + /* on somme les contributions */ + s_stats_snake[0] += d_snake[id_nprec].sum_1 ; + s_stats_snake[1] += d_snake[id_nprec].sum_x ; + s_stats_snake[2] += d_snake[id_nprec].sum_x2 ; + } + else if (code_segment < 0) + { + /* on soustrait les contributions */ + s_stats_snake[0] -= d_snake[id_nprec].sum_1 ; + s_stats_snake[1] -= d_snake[id_nprec].sum_x ; + s_stats_snake[2] -= d_snake[id_nprec].sum_x2 ; + } + // else (code_segment == 0), on ne fait rien + /* gestion des pixels connectant les segments */ + /* pixel de depart du segment actuel np --> np->noeud_suiv */ + /* freeman_out = np->freeman_out ; */ + /* freeman_in = np->noeud_prec->freeman_in ; */ + pos = d_snake[id_nprecprec].freeman_in*8 + d_snake[id_nprec].freeman_out ; + code_noeud = TABLE_CODAGE[pos] ; + pos = d_snake[id_nprec].posi*l + d_snake[id_nprec].posj ; + + if (code_noeud > 0) + { + /* on somme les contributions */ + s_stats_snake[0] += 1 + d_snake[id_nprec].posj ; + s_stats_snake[1] += cumul_x[pos] ; + s_stats_snake[2] += cumul_x2[pos] ; + } + else if (code_noeud < 0) + { + /* on soustrait les contributions */ + s_stats_snake[0] -= 1 + d_snake[id_nprec].posj ; + s_stats_snake[1] -= cumul_x[pos] ; + s_stats_snake[2] -= cumul_x2[pos] ; + } + // else (code_pixel == 0), on ne fait rien + } + d_stats_snake[0] = s_stats_snake[0] ; + d_stats_snake[1] = s_stats_snake[1] ; + d_stats_snake[2] = s_stats_snake[2] ; + + *vrais_min = codage_gl_gauss(s_stats_snake[0], s_stats_snake[1], s_stats_snake[2], + d_stats_snake[3], d_stats_snake[4], d_stats_snake[5]); +} diff --git a/src/lib_kernel_snake_2_gpu.cu~ b/src/lib_kernel_snake_2_gpu.cu~ new file mode 100644 index 0000000..eba3a46 --- /dev/null +++ b/src/lib_kernel_snake_2_gpu.cu~ @@ -0,0 +1,447 @@ + + +__global__ void genere_snake_rectangle_4nodes_gpu(snake_node_gpu * d_snake, int dist_bords, int i_dim, int j_dim){ + if (threadIdx.x == 0){ + int n = 0; + /* n0 */ + d_snake[n].posi = dist_bords ; + d_snake[n].posj = dist_bords ; + n++ ; + /* n1 */ + d_snake[n].posi = i_dim - dist_bords ; + d_snake[n].posj = dist_bords ; + n++ ; + /* n2 */ + d_snake[n].posi = i_dim - dist_bords ; + d_snake[n].posj = j_dim - dist_bords ; + n++ ; + /* n3 */ + d_snake[n].posi = dist_bords ; + d_snake[n].posj = j_dim - dist_bords ; + + for (int i=0; i<4; i++) + { + d_snake[i].freeman_in = 0; + d_snake[i].freeman_out = 0; + d_snake[i].centre_i = 0; + d_snake[i].centre_j = 0; + d_snake[i].last_move = 0; + d_snake[i].nb_pixels = 0; + d_snake[i].code_segment = 0; + + } + } +} + + +__global__ void genere_snake_rectangle_Nnodes_gpu(snake_node_gpu * d_snake, int dist_bords, int i_dim, int j_dim){ + int nb_node_seg = 9 ; + int limite = 64 ; + + int i , h= i_dim-2*dist_bords, l= j_dim-2*dist_bords ; + int inch = h/(nb_node_seg+1), incl= l/(nb_node_seg+1) ; + if (threadIdx.x == 0){ + int n = 0; + /* n0 */ + d_snake[n].posi = dist_bords ; + d_snake[n].posj = dist_bords ; + n++ ; + /*entre sommet 0 et 1*/ + i = 0 ; + while (i < nb_node_seg) + { + if ( (d_snake[n-1].posi + inch)-(i_dim - dist_bords) > limite ) + d_snake[n].posi = d_snake[n-1].posi + inch ; + else + d_snake[n].posi = d_snake[n-1].posi + inch/2 ; + d_snake[n].posj = dist_bords ; + d_snake[n-1].nb_pixels = d_snake[n].posi - d_snake[n-1].posi ; + n++ ; i++ ; + } + /* n1 */ + d_snake[n].posi = i_dim - dist_bords ; + d_snake[n].posj = dist_bords ; + d_snake[n-1].nb_pixels = d_snake[n].posi - d_snake[n-1].posi ; + n++ ; + /*entre S1 et S2*/ + i = 0 ; + while (i< nb_node_seg) + { + if ( (j_dim - dist_bords) - (d_snake[n-1].posj + incl) > limite ) + d_snake[n].posj = d_snake[n-1].posj + incl ; + else + d_snake[n].posj = d_snake[n-1].posj + incl/2 ; + d_snake[n].posi = i_dim - dist_bords ; + d_snake[n-1].nb_pixels = d_snake[n].posj - d_snake[n-1].posj ; + n++ ; i++ ; + } + /* n2 */ + d_snake[n].posi = i_dim - dist_bords ; + d_snake[n].posj = j_dim - dist_bords ; + d_snake[n-1].nb_pixels = d_snake[n].posj - d_snake[n-1].posj ; + n++ ; + /*entre S2 et S3*/ + i = 0 ; + while (i< nb_node_seg) + { + if ( (d_snake[n-1].posi - inch) - dist_bords > limite ) + d_snake[n].posi = d_snake[n-1].posi - inch ; + else + d_snake[n].posi = d_snake[n-1].posi - inch/2 ; + d_snake[n].posj = j_dim - dist_bords ; + d_snake[n-1].nb_pixels = d_snake[n-1].posi - d_snake[n].posi ; + n++ ; i++ ; + } + /* n3 */ + d_snake[n].posi = dist_bords ; + d_snake[n].posj = j_dim - dist_bords ; + d_snake[n-1].nb_pixels = d_snake[n-1].posi - d_snake[n].posi ; + n++ ; + /*entre S3 et S0*/ + i = 0 ; + while (i< nb_node_seg) + { + if ( (d_snake[n-1].posj - incl) - dist_bords > limite) + d_snake[n].posj = d_snake[n-1].posj - incl ; + else + d_snake[n].posj = d_snake[n-1].posj - incl/2 ; + d_snake[n].posi = dist_bords ; + d_snake[n-1].nb_pixels = d_snake[n-1].posj - d_snake[n].posj ; + n++ ; i++ ; + } + d_snake[n-1].nb_pixels = d_snake[n-1].posj - d_snake[0].posj ; + for (i=0; i taille smem dynamique + t_sum_x* scumuls_x = (t_sum_x*) &scumuls_1[CFI(blockDim.x)] ; + t_sum_x2* scumuls_x2 = (t_sum_x2*) &scumuls_x[CFI(blockDim.x)] ; + + //indices des noeuds + uint x1, y1, x2, y2 ; + int n1, n2 ; + + n1 = segment ; + n2 = segment +1 ; + //gestion du bouclage du snake + if (n2 >= nb_nodes) n2 = 0 ; + + //affectation des differentes positions aux différents segments 'blocs de threads' + x1 = d_snake[n1].posj ; + y1 = d_snake[n1].posi ; + x2 = d_snake[n2].posj ; + y2 = d_snake[n2].posi ; + + //params des deplacements + int dx=x2-x1; + int dy=y2-y1; + uint abs_dx = ABS(dx); + uint abs_dy = ABS(dy); + uint nb_pix = abs_dy>abs_dx?(abs_dy+1):(abs_dx+1); // alternative -> lecture ds liste_points[] + int incx=0, incy=0; + uint2 p ; + int xprec, xsuiv ; + + //calcul liste des pixels du segment (x1,y1)-(x2,y2) + if (dy > 0) incy=1; else incy=-1 ; + if (dx > 0) incx=1; else incx=-1 ; + + if (tis < nb_pix){ + if (abs_dy > abs_dx){ + //1 thread par ligne + double k = (double)dx/dy ; + p.x = y1 + incy*tis ; + p.y = x1 + floor((double)incy*k*tis+0.5) ; + //enreg. coords. pixels en global mem pour freemans + + if ((tis < 2)||(tis > nb_pix - 3)||(tis == nb_pix/2)) + { + liste_pix[idx].x = p.x ; + liste_pix[idx].y = p.y ; + } + + } else { + //1 thread par colonne + double k=(double)dy/dx ; + p.x = y1 + floor((double)(incx*k*tis)+0.5) ; + p.y = x1 + incx*tis ; + if ( tis > 0 ){ + xsuiv = y1 + floor((double)(incx*k*(tis+1))+0.5) ; + xprec = y1 + floor((double)(incx*k*(tis-1))+0.5) ; + } + //enreg. coords. pixels en global mem pour freeman + //TODO + //on peut calculer les freemans des segments + //sans stocker l'ensemble des valeurs des pixels + //juste avec les derivees aux extremites a calculer ici + + if ((tis < 2)||(tis > nb_pix - 3)||(tis == nb_pix/2)) + { + liste_pix[idx].x = p.x ; + liste_pix[idx].y = p.y ; + } + + } + } + __syncthreads(); + + //calcul contribs individuelles des pixels + + if ( (tis >0) && (tis < nb_pix-1) + && ( (abs_dy <= abs_dx) + && ( (xprec > p.x) || (xsuiv > p.x)) + || (abs_dy > abs_dx) ) ) + { + int pos = p.x * l + p.y ; + scumuls_1[ CFI(tib)] = 1+p.y; + scumuls_x[ CFI(tib)] = cumul_x[ pos ] ; + scumuls_x2[CFI(tib)] = cumul_x2[ pos ]; + } else { + scumuls_1[ CFI(tib)] = 0; + scumuls_x[ CFI(tib)] = 0; + scumuls_x2[CFI(tib)] = 0; + } + + __syncthreads(); + //somme des contribs individuelles + // unroll des sommes partielles en smem + + if (blockSize >= 512) { + if (tib < 256) { + scumuls_1[ CFI(tib)] += scumuls_1[ CFI(tib + 256) ]; + scumuls_x[ CFI(tib)] += scumuls_x[ CFI(tib + 256) ]; + scumuls_x2[CFI(tib)] += scumuls_x2[CFI(tib + 256) ]; + } + __syncthreads(); + } + + if (blockSize >= 256) { + if (tib < 128) { + scumuls_1[ CFI(tib)] += scumuls_1[ CFI(tib + 128) ]; + scumuls_x[ CFI(tib)] += scumuls_x[ CFI(tib + 128) ]; + scumuls_x2[CFI(tib)] += scumuls_x2[CFI(tib + 128) ]; + } + __syncthreads(); + } + if (blockSize >= 128) { + if (tib < 64) { + scumuls_1[ CFI(tib)] += scumuls_1[ CFI(tib + 64) ]; + scumuls_x[ CFI(tib)] += scumuls_x[ CFI(tib + 64) ]; + scumuls_x2[CFI(tib)] += scumuls_x2[CFI(tib + 64) ]; + } + __syncthreads(); + } + + //32 threads <==> 1 warp + if (tib < 32) + { + { + scumuls_1[ CFI(tib)] += scumuls_1[ CFI(tib + 32) ]; + scumuls_x[ CFI(tib)] += scumuls_x[ CFI(tib + 32) ]; + scumuls_x2[CFI(tib)] += scumuls_x2[CFI(tib + 32) ]; + } + { + scumuls_1[ CFI(tib)] += scumuls_1[ CFI(tib + 16) ]; + scumuls_x[ CFI(tib)] += scumuls_x[ CFI(tib + 16) ]; + scumuls_x2[CFI(tib)] += scumuls_x2[CFI(tib + 16) ]; + } + { + scumuls_1[ CFI(tib)] += scumuls_1[ CFI(tib + 8) ]; + scumuls_x[ CFI(tib)] += scumuls_x[ CFI(tib + 8) ]; + scumuls_x2[CFI(tib)] += scumuls_x2[CFI(tib + 8) ]; + } + scumuls_1[ CFI(tib)] += scumuls_1[ CFI(tib + 4) ]; + scumuls_x[ CFI(tib)] += scumuls_x[ CFI(tib + 4) ]; + scumuls_x2[CFI(tib)] += scumuls_x2[CFI(tib + 4) ]; + + scumuls_1[ CFI(tib)] += scumuls_1[ CFI(tib + 2) ]; + scumuls_x[ CFI(tib)] += scumuls_x[ CFI(tib + 2) ]; + scumuls_x2[CFI(tib)] += scumuls_x2[CFI(tib + 2) ]; + + scumuls_1[ CFI(tib)] += scumuls_1[ CFI(tib + 1) ]; + scumuls_x[ CFI(tib)] += scumuls_x[ CFI(tib + 1) ]; + scumuls_x2[CFI(tib)] += scumuls_x2[CFI(tib + 1) ]; + } + + // resultat sommes partielles en gmem + if (tib == 0) { + gsombloc[ blockIdx.x ] = (t_sum_x2) scumuls_1[0]; + gsombloc[ blockIdx.x + gridDim.x ] = (t_sum_x2) scumuls_x[0]; + gsombloc[ blockIdx.x + 2*gridDim.x ] = (t_sum_x2) scumuls_x2[0]; + } + + //calculs freemans, centre et code segment + //1 uint4 par segment + + int Di, Dj; + if (tis == 0){ + Di = 1 + liste_pix[idx+1].x - liste_pix[idx].x ; + Dj = 1 + liste_pix[idx+1].y - liste_pix[idx].y ; + d_snake[segment].freeman_out = d_table_freeman[3*Di + Dj] ; + //code seg + if (dy > 0 ) d_snake[segment].code_segment = -1 ; + if (dy < 0 ) d_snake[segment].code_segment = 1 ; + if (dy == 0) d_snake[segment].code_segment = 0 ; + } + + if (tis == nb_pix-1){ + Di = 1 + liste_pix[idx].x - liste_pix[idx-1].x ; + Dj = 1 + liste_pix[idx].y - liste_pix[idx-1].y; + d_snake[segment].freeman_in = d_table_freeman[3*Di + Dj] ; + } + + if (tis == (nb_pix/2)){ + d_snake[segment].centre_i = liste_pix[idx].x ; + d_snake[segment].centre_j = liste_pix[idx].y ; + } +} + +/* + sommme des contribs par bloc -> contribs segment, pour le snake + + execution sur : 1bloc / 1 thread par segment + */ + +__global__ void somsom_snake(t_sum_x2 * somblocs, int nb_nodes, unsigned int nb_bl_seg, snake_node_gpu * d_snake){ + + t_sum_x2 sdata[3]; + unsigned int seg = blockIdx.x ; + + //un thread par segment + { + sdata[0] = 0; + sdata[1] = 0; + sdata[2] = 0; + } + + for (int b=0; b < nb_bl_seg ; b++){ + sdata[0] += somblocs[seg*nb_bl_seg + b]; + sdata[1] += somblocs[(seg + nb_nodes)*nb_bl_seg + b]; + sdata[2] += somblocs[(seg + 2*nb_nodes)*nb_bl_seg + b]; + } + + //totaux en gmem + { + d_snake[seg].sum_1 = sdata[0]; + d_snake[seg].sum_x = sdata[1]; + d_snake[seg].sum_x2 = sdata[2]; + } +} + +__device__ double codage_gl_gauss(uint64 stat_sum_1, uint64 stat_sum_x, uint64 stat_sum_x2, + uint64 n_dim, uint64 SUM_X, uint64 SUM_X2){ + uint64 stat_sum_xe ; /* somme des xn region exterieure */ + uint32 ne ; /* nombre de pixel region exterieure */ + double sigi2, sige2; /* variance region interieure et exterieure */ + + /* variance des valeurs des niveaux de gris a l'interieur du snake */ + sigi2 = + ((double)stat_sum_x2/(double)stat_sum_1) - + ((double)stat_sum_x/(uint64)stat_sum_1)*((double)stat_sum_x/(uint64)stat_sum_1) ; + + /* variance des valeurs des niveaux de gris a l'exterieur du snake */ + ne = n_dim-stat_sum_1 ; + stat_sum_xe = SUM_X - stat_sum_x ; + sige2 = + ((double)SUM_X2-stat_sum_x2)/(double)ne - + ((double)stat_sum_xe/(uint64)ne)*((double)stat_sum_xe/(uint64)ne) ; + + if ((sigi2 > 0)|(sige2 > 0)) + return 0.5*((double)stat_sum_1*log(sigi2) + (double)ne*log(sige2)) ; + return -1 ; +} + + +__global__ void calcul_stats_snake(snake_node_gpu * d_snake, int nnodes, int64 * d_stats_snake, double * vrais_min, + t_cumul_x * cumul_x, t_cumul_x2 * cumul_x2, int * TABLE_CODAGE, uint32 l + ) +{ + + int id_nx, id_nprec, id_nprecprec ; + int code_noeud, code_segment, pos ; + __shared__ int64 s_stats_snake[3] ; + + //init stats en shared mem + s_stats_snake[0] = 0 ; + s_stats_snake[1] = 0 ; + s_stats_snake[2] = 0 ; + + + for (id_nx = 0; id_nx < nnodes; id_nx++) + { + if (id_nx == 0) id_nprec = nnodes - 1; + else id_nprec = id_nx - 1; + if (id_nprec == 0) id_nprecprec = nnodes -1 ; + else id_nprecprec = id_nprec - 1 ; + /* gestion des segments partant du noeud */ + /* vers le noeud suivant dans l'ordre trigo */ + code_segment = d_snake[id_nprec].code_segment ; + if (code_segment > 0) + { + /* on somme les contributions */ + s_stats_snake[0] += d_snake[id_nprec].sum_1 ; + s_stats_snake[1] += d_snake[id_nprec].sum_x ; + s_stats_snake[2] += d_snake[id_nprec].sum_x2 ; + } + else if (code_segment < 0) + { + /* on soustrait les contributions */ + s_stats_snake[0] -= d_snake[id_nprec].sum_1 ; + s_stats_snake[1] -= d_snake[id_nprec].sum_x ; + s_stats_snake[2] -= d_snake[id_nprec].sum_x2 ; + } + // else (code_segment == 0), on ne fait rien + /* gestion des pixels connectant les segments */ + /* pixel de depart du segment actuel np --> np->noeud_suiv */ + /* freeman_out = np->freeman_out ; */ + /* freeman_in = np->noeud_prec->freeman_in ; */ + pos = d_snake[id_nprecprec].freeman_in*8 + d_snake[id_nprec].freeman_out ; + code_noeud = TABLE_CODAGE[pos] ; + pos = d_snake[id_nprec].posi*l + d_snake[id_nprec].posj ; + + if (code_noeud > 0) + { + /* on somme les contributions */ + s_stats_snake[0] += 1 + d_snake[id_nprec].posj ; + s_stats_snake[1] += cumul_x[pos] ; + s_stats_snake[2] += cumul_x2[pos] ; + } + else if (code_noeud < 0) + { + /* on soustrait les contributions */ + s_stats_snake[0] -= 1 + d_snake[id_nprec].posj ; + s_stats_snake[1] -= cumul_x[pos] ; + s_stats_snake[2] -= cumul_x2[pos] ; + } + // else (code_pixel == 0), on ne fait rien + } + d_stats_snake[0] = s_stats_snake[0] ; + d_stats_snake[1] = s_stats_snake[1] ; + d_stats_snake[2] = s_stats_snake[2] ; + + *vrais_min = codage_gl_gauss(s_stats_snake[0], s_stats_snake[1], s_stats_snake[2], + d_stats_snake[3], d_stats_snake[4], d_stats_snake[5]); +} diff --git a/src/lib_kernels_contribs.cu b/src/lib_kernels_contribs.cu new file mode 100644 index 0000000..2440f0c --- /dev/null +++ b/src/lib_kernels_contribs.cu @@ -0,0 +1,1043 @@ +#include "constantes.h" + +/* + * determine et retourne le nb de pixels composant le segment (i,j)-(i1,j1) + * + */ +__device__ int calcul_nb_pixels(int i, int j, int i1, int j1){ + int absi, absj,nbpix =0; + //MAX( ABS(i1-i) , ABS(j1-j)) + 1 + if (i1 > i) absi = i1 - i ; else absi = i - i1 ; + if (j1 > j) absj = j1 - j ; else absj = j - j1 ; + if (absi > absj ) nbpix = absi+1 ; else nbpix = absj+1 ; + return nbpix ; +} + +/* + construit la liste des coordonnées des 8 positions a tester pour l'ensemble des N noeuds du snake + ainsi que le liste des nombres de pixels correspondant (en z, seg avant, en w seg apres) + a executer avec N blocs de 8 threads +*/ +__global__ void liste_positions_a_tester(snake_node_gpu * d_snake, uint4 * liste_positions, uint32 * nb_pix_max, int pas, int nb_nodes, int h, int l){ + int tib = threadIdx.x; // une position par thread + int node = blockIdx.x; // 1 noeud par bloc de threads + int node_prec, node_suiv ; // indices des nodes + int i, j, i1, j1, i2, j2, i3, j3, npix_prec, npix_suiv ; // coordonnees des noeuds , nb de pixels + + //lecture des coordonnees + node_prec = (node == 0)? (nb_nodes-1) : (node - 1) ; + node_suiv = (node == nb_nodes-1)? (0) : (node + 1) ; + i1 = d_snake[node_prec].posi ; + j1 = d_snake[node_prec].posj ; + i2 = d_snake[node].posi ; + j2 = d_snake[node].posj ; + i3 = d_snake[node_suiv].posi ; + j3 = d_snake[node_suiv].posj ; + + switch(tib){ // on considere un voisinage a 8 points + case 0: + i = i2 ; + if ((j2 + pas) < l) j = j2 + pas ; else j = l-2 ; + break; + case 1: + if ((j2 + pas) < l) j = j2 + pas ; else j = l-2 ; + if (i2 > pas ) i = i2 - pas ; else i = 1 ; + break; + case 2: + if (i2 > pas ) i = i2 - pas ; else i = 1 ; + j = j2 ; + break; + case 3: + if (i2 > pas) i = i2 - pas ; else i = 1 ; + if (j2 > pas) j = j2 - pas ; else j = 1 ; + break; + case 4: + i = i2 ; + if (j2 > pas) j = j2 - pas ; else j = 1 ; + break; + case 5: + if ((i2 + pas) < h) i = i2 + pas ; else i = h-2 ; + if (j2 > pas) j = j2 - pas ; else j = 1 ; + break; + case 6: + if ((i2 + pas) < h) i = i2 + pas ; else i = h-2 ; + j = j2 ; + break; + case 7: + if ((i2 + pas) < h) i = i2 + pas ; else i = h-2 ; + if ((j2 + pas) < l) j = j2 + pas ; else j = l-2 ; + break; + } + + //calcul des nombres de pixels dans chaque cas + npix_prec = calcul_nb_pixels(i,j,i1,j1) ; + npix_suiv = calcul_nb_pixels(i,j,i3,j3) ; + + liste_positions[8*node + tib] = make_uint4(i,j, (uint)npix_prec, (uint)npix_suiv ); + + // calcule du maximum global du nb de pixels + if ((node == 0) && (tib == 0)) + { + *nb_pix_max = 0 ; + for (int n=0; n *nb_pix_max) *nb_pix_max = liste_positions[n].z ; + if (liste_positions[n].w > *nb_pix_max) *nb_pix_max = liste_positions[n].w ; + } + } +} + +/* + * calcule : + * - les coordonnees de chaque pixel de chaque segment a evaluer pour les 8 voisins de chaque noeud (pair ou impair) + * cela represente 16 segments par noeud pair/impair + * - les contributions de chacun de ces pixels + * - les sommes, par blocs, des contributions des pixels (les sommes totales sont faites par le kernel somsom) + * - le code de chaque segment envisage + */ + +__global__ void calcul_contribs_segments_blocs_full(snake_node_gpu * d_snake, int nb_nodes, uint4 * liste_points, uint32 npix_max, + t_cumul_x * cumul_x, t_cumul_x2 * cumul_x2, int * d_codes_x16, + int l, uint2 * liste_pix, uint64 * gsombloc, bool pairs) +{ + // indices des elements + int blockSize = blockDim.x ; // nb threads par bloc + int tib = threadIdx.x ; // position du thread dans le bloc + int nblocs_noeud = gridDim.x / (nb_nodes/2 + pairs*(nb_nodes%2)) ; // nb de blocs dédié à chaque noeud + int nblocs_seg = nblocs_noeud / 16 ; // nb de blocs dédiés à un segment de test + int idx = blockDim.x*blockIdx.x + threadIdx.x ; // position absolue du thread ds la grille + int id_interval = blockIdx.x / nblocs_noeud ; // indice de l'intervalle du noeud dans la grille + int segment = ( blockIdx.x - id_interval*nblocs_noeud )/nblocs_seg ; // indice du segment de 0 à 15 + int tis = idx - ( id_interval*nblocs_noeud + segment*nblocs_seg )*blockDim.x ; // position du thread ds le segment + int id_base_seg = 16*id_interval + segment ; // indice du segment courant + int id_base_pix = 5*id_base_seg ; // indice du pixel 0/5 du segment courant dans la liste_pix pour le calcul des freemans + + //tab pour contribs pixels + extern __shared__ tcontribs scumuls[]; + + //coordonnees des extremites de segment + uint x1, y1, x2, y2 ; + //indices des noeuds precedent(n1), courant(n2), suivant(n3) + int n1, n2, n3 ; + // pixel courant + uint2 p ; + // nb de pixels du segment precedent, suivant + int xprec, xsuiv ; + + // determine les indices des noeuds prec, courant, suiv + if (pairs) + { + n1 = 2*id_interval -1 ; + n2 = 2*id_interval ; + n3 = 2*id_interval +1 ; + } + else + { + n1 = 2*id_interval ; + n2 = 2*id_interval +1 ; + n3 = 2*id_interval +2 ; + } + //gestion du bouclage du snake + if (n1 < 0) n1 = nb_nodes-1 ; + if (n3 >= nb_nodes) n3 = 0 ; + + + //affectation des differentes positions aux différents segments 'blocs de threads' + if ( segment < 8 ){ + x1 = d_snake[n1].posj ; + y1 = d_snake[n1].posi ; + x2 = liste_points[8*n2 + segment].y ; + y2 = liste_points[8*n2 + segment].x ; + } else { + x1 = liste_points[8*n2 + segment-8].y ; + y1 = liste_points[8*n2 + segment-8].x ; + x2 = d_snake[n3].posj ; + y2 = d_snake[n3].posi ; + } + + //params des deplacements + int dx=x2-x1; + int dy=y2-y1; + uint abs_dx = ABS(dx); + uint abs_dy = ABS(dy); + uint nb_pix = abs_dy>abs_dx?(abs_dy+1):(abs_dx+1); + int incx=0, incy=0; + + + //calcul liste des pixels du segment (x1,y1)-(x2,y2) + if (dy > 0) incy=1; else incy=-1 ; + if (dx > 0) incx=1; else incx=-1 ; + + if (tis < nb_pix){ + if (abs_dy > abs_dx){ + //1 thread par ligne pente 1/k + double k = (double)dx/dy ; + //coordonnees pixel + p.x = y1 + incy*tis ; + p.y = x1 + floor((double)incy*k*tis+0.5) ; + } else { + //1 thread par colonne pente k + double k=(double)dy/dx ; + //coordonnees pixel + p.x = y1 + floor((double)(incx*k*tis)+0.5) ; + p.y = x1 + incx*tis ; + // ordonnees des pixels suivant & precedent + if ( tis > 0 ){ + xsuiv = y1 + floor((double)(incx*k*(tis+1))+0.5) ; + xprec = y1 + floor((double)(incx*k*(tis-1))+0.5) ; + } + } + // memorisation des valeurs necessaires au calcul des freemans et des centres + if (tis == 0) liste_pix[id_base_pix] = make_uint2(p.x, p.y) ; + if (tis == 1) liste_pix[id_base_pix +1] = make_uint2(p.x,p.y) ; + if (tis == nb_pix/2) liste_pix[id_base_pix +2] = make_uint2(p.x,p.y) ; + if (tis == nb_pix-2) liste_pix[id_base_pix +3] = make_uint2(p.x,p.y) ; + if (tis == nb_pix-1) liste_pix[id_base_pix +4] = make_uint2(p.x,p.y) ; + } + + __syncthreads(); + // calcul contribs individuelles des pixels + // dans le cas des segments 'plutot horizontaux', on ne garde qu'une contrib par ligne ; celle du pixel le plus a l'interieur du snake + if ( (tis >0) && (tis < nb_pix-1) + && ( ((abs_dy <= abs_dx) && ( ( xprec > p.x) || ( xsuiv > p.x))) + || (abs_dy > abs_dx) ) ) + { + int pos = p.x * l + p.y ; + scumuls[ CFI(tib)].c1 = 1 + p.y ; + scumuls[ CFI(tib)].cx = cumul_x[ pos ] ; + scumuls[CFI(tib)].cx2 = cumul_x2[ pos ]; + } else { + scumuls[ CFI(tib)].c1 = 0; + scumuls[ CFI(tib)].cx = 0; + scumuls[ CFI(tib)].cx2 = 0; + } + + __syncthreads(); + // somme des contribs individuelles + // unroll des sommes partielles en shared memory + if (blockSize >= 512) { + if (tib < 256) { + scumuls[ CFI(tib)].c1 += scumuls[ CFI(tib + 256) ].c1; + scumuls[ CFI(tib)].cx += scumuls[ CFI(tib + 256) ].cx; + scumuls[ CFI(tib)].cx2 += scumuls[CFI(tib + 256) ].cx2; + } + __syncthreads(); + } + + if (blockSize >= 256) { + if (tib < 128) { + scumuls[ CFI(tib)].c1 += scumuls[ CFI(tib + 128) ].c1; + scumuls[ CFI(tib)].cx += scumuls[ CFI(tib + 128) ].cx; + scumuls[ CFI(tib)].cx2 += scumuls[CFI(tib + 128) ].cx2; + } + __syncthreads(); + } + if (blockSize >= 128) { + if (tib < 64) { + scumuls[ CFI(tib)].c1 += scumuls[ CFI(tib + 64) ].c1; + scumuls[ CFI(tib)].cx += scumuls[ CFI(tib + 64) ].cx; + scumuls[CFI(tib)].cx2 += scumuls[CFI(tib + 64) ].cx2; + } + __syncthreads(); + } + + //32 threads <==> 1 warp + volatile tcontribs * scontribs = scumuls ; + if (tib < 32){ + { + scontribs[ CFI(tib)].c1 += scontribs[ CFI(tib + 32) ].c1; + scontribs[ CFI(tib)].cx += scontribs[ CFI(tib + 32) ].cx; + scontribs[CFI(tib)].cx2 += scontribs[CFI(tib + 32) ].cx2; + } + if (tib < 16) + { + scontribs[ CFI(tib)].c1 += scontribs[ CFI(tib + 16) ].c1; + scontribs[ CFI(tib)].cx += scontribs[ CFI(tib + 16) ].cx; + scontribs[CFI(tib)].cx2 += scontribs[CFI(tib + 16) ].cx2; + } + if (tib < 8) + { + scontribs[ CFI(tib)].c1 += scontribs[ CFI(tib + 8) ].c1; + scontribs[ CFI(tib)].cx += scontribs[ CFI(tib + 8) ].cx; + scontribs[CFI(tib)].cx2 += scontribs[CFI(tib + 8) ].cx2; + } + if (tib < 4) + { + scontribs[ CFI(tib)].c1 += scontribs[ CFI(tib + 4) ].c1; + scontribs[ CFI(tib)].cx += scontribs[ CFI(tib + 4) ].cx; + scontribs[ CFI(tib)].cx2 +=scontribs[ CFI(tib + 4) ].cx2; + } + if (tib < 2) + { + scontribs[ CFI(tib)].c1 += scontribs[ CFI(tib + 2) ].c1; + scontribs[ CFI(tib)].cx += scontribs[ CFI(tib + 2) ].cx; + scontribs[CFI(tib)].cx2 += scontribs[CFI(tib + 2) ].cx2; + } + if (tib == 0) + { + scontribs[ CFI(tib)].c1 += scontribs[ CFI(tib + 1) ].c1; + scontribs[ CFI(tib)].cx += scontribs[ CFI(tib + 1) ].cx; + scontribs[CFI(tib)].cx2 += scontribs[CFI(tib + 1) ].cx2; + + // resultat sommes partielles en gmem + //if (tib == 0) { + gsombloc[ blockIdx.x ] = scontribs[0].c1 ; + gsombloc[ blockIdx.x + gridDim.x ] = scontribs[0].cx ; + gsombloc[ blockIdx.x + 2*gridDim.x ] = scontribs[0].cx2 ; + + //code seg + if (dy > 0 ) d_codes_x16[id_base_seg] = -1 ; + if (dy < 0 ) d_codes_x16[id_base_seg] = 1 ; + if (dy == 0) d_codes_x16[id_base_seg]= 0 ; + } + } +} + +/* + calcul des freeman et du centre de chaque segment de test + a executer sur 'n_interval' blocs de 16 threads + soit un thread par segment +*/ +__global__ void calcul_freemans_centre(uint2 * liste_pix, int * d_table_freeman, uint4 * d_freemans_x16){ + + int id_segment = threadIdx.x ; + int id_freeman = ( blockDim.x*blockIdx.x + id_segment ) ; + int id_base_pix = 5*id_freeman ; + + //calculs freemans, centre et code segment + //1 uint4 par segment + int Dio, Djo, Dii, Dji; + + //freeman out + Dio = 1 + liste_pix[id_base_pix +1].x - liste_pix[id_base_pix].x ; + Djo = 1 + liste_pix[id_base_pix +1].y - liste_pix[id_base_pix].y ; + d_freemans_x16[id_freeman].z = d_table_freeman[3*Dio + Djo] ; + + + //freeman_in + Dii = 1 + liste_pix[id_base_pix +4].x - liste_pix[id_base_pix +3].x ; + Dji = 1 + liste_pix[id_base_pix +4].y - liste_pix[id_base_pix +3].y ; + d_freemans_x16[id_freeman].w = d_table_freeman[3*Dii + Dji] ; + + //centre + d_freemans_x16[id_freeman].x = liste_pix[id_base_pix +2].x ; + d_freemans_x16[id_freeman].y = liste_pix[id_base_pix +2].y ; + +} + + +/* + calcul des contribs 1, x et x2 des 16 segments + autour du noeud + a partir des sommes partielles des blocs + 1 bloc / 1 thread par segment +*/ + +__global__ void somsom_full(uint64 * somblocs, int nb_nodes, unsigned int nb_bl_seg, uint64 * somsom){ + + uint64 sdata[3]; + unsigned int seg = blockIdx.x ; + unsigned int nb_seg = gridDim.x ; + + //un thread par segment + { + sdata[0] = 0; + sdata[1] = 0; + sdata[2] = 0; + } + + for (int b=0; b < nb_bl_seg ; b++){ //voir atomicadd 64bits + sdata[0] += somblocs[seg*nb_bl_seg + b]; + sdata[1] += somblocs[(seg + nb_seg)*nb_bl_seg + b]; + sdata[2] += somblocs[(seg + 2*nb_seg)*nb_bl_seg + b]; + } + + //totaux en gmem + { + somsom[3*seg] = sdata[0]; + somsom[3*seg + 1] = sdata[1]; + somsom[3*seg + 2] = sdata[2]; + } +} + +/* + version GPU de la fonction definie ds src/lib_math.c +*/ +__device__ bool test_inf_gpu(double arg1, double arg2){ + if (arg2 > 0) + return (arg1 < (arg2*COEF_DECROI)) ; + else + return (arg1 < (arg2*INV_COEF_DECROI)) ; +} + +/* + version GPU de la fonction codage_niveau_gris_hyp_gaussienne + */ +__device__ double codage_gl_hyp_gauss(uint64 stat_sum_1, uint64 stat_sum_x, uint64 stat_sum_x2, + uint64 n_dim, uint64 SUM_X, uint64 SUM_X2){ + uint64 stat_sum_xe ; /* somme des xn region exterieure */ + uint32 ne ; /* nombre de pixel region exterieure */ + double sigi2, sige2; /* variance region interieure et exterieure */ + + /* variance des valeurs des niveaux de gris a l'interieur du snake */ + sigi2 = + (double)stat_sum_x2/(double)stat_sum_1 - + ((double)stat_sum_x/stat_sum_1)*((double)stat_sum_x/stat_sum_1) ; + + /* variance des valeurs des niveaux de gris a l'exterieur du snake */ + ne = n_dim-stat_sum_1 ; + stat_sum_xe = SUM_X - stat_sum_x ; + sige2 = + ((double)SUM_X2-stat_sum_x2)/ne - + ((double)stat_sum_xe/ne)*((double)stat_sum_xe/ne) ; + + if ((sigi2 > 0)&&(sige2 > 0)) + return 0.5*((double)stat_sum_1*log(sigi2) + (double)ne*log(sige2)) ; + return -1 ; +} + +/* + soustrait, pour chaque intervalle [N1--Nx--N2] + les contributions des segments N1--Nx et Nx--N2 + + A executer par nnodes blocs de 1 thread par intervalle + */ + +__global__ void soustrait_aux_stats_2N_segments_noeud(snake_node_gpu * d_snake, int64 * d_stats_snake, int64 * d_stats_ref, + t_cumul_x * cumul_x, t_cumul_x2 * cumul_x2, + int * TABLE_CODAGE, uint32 l + ) +{ + int nnodes = gridDim.x ; + int id_nx, id_nprec, id_nprecprec, id_nsuiv ; + int code_noeud, pos; + __shared__ int64 s_stats_snake[3] ; + + id_nx = blockIdx.x ; + if (id_nx == 0) id_nprec = nnodes - 1; + else id_nprec = id_nx - 1; + if (id_nprec == 0) id_nprecprec = nnodes -1 ; + else id_nprecprec = id_nprec - 1 ; + if (id_nx == nnodes-1) id_nsuiv = 0; + else id_nsuiv = id_nx + 1 ; + + //init avec les valeurs du contour actuel + s_stats_snake[0] = d_stats_snake[0] ; + s_stats_snake[1] = d_stats_snake[1] ; + s_stats_snake[2] = d_stats_snake[2] ; + + /* segment Na -- Nx */ + if (d_snake[id_nprec].code_segment > 0) + { + s_stats_snake[0] -= d_snake[id_nprec].sum_1 ; + s_stats_snake[1] -= d_snake[id_nprec].sum_x ; + s_stats_snake[2] -= d_snake[id_nprec].sum_x2 ; + } + else if (d_snake[id_nprec].code_segment < 0) + { + s_stats_snake[0] += d_snake[id_nprec].sum_1 ; + s_stats_snake[1] += d_snake[id_nprec].sum_x ; + s_stats_snake[2] += d_snake[id_nprec].sum_x2 ; + } + // else (code_segment_NaNx == 0), on ne fait rien + + /* gestion des pixels connectant les segments */ + /* pixel de depart du segment Na --> Nx */ + pos = d_snake[id_nprecprec].freeman_in*8 + d_snake[id_nprec].freeman_out ; + code_noeud = TABLE_CODAGE[pos] ; + pos = d_snake[id_nprec].posi*l + d_snake[id_nprec].posj ; + if (code_noeud > 0) + { + s_stats_snake[0] -= 1 + d_snake[id_nprec].posj ; + s_stats_snake[1] -= cumul_x[pos] ; + s_stats_snake[2] -= cumul_x2[pos]; + } + else if (code_noeud < 0) + { + s_stats_snake[0] += 1 + d_snake[id_nprec].posj ; + s_stats_snake[1] += cumul_x[pos] ; + s_stats_snake[2] += cumul_x2[pos]; + } + // else (code_noeud == 0), on ne fait rien + + /* -------------------------- */ + /* -------------------------- */ + /* segment Nx -- Nb */ + if ( d_snake[id_nx].code_segment > 0 ) + { + // on soustrait la contribution + s_stats_snake[0] -= d_snake[id_nx].sum_1 ; + s_stats_snake[1] -= d_snake[id_nx].sum_x ; + s_stats_snake[2] -= d_snake[id_nx].sum_x2 ; + } + else if ( d_snake[id_nx].code_segment < 0) + { + s_stats_snake[0] += d_snake[id_nx].sum_1 ; + s_stats_snake[1] += d_snake[id_nx].sum_x ; + s_stats_snake[2] += d_snake[id_nx].sum_x2 ; + } + // else (code_segment_NxNb == 0), on ne fait rien + + /* gestion des pixels connectant les segments */ + /* pixel de depart du segment Nx --> Nb */ + pos = d_snake[id_nprec].freeman_in*8 + d_snake[id_nx].freeman_out ; + code_noeud = TABLE_CODAGE[pos] ; + pos = d_snake[id_nx].posi*l + d_snake[id_nx].posj ; + if (code_noeud > 0) + { + s_stats_snake[0] -= 1 + d_snake[id_nx].posj ; + s_stats_snake[1] -= cumul_x[pos] ; + s_stats_snake[2] -= cumul_x2[pos]; + } + else if (code_noeud < 0) + { + s_stats_snake[0] += 1 + d_snake[id_nx].posj ; + s_stats_snake[1] += cumul_x[pos] ; + s_stats_snake[2] += cumul_x2[pos]; + } + // else (code_noeud == 0), on ne fait rien + + /* pixel d'arrivee du segment Nx --> Nb */ + pos = d_snake[id_nx].freeman_in*8 + d_snake[id_nsuiv].freeman_out ; + code_noeud = TABLE_CODAGE[pos] ; + pos = d_snake[id_nsuiv].posi*l + d_snake[id_nsuiv].posj ; + if (code_noeud > 0) + { + s_stats_snake[0] -= 1 + d_snake[id_nsuiv].posj ; + s_stats_snake[1] -= cumul_x[pos] ; + s_stats_snake[2] -= cumul_x2[pos]; + } + else if (code_noeud < 0) + { + s_stats_snake[0] += 1 + d_snake[id_nsuiv].posj ; + s_stats_snake[1] += cumul_x[pos] ; + s_stats_snake[2] += cumul_x2[pos]; + } + // else (code_noeud == 0), on ne fait rien + __syncthreads(); + + d_stats_ref[3*id_nx] = s_stats_snake[0] ; + d_stats_ref[3*id_nx + 1] = s_stats_snake[1] ; + d_stats_ref[3*id_nx + 2] = s_stats_snake[2] ; + +} + + +/* + calcul des stats associees a chaque position de test + + EXEC : sur n_interval blocs de 8 threads +*/ +__global__ void calcul_stats_full(snake_node_gpu * d_snake, int nnodes, bool pairs, int64 * d_stats_snake, + int64 * d_stats_ref, int64 * d_stats, uint64 * d_contribs, + uint4 * d_liste_points, int * code_segment, uint4 * d_freemans, + int * d_table_codes, t_cumul_x * cumul_x, t_cumul_x2 * cumul_x2, + uint32 h, uint32 l, double * vrais, double * vrais_min, bool * move){ + + int interval = blockIdx.x ; + int seg = threadIdx.x ; + int thread = seg; + int id_nx, id_nprec, id_nprecprec, id_nsuiv, id_seg ; + int code_noeud; + __shared__ int64 s_stats_snake[3*8] ; + + id_nx = 2*interval + !pairs ; + if (id_nx == 0) id_nprec = nnodes - 1 ; + else id_nprec = id_nx - 1 ; + if (id_nprec == 0) id_nprecprec = nnodes -1 ; + else id_nprecprec = id_nprec - 1 ; + if (id_nx == nnodes-1) id_nsuiv = 0 ; + else id_nsuiv = id_nx + 1 ; + + //chargement en smem , prevoir CFI car on a 24x64bits par blocs => conflits + s_stats_snake[3*thread + 0] = d_stats_ref[3*id_nx] ; + s_stats_snake[3*thread + 1] = d_stats_ref[3*id_nx + 1] ; + s_stats_snake[3*thread + 2] = d_stats_ref[3*id_nx + 2] ; + + + //stats segments N1-Nx + id_seg = 16*interval + seg ; + if ( code_segment[id_seg] > 0 ){ + s_stats_snake[3*thread +0] += d_contribs[3*id_seg] ; + s_stats_snake[3*thread +1] += d_contribs[3*id_seg + 1 ] ; + s_stats_snake[3*thread +2] += d_contribs[3*id_seg + 2 ] ; + } else if ( code_segment[16*interval + seg] < 0 ) { + s_stats_snake[3*thread +0] -= d_contribs[3*id_seg] ; + s_stats_snake[3*thread +1] -= d_contribs[3*id_seg + 1] ; + s_stats_snake[3*thread +2] -= d_contribs[3*id_seg + 2] ; + } + + //stats noeud N1(i1,j1) + int fo_N1 = d_freemans[id_seg].z ; + int fi_Nprecprec = d_snake[id_nprecprec].freeman_in ; + int pos = d_snake[id_nprec].posi*l + d_snake[id_nprec].posj ; + + code_noeud = d_table_codes[fi_Nprecprec*8 + fo_N1]; + if (code_noeud > 0){ + s_stats_snake[3*thread +0] += 1 + d_snake[id_nprec].posj ; + s_stats_snake[3*thread +1] += cumul_x[ pos ] ; + s_stats_snake[3*thread +2] += cumul_x2[pos ] ; + } else if (code_noeud < 0){ + s_stats_snake[3*thread +0] -= 1 + d_snake[id_nprec].posj ; + s_stats_snake[3*thread +1] -= cumul_x[ pos ] ; + s_stats_snake[3*thread +2] -= cumul_x2[pos ] ; + } + + //stats noeud Nx + int fo_Nx = d_freemans[id_seg + 8].z ; + int fi_Nx = d_freemans[id_seg].w ; + int Nxi = d_liste_points[8*id_nx + seg].x ; + int Nxj = d_liste_points[8*id_nx + seg].y ; + pos = Nxi*l + Nxj ; + + code_noeud = d_table_codes[fi_Nx*8 + fo_Nx]; + if (code_noeud > 0){ + s_stats_snake[3*thread +0] += 1 + Nxj ; + s_stats_snake[3*thread +1] += cumul_x[ pos ] ; + s_stats_snake[3*thread +2] += cumul_x2[pos ] ; + } + if (code_noeud < 0){ + s_stats_snake[3*thread +0] -= 1 + Nxj ; + s_stats_snake[3*thread +1] -= cumul_x[ pos ] ; + s_stats_snake[3*thread +2] -= cumul_x2[pos ] ; + } + + //stats segments Nx-N2 + seg += 8; + id_seg = 16*interval + seg ; + if ( code_segment[id_seg] > 0 ){ + s_stats_snake[3*thread +0] += d_contribs[3*id_seg] ; + s_stats_snake[3*thread +1] += d_contribs[3*id_seg + 1] ; + s_stats_snake[3*thread +2] += d_contribs[3*id_seg + 2] ; + } + if ( code_segment[id_seg] < 0 ) { + s_stats_snake[3*thread +0] -= d_contribs[3*id_seg] ; + s_stats_snake[3*thread +1] -= d_contribs[3*id_seg + 1] ; + s_stats_snake[3*thread +2] -= d_contribs[3*id_seg + 2] ; + } + + //stats noeud N2(i2,j2) + int fi_N2 = d_freemans[id_seg].w ; + int fo_N2 = d_snake[id_nsuiv].freeman_out ; + pos = d_snake[id_nsuiv].posi*l + d_snake[id_nsuiv].posj ; + + code_noeud = d_table_codes[fi_N2*8 + fo_N2]; + if (code_noeud > 0){ + s_stats_snake[3*thread +0] += 1 + d_snake[id_nsuiv].posj ; + s_stats_snake[3*thread +1] += cumul_x[ pos ] ; + s_stats_snake[3*thread +2] += cumul_x2[pos ] ; + } + if (code_noeud < 0){ + s_stats_snake[3*thread +0] -= 1 + d_snake[id_nsuiv].posj ; + s_stats_snake[3*thread +1] -= cumul_x[ pos ] ; + s_stats_snake[3*thread +2] -= cumul_x2[pos ] ; + } + + //TODO + //voir si on peut s'en passer + d_stats[3*(8*interval + thread)] = s_stats_snake[3*thread +0]; + d_stats[3*(8*interval + thread) + 1] = s_stats_snake[3*thread +1]; + d_stats[3*(8*interval + thread) + 2] = s_stats_snake[3*thread +2]; + + //codage hyp gaussienne + uint64 stat_sum_xe[8] ; //somme des xn region exterieure + uint32 ne[8] ; // nombre de pixels region exterieure + double sigi2[8], sige2[8]; // carres des variances, regions interieure et exterieure + + /* variance des valeurs des niveaux de gris a l'interieur du snake */ + sigi2[thread] = + ((double)s_stats_snake[3*thread +2]/(double)s_stats_snake[3*thread +0]) - + ((double)s_stats_snake[3*thread +1]/s_stats_snake[3*thread +0])*((double)s_stats_snake[3*thread +1]/s_stats_snake[3*thread +0]) ; + + /* variance des valeurs des niveaux de gris a l'exterieur du snake */ + ne[thread] = h*l-s_stats_snake[3*thread +0] ; + stat_sum_xe[thread] = d_stats_snake[4] - s_stats_snake[3*thread +1] ; + sige2[thread] = + (double)(d_stats_snake[5]-s_stats_snake[3*thread +2])/(double)ne[thread] - + ((double)stat_sum_xe[thread]/ne[thread])*((double)stat_sum_xe[thread]/ne[thread]) ; + + if (sige2[thread]>0 && sigi2[thread]>0) + vrais[8*interval + thread] = 0.5*((double)s_stats_snake[3*thread]*log(sigi2[thread]) + (double)ne[thread]*log(sige2[thread])) ; + else + vrais[8*interval + thread] = -1.0; + + if ( thread == 0 ){ + //init move + move[id_nx] = false ; + int pos_optim = -1; + double vrais_tmp = *vrais_min; + for (int v=0; v < 8; v++){ + if ( (vrais[8*interval + v] > 0) && (vrais[8*interval + v] < vrais_tmp*COEF_DECROI) ) { + vrais_tmp = vrais[8*interval + v]; + pos_optim = v; + } + } + if (pos_optim >-1){ + if ( !croisement(d_snake, id_nx, d_liste_points[8*id_nx + pos_optim].x, d_liste_points[8*id_nx + pos_optim].y, nnodes) ) + { + /*maj data snake*/ + move[id_nx] = true ; + //new position + d_snake[id_nx].posi = d_liste_points[8*id_nx + pos_optim].x ; + d_snake[id_nx].posj = d_liste_points[8*id_nx + pos_optim].y ; + //nb pixels segment precedent + d_snake[id_nprec].nb_pixels = d_liste_points[8*id_nx + pos_optim].z ; + //nb pixels segment suivant + d_snake[id_nx].nb_pixels = d_liste_points[8*id_nx + pos_optim].w ; + //contribs segment precedent + d_snake[id_nprec].sum_1 = d_contribs[3*(16*interval + pos_optim)] ; + d_snake[id_nprec].sum_x = d_contribs[3*(16*interval + pos_optim) + 1] ; + d_snake[id_nprec].sum_x2 = d_contribs[3*(16*interval + pos_optim) + 2] ; + //contribs segment suivant + d_snake[id_nx].sum_1 = d_contribs[3*(16*interval + pos_optim + 8)] ; + d_snake[id_nx].sum_x = d_contribs[3*(16*interval + pos_optim + 8) + 1] ; + d_snake[id_nx].sum_x2 = d_contribs[3*(16*interval + pos_optim + 8) + 2] ; + //freemans segment precedent + d_snake[id_nprec].freeman_out = d_freemans[16*interval + pos_optim].z ; + d_snake[id_nprec].freeman_in = d_freemans[16*interval + pos_optim].w ; + //freemans segment suivant + d_snake[id_nx].freeman_out = d_freemans[16*interval + pos_optim + 8].z ; + d_snake[id_nx].freeman_in = d_freemans[16*interval + pos_optim + 8].w ; + //codes segment precedent + d_snake[id_nprec].code_segment = code_segment[16*interval + pos_optim] ; + //code segment suivant + d_snake[id_nx].code_segment = code_segment[16*interval + pos_optim + 8] ; + //centre segment precedent + d_snake[id_nprec].centre_i = d_freemans[16*interval + pos_optim ].x ; + d_snake[id_nprec].centre_j = d_freemans[16*interval + pos_optim ].y ; + //centre segment suivant + d_snake[id_nx].centre_i = d_freemans[16*interval + pos_optim + 8].x ; + d_snake[id_nx].centre_j = d_freemans[16*interval + pos_optim + 8].y ; + + } + } + } + + +} + +__global__ void recalcul_stats_snake(snake_node_gpu * d_snake, int nnodes, int64 * d_stats_snake, double * vrais_min, + t_cumul_x * cumul_x, t_cumul_x2 * cumul_x2, int * TABLE_CODAGE, uint32 l + ) +{ + + int id_nx, id_nprec, id_nprecprec ; + int code_noeud, code_segment, pos; + int64 s_stats_snake[3] ; + + //init stats en shared mem + s_stats_snake[0] = 0 ; + s_stats_snake[1] = 0 ; + s_stats_snake[2] = 0 ; + + + for (id_nx = 0; id_nx < nnodes; id_nx++) + { + if (id_nx == 0) id_nprec = nnodes - 1; + else id_nprec = id_nx - 1; + if (id_nprec == 0) id_nprecprec = nnodes -1 ; + else id_nprecprec = id_nprec - 1 ; + /* gestion des segments partant du noeud */ + /* vers le noeud suivant dans l'ordre trigo */ + code_segment = d_snake[id_nprec].code_segment ; + if (code_segment > 0) + { + /* on somme les contributions */ + s_stats_snake[0] += d_snake[id_nprec].sum_1 ; + s_stats_snake[1] += d_snake[id_nprec].sum_x ; + s_stats_snake[2] += d_snake[id_nprec].sum_x2 ; + } + else if (code_segment < 0) + { + /* on soustrait les contributions */ + s_stats_snake[0] -= d_snake[id_nprec].sum_1 ; + s_stats_snake[1] -= d_snake[id_nprec].sum_x ; + s_stats_snake[2] -= d_snake[id_nprec].sum_x2 ; + } + // else (code_segment == 0), on ne fait rien + /* gestion des pixels connectant les segments */ + pos = d_snake[id_nprecprec].freeman_in*8 + d_snake[id_nprec].freeman_out ; + code_noeud = TABLE_CODAGE[pos] ; + pos = d_snake[id_nprec].posi*l + d_snake[id_nprec].posj ; + + if (code_noeud > 0) + { + /* on somme les contributions */ + s_stats_snake[0] += 1 + d_snake[id_nprec].posj ; + s_stats_snake[1] += cumul_x[pos] ; + s_stats_snake[2] += cumul_x2[pos] ; + } + else if (code_noeud < 0) + { + /* on soustrait les contributions */ + s_stats_snake[0] -= 1 + d_snake[id_nprec].posj ; + s_stats_snake[1] -= cumul_x[pos] ; + s_stats_snake[2] -= cumul_x2[pos] ; + } + // else (code_pixel == 0), on ne fait rien + } + d_stats_snake[0] = s_stats_snake[0] ; + d_stats_snake[1] = s_stats_snake[1] ; + d_stats_snake[2] = s_stats_snake[2] ; + + *vrais_min = codage_gl_hyp_gauss(s_stats_snake[0], s_stats_snake[1], s_stats_snake[2], + d_stats_snake[3], d_stats_snake[4], d_stats_snake[5]); +} + + +__global__ void ajoute_noeuds(snake_node_gpu * snake, snake_node_gpu * snake_tmp, int nnodes, int seuil, int * new_nb_nodes){ + + volatile snake_node_gpu * st = snake_tmp ; + + int id_cpy = 0; + for (int id_nx=0; id_nx < nnodes; id_nx++){ + //position du noeud existant + st[id_cpy].posi = snake[id_nx].posi ; + st[id_cpy].posj = snake[id_nx].posj ; + + id_cpy++ ; + + if ( snake[id_nx].nb_pixels > seuil) + { + //position du nouveau noeud + st[id_cpy].posi = snake[id_nx].centre_i ; + st[id_cpy].posj = snake[id_nx].centre_j ; + id_cpy++ ; + } + } + for( int node=0; node= nb_nodes) n2 = 0 ; + + //affectation des differentes positions aux différents segments 'blocs de threads' + x1 = d_snake[n1].posj ; + y1 = d_snake[n1].posi ; + x2 = d_snake[n2].posj ; + y2 = d_snake[n2].posi ; + + //params des deplacements + int dx=x2-x1; + int dy=y2-y1; + uint abs_dx = ABS(dx); + uint abs_dy = ABS(dy); + uint nb_pix = abs_dy>abs_dx?(abs_dy+1):(abs_dx+1); // alternative -> lecture ds liste_points[] + int incx=0, incy=0; + + + //calcul liste des pixels du segment (x1,y1)-(x2,y2) + if (dy > 0) incy=1; else incy=-1 ; + if (dx > 0) incx=1; else incx=-1 ; + + if (tis < nb_pix){ + if (abs_dy > abs_dx){ + //1 thread par ligne + double k = (double)dx/dy ; + p.x = y1 + incy*tis ; + p.y = x1 + floor((double)incy*k*tis+0.5) ; + + } else { + //1 thread par colonne + double k=(double)dy/dx ; + p.x = y1 + floor((double)(incx*k*tis)+0.5) ; + p.y = x1 + incx*tis ; + if ( tis > 0 ){ + xsuiv = y1 + floor((double)(incx*k*(tis+1))+0.5) ; + xprec = y1 + floor((double)(incx*k*(tis-1))+0.5) ; + } + + } + if (tis == 0) liste_pix[5*segment] = make_uint2(p.x, p.y) ; + if (tis == 1) liste_pix[5*segment +1] = make_uint2(p.x,p.y) ; + if (tis == nb_pix/2) liste_pix[5*segment +2] = make_uint2(p.x,p.y) ; + if (tis == nb_pix-2) liste_pix[5*segment +3] = make_uint2(p.x,p.y) ; + if (tis == nb_pix-1) liste_pix[5*segment +4] = make_uint2(p.x,p.y) ; + + } + __syncthreads(); + + //calcul contribs individuelles des pixels + + if ( (tis >0) && (tis < nb_pix-1) + && ( ( (abs_dy <= abs_dx) && ( ( xprec > p.x) || ( xsuiv > p.x)) ) + || (abs_dy > abs_dx) ) ) + { + int pos = p.x * l + p.y ; + scumuls[ CFI(tib)].c1 = 1+p.y; + scumuls[ CFI(tib)].cx = cumul_x[ pos ] ; + scumuls[CFI(tib)].cx2 = cumul_x2[ pos ]; + } else { + scumuls[ CFI(tib)].c1 = 0; + scumuls[ CFI(tib)].cx = 0; + scumuls[CFI(tib)].cx2 = 0; + } + + __syncthreads(); + //somme des contribs individuelles + // unroll des sommes partielles en smem + + if (blockSize >= 512) { + if (tib < 256) { + scumuls[ CFI(tib)].c1 += scumuls[ CFI(tib + 256) ].c1; + scumuls[ CFI(tib)].cx += scumuls[ CFI(tib + 256) ].cx; + scumuls[CFI(tib)].cx2 += scumuls[CFI(tib + 256) ].cx2; + } + __syncthreads(); + } + + if (blockSize >= 256) { + if (tib < 128) { + scumuls[ CFI(tib)].c1 += scumuls[ CFI(tib + 128) ].c1; + scumuls[ CFI(tib)].cx += scumuls[ CFI(tib + 128) ].cx; + scumuls[CFI(tib)].cx2 += scumuls[CFI(tib + 128) ].cx2; + } + __syncthreads(); + } + if (blockSize >= 128) { + if (tib < 64) { + scumuls[ CFI(tib)].c1 += scumuls[ CFI(tib + 64) ].c1; + scumuls[ CFI(tib)].cx += scumuls[ CFI(tib + 64) ].cx; + scumuls[CFI(tib)].cx2 += scumuls[ CFI(tib + 64) ].cx2; + } + __syncthreads(); + } + + //32 threads <==> 1 warp + volatile tcontribs * scontribs = scumuls ; + if (tib < 32) + { + { + scontribs[ CFI(tib)].c1 += scontribs[ CFI(tib + 32) ].c1; + scontribs[ CFI(tib)].cx += scontribs[ CFI(tib + 32) ].cx; + scontribs[ CFI(tib)].cx2 +=scontribs[ CFI(tib + 32) ].cx2; + } + if (tib<16) + { + scontribs[ CFI(tib)].c1 += scontribs[ CFI(tib + 16) ].c1; + scontribs[ CFI(tib)].cx += scontribs[ CFI(tib + 16) ].cx; + scontribs[ CFI(tib)].cx2 +=scontribs[ CFI(tib + 16) ].cx2; + } + if (tib<8) + { + scontribs[ CFI(tib)].c1 += scontribs[ CFI(tib + 8) ].c1; + scontribs[ CFI(tib)].cx += scontribs[ CFI(tib + 8) ].cx; + scontribs[ CFI(tib)].cx2 +=scontribs[ CFI(tib + 8) ].cx2;} + if (tib<4){ + scontribs[ CFI(tib)].c1 += scontribs[ CFI(tib + 4) ].c1; + scontribs[ CFI(tib)].cx += scontribs[ CFI(tib + 4) ].cx; + scontribs[ CFI(tib)].cx2 +=scontribs[ CFI(tib + 4) ].cx2; + } + if (tib<2){ + scontribs[ CFI(tib)].c1 += scontribs[ CFI(tib + 2) ].c1; + scontribs[ CFI(tib)].cx += scontribs[ CFI(tib + 2) ].cx; + scontribs[ CFI(tib)].cx2 +=scontribs[ CFI(tib + 2) ].cx2; + } + if (tib==0){ + scontribs[ CFI(tib)].c1 += scontribs[ CFI(tib + 1) ].c1; + scontribs[ CFI(tib)].cx += scontribs[ CFI(tib + 1) ].cx; + scontribs[ CFI(tib)].cx2 +=scontribs[ CFI(tib + 1) ].cx2; + // resultat sommes partielles en gmem + //if (tib == 0) { + gsombloc[ blockIdx.x ] = scontribs[0].c1; + gsombloc[ blockIdx.x + gridDim.x ] = scontribs[0].cx; + gsombloc[ blockIdx.x + 2*gridDim.x ] = scontribs[0].cx2; + + //calculs code segment + //code seg + if (dy > 0 ) d_snake[segment].code_segment = -1 ; + if (dy < 0 ) d_snake[segment].code_segment = 1 ; + if (dy == 0) d_snake[segment].code_segment = 0 ; + } + } +} + +__global__ void recalcul_freemans_centre(snake_node_gpu * snake, uint2 * liste_pix, int * d_table_freeman){ + + int id_segment = blockIdx.x ; + int id_base_pix = 5*id_segment ; + + //calculs freemans, centre et code segment + //1 uint4 par segment + int Dio, Djo, Dii, Dji; + + //freeman out + Dio = 1 + liste_pix[id_base_pix +1].x - liste_pix[id_base_pix].x ; + Djo = 1 + liste_pix[id_base_pix +1].y - liste_pix[id_base_pix].y ; + snake[id_segment].freeman_out = d_table_freeman[3*Dio + Djo] ; + + //freeman_in + Dii = 1 + liste_pix[id_base_pix +4].x - liste_pix[id_base_pix +3].x ; + Dji = 1 + liste_pix[id_base_pix +4].y - liste_pix[id_base_pix +3].y ; + snake[id_segment].freeman_in = d_table_freeman[3*Dii + Dji] ; + + //centre + snake[id_segment].centre_i = liste_pix[id_base_pix +2].x ; + snake[id_segment].centre_j = liste_pix[id_base_pix +2].y ; + +} + + +/* + sommme des contribs par bloc -> contribs segment, pour le snake + + execution sur : 1bloc / 1 thread par segment + */ + +__global__ void resomsom_snake(uint64 * somblocs, int nb_nodes, unsigned int nb_bl_seg, snake_node_gpu * d_snake){ + + uint64 sdata[3]; + unsigned int seg = blockIdx.x ; + + //un thread par segment + { + sdata[0] = 0; + sdata[1] = 0; + sdata[2] = 0; + } + + for (int b=0; b < nb_bl_seg ; b++){ + sdata[0] += somblocs[seg*nb_bl_seg + b]; + sdata[1] += somblocs[(seg + nb_nodes)*nb_bl_seg + b]; + sdata[2] += somblocs[(seg + 2*nb_nodes)*nb_bl_seg + b]; + } + + //totaux en gmem + { + d_snake[seg].sum_1 = sdata[0]; + d_snake[seg].sum_x = sdata[1]; + d_snake[seg].sum_x2 = sdata[2]; + } +} + diff --git a/src/lib_kernels_contribs.cu~ b/src/lib_kernels_contribs.cu~ new file mode 100644 index 0000000..2052ac2 --- /dev/null +++ b/src/lib_kernels_contribs.cu~ @@ -0,0 +1,1043 @@ +#include "constantes.h" + +/* + * determine et retourne le nb de pixels composant le segment (i,j)-(i1,j1) + * + */ +__device__ int calcul_nb_pixels(int i, int j, int i1, int j1){ + int absi, absj,nbpix =0; + //MAX( ABS(i1-i) , ABS(j1-j)) + 1 + if (i1 > i) absi = i1 - i ; else absi = i - i1 ; + if (j1 > j) absj = j1 - j ; else absj = j - j1 ; + if (absi > absj ) nbpix = absi+1 ; else nbpix = absj+1 ; + return nbpix ; +} + +/* + construit la liste des coordonnées des 8 positions a tester pour l'ensemble des N noeuds du snake + ainsi que le liste des nombres de pixels correspondant (en z, seg avant, en w seg apres) + a executer avec N blocs de 8 threads +*/ +__global__ void liste_positions_a_tester(snake_node_gpu * d_snake, uint4 * liste_positions, uint32 * nb_pix_max, int pas, int nb_nodes, int h, int l){ + int tib = threadIdx.x; // une position par thread + int node = blockIdx.x; // 1 noeud par bloc de threads + int node_prec, node_suiv ; // indices des nodes + int i, j, i1, j1, i2, j2, i3, j3, npix_prec, npix_suiv ; // coordonnees des noeuds , nb de pixels + + //lecture des coordonnees + node_prec = (node == 0)? (nb_nodes-1) : (node - 1) ; + node_suiv = (node == nb_nodes-1)? (0) : (node + 1) ; + i1 = d_snake[node_prec].posi ; + j1 = d_snake[node_prec].posj ; + i2 = d_snake[node].posi ; + j2 = d_snake[node].posj ; + i3 = d_snake[node_suiv].posi ; + j3 = d_snake[node_suiv].posj ; + + switch(tib){ // on considere un voisinage a 8 points + case 0: + i = i2 ; + if ((j2 + pas) < l) j = j2 + pas ; else j = l-2 ; + break; + case 1: + if ((j2 + pas) < l) j = j2 + pas ; else j = l-2 ; + if (i2 > pas ) i = i2 - pas ; else i = 1 ; + break; + case 2: + if (i2 > pas ) i = i2 - pas ; else i = 1 ; + j = j2 ; + break; + case 3: + if (i2 > pas) i = i2 - pas ; else i = 1 ; + if (j2 > pas) j = j2 - pas ; else j = 1 ; + break; + case 4: + i = i2 ; + if (j2 > pas) j = j2 - pas ; else j = 1 ; + break; + case 5: + if ((i2 + pas) < h) i = i2 + pas ; else i = h-2 ; + if (j2 > pas) j = j2 - pas ; else j = 1 ; + break; + case 6: + if ((i2 + pas) < h) i = i2 + pas ; else i = h-2 ; + j = j2 ; + break; + case 7: + if ((i2 + pas) < h) i = i2 + pas ; else i = h-2 ; + if ((j2 + pas) < l) j = j2 + pas ; else j = l-2 ; + break; + } + + //calcul des nombres de pixels dans chaque cas + npix_prec = calcul_nb_pixels(i,j,i1,j1) ; + npix_suiv = calcul_nb_pixels(i,j,i3,j3) ; + + liste_positions[8*node + tib] = make_uint4(i,j, (uint)npix_prec, (uint)npix_suiv ); + + // calcule du maximum global du nb de pixels + if ((node == 0) && (tib == 0)) + { + *nb_pix_max = 0 ; + for (int n=0; n *nb_pix_max) *nb_pix_max = liste_positions[n].z ; + if (liste_positions[n].w > *nb_pix_max) *nb_pix_max = liste_positions[n].w ; + } + } +} + +/* + * calcule : + * - les coordonnees de chaque pixel de chaque segment a evaluer pour les 8 voisins de chaque noeud (pair ou impair) + * cela represente 16 segments par noeud pair/impair + * - les contributions de chacun de ces pixels + * - les sommes, par blocs, des contributions des pixels (les sommes totales sont faites par le kernel somsom) + * - le code de chaque segment envisage + */ + +__global__ void calcul_contribs_segments_blocs_full(snake_node_gpu * d_snake, int nb_nodes, uint4 * liste_points, uint32 npix_max, + t_cumul_x * cumul_x, t_cumul_x2 * cumul_x2, int * d_codes_x16, + int l, uint2 * liste_pix, uint64 * gsombloc, bool pairs) +{ + // indices des elements + int blockSize = blockDim.x ; // nb threads par bloc + int tib = threadIdx.x ; // position du thread dans le bloc + int nblocs_noeud = gridDim.x / (nb_nodes/2 + pairs*(nb_nodes%2)) ; // nb de blocs dédié à chaque noeud + int nblocs_seg = nblocs_noeud / 16 ; // nb de blocs dédiés à un segment de test + int idx = blockDim.x*blockIdx.x + threadIdx.x ; // position absolue du thread ds la grille + int id_interval = blockIdx.x / nblocs_noeud ; // indice de l'intervalle du noeud dans la grille + int segment = ( blockIdx.x - id_interval*nblocs_noeud )/nblocs_seg ; // indice du segment de 0 à 15 + int tis = idx - ( id_interval*nblocs_noeud + segment*nblocs_seg )*blockDim.x ; // position du thread ds le segment + int id_base_seg = 16*id_interval + segment ; // indice du segment courant + int id_base_pix = 5*id_base_seg ; // indice du pixel 0/5 du segment courant dans la liste_pix pour le calcul des freemans + + //tab pour contribs pixels + extern __shared__ tcontribs scumuls[]; + + //coordonnees des extremites de segment + uint x1, y1, x2, y2 ; + //indices des noeuds precedent(n1), courant(n2), suivant(n3) + int n1, n2, n3 ; + // pixel courant + uint2 p ; + // nb de pixels du segment precedent, suivant + int xprec, xsuiv ; + + // determine les indices des noeuds prec, courant, suiv + if (pairs) + { + n1 = 2*id_interval -1 ; + n2 = 2*id_interval ; + n3 = 2*id_interval +1 ; + } + else + { + n1 = 2*id_interval ; + n2 = 2*id_interval +1 ; + n3 = 2*id_interval +2 ; + } + //gestion du bouclage du snake + if (n1 < 0) n1 = nb_nodes-1 ; + if (n3 >= nb_nodes) n3 = 0 ; + + + //affectation des differentes positions aux différents segments 'blocs de threads' + if ( segment < 8 ){ + x1 = d_snake[n1].posj ; + y1 = d_snake[n1].posi ; + x2 = liste_points[8*n2 + segment].y ; + y2 = liste_points[8*n2 + segment].x ; + } else { + x1 = liste_points[8*n2 + segment-8].y ; + y1 = liste_points[8*n2 + segment-8].x ; + x2 = d_snake[n3].posj ; + y2 = d_snake[n3].posi ; + } + + //params des deplacements + int dx=x2-x1; + int dy=y2-y1; + uint abs_dx = ABS(dx); + uint abs_dy = ABS(dy); + uint nb_pix = abs_dy>abs_dx?(abs_dy+1):(abs_dx+1); + int incx=0, incy=0; + + + //calcul liste des pixels du segment (x1,y1)-(x2,y2) + if (dy > 0) incy=1; else incy=-1 ; + if (dx > 0) incx=1; else incx=-1 ; + + if (tis < nb_pix){ + if (abs_dy > abs_dx){ + //1 thread par ligne pente 1/k + double k = (double)dx/dy ; + //coordonnees pixel + p.x = y1 + incy*tis ; + p.y = x1 + floor((double)incy*k*tis+0.5) ; + } else { + //1 thread par colonne pente k + double k=(double)dy/dx ; + //coordonnees pixel + p.x = y1 + floor((double)(incx*k*tis)+0.5) ; + p.y = x1 + incx*tis ; + // ordonnees des pixels suivant & precedent + if ( tis > 0 ){ + xsuiv = y1 + floor((double)(incx*k*(tis+1))+0.5) ; + xprec = y1 + floor((double)(incx*k*(tis-1))+0.5) ; + } + } + // memorisation des valeurs necessaires au calcul des freemans et des centres + if (tis == 0) liste_pix[id_base_pix] = make_uint2(p.x, p.y) ; + if (tis == 1) liste_pix[id_base_pix +1] = make_uint2(p.x,p.y) ; + if (tis == nb_pix/2) liste_pix[id_base_pix +2] = make_uint2(p.x,p.y) ; + if (tis == nb_pix-2) liste_pix[id_base_pix +3] = make_uint2(p.x,p.y) ; + if (tis == nb_pix-1) liste_pix[id_base_pix +4] = make_uint2(p.x,p.y) ; + } + + __syncthreads(); + // calcul contribs individuelles des pixels + // dans le cas des segments 'plutot horizontaux', on ne garde qu'une contrib par ligne ; celle du pixel le plus a l'interieur du snake + if ( (tis >0) && (tis < nb_pix-1) + && ( ((abs_dy <= abs_dx) && ( ( xprec > p.x) || ( xsuiv > p.x))) + || (abs_dy > abs_dx) ) ) + { + int pos = p.x * l + p.y ; + scumuls[ CFI(tib)].c1 = 1 + p.y ; + scumuls[ CFI(tib)].cx = cumul_x[ pos ] ; + scumuls[CFI(tib)].cx2 = cumul_x2[ pos ]; + } else { + scumuls[ CFI(tib)].c1 = 0; + scumuls[ CFI(tib)].cx = 0; + scumuls[ CFI(tib)].cx2 = 0; + } + + __syncthreads(); + // somme des contribs individuelles + // unroll des sommes partielles en shared memory + if (blockSize >= 512) { + if (tib < 256) { + scumuls[ CFI(tib)].c1 += scumuls[ CFI(tib + 256) ].c1; + scumuls[ CFI(tib)].cx += scumuls[ CFI(tib + 256) ].cx; + scumuls[ CFI(tib)].cx2 += scumuls[CFI(tib + 256) ].cx2; + } + __syncthreads(); + } + + if (blockSize >= 256) { + if (tib < 128) { + scumuls[ CFI(tib)].c1 += scumuls[ CFI(tib + 128) ].c1; + scumuls[ CFI(tib)].cx += scumuls[ CFI(tib + 128) ].cx; + scumuls[ CFI(tib)].cx2 += scumuls[CFI(tib + 128) ].cx2; + } + __syncthreads(); + } + if (blockSize >= 128) { + if (tib < 64) { + scumuls[ CFI(tib)].c1 += scumuls[ CFI(tib + 64) ].c1; + scumuls[ CFI(tib)].cx += scumuls[ CFI(tib + 64) ].cx; + scumuls[CFI(tib)].cx2 += scumuls[CFI(tib + 64) ].cx2; + } + __syncthreads(); + } + + //32 threads <==> 1 warp + volatile tcontribs * scontribs = scumuls ; + if (tib < 32){ + { + scontribs[ CFI(tib)].c1 += scontribs[ CFI(tib + 32) ].c1; + scontribs[ CFI(tib)].cx += scontribs[ CFI(tib + 32) ].cx; + scontribs[CFI(tib)].cx2 += scontribs[CFI(tib + 32) ].cx2; + } + if (tib < 16) + { + scontribs[ CFI(tib)].c1 += scontribs[ CFI(tib + 16) ].c1; + scontribs[ CFI(tib)].cx += scontribs[ CFI(tib + 16) ].cx; + scontribs[CFI(tib)].cx2 += scontribs[CFI(tib + 16) ].cx2; + } + if (tib < 8) + { + scontribs[ CFI(tib)].c1 += scontribs[ CFI(tib + 8) ].c1; + scontribs[ CFI(tib)].cx += scontribs[ CFI(tib + 8) ].cx; + scontribs[CFI(tib)].cx2 += scontribs[CFI(tib + 8) ].cx2; + } + if (tib < 4) + { + scontribs[ CFI(tib)].c1 += scontribs[ CFI(tib + 4) ].c1; + scontribs[ CFI(tib)].cx += scontribs[ CFI(tib + 4) ].cx; + scontribs[ CFI(tib)].cx2 +=scontribs[ CFI(tib + 4) ].cx2; + } + if (tib < 2) + { + scontribs[ CFI(tib)].c1 += scontribs[ CFI(tib + 2) ].c1; + scontribs[ CFI(tib)].cx += scontribs[ CFI(tib + 2) ].cx; + scontribs[CFI(tib)].cx2 += scontribs[CFI(tib + 2) ].cx2; + } + if (tib == 0) + { + scontribs[ CFI(tib)].c1 += scontribs[ CFI(tib + 1) ].c1; + scontribs[ CFI(tib)].cx += scontribs[ CFI(tib + 1) ].cx; + scontribs[CFI(tib)].cx2 += scontribs[CFI(tib + 1) ].cx2; + + // resultat sommes partielles en gmem + //if (tib == 0) { + gsombloc[ blockIdx.x ] = scontribs[0].c1 ; + gsombloc[ blockIdx.x + gridDim.x ] = scontribs[0].cx ; + gsombloc[ blockIdx.x + 2*gridDim.x ] = scontribs[0].cx2 ; + + //code seg + if (dy > 0 ) d_codes_x16[id_base_seg] = -1 ; + if (dy < 0 ) d_codes_x16[id_base_seg] = 1 ; + if (dy == 0) d_codes_x16[id_base_seg]= 0 ; + } + } +} + +/* + calcul des freeman et du centre de chaque segment de test + a executer sur 'n_interval' blocs de 16 threads + soit un thread par segment +*/ +__global__ void calcul_freemans_centre(uint2 * liste_pix, int * d_table_freeman, uint4 * d_freemans_x16){ + + int id_segment = threadIdx.x ; + int id_freeman = ( blockDim.x*blockIdx.x + id_segment ) ; + int id_base_pix = 5*id_freeman ; + + //calculs freemans, centre et code segment + //1 uint4 par segment + int Dio, Djo, Dii, Dji; + + //freeman out + Dio = 1 + liste_pix[id_base_pix +1].x - liste_pix[id_base_pix].x ; + Djo = 1 + liste_pix[id_base_pix +1].y - liste_pix[id_base_pix].y ; + d_freemans_x16[id_freeman].z = d_table_freeman[3*Dio + Djo] ; + + + //freeman_in + Dii = 1 + liste_pix[id_base_pix +4].x - liste_pix[id_base_pix +3].x ; + Dji = 1 + liste_pix[id_base_pix +4].y - liste_pix[id_base_pix +3].y ; + d_freemans_x16[id_freeman].w = d_table_freeman[3*Dii + Dji] ; + + //centre + d_freemans_x16[id_freeman].x = liste_pix[id_base_pix +2].x ; + d_freemans_x16[id_freeman].y = liste_pix[id_base_pix +2].y ; + +} + + +/* + calcul des contribs 1, x et x2 des 16 segments + autour du noeud + a partir des sommes partielles des blocs + 1 bloc / 1 thread par segment +*/ + +__global__ void somsom_full(uint64 * somblocs, int nb_nodes, unsigned int nb_bl_seg, uint64 * somsom){ + + uint64 sdata[3]; + unsigned int seg = blockIdx.x ; + unsigned int nb_seg = gridDim.x ; + + //un thread par segment + { + sdata[0] = 0; + sdata[1] = 0; + sdata[2] = 0; + } + + for (int b=0; b < nb_bl_seg ; b++){ //voir atomicadd 64bits + sdata[0] += somblocs[seg*nb_bl_seg + b]; + sdata[1] += somblocs[(seg + nb_seg)*nb_bl_seg + b]; + sdata[2] += somblocs[(seg + 2*nb_seg)*nb_bl_seg + b]; + } + + //totaux en gmem + { + somsom[3*seg] = sdata[0]; + somsom[3*seg + 1] = sdata[1]; + somsom[3*seg + 2] = sdata[2]; + } +} + +/* + version GPU de la fonction definie ds src/lib_math.c +*/ +__device__ bool test_inf_gpu(double arg1, double arg2){ + if (arg2 > 0) + return (arg1 < (arg2*COEF_DECROI)) ; + else + return (arg1 < (arg2*INV_COEF_DECROI)) ; +} + +/* + version GPU de la fonction codage_niveau_gris_hyp_gaussienne + */ +__device__ double codage_gl_hyp_gauss(uint64 stat_sum_1, uint64 stat_sum_x, uint64 stat_sum_x2, + uint64 n_dim, uint64 SUM_X, uint64 SUM_X2){ + uint64 stat_sum_xe ; /* somme des xn region exterieure */ + uint32 ne ; /* nombre de pixel region exterieure */ + double sigi2, sige2; /* variance region interieure et exterieure */ + + /* variance des valeurs des niveaux de gris a l'interieur du snake */ + sigi2 = + (double)stat_sum_x2/(double)stat_sum_1 - + ((double)stat_sum_x/stat_sum_1)*((double)stat_sum_x/stat_sum_1) ; + + /* variance des valeurs des niveaux de gris a l'exterieur du snake */ + ne = n_dim-stat_sum_1 ; + stat_sum_xe = SUM_X - stat_sum_x ; + sige2 = + ((double)SUM_X2-stat_sum_x2)/ne - + ((double)stat_sum_xe/ne)*((double)stat_sum_xe/ne) ; + + if ((sigi2 > 0)&&(sige2 > 0)) + return 0.5*((double)stat_sum_1*log(sigi2) + (double)ne*log(sige2)) ; + return -1 ; +} + +/* + soustrait, pour chaque intervalle [N1--Nx--N2] + les contributions des segments N1--Nx et Nx--N2 + + A executer par nnodes blocs de 1 thread par intervalle + */ + +__global__ void soustrait_aux_stats_2N_segments_noeud(snake_node_gpu * d_snake, int64 * d_stats_snake, int64 * d_stats_ref, + t_cumul_x * cumul_x, t_cumul_x2 * cumul_x2, + int * TABLE_CODAGE, uint32 l + ) +{ + int nnodes = gridDim.x ; + int id_nx, id_nprec, id_nprecprec, id_nsuiv ; + int code_noeud, pos; + __shared__ int64 s_stats_snake[3] ; + + id_nx = blockIdx.x ; + if (id_nx == 0) id_nprec = nnodes - 1; + else id_nprec = id_nx - 1; + if (id_nprec == 0) id_nprecprec = nnodes -1 ; + else id_nprecprec = id_nprec - 1 ; + if (id_nx == nnodes-1) id_nsuiv = 0; + else id_nsuiv = id_nx + 1 ; + + //init avec les valeurs du contour actuel + s_stats_snake[0] = d_stats_snake[0] ; + s_stats_snake[1] = d_stats_snake[1] ; + s_stats_snake[2] = d_stats_snake[2] ; + + /* segment Na -- Nx */ + if (d_snake[id_nprec].code_segment > 0) + { + s_stats_snake[0] -= d_snake[id_nprec].sum_1 ; + s_stats_snake[1] -= d_snake[id_nprec].sum_x ; + s_stats_snake[2] -= d_snake[id_nprec].sum_x2 ; + } + else if (d_snake[id_nprec].code_segment < 0) + { + s_stats_snake[0] += d_snake[id_nprec].sum_1 ; + s_stats_snake[1] += d_snake[id_nprec].sum_x ; + s_stats_snake[2] += d_snake[id_nprec].sum_x2 ; + } + // else (code_segment_NaNx == 0), on ne fait rien + + /* gestion des pixels connectant les segments */ + /* pixel de depart du segment Na --> Nx */ + pos = d_snake[id_nprecprec].freeman_in*8 + d_snake[id_nprec].freeman_out ; + code_noeud = TABLE_CODAGE[pos] ; + pos = d_snake[id_nprec].posi*l + d_snake[id_nprec].posj ; + if (code_noeud > 0) + { + s_stats_snake[0] -= 1 + d_snake[id_nprec].posj ; + s_stats_snake[1] -= cumul_x[pos] ; + s_stats_snake[2] -= cumul_x2[pos]; + } + else if (code_noeud < 0) + { + s_stats_snake[0] += 1 + d_snake[id_nprec].posj ; + s_stats_snake[1] += cumul_x[pos] ; + s_stats_snake[2] += cumul_x2[pos]; + } + // else (code_noeud == 0), on ne fait rien + + /* -------------------------- */ + /* -------------------------- */ + /* segment Nx -- Nb */ + if ( d_snake[id_nx].code_segment > 0 ) + { + // on soustrait la contribution + s_stats_snake[0] -= d_snake[id_nx].sum_1 ; + s_stats_snake[1] -= d_snake[id_nx].sum_x ; + s_stats_snake[2] -= d_snake[id_nx].sum_x2 ; + } + else if ( d_snake[id_nx].code_segment < 0) + { + s_stats_snake[0] += d_snake[id_nx].sum_1 ; + s_stats_snake[1] += d_snake[id_nx].sum_x ; + s_stats_snake[2] += d_snake[id_nx].sum_x2 ; + } + // else (code_segment_NxNb == 0), on ne fait rien + + /* gestion des pixels connectant les segments */ + /* pixel de depart du segment Nx --> Nb */ + pos = d_snake[id_nprec].freeman_in*8 + d_snake[id_nx].freeman_out ; + code_noeud = TABLE_CODAGE[pos] ; + pos = d_snake[id_nx].posi*l + d_snake[id_nx].posj ; + if (code_noeud > 0) + { + s_stats_snake[0] -= 1 + d_snake[id_nx].posj ; + s_stats_snake[1] -= cumul_x[pos] ; + s_stats_snake[2] -= cumul_x2[pos]; + } + else if (code_noeud < 0) + { + s_stats_snake[0] += 1 + d_snake[id_nx].posj ; + s_stats_snake[1] += cumul_x[pos] ; + s_stats_snake[2] += cumul_x2[pos]; + } + // else (code_noeud == 0), on ne fait rien + + /* pixel d'arrivee du segment Nx --> Nb */ + pos = d_snake[id_nx].freeman_in*8 + d_snake[id_nsuiv].freeman_out ; + code_noeud = TABLE_CODAGE[pos] ; + pos = d_snake[id_nsuiv].posi*l + d_snake[id_nsuiv].posj ; + if (code_noeud > 0) + { + s_stats_snake[0] -= 1 + d_snake[id_nsuiv].posj ; + s_stats_snake[1] -= cumul_x[pos] ; + s_stats_snake[2] -= cumul_x2[pos]; + } + else if (code_noeud < 0) + { + s_stats_snake[0] += 1 + d_snake[id_nsuiv].posj ; + s_stats_snake[1] += cumul_x[pos] ; + s_stats_snake[2] += cumul_x2[pos]; + } + // else (code_noeud == 0), on ne fait rien + __syncthreads(); + + d_stats_ref[3*id_nx] = s_stats_snake[0] ; + d_stats_ref[3*id_nx + 1] = s_stats_snake[1] ; + d_stats_ref[3*id_nx + 2] = s_stats_snake[2] ; + +} + + +/* + calcul des stats associees a chaque position de test + + EXEC : sur n_interval blocs de 8 threads +*/ +__global__ void calcul_stats_full(snake_node_gpu * d_snake, int nnodes, bool pairs, int64 * d_stats_snake, + int64 * d_stats_ref, int64 * d_stats, uint64 * d_contribs, + uint4 * d_liste_points, int * code_segment, uint4 * d_freemans, + int * d_table_codes, t_cumul_x * cumul_x, t_cumul_x2 * cumul_x2, + uint32 h, uint32 l, double * vrais, double * vrais_min, bool * move){ + + int interval = blockIdx.x ; + int seg = threadIdx.x ; + int thread = seg; + int id_nx, id_nprec, id_nprecprec, id_nsuiv, id_seg ; + int code_noeud; + __shared__ int64 s_stats_snake[3*8] ; + + id_nx = 2*interval + !pairs ; + if (id_nx == 0) id_nprec = nnodes - 1 ; + else id_nprec = id_nx - 1 ; + if (id_nprec == 0) id_nprecprec = nnodes -1 ; + else id_nprecprec = id_nprec - 1 ; + if (id_nx == nnodes-1) id_nsuiv = 0 ; + else id_nsuiv = id_nx + 1 ; + + //chargement en smem , prevoir CFI car on a 24x64bits par blocs => conflits + s_stats_snake[3*thread + 0] = d_stats_ref[3*id_nx] ; + s_stats_snake[3*thread + 1] = d_stats_ref[3*id_nx + 1] ; + s_stats_snake[3*thread + 2] = d_stats_ref[3*id_nx + 2] ; + + + //stats segments N1-Nx + id_seg = 16*interval + seg ; + if ( code_segment[id_seg] > 0 ){ + s_stats_snake[3*thread +0] += d_contribs[3*id_seg] ; + s_stats_snake[3*thread +1] += d_contribs[3*id_seg + 1 ] ; + s_stats_snake[3*thread +2] += d_contribs[3*id_seg + 2 ] ; + } else if ( code_segment[16*interval + seg] < 0 ) { + s_stats_snake[3*thread +0] -= d_contribs[3*id_seg] ; + s_stats_snake[3*thread +1] -= d_contribs[3*id_seg + 1] ; + s_stats_snake[3*thread +2] -= d_contribs[3*id_seg + 2] ; + } + + //stats noeud N1(i1,j1) + int fo_N1 = d_freemans[id_seg].z ; + int fi_Nprecprec = d_snake[id_nprecprec].freeman_in ; + int pos = d_snake[id_nprec].posi*l + d_snake[id_nprec].posj ; + + code_noeud = d_table_codes[fi_Nprecprec*8 + fo_N1]; + if (code_noeud > 0){ + s_stats_snake[3*thread +0] += 1 + d_snake[id_nprec].posj ; + s_stats_snake[3*thread +1] += cumul_x[ pos ] ; + s_stats_snake[3*thread +2] += cumul_x2[pos ] ; + } else if (code_noeud < 0){ + s_stats_snake[3*thread +0] -= 1 + d_snake[id_nprec].posj ; + s_stats_snake[3*thread +1] -= cumul_x[ pos ] ; + s_stats_snake[3*thread +2] -= cumul_x2[pos ] ; + } + + //stats noeud Nx + int fo_Nx = d_freemans[id_seg + 8].z ; + int fi_Nx = d_freemans[id_seg].w ; + int Nxi = d_liste_points[8*id_nx + seg].x ; + int Nxj = d_liste_points[8*id_nx + seg].y ; + pos = Nxi*l + Nxj ; + + code_noeud = d_table_codes[fi_Nx*8 + fo_Nx]; + if (code_noeud > 0){ + s_stats_snake[3*thread +0] += 1 + Nxj ; + s_stats_snake[3*thread +1] += cumul_x[ pos ] ; + s_stats_snake[3*thread +2] += cumul_x2[pos ] ; + } + if (code_noeud < 0){ + s_stats_snake[3*thread +0] -= 1 + Nxj ; + s_stats_snake[3*thread +1] -= cumul_x[ pos ] ; + s_stats_snake[3*thread +2] -= cumul_x2[pos ] ; + } + + //stats segments Nx-N2 + seg += 8; + id_seg = 16*interval + seg ; + if ( code_segment[id_seg] > 0 ){ + s_stats_snake[3*thread +0] += d_contribs[3*id_seg] ; + s_stats_snake[3*thread +1] += d_contribs[3*id_seg + 1] ; + s_stats_snake[3*thread +2] += d_contribs[3*id_seg + 2] ; + } + if ( code_segment[id_seg] < 0 ) { + s_stats_snake[3*thread +0] -= d_contribs[3*id_seg] ; + s_stats_snake[3*thread +1] -= d_contribs[3*id_seg + 1] ; + s_stats_snake[3*thread +2] -= d_contribs[3*id_seg + 2] ; + } + + //stats noeud N2(i2,j2) + int fi_N2 = d_freemans[id_seg].w ; + int fo_N2 = d_snake[id_nsuiv].freeman_out ; + pos = d_snake[id_nsuiv].posi*l + d_snake[id_nsuiv].posj ; + + code_noeud = d_table_codes[fi_N2*8 + fo_N2]; + if (code_noeud > 0){ + s_stats_snake[3*thread +0] += 1 + d_snake[id_nsuiv].posj ; + s_stats_snake[3*thread +1] += cumul_x[ pos ] ; + s_stats_snake[3*thread +2] += cumul_x2[pos ] ; + } + if (code_noeud < 0){ + s_stats_snake[3*thread +0] -= 1 + d_snake[id_nsuiv].posj ; + s_stats_snake[3*thread +1] -= cumul_x[ pos ] ; + s_stats_snake[3*thread +2] -= cumul_x2[pos ] ; + } + + //TODO + //voir si on peut s'en passer + d_stats[3*(8*interval + thread)] = s_stats_snake[3*thread +0]; + d_stats[3*(8*interval + thread) + 1] = s_stats_snake[3*thread +1]; + d_stats[3*(8*interval + thread) + 2] = s_stats_snake[3*thread +2]; + + //codage hyp gaussienne + uint64 stat_sum_xe[8] ; //somme des xn region exterieure + uint32 ne[8] ; // nombre de pixels region exterieure + double sigi2[8], sige2[8]; // carres des variances, regions interieure et exterieure + + /* variance des valeurs des niveaux de gris a l'interieur du snake */ + sigi2[thread] = + ((double)s_stats_snake[3*thread +2]/(double)s_stats_snake[3*thread +0]) - + ((double)s_stats_snake[3*thread +1]/s_stats_snake[3*thread +0])*((double)s_stats_snake[3*thread +1]/s_stats_snake[3*thread +0]) ; + + /* variance des valeurs des niveaux de gris a l'exterieur du snake */ + ne[thread] = h*l-s_stats_snake[3*thread +0] ; + stat_sum_xe[thread] = d_stats_snake[4] - s_stats_snake[3*thread +1] ; + sige2[thread] = + (double)(d_stats_snake[5]-s_stats_snake[3*thread +2])/(double)ne[thread] - + ((double)stat_sum_xe[thread]/ne[thread])*((double)stat_sum_xe[thread]/ne[thread]) ; + + if (sige2[thread]>0 && sigi2[thread]>0) + vrais[8*interval + thread] = 0.5*((double)s_stats_snake[3*thread]*log(sigi2[thread]) + (double)ne[thread]*log(sige2[thread])) ; + else + vrais[8*interval + thread] = -1.0; + + if ( thread == 0 ){ + //init move + move[id_nx] = false ; + int pos_optim = -1; + double vrais_tmp = *vrais_min; + for (int v=0; v < 8; v++){ + if ( (vrais[8*interval + v] > 0) && (vrais[8*interval + v] < vrais_tmp*COEF_DECROI) ) { + vrais_tmp = vrais[8*interval + v]; + pos_optim = v; + } + } + if (pos_optim >-1){ + if ( !croisement(d_snake, id_nx, d_liste_points[8*id_nx + pos_optim].x, d_liste_points[8*id_nx + pos_optim].y, nnodes) ) + { + /*maj data snake*/ + move[id_nx] = true ; + //new position + d_snake[id_nx].posi = d_liste_points[8*id_nx + pos_optim].x ; + d_snake[id_nx].posj = d_liste_points[8*id_nx + pos_optim].y ; + //nb pixels segment precedent + d_snake[id_nprec].nb_pixels = d_liste_points[8*id_nx + pos_optim].z ; + //nb pixels segment suivant + d_snake[id_nx].nb_pixels = d_liste_points[8*id_nx + pos_optim].w ; + //contribs segment precedent + d_snake[id_nprec].sum_1 = d_contribs[3*(16*interval + pos_optim)] ; + d_snake[id_nprec].sum_x = d_contribs[3*(16*interval + pos_optim) + 1] ; + d_snake[id_nprec].sum_x2 = d_contribs[3*(16*interval + pos_optim) + 2] ; + //contribs segment suivant + d_snake[id_nx].sum_1 = d_contribs[3*(16*interval + pos_optim + 8)] ; + d_snake[id_nx].sum_x = d_contribs[3*(16*interval + pos_optim + 8) + 1] ; + d_snake[id_nx].sum_x2 = d_contribs[3*(16*interval + pos_optim + 8) + 2] ; + //freemans segment precedent + d_snake[id_nprec].freeman_out = d_freemans[16*interval + pos_optim].z ; + d_snake[id_nprec].freeman_in = d_freemans[16*interval + pos_optim].w ; + //freemans segment suivant + d_snake[id_nx].freeman_out = d_freemans[16*interval + pos_optim + 8].z ; + d_snake[id_nx].freeman_in = d_freemans[16*interval + pos_optim + 8].w ; + //codes segment precedent + d_snake[id_nprec].code_segment = code_segment[16*interval + pos_optim] ; + //code segment suivant + d_snake[id_nx].code_segment = code_segment[16*interval + pos_optim + 8] ; + //centre segment precedent + d_snake[id_nprec].centre_i = d_freemans[16*interval + pos_optim ].x ; + d_snake[id_nprec].centre_j = d_freemans[16*interval + pos_optim ].y ; + //centre segment suivant + d_snake[id_nx].centre_i = d_freemans[16*interval + pos_optim + 8].x ; + d_snake[id_nx].centre_j = d_freemans[16*interval + pos_optim + 8].y ; + + } + } + } + + +} + +__global__ void recalcul_stats_snake(snake_node_gpu * d_snake, int nnodes, int64 * d_stats_snake, double * vrais_min, + t_cumul_x * cumul_x, t_cumul_x2 * cumul_x2, int * TABLE_CODAGE, uint32 l + ) +{ + + int id_nx, id_nprec, id_nprecprec ; + int code_noeud, code_segment, pos; + int64 s_stats_snake[3] ; + + //init stats en shared mem + s_stats_snake[0] = 0 ; + s_stats_snake[1] = 0 ; + s_stats_snake[2] = 0 ; + + + for (id_nx = 0; id_nx < nnodes; id_nx++) + { + if (id_nx == 0) id_nprec = nnodes - 1; + else id_nprec = id_nx - 1; + if (id_nprec == 0) id_nprecprec = nnodes -1 ; + else id_nprecprec = id_nprec - 1 ; + /* gestion des segments partant du noeud */ + /* vers le noeud suivant dans l'ordre trigo */ + code_segment = d_snake[id_nprec].code_segment ; + if (code_segment > 0) + { + /* on somme les contributions */ + s_stats_snake[0] += d_snake[id_nprec].sum_1 ; + s_stats_snake[1] += d_snake[id_nprec].sum_x ; + s_stats_snake[2] += d_snake[id_nprec].sum_x2 ; + } + else if (code_segment < 0) + { + /* on soustrait les contributions */ + s_stats_snake[0] -= d_snake[id_nprec].sum_1 ; + s_stats_snake[1] -= d_snake[id_nprec].sum_x ; + s_stats_snake[2] -= d_snake[id_nprec].sum_x2 ; + } + // else (code_segment == 0), on ne fait rien + /* gestion des pixels connectant les segments */ + pos = d_snake[id_nprecprec].freeman_in*8 + d_snake[id_nprec].freeman_out ; + code_noeud = TABLE_CODAGE[pos] ; + pos = d_snake[id_nprec].posi*l + d_snake[id_nprec].posj ; + + if (code_noeud > 0) + { + /* on somme les contributions */ + s_stats_snake[0] += 1 + d_snake[id_nprec].posj ; + s_stats_snake[1] += cumul_x[pos] ; + s_stats_snake[2] += cumul_x2[pos] ; + } + else if (code_noeud < 0) + { + /* on soustrait les contributions */ + s_stats_snake[0] -= 1 + d_snake[id_nprec].posj ; + s_stats_snake[1] -= cumul_x[pos] ; + s_stats_snake[2] -= cumul_x2[pos] ; + } + // else (code_pixel == 0), on ne fait rien + } + d_stats_snake[0] = s_stats_snake[0] ; + d_stats_snake[1] = s_stats_snake[1] ; + d_stats_snake[2] = s_stats_snake[2] ; + + *vrais_min = codage_gl_hyp_gauss(s_stats_snake[0], s_stats_snake[1], s_stats_snake[2], + d_stats_snake[3], d_stats_snake[4], d_stats_snake[5]); +} + + +__global__ void ajoute_noeuds(snake_node_gpu * snake, snake_node_gpu * snake_tmp, int nnodes, int seuil, int * new_nb_nodes){ + + volatile snake_node_gpu * st = snake_tmp ; + + int id_cpy = 0; + for (int id_nx=0; id_nx < nnodes; id_nx++){ + //position du noeud existant + st[id_cpy].posi = snake[id_nx].posi ; + st[id_cpy].posj = snake[id_nx].posj ; + + id_cpy++ ; + + if ( snake[id_nx].nb_pixels > seuil) + { + //position du nouveau noeud + st[id_cpy].posi = snake[id_nx].centre_i ; + st[id_cpy].posj = snake[id_nx].centre_j ; + id_cpy++ ; + } + } + for( int node=0; node= nb_nodes) n2 = 0 ; + + //affectation des differentes positions aux différents segments 'blocs de threads' + x1 = d_snake[n1].posj ; + y1 = d_snake[n1].posi ; + x2 = d_snake[n2].posj ; + y2 = d_snake[n2].posi ; + + //params des deplacements + int dx=x2-x1; + int dy=y2-y1; + uint abs_dx = ABS(dx); + uint abs_dy = ABS(dy); + uint nb_pix = abs_dy>abs_dx?(abs_dy+1):(abs_dx+1); // alternative -> lecture ds liste_points[] + int incx=0, incy=0; + + + //calcul liste des pixels du segment (x1,y1)-(x2,y2) + if (dy > 0) incy=1; else incy=-1 ; + if (dx > 0) incx=1; else incx=-1 ; + + if (tis < nb_pix){ + if (abs_dy > abs_dx){ + //1 thread par ligne + double k = (double)dx/dy ; + p.x = y1 + incy*tis ; + p.y = x1 + floor((double)incy*k*tis+0.5) ; + + } else { + //1 thread par colonne + double k=(double)dy/dx ; + p.x = y1 + floor((double)(incx*k*tis)+0.5) ; + p.y = x1 + incx*tis ; + if ( tis > 0 ){ + xsuiv = y1 + floor((double)(incx*k*(tis+1))+0.5) ; + xprec = y1 + floor((double)(incx*k*(tis-1))+0.5) ; + } + + } + if (tis == 0) liste_pix[5*segment] = make_uint2(p.x, p.y) ; + if (tis == 1) liste_pix[5*segment +1] = make_uint2(p.x,p.y) ; + if (tis == nb_pix/2) liste_pix[5*segment +2] = make_uint2(p.x,p.y) ; + if (tis == nb_pix-2) liste_pix[5*segment +3] = make_uint2(p.x,p.y) ; + if (tis == nb_pix-1) liste_pix[5*segment +4] = make_uint2(p.x,p.y) ; + + } + __syncthreads(); + + //calcul contribs individuelles des pixels + + if ( (tis >0) && (tis < nb_pix-1) + && ( ( (abs_dy <= abs_dx) && ( ( xprec > p.x) || ( xsuiv > p.x)) ) + || (abs_dy > abs_dx) ) ) + { + int pos = p.x * l + p.y ; + scumuls[ CFI(tib)].c1 = 1+p.y; + scumuls[ CFI(tib)].cx = cumul_x[ pos ] ; + scumuls[CFI(tib)].cx2 = cumul_x2[ pos ]; + } else { + scumuls[ CFI(tib)].c1 = 0; + scumuls[ CFI(tib)].cx = 0; + scumuls[CFI(tib)].cx2 = 0; + } + + __syncthreads(); + //somme des contribs individuelles + // unroll des sommes partielles en smem + + if (blockSize >= 512) { + if (tib < 256) { + scumuls[ CFI(tib)].c1 += scumuls[ CFI(tib + 256) ].c1; + scumuls[ CFI(tib)].cx += scumuls[ CFI(tib + 256) ].cx; + scumuls[CFI(tib)].cx2 += scumuls[CFI(tib + 256) ].cx2; + } + __syncthreads(); + } + + if (blockSize >= 256) { + if (tib < 128) { + scumuls[ CFI(tib)].c1 += scumuls[ CFI(tib + 128) ].c1; + scumuls[ CFI(tib)].cx += scumuls[ CFI(tib + 128) ].cx; + scumuls[CFI(tib)].cx2 += scumuls[CFI(tib + 128) ].cx2; + } + __syncthreads(); + } + if (blockSize >= 128) { + if (tib < 64) { + scumuls[ CFI(tib)].c1 += scumuls[ CFI(tib + 64) ].c1; + scumuls[ CFI(tib)].cx += scumuls[ CFI(tib + 64) ].cx; + scumuls[CFI(tib)].cx2 += scumuls[ CFI(tib + 64) ].cx2; + } + __syncthreads(); + } + + //32 threads <==> 1 warp + volatile tcontribs * scontribs = scumuls ; + if (tib < 32) + { + { + scontribs[ CFI(tib)].c1 += scontribs[ CFI(tib + 32) ].c1; + scontribs[ CFI(tib)].cx += scontribs[ CFI(tib + 32) ].cx; + scontribs[ CFI(tib)].cx2 +=scontribs[ CFI(tib + 32) ].cx2; + } + if (tib<16) + { + scontribs[ CFI(tib)].c1 += scontribs[ CFI(tib + 16) ].c1; + scontribs[ CFI(tib)].cx += scontribs[ CFI(tib + 16) ].cx; + scontribs[ CFI(tib)].cx2 +=scontribs[ CFI(tib + 16) ].cx2; + } + if (tib<8) + { + scontribs[ CFI(tib)].c1 += scontribs[ CFI(tib + 8) ].c1; + scontribs[ CFI(tib)].cx += scontribs[ CFI(tib + 8) ].cx; + scontribs[ CFI(tib)].cx2 +=scontribs[ CFI(tib + 8) ].cx2;} + if (tib<4){ + scontribs[ CFI(tib)].c1 += scontribs[ CFI(tib + 4) ].c1; + scontribs[ CFI(tib)].cx += scontribs[ CFI(tib + 4) ].cx; + scontribs[ CFI(tib)].cx2 +=scontribs[ CFI(tib + 4) ].cx2; + } + if (tib<2){ + scontribs[ CFI(tib)].c1 += scontribs[ CFI(tib + 2) ].c1; + scontribs[ CFI(tib)].cx += scontribs[ CFI(tib + 2) ].cx; + scontribs[ CFI(tib)].cx2 +=scontribs[ CFI(tib + 2) ].cx2; + } + if (tib==0){ + scontribs[ CFI(tib)].c1 += scontribs[ CFI(tib + 1) ].c1; + scontribs[ CFI(tib)].cx += scontribs[ CFI(tib + 1) ].cx; + scontribs[ CFI(tib)].cx2 +=scontribs[ CFI(tib + 1) ].cx2; + // resultat sommes partielles en gmem + //if (tib == 0) { + gsombloc[ blockIdx.x ] = scontribs[0].c1; + gsombloc[ blockIdx.x + gridDim.x ] = scontribs[0].cx; + gsombloc[ blockIdx.x + 2*gridDim.x ] = scontribs[0].cx2; + + //calculs code segment + //code seg + if (dy > 0 ) d_snake[segment].code_segment = -1 ; + if (dy < 0 ) d_snake[segment].code_segment = 1 ; + if (dy == 0) d_snake[segment].code_segment = 0 ; + } + } +} + +__global__ void recalcul_freemans_centre(snake_node_gpu * snake, uint2 * liste_pix, int * d_table_freeman){ + + int id_segment = blockIdx.x ; + int id_base_pix = 5*id_segment ; + + //calculs freemans, centre et code segment + //1 uint4 par segment + int Dio, Djo, Dii, Dji; + + //freeman out + Dio = 1 + liste_pix[id_base_pix +1].x - liste_pix[id_base_pix].x ; + Djo = 1 + liste_pix[id_base_pix +1].y - liste_pix[id_base_pix].y ; + snake[id_segment].freeman_out = d_table_freeman[3*Dio + Djo] ; + + //freeman_in + Dii = 1 + liste_pix[id_base_pix +4].x - liste_pix[id_base_pix +3].x ; + Dji = 1 + liste_pix[id_base_pix +4].y - liste_pix[id_base_pix +3].y ; + snake[id_segment].freeman_in = d_table_freeman[3*Dii + Dji] ; + + //centre + snake[id_segment].centre_i = liste_pix[id_base_pix +2].x ; + snake[id_segment].centre_j = liste_pix[id_base_pix +2].y ; + +} + + +/* + sommme des contribs par bloc -> contribs segment, pour le snake + + execution sur : 1bloc / 1 thread par segment + */ + +__global__ void resomsom_snake(uint64 * somblocs, int nb_nodes, unsigned int nb_bl_seg, snake_node_gpu * d_snake){ + + uint64 sdata[3]; + unsigned int seg = blockIdx.x ; + + //un thread par segment + { + sdata[0] = 0; + sdata[1] = 0; + sdata[2] = 0; + } + + for (int b=0; b < nb_bl_seg ; b++){ + sdata[0] += somblocs[seg*nb_bl_seg + b]; + sdata[1] += somblocs[(seg + nb_nodes)*nb_bl_seg + b]; + sdata[2] += somblocs[(seg + 2*nb_nodes)*nb_bl_seg + b]; + } + + //totaux en gmem + { + d_snake[seg].sum_1 = sdata[0]; + d_snake[seg].sum_x = sdata[1]; + d_snake[seg].sum_x2 = sdata[2]; + } +} + diff --git a/src/lib_kernels_contribs.h b/src/lib_kernels_contribs.h new file mode 100644 index 0000000..486b0ba --- /dev/null +++ b/src/lib_kernels_contribs.h @@ -0,0 +1,33 @@ +#ifndef __KERNEL_CONTRIB__ +#define __KERNEL_CONTRIB__ + +__global__ void liste_positions_a_tester(snake_node_gpu * d_snake, uint4 * liste_positions, uint32 * nb_pix_max, + int pas, int nb_nodes, int h, int l); +__global__ void calcul_contribs_segments_snake(snake_node_gpu * d_snake, int nb_nodes, + uint64 * cumul_1, uint64 * cumul_x, uint64 * cumul_x2, + int l, uint2 * liste_pix, uint64 * gsombloc, int * d_table_freeman); + +__global__ void somsom_snake(uint64 * somblocs, int nb_nodes, unsigned int nb_bl_seg, snake_node_gpu * d_snake); + + +__global__ void calcul_contribs_segments_blocs_full(snake_node_gpu * d_snake, int nb_nodes, uint4 * liste_points, uint32 npix_max, + uint64 * cumul_1, uint64 * cumul_x, uint64 * cumul_x2, int * d_codes_x16, + int l, uint2 * liste_pix, uint64 * gsombloc, int * d_table_freeman, + uint4 * d_freemans_x16, bool pairs); + +__global__ void somsom_full(uint64 * somblocs, int nb_nodes, unsigned int nb_bl_seg, uint64 * somsom, bool pairs); +__device__ bool test_inf_gpu(double arg1, double arg2); + +__global__ void calcul_stats_snake(snake_node_gpu * d_snake, int nnodes, int64 * d_stats_snake, double * vrais_min, + uint64 * cumul_1, uint64 * cumul_x, uint64 * cumul_x2, int * TABLE_CODAGE, uint32 l); + +__global__ void soustrait_aux_stats_2N_segments_noeud(snake_node_gpu * d_snake, int64 * d_stats_snake, int64 * d_stats_ref, + uint64 * cumul_1, uint64 * cumul_x, uint64 * cumul_x2, + int * TABLE_CODAGE, uint32 l); + +__global__ void calcul_stats_full(snake_node_gpu * d_snake, int nnodes, bool pairs, int64 * d_stats_snake, + int64 * d_stats_ref, int64 * d_stats, uint64 * d_contribs, + uint4 * d_liste_points, int * code_segment, uint4 * d_freemans, + int * d_table_codes, uint64 * cumul_1, uint64 * cumul_x, uint64 * cumul_x2, + uint32 h, uint32 l, double * vrais, double * vrais_min, bool * move); +#endif // __KERNEL_CONTRIB__ diff --git a/src/lib_kernels_cumuls.cu b/src/lib_kernels_cumuls.cu new file mode 100644 index 0000000..641a0ff --- /dev/null +++ b/src/lib_kernels_cumuls.cu @@ -0,0 +1,233 @@ + + + +__global__ void calcul_cumuls_gpu(unsigned short * img, t_cumul_x * cumul_x, t_cumul_x2 * cumul_x2, uint32 h, uint32 l, uint64 * gsomblocs, unsigned int N_BLOCS_LIGNE, unsigned int baseline, unsigned int nb_lines ) +{ + int bs = blockDim.x ; + int offsetSom = N_BLOCS_LIGNE * nb_lines; //indice du premier element de cumul_x2 dans le tableau + + int tib = threadIdx.x ; //indice du thread dans le bloc + int idx = blockIdx.x*bs + tib ; //indice global du thread dans la portion d'image + int num_ligne = blockIdx.x / N_BLOCS_LIGNE ; //indice de la ligne du thread dans la portion d'image + int til = idx - num_ligne*bs*N_BLOCS_LIGNE ; //indice du thread ds la ligne + int pos = num_ligne*l + til ; //indice du pixel dans la 'tranche' courante + int pos_img = pos + baseline*l ; //indice correspondant dans l'image originale + int id, stride=1; + + extern __shared__ tcumuls sdata[]; + //chargement en smem avec complement de 0 + id = CFI(tib); + if (til < l){ + sdata[id].x = img[ pos_img ] ; + sdata[id].x2 = img[ pos_img ]*img[ pos_img ] ; + } else { + sdata[id].x = 0 ; + sdata[id].x2 = 0 ; + } + /* + *prefix sum en smem + */ + //passe 1 : construction des sommes + + for (int d = bs/2; d > 0; d >>= 1) + { + __syncthreads(); + + if (tib < d) + { + int i = __mul24(__mul24(2, stride), tib); + int ai = i + stride - 1; + int bi = CFI(ai + stride); + + ai = CFI(ai); + //bi = CFI(bi); + + sdata[bi].x += sdata[ai].x; + sdata[bi].x2 += sdata[ai].x2; + + } + stride *= 2; + } + + //stockage somme du bloc en gmem + // et + //mise a zero dernier element du bloc + + __syncthreads(); + + if (tib == bs-1) { + t_cumul_x som_x = sdata[CFI(tib)].x; + t_cumul_x2 som_x2 = sdata[CFI(tib)].x2; + if (til < l){ //il ne faut pas ecrire de somme dans le dernier bloc, elle serait hors image + cumul_x[ pos_img ] = som_x; + cumul_x2[pos_img ] = som_x2; + } + gsomblocs[blockIdx.x] = som_x; + gsomblocs[blockIdx.x + offsetSom] = som_x2; + sdata[CFI(tib)].x = 0; + sdata[CFI(tib)].x2 = 0; + } + + //Passe 2 : construction des scans + for (int d = 1; d <= (bs/2); d *= 2) + { + stride >>= 1; + __syncthreads(); + + if (tib < d) + { + int i = __mul24(__mul24(2, stride), tib); + int ai = i + stride - 1; + int bi = CFI(ai + stride); + + ai = CFI(ai); + //bi = CFI(bi); + + t_cumul_x tx = sdata[ai].x; + t_cumul_x2 tx2 = sdata[ai].x2; + + sdata[ai].x = sdata[bi].x; + sdata[bi].x += tx; + + sdata[ai].x2 = sdata[bi].x2; + sdata[bi].x2 += tx2; + } + } + + //transfert en gmem des scans + //il faut décaler à gauche de 1 element + __syncthreads(); + if ( (til < l) && ( tib < (bs-1) ) ){ + cumul_x[ pos_img ] = sdata[CFI(tib+1)].x; + cumul_x2[pos_img ] = sdata[CFI(tib+1)].x2; + } + +} + + + +/* + kernel pour faire le prefixsum des sommes de blocs des lignes de l'image + les params d'executions kivonbien sont : + - threads : la moitié de la nextpow2 du nb de blocs par ligne + - grid : 2 x le nombre de blocs de sommes dans l'image , soit le nb_blocs_par_ligne x nb_lignes x 2 + - shared mem : 2 x nextPow2(nb_blocs par ligne) +*/ + + + +__global__ void scan_somblocs(uint64 * g_idata, int n_bl_l){ + + extern __shared__ uint64 temp[]; + + int tib = threadIdx.x; + int toff = blockIdx.x*n_bl_l; // offset des positions des sommes partielles dans le tableau g_idata + int nmax = blockDim.x*2; //nb max de sommes partielles par ligne + + + int aai = tib; + int bbi = tib + blockDim.x; + + + // chargement data en smem + temp[ CFI(aai) ] = (aai < n_bl_l)? g_idata[ toff + aai ] : 0; + temp[ CFI(bbi) ] = (bbi < n_bl_l)? g_idata[ toff + bbi ] : 0; + + int offset = 1; + + // passe 1 de bas en haut + for (int d = nmax/2; d > 0; d >>= 1) + { + __syncthreads(); + + if (tib < d) + { + int ai = CFI(offset*(2*tib+1)-1); + int bi = CFI(offset*(2*tib+2)-1); + + temp[bi] += temp[ai]; + } + + offset *= 2; + } + + // zero le dernier element + __syncthreads(); + if (tib == 0) + { + int index = CFI(nmax - 1); + g_idata[ toff + n_bl_l - 1 ] = temp[ index ]; //memorisation de la somme du bloc a sa place en gmem + temp[index] = 0; + } + + // passe 2 de haut en bas + for (int d = 1; d < nmax; d *= 2) + { + offset /= 2; + + __syncthreads(); + + if (tib < d) + { + int ai = CFI(offset*(2*tib+1)-1); + int bi = CFI(offset*(2*tib+2)-1); + + uint64 t = temp[ai]; + temp[ai] = temp[bi]; + temp[bi] += t; //tester atomicadd 64 bits + } + + } + + __syncthreads(); + + // transfert resultats en gmem decales d'un element vers la gauche + g_idata[ toff + aai ] = temp[ CFI(aai + 1) ]; //pour le demi tableau smem inferieur + if ( bbi < n_bl_l-1 ) g_idata[ toff + bbi ] = temp[CFI( bbi + 1) ]; //demi tableau smem sup. sauf dernier=somme. + +} + + +__global__ void add_soms_to_cumuls(t_cumul_x * cumul_x, t_cumul_x2 * cumul_x2, uint32 h, uint32 l, uint64 * gsomblocs, + unsigned int N_BLOCS_LIGNE, unsigned int baseline, unsigned int nb_lines){ + + int bs = blockDim.x ; + int offsetSom = N_BLOCS_LIGNE * nb_lines; + int tib = threadIdx.x ; //indice du thread dans le bloc + int idx = blockIdx.x*blockDim.x + tib ; //indice global du thread + int num_ligne = blockIdx.x / N_BLOCS_LIGNE ; //indice de la ligne du thread dans l'image partielle + int til = idx - num_ligne*bs*N_BLOCS_LIGNE ; //indice du thread ds la ligne + int pos = num_ligne*l + til; + int pos_img = pos + baseline*l ; + + + //chargement des valeurs a ajouter en smem 0<-->x et 1<-->x2 + uint64 __shared__ ajout[2]; + + if ( til >= bs ){ + ajout[0] = gsomblocs[blockIdx.x -1 ]; + ajout[1] = gsomblocs[blockIdx.x -1 + offsetSom]; + __syncthreads(); //tester les nouveaux __sync__ + + //addition par bloc + if (til < l) { + cumul_x[pos_img] += ajout[0]; + cumul_x2[pos_img]+= ajout[1]; + } + } +} + +//calcul des SUM_1, SUM_X et SUM_X2 de l'image + +__global__ void calcul_stats_image( t_cumul_x * cumul_x, t_cumul_x2 * cumul_x2, uint32 h, uint32 l, uint64 * d_stats_snake){ + uint64 sigX = 0, sigX2 = 0 ; + for (int i=l-1; i 0; d >>= 1) + { + __syncthreads(); + + if (tib < d) + { + int i = __mul24(__mul24(2, stride), tib); + int ai = i + stride - 1; + int bi = CFI(ai + stride); + + ai = CFI(ai); + //bi = CFI(bi); + + sdata[bi].x += sdata[ai].x; + sdata[bi].x2 += sdata[ai].x2; + + } + stride *= 2; + } + + //stockage somme du bloc en gmem + // et + //mise a zero dernier element du bloc + + __syncthreads(); + + if (tib == bs-1) { + t_cumul_x som_x = sdata[CFI(tib)].x; + t_cumul_x2 som_x2 = sdata[CFI(tib)].x2; + if (til < l){ //il ne faut pas ecrire de somme dans le dernier bloc, elle serait hors image + cumul_x[ pos_img ] = som_x; + cumul_x2[pos_img ] = som_x2; + } + gsomblocs[blockIdx.x] = som_x; + gsomblocs[blockIdx.x + offsetSom] = som_x2; + sdata[CFI(tib)].x = 0; + sdata[CFI(tib)].x2 = 0; + } + + //Passe 2 : construction des scans + for (int d = 1; d <= (bs/2); d *= 2) + { + stride >>= 1; + __syncthreads(); + + if (tib < d) + { + int i = __mul24(__mul24(2, stride), tib); + int ai = i + stride - 1; + int bi = CFI(ai + stride); + + ai = CFI(ai); + //bi = CFI(bi); + + t_cumul_x tx = sdata[ai].x; + t_cumul_x2 tx2 = sdata[ai].x2; + + sdata[ai].x = sdata[bi].x; + sdata[bi].x += tx; + + sdata[ai].x2 = sdata[bi].x2; + sdata[bi].x2 += tx2; + } + } + + //transfert en gmem des scans + //il faut décaler à gauche de 1 element + __syncthreads(); + if ( (til < l) && ( tib < (bs-1) ) ){ + cumul_x[ pos_img ] = sdata[CFI(tib+1)].x; + cumul_x2[pos_img ] = sdata[CFI(tib+1)].x2; + } + +} + + + +/* + kernel pour faire le prefixsum des sommes de blocs des lignes de l'image + les params d'executions kivonbien sont : + - threads : la moitié de la nextpow2 du nb de blocs par ligne + - grid : 2 x le nombre de blocs de sommes dans l'image , soit le nb_blocs_par_ligne x nb_lignes x 2 + - shared mem : 2 x nextPow2(nb_blocs par ligne) +*/ + + + +__global__ void scan_somblocs(uint64 * g_idata, int n_bl_l){ + + extern __shared__ uint64 temp[]; + + int tib = threadIdx.x; + int toff = blockIdx.x*n_bl_l; // offset des positions des sommes partielles dans le tableau g_idata + int nmax = blockDim.x*2; //nb max de sommes partielles par ligne + + + int aai = tib; + int bbi = tib + blockDim.x; + + + // chargement data en smem + temp[ CFI(aai) ] = (aai < n_bl_l)? g_idata[ toff + aai ] : 0; + temp[ CFI(bbi) ] = (bbi < n_bl_l)? g_idata[ toff + bbi ] : 0; + + int offset = 1; + + // passe 1 de bas en haut + for (int d = nmax/2; d > 0; d >>= 1) + { + __syncthreads(); + + if (tib < d) + { + int ai = CFI(offset*(2*tib+1)-1); + int bi = CFI(offset*(2*tib+2)-1); + + temp[bi] += temp[ai]; + } + + offset *= 2; + } + + // zero le dernier element + __syncthreads(); + if (tib == 0) + { + int index = CFI(nmax - 1); + g_idata[ toff + n_bl_l - 1 ] = temp[ index ]; //memorisation de la somme du bloc a sa place en gmem + temp[index] = 0; + } + + // passe 2 de haut en bas + for (int d = 1; d < nmax; d *= 2) + { + offset /= 2; + + __syncthreads(); + + if (tib < d) + { + int ai = CFI(offset*(2*tib+1)-1); + int bi = CFI(offset*(2*tib+2)-1); + + uint64 t = temp[ai]; + temp[ai] = temp[bi]; + temp[bi] += t; //tester atomicadd 64 bits + } + + } + + __syncthreads(); + + // transfert resultats en gmem decales d'un element vers la gauche + g_idata[ toff + aai ] = temp[ CFI(aai + 1) ]; //pour le demi tableau smem inferieur + if ( bbi < n_bl_l-1 ) g_idata[ toff + bbi ] = temp[CFI( bbi + 1) ]; //demi tableau smem sup. sauf dernier=somme. + +} + + +__global__ void add_soms_to_cumuls(t_cumul_x * cumul_x, t_cumul_x2 * cumul_x2, uint32 h, uint32 l, uint64 * gsomblocs, + unsigned int N_BLOCS_LIGNE, unsigned int baseline, unsigned int nb_lines){ + + int bs = blockDim.x ; + int offsetSom = N_BLOCS_LIGNE * nb_lines; + int tib = threadIdx.x ; //indice du thread dans le bloc + int idx = blockIdx.x*blockDim.x + tib ; //indice global du thread + int num_ligne = blockIdx.x / N_BLOCS_LIGNE ; //indice de la ligne du thread dans l'image partielle + int til = idx - num_ligne*bs*N_BLOCS_LIGNE ; //indice du thread ds la ligne + int pos = num_ligne*l + til; + int pos_img = pos + baseline*l ; + + + //chargement des valeurs a ajouter en smem 0<-->x et 1<-->x2 + uint64 __shared__ ajout[2]; + + if ( til >= bs ){ + ajout[0] = gsomblocs[blockIdx.x -1 ]; + ajout[1] = gsomblocs[blockIdx.x -1 + offsetSom]; + __syncthreads(); //tester les nouveaux __sync__ + + //addition par bloc + if (til < l) { + cumul_x[pos_img] += ajout[0]; + cumul_x2[pos_img]+= ajout[1]; + } + } +} + +//calcul des SUM_1, SUM_X et SUM_X2 de l'image + +__global__ void calcul_stats_image( t_cumul_x * cumul_x, t_cumul_x2 * cumul_x2, uint32 h, uint32 l, uint64 * d_stats_snake){ + uint64 sigX = 0, sigX2 = 0 ; + for (int i=l-1; i 0)? (nx - 1):(nb_nodes - 1); + nsuiv = (nx < nb_nodes - 1)? (nx + 1):(0); +} + +__device__ double codage_niveau_gris_hyp_gaussienne_gpu(uint32 stat_sum_1, uint64 stat_sum_x, + uint64 stat_sum_x2, uint32 n_dim, + uint64 SUM_X, uint64 SUM_X2) +{ + uint64 stat_sum_xe ; /* somme des xn region exterieure */ + uint32 ne ; /* nombre de pixel region exterieure */ + double sigi2, sige2; /* variance region interieure et exterieure */ + + /* variance des valeurs des niveaux de gris a l'interieur du snake */ + sigi2 = + (double)stat_sum_x2/(double)stat_sum_1 - + (double)(stat_sum_x*stat_sum_x)/(double)((uint64)stat_sum_1*(uint64)stat_sum_1) ; + + /* variance des valeurs des niveaux de gris a l'exterieur du snake */ + ne = n_dim-stat_sum_1 ; + stat_sum_xe = SUM_X - stat_sum_x ; + sige2 = + (double)(SUM_X2-stat_sum_x2)/(double)ne - + (double)(stat_sum_xe*stat_sum_xe)/(double)((uint64)ne*(uint64)ne) ; + + if ((sigi2 <= 0)|(sige2 <= 0)) return -1; + + return 0.5*((double)stat_sum_1*log(sigi2) + (double)ne*log(sige2)) ; +} + +__device__ double codage_gl_hyp_gaussienne(uint64 stat_sum_1, uint64 stat_sum_x, uint64 stat_sum_x2, + uint64 n_dim, uint64 SUM_X, uint64 SUM_X2){ + uint64 stat_sum_xe ; /* somme des xn region exterieure */ + uint32 ne ; /* nombre de pixel region exterieure */ + double sigi2, sige2; /* variance region interieure et exterieure */ + + /* variance des valeurs des niveaux de gris a l'interieur du snake */ + sigi2 = + (double)stat_sum_x2/(double)stat_sum_1 - + (double)(stat_sum_x*stat_sum_x)/(double)((uint64)stat_sum_1*(uint64)stat_sum_1) ; + + /* variance des valeurs des niveaux de gris a l'exterieur du snake */ + ne = n_dim-stat_sum_1 ; + stat_sum_xe = SUM_X - stat_sum_x ; + sige2 = + (double)(SUM_X2-stat_sum_x2)/(double)ne - + (double)(stat_sum_xe*stat_sum_xe)/(double)((uint64)ne*(uint64)ne) ; + + if ((sigi2 > 0)|(sige2 > 0)) + return 0.5*((double)stat_sum_1*log(sigi2) + (double)ne*log(sige2)) ; + return -1 ; +} + +__device__ inline unsigned int nextPow2_gpu( unsigned int x ) { + --x; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + return ++x; +} + + +__device__ inline int sign_diff_strict(int val1, int val2) +{ + if (val1 > 0) + { + if (val2 >= 0) return 0 ; + else return 1 ; + } + else + if (val1 < 0) + { + if (val2 <= 0) return 0 ; + else return 1 ; + } + else + return 0 ;/* val1 == 0 */ +} + +__device__ inline int sign_diff_ou_egal_zero(int val1, int val2) +{ + if (val1 > 0) + { + if (val2 > 0) return 0 ; + else return 1 ; + } + else + if (val1 < 0) + { + if (val2 < 0) return 0 ; + else return 1 ; + } + else + return 1 ;/* val1 == 0 */ +} + + +__device__ int sinus_triangle(int Ai, int Aj, int Bi, int Bj, int Ci, int Cj) +{ + return (((Bi-Ai)*(Cj-Aj)) - ((Ci-Ai)*(Bj-Aj))) ; +} + +__device__ int test_croisement_large(uint32 Ai, uint32 Aj, uint32 Bi, uint32 Bj, + uint32 Ui, uint32 Uj, uint32 Vi, uint32 Vj) +{ + if (sign_diff_strict(sinus_triangle(Ai, Aj, Bi, Bj, Ui, Uj), // Bi-Ai, Uj-Aj, Ui-Ai, Bj-Aj + sinus_triangle(Ai, Aj, Bi, Bj, Vi, Vj))) // Bi-Ai, Vj-Aj, Vi-Ai, Bj-Aj + if (sign_diff_strict(sinus_triangle(Ui, Uj, Vi, Vj, Ai, Aj), // Vi-Ui, Aj-Uj, Ai-Ui, Vj-Uj + sinus_triangle(Ui, Uj, Vi, Vj, Bi, Bj))) // Vi-Ui, Bj-Uj, Bi-Ui, Vj-Uj + return 1 ; + return 0 ; +} + +__device__ int test_croisement_strict(uint32 Ai, uint32 Aj, uint32 Bi, uint32 Bj, + uint32 Ui, uint32 Uj, uint32 Vi, uint32 Vj ) +{ + if (sign_diff_ou_egal_zero(sinus_triangle(Ai, Aj, Bi, Bj, Ui, Uj), + sinus_triangle(Ai, Aj, Bi, Bj, Vi, Vj))) + if (sign_diff_ou_egal_zero(sinus_triangle(Ui, Uj, Vi, Vj, Ai, Aj), + sinus_triangle(Ui, Uj, Vi, Vj, Bi, Bj))) + return 1 ; + return 0 ; +} + +__global__ void kernel_test_croisement_move_seg_strict(struct snake_node_gpu *d_snake, uint32 nx, uint32 Nxi, uint32 Nxj, int Nb_nodes, bool * croist) +{ + int idx = blockDim.x*blockIdx.x + threadIdx.x, idx_prec, idx_suiv ; + int na, nb, naa, nbb ; + int Nai, Naj, Nbi, Nbj ; + bool croistil = false ; + + calcul_indices_prec_suiv(Nb_nodes, nx, na, nb); + calcul_indices_prec_suiv(Nb_nodes, na, naa, nbb); + + //coord. nodes extremites des segments a tester + Nai = d_snake[na].posi ; + Naj = d_snake[na].posj ; + Nbi = d_snake[nb].posi ; + Nbj = d_snake[nb].posj ; + + if (idx == nb) //premier segment apres + { + calcul_indices_prec_suiv(Nb_nodes, idx, idx_prec, idx_suiv); + if ( test_croisement_large(Nxi, Nxj, Nbi, Nbj, + Nbi, Nbj, d_snake[idx_suiv].posi, d_snake[idx_suiv].posj)) + croistil = true ; + else if ( test_croisement_strict(Nai, Naj, Nxi, Nxj, + Nbi, Nbj, d_snake[idx_suiv].posi, d_snake[idx_suiv].posj) ) + croistil = true ; + } + else if ( idx == naa ) //premier segment avant + { + if ( test_croisement_large(d_snake[naa].posi, d_snake[naa].posj, Nai, Naj, + Nai, Naj, Nxi, Nxj) ) + croistil = true ; + else if ( test_croisement_strict(d_snake[naa].posi, d_snake[naa].posj, Nai, Naj, + Nxi, Nxj, Nbi, Nbj)) + croistil = true ; + } + else if ( (idx != nx) && (idx != na) ) //les autres segments + { + calcul_indices_prec_suiv(Nb_nodes, idx, idx_prec, idx_suiv); + if ( test_croisement_strict(Nai, Naj, Nxi, Nxj, + d_snake[idx].posi, d_snake[idx].posj, + d_snake[idx_suiv].posi, d_snake[idx_suiv].posj) ) + croistil = true ; + else if ( test_croisement_strict(Nxi, Nxj, Nbi, Nbj, + d_snake[idx].posi, d_snake[idx].posj, + d_snake[idx_suiv].posi, d_snake[idx_suiv].posj) ) + croistil = true ; + } + + croist[idx] = croistil ; +} + +__device__ bool croisement(snake_node_gpu *d_snake, uint32 nx, uint32 Nxi, uint32 Nxj, int Nb_nodes) +{ + int idx_prec, idx_suiv ; + int na, nb, naa, nbb ; + int Nai, Naj, Nbi, Nbj ; + bool croistil = false ; + + //coord. nodes extremites des segments a tester + calcul_indices_prec_suiv(Nb_nodes, nx, na, nb); + calcul_indices_prec_suiv(Nb_nodes, na, naa, nbb); + + Nai = d_snake[na].posi ; + Naj = d_snake[na].posj ; + Nbi = d_snake[nb].posi ; + Nbj = d_snake[nb].posj ; + + //parcours du snake + for (int idx=0; idx < Nb_nodes; idx++){ + if (idx == nb) //premier segment apres + { + calcul_indices_prec_suiv(Nb_nodes, idx, idx_prec, idx_suiv); + if ( test_croisement_large(Nxi, Nxj, Nbi, Nbj, Nbi, Nbj, d_snake[idx_suiv].posi, d_snake[idx_suiv].posj) ) + { + croistil = true ; + break ; + } + if ( test_croisement_strict(Nai, Naj, Nxi, Nxj, Nbi, Nbj, d_snake[idx_suiv].posi, d_snake[idx_suiv].posj) ) + { + croistil = true ; + break ; + } + } + if ( idx == naa ) //premier segment avant + { + if ( test_croisement_large(d_snake[naa].posi, d_snake[naa].posj, Nai, Naj, Nai, Naj, Nxi, Nxj) ) + { + croistil = true ; + break ; + } + if ( test_croisement_strict(d_snake[naa].posi, d_snake[naa].posj, Nai, Naj, Nxi, Nxj, Nbi, Nbj)) + { + croistil = true ; + break ; + } + } + if ( (idx != nx) && (idx != na) && (idx != nb) && (idx != naa) ) //les autres segments + { + calcul_indices_prec_suiv(Nb_nodes, idx, idx_prec, idx_suiv); + if ( test_croisement_strict(Nai, Naj, Nxi, Nxj, + d_snake[idx].posi, d_snake[idx].posj, + d_snake[idx_suiv].posi, d_snake[idx_suiv].posj) ) + { + croistil = true ; + break ; + } + if ( test_croisement_strict(Nxi, Nxj, Nbi, Nbj, + d_snake[idx].posi, d_snake[idx].posj, + d_snake[idx_suiv].posi, d_snake[idx_suiv].posj) ) + { + croistil = true ; + break ; + } + } + } + return croistil; +} diff --git a/src/lib_kernels_maths.cu~ b/src/lib_kernels_maths.cu~ new file mode 100644 index 0000000..c98f3bc --- /dev/null +++ b/src/lib_kernels_maths.cu~ @@ -0,0 +1,226 @@ + +__device__ void calcul_indices_prec_suiv(int nb_nodes, int nx, int& nprec, int& nsuiv){ + nprec = (nx > 0)? (nx - 1):(nb_nodes - 1); + nsuiv = (nx < nb_nodes - 1)? (nx + 1):(0); +} + +__device__ double codage_niveau_gris_hyp_gaussienne_gpu(uint32 stat_sum_1, uint64 stat_sum_x, + uint64 stat_sum_x2, uint32 n_dim, + uint64 SUM_X, uint64 SUM_X2) +{ + uint64 stat_sum_xe ; /* somme des xn region exterieure */ + uint32 ne ; /* nombre de pixel region exterieure */ + double sigi2, sige2; /* variance region interieure et exterieure */ + + /* variance des valeurs des niveaux de gris a l'interieur du snake */ + sigi2 = + (double)stat_sum_x2/(double)stat_sum_1 - + (double)(stat_sum_x*stat_sum_x)/(double)((uint64)stat_sum_1*(uint64)stat_sum_1) ; + + /* variance des valeurs des niveaux de gris a l'exterieur du snake */ + ne = n_dim-stat_sum_1 ; + stat_sum_xe = SUM_X - stat_sum_x ; + sige2 = + (double)(SUM_X2-stat_sum_x2)/(double)ne - + (double)(stat_sum_xe*stat_sum_xe)/(double)((uint64)ne*(uint64)ne) ; + + if ((sigi2 <= 0)|(sige2 <= 0)) return -1; + + return 0.5*((double)stat_sum_1*log(sigi2) + (double)ne*log(sige2)) ; +} + +__device__ double codage_gl_hyp_gaussienne(uint64 stat_sum_1, uint64 stat_sum_x, uint64 stat_sum_x2, + uint64 n_dim, uint64 SUM_X, uint64 SUM_X2){ + uint64 stat_sum_xe ; /* somme des xn region exterieure */ + uint32 ne ; /* nombre de pixel region exterieure */ + double sigi2, sige2; /* variance region interieure et exterieure */ + + /* variance des valeurs des niveaux de gris a l'interieur du snake */ + sigi2 = + (double)stat_sum_x2/(double)stat_sum_1 - + (double)(stat_sum_x*stat_sum_x)/(double)((uint64)stat_sum_1*(uint64)stat_sum_1) ; + + /* variance des valeurs des niveaux de gris a l'exterieur du snake */ + ne = n_dim-stat_sum_1 ; + stat_sum_xe = SUM_X - stat_sum_x ; + sige2 = + (double)(SUM_X2-stat_sum_x2)/(double)ne - + (double)(stat_sum_xe*stat_sum_xe)/(double)((uint64)ne*(uint64)ne) ; + + if ((sigi2 > 0)|(sige2 > 0)) + return 0.5*((double)stat_sum_1*log(sigi2) + (double)ne*log(sige2)) ; + return -1 ; +} + +__device__ inline unsigned int nextPow2_gpu( unsigned int x ) { + --x; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + return ++x; +} + + +__device__ inline int sign_diff_strict(int val1, int val2) +{ + if (val1 > 0) + { + if (val2 >= 0) return 0 ; + else return 1 ; + } + else + if (val1 < 0) + { + if (val2 <= 0) return 0 ; + else return 1 ; + } + else + return 0 ;/* val1 == 0 */ +} + +__device__ inline int sign_diff_ou_egal_zero(int val1, int val2) +{ + if (val1 > 0) + { + if (val2 > 0) return 0 ; + else return 1 ; + } + else + if (val1 < 0) + { + if (val2 < 0) return 0 ; + else return 1 ; + } + else + return 1 ;/* val1 == 0 */ +} + + +__device__ int sinus_triangle(int Ai, int Aj, int Bi, int Bj, int Ci, int Cj) +{ + return (((Bi-Ai)*(Cj-Aj)) - ((Ci-Ai)*(Bj-Aj))) ; +} + +__device__ int test_croisement_large(uint32 Ai, uint32 Aj, uint32 Bi, uint32 Bj, + uint32 Ui, uint32 Uj, uint32 Vi, uint32 Vj) +{ + if (sign_diff_strict(sinus_triangle(Ai, Aj, Bi, Bj, Ui, Uj), // Bi-Ai, Uj-Aj, Ui-Ai, Bj-Aj + sinus_triangle(Ai, Aj, Bi, Bj, Vi, Vj))) // Bi-Ai, Vj-Aj, Vi-Ai, Bj-Aj + if (sign_diff_strict(sinus_triangle(Ui, Uj, Vi, Vj, Ai, Aj), // Vi-Ui, Aj-Uj, Ai-Ui, Vj-Uj + sinus_triangle(Ui, Uj, Vi, Vj, Bi, Bj))) // Vi-Ui, Bj-Uj, Bi-Ui, Vj-Uj + return 1 ; + return 0 ; +} + +__device__ int test_croisement_strict(uint32 Ai, uint32 Aj, uint32 Bi, uint32 Bj, + uint32 Ui, uint32 Uj, uint32 Vi, uint32 Vj ) +{ + if (sign_diff_ou_egal_zero(sinus_triangle(Ai, Aj, Bi, Bj, Ui, Uj), + sinus_triangle(Ai, Aj, Bi, Bj, Vi, Vj))) + if (sign_diff_ou_egal_zero(sinus_triangle(Ui, Uj, Vi, Vj, Ai, Aj), + sinus_triangle(Ui, Uj, Vi, Vj, Bi, Bj))) + return 1 ; + return 0 ; +} + +__global__ void kernel_test_croisement_move_seg_strict(struct snake_node_gpu *d_snake, uint32 nx, uint32 Nxi, uint32 Nxj, int Nb_nodes, bool * croist) +{ + int idx = blockDim.x*blockIdx.x + threadIdx.x, idx_prec, idx_suiv ; + int na, nb, naa, nbb ; + int Nai, Naj, Nbi, Nbj ; + bool croistil = false ; + + calcul_indices_prec_suiv(Nb_nodes, nx, na, nb); + calcul_indices_prec_suiv(Nb_nodes, na, naa, nbb); + + //coord. nodes extremites des segments a tester + Nai = d_snake[na].posi ; + Naj = d_snake[na].posj ; + Nbi = d_snake[nb].posi ; + Nbj = d_snake[nb].posj ; + + if (idx == nb) //premier segment apres + { + calcul_indices_prec_suiv(Nb_nodes, idx, idx_prec, idx_suiv); + if ( test_croisement_large(Nxi, Nxj, Nbi, Nbj, + Nbi, Nbj, d_snake[idx_suiv].posi, d_snake[idx_suiv].posj)) + croistil = true ; + else if ( test_croisement_strict(Nai, Naj, Nxi, Nxj, + Nbi, Nbj, d_snake[idx_suiv].posi, d_snake[idx_suiv].posj) ) + croistil = true ; + } + else if ( idx == naa ) //premier segment avant + { + if ( test_croisement_large(d_snake[naa].posi, d_snake[naa].posj, Nai, Naj, + Nai, Naj, Nxi, Nxj) ) + croistil = true ; + else if ( test_croisement_strict(d_snake[naa].posi, d_snake[naa].posj, Nai, Naj, + Nxi, Nxj, Nbi, Nbj)) + croistil = true ; + } + else if ( (idx != nx) && (idx != na) ) //les autres segments + { + calcul_indices_prec_suiv(Nb_nodes, idx, idx_prec, idx_suiv); + if ( test_croisement_strict(Nai, Naj, Nxi, Nxj, + d_snake[idx].posi, d_snake[idx].posj, + d_snake[idx_suiv].posi, d_snake[idx_suiv].posj) ) + croistil = true ; + else if ( test_croisement_strict(Nxi, Nxj, Nbi, Nbj, + d_snake[idx].posi, d_snake[idx].posj, + d_snake[idx_suiv].posi, d_snake[idx_suiv].posj) ) + croistil = true ; + } + + croist[idx] = croistil ; +} + +__device__ bool croisement(snake_node_gpu *d_snake, uint32 nx, uint32 Nxi, uint32 Nxj, int Nb_nodes) +{ + int idx_prec, idx_suiv ; + int na, nb, naa, nbb ; + int Nai, Naj, Nbi, Nbj ; + bool croistil = false ; + + //coord. nodes extremites des segments a tester + calcul_indices_prec_suiv(Nb_nodes, nx, na, nb); + calcul_indices_prec_suiv(Nb_nodes, na, naa, nbb); + + Nai = d_snake[na].posi ; + Naj = d_snake[na].posj ; + Nbi = d_snake[nb].posi ; + Nbj = d_snake[nb].posj ; + + //parcours du snake + for (int idx=0; idx < Nb_nodes; idx++){ + if (idx == nb) //premier segment apres + { + calcul_indices_prec_suiv(Nb_nodes, idx, idx_prec, idx_suiv); + if ( test_croisement_large(Nxi, Nxj, Nbi, Nbj, Nbi, Nbj, d_snake[idx_suiv].posi, d_snake[idx_suiv].posj) ) + return true ; + if ( test_croisement_strict(Nai, Naj, Nxi, Nxj, Nbi, Nbj, d_snake[idx_suiv].posi, d_snake[idx_suiv].posj) ) + return true ; + } + if ( idx == naa ) //premier segment avant + { + if ( test_croisement_large(d_snake[naa].posi, d_snake[naa].posj, Nai, Naj, Nai, Naj, Nxi, Nxj) ) + return true ; + if ( test_croisement_strict(d_snake[naa].posi, d_snake[naa].posj, Nai, Naj, Nxi, Nxj, Nbi, Nbj)) + return true ; + } + if ( (idx != nx) && (idx != na) && (idx != nb) && (idx != naa) ) //les autres segments + { + calcul_indices_prec_suiv(Nb_nodes, idx, idx_prec, idx_suiv); + if ( test_croisement_strict(Nai, Naj, Nxi, Nxj, + d_snake[idx].posi, d_snake[idx].posj, + d_snake[idx_suiv].posi, d_snake[idx_suiv].posj) ) + return true ; + if ( test_croisement_strict(Nxi, Nxj, Nbi, Nbj, + d_snake[idx].posi, d_snake[idx].posj, + d_snake[idx_suiv].posi, d_snake[idx_suiv].posj) ) + return true ; + } + } + return false; +} diff --git a/src/lib_kernels_maths.h b/src/lib_kernels_maths.h new file mode 100644 index 0000000..d827597 --- /dev/null +++ b/src/lib_kernels_maths.h @@ -0,0 +1,37 @@ + +#ifndef __KERNELS_MATHS__ +#define __KERNELS_MATHS__ + +__device__ void calcul_indices_prec_suiv(int nb_nodes, int nx, int& nprec, int& nsuiv); + +__device__ double codage_niveau_gris_hyp_gaussienne_gpu(uint32 stat_sum_1, uint64 stat_sum_x, + uint64 stat_sum_x2, uint32 n_dim, + uint64 SUM_X, uint64 SUM_X2); + +__device__ double codage_gl_hyp_gaussienne(uint64 stat_sum_1, uint64 stat_sum_x, uint64 stat_sum_x2, + uint64 n_dim, uint64 SUM_X, uint64 SUM_X2); + +__device__ inline unsigned int nextPow2_gpu( unsigned int x ); + +__device__ inline int sign_diff_strict(int val1, int val2); + + +__device__ inline int sign_diff_ou_egal_zero(int val1, int val2); + + +__device__ inline int sinus_triangle(int Ai, int Aj, int Bi, int Bj, int Ci, int Cj); + +__device__ inline int test_croisement_large(uint32 Ai, uint32 Aj, uint32 Bi, uint32 Bj, + uint32 Ui, uint32 Uj, uint32 Vi, uint32 Vj); + + +__device__ inline int test_croisement_strict(uint32 Ai, uint32 Aj, uint32 Bi, uint32 Bj, + uint32 Ui, uint32 Uj, uint32 Vi, uint32 Vj ); + + +__global__ void kernel_test_croisement_move_seg_strict(struct snake_node_gpu *d_snake, uint32 nx, uint32 Nxi, uint32 Nxj, int Nb_nodes, bool * croist); + + +__device__ bool test_croisement(struct snake_node_gpu *d_snake, uint32 nx, uint32 Nxi, uint32 Nxj, int Nb_nodes); + +#endif diff --git a/src/lib_kernels_maths.h~ b/src/lib_kernels_maths.h~ new file mode 100644 index 0000000..98d2830 --- /dev/null +++ b/src/lib_kernels_maths.h~ @@ -0,0 +1,231 @@ +#include "lib_kernels_maths.h" + +__device__ void calcul_indices_prec_suiv(int nb_nodes, int nx, int& nprec, int& nsuiv){ + nprec = (nx > 0)? (nx - 1):(nb_nodes - 1); + nsuiv = (nx < nb_nodes - 1)? (nx + 1):(0); +} + +__device__ double codage_niveau_gris_hyp_gaussienne_gpu(uint32 stat_sum_1, uint64 stat_sum_x, + uint64 stat_sum_x2, uint32 n_dim, + uint64 SUM_X, uint64 SUM_X2) +{ + uint64 stat_sum_xe ; /* somme des xn region exterieure */ + uint32 ne ; /* nombre de pixel region exterieure */ + double sigi2, sige2; /* variance region interieure et exterieure */ + + /* variance des valeurs des niveaux de gris a l'interieur du snake */ + sigi2 = + (double)stat_sum_x2/(double)stat_sum_1 - + (double)(stat_sum_x*stat_sum_x)/(double)((uint64)stat_sum_1*(uint64)stat_sum_1) ; + + /* variance des valeurs des niveaux de gris a l'exterieur du snake */ + ne = n_dim-stat_sum_1 ; + stat_sum_xe = SUM_X - stat_sum_x ; + sige2 = + (double)(SUM_X2-stat_sum_x2)/(double)ne - + (double)(stat_sum_xe*stat_sum_xe)/(double)((uint64)ne*(uint64)ne) ; + + if ((sigi2 <= 0)|(sige2 <= 0)) return -1; + + return 0.5*((double)stat_sum_1*log(sigi2) + (double)ne*log(sige2)) ; +} + +__device__ double codage_gl_hyp_gaussienne(uint64 stat_sum_1, uint64 stat_sum_x, uint64 stat_sum_x2, + uint64 n_dim, uint64 SUM_X, uint64 SUM_X2){ + uint64 stat_sum_xe ; /* somme des xn region exterieure */ + uint32 ne ; /* nombre de pixel region exterieure */ + double sigi2, sige2; /* variance region interieure et exterieure */ + + /* variance des valeurs des niveaux de gris a l'interieur du snake */ + sigi2 = + (double)stat_sum_x2/(double)stat_sum_1 - + (double)(stat_sum_x*stat_sum_x)/(double)((uint64)stat_sum_1*(uint64)stat_sum_1) ; + + /* variance des valeurs des niveaux de gris a l'exterieur du snake */ + ne = n_dim-stat_sum_1 ; + stat_sum_xe = SUM_X - stat_sum_x ; + sige2 = + (double)(SUM_X2-stat_sum_x2)/(double)ne - + (double)(stat_sum_xe*stat_sum_xe)/(double)((uint64)ne*(uint64)ne) ; + + if ((sigi2 > 0)|(sige2 > 0)) + return 0.5*((double)stat_sum_1*log(sigi2) + (double)ne*log(sige2)) ; + return -1 ; +} + +__device__ inline unsigned int nextPow2_gpu( unsigned int x ) { + --x; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + return ++x; +} + + +__device__ inline int sign_diff_strict(int val1, int val2) +{ + if (val1 > 0) + { + if (val2 >= 0) return 0 ; + else return 1 ; + } + else + if (val1 < 0) + { + if (val2 <= 0) return 0 ; + else return 1 ; + } + else + return 0 ;/* val1 == 0 */ +} + +__device__ inline int sign_diff_ou_egal_zero(int val1, int val2) +{ + if (val1 > 0) + { + if (val2 > 0) return 0 ; + else return 1 ; + } + else + if (val1 < 0) + { + if (val2 < 0) return 0 ; + else return 1 ; + } + else + return 1 ;/* val1 == 0 */ +} + + +__device__ inline int sinus_triangle(int Ai, int Aj, int Bi, int Bj, int Ci, int Cj) +{ + return (((Bi-Ai)*(Cj-Aj)) - ((Ci-Ai)*(Bj-Aj))) ; +} + +__device__ inline int test_croisement_large(uint32 Ai, uint32 Aj, uint32 Bi, uint32 Bj, + uint32 Ui, uint32 Uj, uint32 Vi, uint32 Vj) +{ + if (sign_diff_strict(sinus_triangle(Ai, Aj, Bi, Bj, Ui, Uj), // Bi-Ai, Uj-Aj, Ui-Ai, Bj-Aj + sinus_triangle(Ai, Aj, Bi, Bj, Vi, Vj))) // Bi-Ai, Vj-Aj, Vi-Ai, Bj-Aj + if (sign_diff_strict(sinus_triangle(Ui, Uj, Vi, Vj, Ai, Aj), // Vi-Ui, Aj-Uj, Ai-Ui, Vj-Uj + sinus_triangle(Ui, Uj, Vi, Vj, Bi, Bj))) // Vi-Ui, Bj-Uj, Bi-Ui, Vj-Uj + return 1 ; + return 0 ; +} + +__device__ inline int test_croisement_strict(uint32 Ai, uint32 Aj, uint32 Bi, uint32 Bj, + uint32 Ui, uint32 Uj, uint32 Vi, uint32 Vj ) +{ + if (sign_diff_ou_egal_zero(sinus_triangle(Ai, Aj, Bi, Bj, Ui, Uj), + sinus_triangle(Ai, Aj, Bi, Bj, Vi, Vj))) + if (sign_diff_ou_egal_zero(sinus_triangle(Ui, Uj, Vi, Vj, Ai, Aj), + sinus_triangle(Ui, Uj, Vi, Vj, Bi, Bj))) + return 1 ; + return 0 ; +} + +__global__ void kernel_test_croisement_move_seg_strict(struct snake_node_gpu *d_snake, uint32 nx, uint32 Nxi, uint32 Nxj, int Nb_nodes, bool * croist) +{ + int idx = blockDim.x*blockIdx.x + threadIdx.x, idx_prec, idx_suiv ; + int na, nb, naa, nbb ; + int Nai, Naj, Nbi, Nbj ; + bool croistil = false ; + + calcul_indices_prec_suiv(Nb_nodes, nx, na, nb); + calcul_indices_prec_suiv(Nb_nodes, na, naa, nbb); + + //coord. nodes extremites des segments a tester + Nai = d_snake[na].posi ; + Naj = d_snake[na].posj ; + Nbi = d_snake[nb].posi ; + Nbj = d_snake[nb].posj ; + + if (idx == nb) //premier segment apres + { + calcul_indices_prec_suiv(Nb_nodes, idx, idx_prec, idx_suiv); + if ( test_croisement_large(Nxi, Nxj, Nbi, Nbj, + Nbi, Nbj, d_snake[idx_suiv].posi, d_snake[idx_suiv].posj)) + croistil = true ; + else if ( test_croisement_strict(Nai, Naj, Nxi, Nxj, + Nbi, Nbj, d_snake[idx_suiv].posi, d_snake[idx_suiv].posj) ) + croistil = true ; + } + else if ( idx == naa ) //premier segment avant + { + if ( test_croisement_large(d_snake[naa].posi, d_snake[naa].posj, Nai, Naj, + Nai, Naj, Nxi, Nxj) ) + croistil = true ; + else if ( test_croisement_strict(d_snake[naa].posi, d_snake[naa].posj, Nai, Naj, + Nxi, Nxj, Nbi, Nbj)) + croistil = true ; + } + else if ( (idx != nx) && (idx != na) ) //les autres segments + { + calcul_indices_prec_suiv(Nb_nodes, idx, idx_prec, idx_suiv); + if ( test_croisement_strict(Nai, Naj, Nxi, Nxj, + d_snake[idx].posi, d_snake[idx].posj, + d_snake[idx_suiv].posi, d_snake[idx_suiv].posj) ) + croistil = true ; + else if ( test_croisement_strict(Nxi, Nxj, Nbi, Nbj, + d_snake[idx].posi, d_snake[idx].posj, + d_snake[idx_suiv].posi, d_snake[idx_suiv].posj) ) + croistil = true ; + } + + croist[idx] = croistil ; +} + +__device__ bool test_croisement(struct snake_node_gpu *d_snake, uint32 nx, uint32 Nxi, uint32 Nxj, int Nb_nodes) +{ + int idx_prec, idx_suiv ; + int na, nb, naa, nbb ; + int Nai, Naj, Nbi, Nbj ; + bool croistil = false ; + + //coord. nodes extremites des segments a tester + calcul_indices_prec_suiv(Nb_nodes, nx, na, nb); + calcul_indices_prec_suiv(Nb_nodes, na, naa, nbb); + + Nai = d_snake[na].posi ; + Naj = d_snake[na].posj ; + Nbi = d_snake[nb].posi ; + Nbj = d_snake[nb].posj ; + + //parcours du snake + for (int idx=0; idx < Nb_nodes; idx++){ + if (idx == nb) //premier segment apres + { + calcul_indices_prec_suiv(Nb_nodes, idx, idx_prec, idx_suiv); + if ( test_croisement_large(Nxi, Nxj, Nbi, Nbj, + Nbi, Nbj, d_snake[idx_suiv].posi, d_snake[idx_suiv].posj)) + croistil = true ; + else if ( test_croisement_strict(Nai, Naj, Nxi, Nxj, + Nbi, Nbj, d_snake[idx_suiv].posi, d_snake[idx_suiv].posj) ) + croistil = true ; + } + else if ( idx == naa ) //premier segment avant + { + if ( test_croisement_large(d_snake[naa].posi, d_snake[naa].posj, Nai, Naj, + Nai, Naj, Nxi, Nxj) ) + croistil = true ; + else if ( test_croisement_strict(d_snake[naa].posi, d_snake[naa].posj, Nai, Naj, + Nxi, Nxj, Nbi, Nbj)) + croistil = true ; + } + else if ( (idx != nx) && (idx != na) ) //les autres segments + { + calcul_indices_prec_suiv(Nb_nodes, idx, idx_prec, idx_suiv); + if ( test_croisement_strict(Nai, Naj, Nxi, Nxj, + d_snake[idx].posi, d_snake[idx].posj, + d_snake[idx_suiv].posi, d_snake[idx_suiv].posj) ) + croistil = true ; + else if ( test_croisement_strict(Nxi, Nxj, Nbi, Nbj, + d_snake[idx].posi, d_snake[idx].posj, + d_snake[idx_suiv].posi, d_snake[idx_suiv].posj) ) + croistil = true ; + } + } + return ( croistil ); +} diff --git a/src/lib_kernels_maths_gpu.cu~ b/src/lib_kernels_maths_gpu.cu~ new file mode 100644 index 0000000..98d2830 --- /dev/null +++ b/src/lib_kernels_maths_gpu.cu~ @@ -0,0 +1,231 @@ +#include "lib_kernels_maths.h" + +__device__ void calcul_indices_prec_suiv(int nb_nodes, int nx, int& nprec, int& nsuiv){ + nprec = (nx > 0)? (nx - 1):(nb_nodes - 1); + nsuiv = (nx < nb_nodes - 1)? (nx + 1):(0); +} + +__device__ double codage_niveau_gris_hyp_gaussienne_gpu(uint32 stat_sum_1, uint64 stat_sum_x, + uint64 stat_sum_x2, uint32 n_dim, + uint64 SUM_X, uint64 SUM_X2) +{ + uint64 stat_sum_xe ; /* somme des xn region exterieure */ + uint32 ne ; /* nombre de pixel region exterieure */ + double sigi2, sige2; /* variance region interieure et exterieure */ + + /* variance des valeurs des niveaux de gris a l'interieur du snake */ + sigi2 = + (double)stat_sum_x2/(double)stat_sum_1 - + (double)(stat_sum_x*stat_sum_x)/(double)((uint64)stat_sum_1*(uint64)stat_sum_1) ; + + /* variance des valeurs des niveaux de gris a l'exterieur du snake */ + ne = n_dim-stat_sum_1 ; + stat_sum_xe = SUM_X - stat_sum_x ; + sige2 = + (double)(SUM_X2-stat_sum_x2)/(double)ne - + (double)(stat_sum_xe*stat_sum_xe)/(double)((uint64)ne*(uint64)ne) ; + + if ((sigi2 <= 0)|(sige2 <= 0)) return -1; + + return 0.5*((double)stat_sum_1*log(sigi2) + (double)ne*log(sige2)) ; +} + +__device__ double codage_gl_hyp_gaussienne(uint64 stat_sum_1, uint64 stat_sum_x, uint64 stat_sum_x2, + uint64 n_dim, uint64 SUM_X, uint64 SUM_X2){ + uint64 stat_sum_xe ; /* somme des xn region exterieure */ + uint32 ne ; /* nombre de pixel region exterieure */ + double sigi2, sige2; /* variance region interieure et exterieure */ + + /* variance des valeurs des niveaux de gris a l'interieur du snake */ + sigi2 = + (double)stat_sum_x2/(double)stat_sum_1 - + (double)(stat_sum_x*stat_sum_x)/(double)((uint64)stat_sum_1*(uint64)stat_sum_1) ; + + /* variance des valeurs des niveaux de gris a l'exterieur du snake */ + ne = n_dim-stat_sum_1 ; + stat_sum_xe = SUM_X - stat_sum_x ; + sige2 = + (double)(SUM_X2-stat_sum_x2)/(double)ne - + (double)(stat_sum_xe*stat_sum_xe)/(double)((uint64)ne*(uint64)ne) ; + + if ((sigi2 > 0)|(sige2 > 0)) + return 0.5*((double)stat_sum_1*log(sigi2) + (double)ne*log(sige2)) ; + return -1 ; +} + +__device__ inline unsigned int nextPow2_gpu( unsigned int x ) { + --x; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + return ++x; +} + + +__device__ inline int sign_diff_strict(int val1, int val2) +{ + if (val1 > 0) + { + if (val2 >= 0) return 0 ; + else return 1 ; + } + else + if (val1 < 0) + { + if (val2 <= 0) return 0 ; + else return 1 ; + } + else + return 0 ;/* val1 == 0 */ +} + +__device__ inline int sign_diff_ou_egal_zero(int val1, int val2) +{ + if (val1 > 0) + { + if (val2 > 0) return 0 ; + else return 1 ; + } + else + if (val1 < 0) + { + if (val2 < 0) return 0 ; + else return 1 ; + } + else + return 1 ;/* val1 == 0 */ +} + + +__device__ inline int sinus_triangle(int Ai, int Aj, int Bi, int Bj, int Ci, int Cj) +{ + return (((Bi-Ai)*(Cj-Aj)) - ((Ci-Ai)*(Bj-Aj))) ; +} + +__device__ inline int test_croisement_large(uint32 Ai, uint32 Aj, uint32 Bi, uint32 Bj, + uint32 Ui, uint32 Uj, uint32 Vi, uint32 Vj) +{ + if (sign_diff_strict(sinus_triangle(Ai, Aj, Bi, Bj, Ui, Uj), // Bi-Ai, Uj-Aj, Ui-Ai, Bj-Aj + sinus_triangle(Ai, Aj, Bi, Bj, Vi, Vj))) // Bi-Ai, Vj-Aj, Vi-Ai, Bj-Aj + if (sign_diff_strict(sinus_triangle(Ui, Uj, Vi, Vj, Ai, Aj), // Vi-Ui, Aj-Uj, Ai-Ui, Vj-Uj + sinus_triangle(Ui, Uj, Vi, Vj, Bi, Bj))) // Vi-Ui, Bj-Uj, Bi-Ui, Vj-Uj + return 1 ; + return 0 ; +} + +__device__ inline int test_croisement_strict(uint32 Ai, uint32 Aj, uint32 Bi, uint32 Bj, + uint32 Ui, uint32 Uj, uint32 Vi, uint32 Vj ) +{ + if (sign_diff_ou_egal_zero(sinus_triangle(Ai, Aj, Bi, Bj, Ui, Uj), + sinus_triangle(Ai, Aj, Bi, Bj, Vi, Vj))) + if (sign_diff_ou_egal_zero(sinus_triangle(Ui, Uj, Vi, Vj, Ai, Aj), + sinus_triangle(Ui, Uj, Vi, Vj, Bi, Bj))) + return 1 ; + return 0 ; +} + +__global__ void kernel_test_croisement_move_seg_strict(struct snake_node_gpu *d_snake, uint32 nx, uint32 Nxi, uint32 Nxj, int Nb_nodes, bool * croist) +{ + int idx = blockDim.x*blockIdx.x + threadIdx.x, idx_prec, idx_suiv ; + int na, nb, naa, nbb ; + int Nai, Naj, Nbi, Nbj ; + bool croistil = false ; + + calcul_indices_prec_suiv(Nb_nodes, nx, na, nb); + calcul_indices_prec_suiv(Nb_nodes, na, naa, nbb); + + //coord. nodes extremites des segments a tester + Nai = d_snake[na].posi ; + Naj = d_snake[na].posj ; + Nbi = d_snake[nb].posi ; + Nbj = d_snake[nb].posj ; + + if (idx == nb) //premier segment apres + { + calcul_indices_prec_suiv(Nb_nodes, idx, idx_prec, idx_suiv); + if ( test_croisement_large(Nxi, Nxj, Nbi, Nbj, + Nbi, Nbj, d_snake[idx_suiv].posi, d_snake[idx_suiv].posj)) + croistil = true ; + else if ( test_croisement_strict(Nai, Naj, Nxi, Nxj, + Nbi, Nbj, d_snake[idx_suiv].posi, d_snake[idx_suiv].posj) ) + croistil = true ; + } + else if ( idx == naa ) //premier segment avant + { + if ( test_croisement_large(d_snake[naa].posi, d_snake[naa].posj, Nai, Naj, + Nai, Naj, Nxi, Nxj) ) + croistil = true ; + else if ( test_croisement_strict(d_snake[naa].posi, d_snake[naa].posj, Nai, Naj, + Nxi, Nxj, Nbi, Nbj)) + croistil = true ; + } + else if ( (idx != nx) && (idx != na) ) //les autres segments + { + calcul_indices_prec_suiv(Nb_nodes, idx, idx_prec, idx_suiv); + if ( test_croisement_strict(Nai, Naj, Nxi, Nxj, + d_snake[idx].posi, d_snake[idx].posj, + d_snake[idx_suiv].posi, d_snake[idx_suiv].posj) ) + croistil = true ; + else if ( test_croisement_strict(Nxi, Nxj, Nbi, Nbj, + d_snake[idx].posi, d_snake[idx].posj, + d_snake[idx_suiv].posi, d_snake[idx_suiv].posj) ) + croistil = true ; + } + + croist[idx] = croistil ; +} + +__device__ bool test_croisement(struct snake_node_gpu *d_snake, uint32 nx, uint32 Nxi, uint32 Nxj, int Nb_nodes) +{ + int idx_prec, idx_suiv ; + int na, nb, naa, nbb ; + int Nai, Naj, Nbi, Nbj ; + bool croistil = false ; + + //coord. nodes extremites des segments a tester + calcul_indices_prec_suiv(Nb_nodes, nx, na, nb); + calcul_indices_prec_suiv(Nb_nodes, na, naa, nbb); + + Nai = d_snake[na].posi ; + Naj = d_snake[na].posj ; + Nbi = d_snake[nb].posi ; + Nbj = d_snake[nb].posj ; + + //parcours du snake + for (int idx=0; idx < Nb_nodes; idx++){ + if (idx == nb) //premier segment apres + { + calcul_indices_prec_suiv(Nb_nodes, idx, idx_prec, idx_suiv); + if ( test_croisement_large(Nxi, Nxj, Nbi, Nbj, + Nbi, Nbj, d_snake[idx_suiv].posi, d_snake[idx_suiv].posj)) + croistil = true ; + else if ( test_croisement_strict(Nai, Naj, Nxi, Nxj, + Nbi, Nbj, d_snake[idx_suiv].posi, d_snake[idx_suiv].posj) ) + croistil = true ; + } + else if ( idx == naa ) //premier segment avant + { + if ( test_croisement_large(d_snake[naa].posi, d_snake[naa].posj, Nai, Naj, + Nai, Naj, Nxi, Nxj) ) + croistil = true ; + else if ( test_croisement_strict(d_snake[naa].posi, d_snake[naa].posj, Nai, Naj, + Nxi, Nxj, Nbi, Nbj)) + croistil = true ; + } + else if ( (idx != nx) && (idx != na) ) //les autres segments + { + calcul_indices_prec_suiv(Nb_nodes, idx, idx_prec, idx_suiv); + if ( test_croisement_strict(Nai, Naj, Nxi, Nxj, + d_snake[idx].posi, d_snake[idx].posj, + d_snake[idx_suiv].posi, d_snake[idx_suiv].posj) ) + croistil = true ; + else if ( test_croisement_strict(Nxi, Nxj, Nbi, Nbj, + d_snake[idx].posi, d_snake[idx].posj, + d_snake[idx_suiv].posi, d_snake[idx_suiv].posj) ) + croistil = true ; + } + } + return ( croistil ); +} diff --git a/src/lib_math.c b/src/lib_math.c new file mode 100644 index 0000000..79a2b20 --- /dev/null +++ b/src/lib_math.c @@ -0,0 +1,276 @@ +/** + * \file lib_math.c + * \brief routines + * \author NB - PhyTI + * \version x.x + * \date 20 decembre 2009 + * + */ + +#include +#include "lib_math.h" +#include "constantes.h" + +/** + * \fn void tic(struct timeval* temps, char* texte) + * \brief Initialise le compteur de temps + * + * \param[out] temps + * \param[in] texte texte a afficher + * + */ +void tic(struct timeval* temps, char* texte) +{ + gettimeofday(temps, NULL); + + if (texte != NULL) + printf("%s\n", texte) ; +} + +/** + * \fn double toc(struct timeval start, char* texte) + * \brief Calcule le temps ecoule + * + * \param[in] start temps de debut du chrono + * \param[in] texte texte a afficher + * + * \return le temps ecoule entre tic et toc + */ +double toc(struct timeval start, char* texte) +{ + struct timeval end ; + double elapsed ; + + gettimeofday(&end, NULL); + + elapsed = (double)(end.tv_sec-start.tv_sec) + + 0.000001*(double)(end.tv_usec-start.tv_usec); + if (texte != NULL) + printf("%s : %f\n", texte, elapsed) ; + + return elapsed ; +} + + +/** + * \fn void min_max_int1d(int *val_min, int *val_max, int *vect, int dim) + * \brief determine le min et max d'un vecteur de int + * + * \param[out] val_min + * \param[out] val_max + * \param[in] vect + * \param[in] dim dimension du vecteur + * + */ + +void min_max_int1d(int *val_min, int *val_max, int *vect, int dim) +{ + int n, min, max ; + + min = vect[1]; + max = min; + + for (n=0;n max) max = vect[n]; + if (vect[n] < min) min = vect[n]; + } + + *val_min = min ; + *val_max = max ; +} + +void min_max_ushort1d(int *val_min, int *val_max, unsigned short *vect, int dim) +{ + int n ; + unsigned short min, max ; + + min = vect[1]; + max = min; + + for (n=0;n max) max = vect[n]; + if (vect[n] < min) min = vect[n]; + } + + *val_min = min ; + *val_max = max ; +} + + + + +/** + * \fn inline int test_inf(double arg1, double arg2) + * + * \brief test (arg1 < arg2) inferieur a avec pourcentage minimum + * + * \param[in] arg1 + * \param[in] arg2 + * + * return test + */ +inline int test_inf(double arg1, double arg2) +{ + if (arg2 > 0) + return arg1 < (arg2*COEF_DECROI) ; + else + return arg1 < (arg2*INV_COEF_DECROI) ; +} + + + +/** + * \fn int calcul_px_autour_noeud(int ni, int nj, int pas, + * int idim_m1, int jdim_m1, + * int *liste_pixel_move) + * + * \brief determine les positions de test autout d'un noeud + * + * \param[in] ni coordonnee i + * \param[in] nj coordonnee j + * \param[in] pas ecartement par rapport au centre + * \param[in] idim_m1 dimension de l'image sur l'axe i (-1) + * \param[in] jdim_m1 dimension de l'image sur l'axe j (-1) + * \param[out] liste_pixel_move liste des coordonnees [i,j,i,j,...] + * + * return le nombre de coordonnees (*2) + */ +uint32 calcul_px_autour_noeud(uint32 ni, uint32 nj, int pas, + uint32 idim_m1, uint32 jdim_m1, + uint32 *liste_pixel_move) +{ + uint32 ind = 0 ; + + liste_pixel_move[ind++] = min(ni + pas, idim_m1) ; + liste_pixel_move[ind++] = nj ; + + liste_pixel_move[ind++] = min(ni + pas, idim_m1) ; + liste_pixel_move[ind++] = min(nj + pas, jdim_m1) ; + + liste_pixel_move[ind++] = ni ; + liste_pixel_move[ind++] = min(nj + pas, jdim_m1) ; + + liste_pixel_move[ind++] = max(ni - pas, 0) ; + liste_pixel_move[ind++] = min(nj + pas, jdim_m1) ; + + liste_pixel_move[ind++] = max(ni - pas, 0) ; + liste_pixel_move[ind++] = nj ; + + liste_pixel_move[ind++] = max(ni - pas, 0) ; + liste_pixel_move[ind++] = max(nj - pas, 0) ; + + liste_pixel_move[ind++] = ni ; + liste_pixel_move[ind++] = max(nj - pas, 0) ; + + liste_pixel_move[ind++] = min(ni + pas, idim_m1) ; + liste_pixel_move[ind++] = max(nj - pas, 0) ; + + return ind ; +} + + + +/** + * \fn inline int sign_diff_ou_egal_zero(int val1, int val2) + * + * \brief fonction qui test si les arguments sont de signes differents ou nuls + * \author NB - PhyTI + * + * \param[in] val1 + * \param[in] val2 + * + * \return le test 0/1 + * + */ +inline int sign_diff_ou_egal_zero(int val1, int val2) +{ + if (val1 > 0) + { + if (val2 > 0) return 0 ; + else return 1 ; + } + else + if (val1 < 0) + { + if (val2 < 0) return 0 ; + else return 1 ; + } + else + return 1 ;/* val1 == 0 */ +} + +/** + * \fn inline int sign_diff_strict(int val1, int val2) + * + * \brief fonction qui test si les arguments sont de signes differents strictement + * \author NB - PhyTI + * + * \param[in] val1 + * \param[in] val2 + * + * \return le test 0/1 + * + */ +inline int sign_diff_strict(int val1, int val2) +{ + if (val1 > 0) + { + if (val2 >= 0) return 0 ; + else return 1 ; + } + else + if (val1 < 0) + { + if (val2 <= 0) return 0 ; + else return 1 ; + } + else + return 0 ;/* val1 == 0 */ +} + + + +/** + * \fn inline int sinus_triangle(int Ai, int Aj, int Bi, int Bj, int Ci, int Cj) + * + * \brief calcul le "sinus" de l'angle du triangle ABC + * \author NB - PhyTI + * + * \param[in] Ai les coordonnees + * \param[in] Aj + * \param[in] Bi + * \param[in] Bj + * \param[in] Ci + * \param[in] Cj + * + * \return le sinus non normalise + * + * Cette fonction est utile pour determiner si un triangle ABC + * est donne dans l'ordre trigo. + * Signe > 0: sens trigo, + * signe < 0: sens antitrigo + * = 0: plat + */ +inline int sinus_triangle(int Ai, int Aj, int Bi, int Bj, int Ci, int Cj) +{ + return (((Bi-Ai)*(Cj-Aj)) - ((Ci-Ai)*(Bj-Aj))) ; +} + + +/** + * \fn void recopie_vecteur(int *in, int *out, int dim) + * + * \brief recopie le vecteur out vers in + * \author NB - PhyTI + * + * \param[in] in vecteur d'entree + * \param[out] out vecteur recopier + * \param[in] dim longueur du vecteur + */ +void recopie_vecteur(int *in, int *out, int dim) +{ + int n ; + for (n=0; n +#include "lib_math.h" +#include "constantes.h" + +/** + * \fn void tic(struct timeval* temps, char* texte) + * \brief Initialise le compteur de temps + * + * \param[out] temps + * \param[in] texte texte a afficher + * + */ +void tic(struct timeval* temps, char* texte) +{ + gettimeofday(temps, NULL); + + if (texte != NULL) + printf("%s\n", texte) ; +} + +/** + * \fn double toc(struct timeval start, char* texte) + * \brief Calcule le temps ecoule + * + * \param[in] start temps de debut du chrono + * \param[in] texte texte a afficher + * + * \return le temps ecoule entre tic et toc + */ +double toc(struct timeval start, char* texte) +{ + struct timeval end ; + double elapsed ; + + gettimeofday(&end, NULL); + + elapsed = (double)(end.tv_sec-start.tv_sec) + + 0.000001*(double)(end.tv_usec-start.tv_usec); + if (texte != NULL) + printf("%s : %f\n", texte, elapsed) ; + + return elapsed ; +} + + +/** + * \fn void min_max_int1d(int *val_min, int *val_max, int *vect, int dim) + * \brief determine le min et max d'un vecteur de int + * + * \param[out] val_min + * \param[out] val_max + * \param[in] vect + * \param[in] dim dimension du vecteur + * + */ +void min_max_int1d(int *val_min, int *val_max, int *vect, int dim) +{ + int n, min, max ; + + min = vect[1]; + max = min; + + for (n=0;n max) max = vect[n]; + if (vect[n] < min) min = vect[n]; + } + + *val_min = min ; + *val_max = max ; +} + + + + +/** + * \fn inline int test_inf(double arg1, double arg2) + * + * \brief test (arg1 < arg2) inferieur a avec pourcentage minimum + * + * \param[in] arg1 + * \param[in] arg2 + * + * return test + */ +inline int test_inf(double arg1, double arg2) +{ + if (arg2 > 0) + return arg1 < (arg2*COEF_DECROI) ; + else + return arg1 < (arg2*INV_COEF_DECROI) ; +} + + + +/** + * \fn int calcul_px_autour_noeud(int ni, int nj, int pas, + * int idim_m1, int jdim_m1, + * int *liste_pixel_move) + * + * \brief determine les positions de test autout d'un noeud + * + * \param[in] ni coordonnee i + * \param[in] nj coordonnee j + * \param[in] pas ecartement par rapport au centre + * \param[in] idim_m1 dimension de l'image sur l'axe i (-1) + * \param[in] jdim_m1 dimension de l'image sur l'axe j (-1) + * \param[out] liste_pixel_move liste des coordonnees [i,j,i,j,...] + * + * return le nombre de coordonnees (*2) + */ +uint32 calcul_px_autour_noeud(uint32 ni, uint32 nj, int pas, + uint32 idim_m1, uint32 jdim_m1, + uint32 *liste_pixel_move) +{ + uint32 ind = 0 ; + + liste_pixel_move[ind++] = min(ni + pas, idim_m1) ; + liste_pixel_move[ind++] = nj ; + + liste_pixel_move[ind++] = min(ni + pas, idim_m1) ; + liste_pixel_move[ind++] = min(nj + pas, jdim_m1) ; + + liste_pixel_move[ind++] = ni ; + liste_pixel_move[ind++] = min(nj + pas, jdim_m1) ; + + liste_pixel_move[ind++] = max(ni - pas, 0) ; + liste_pixel_move[ind++] = min(nj + pas, jdim_m1) ; + + liste_pixel_move[ind++] = max(ni - pas, 0) ; + liste_pixel_move[ind++] = nj ; + + liste_pixel_move[ind++] = max(ni - pas, 0) ; + liste_pixel_move[ind++] = max(nj - pas, 0) ; + + liste_pixel_move[ind++] = ni ; + liste_pixel_move[ind++] = max(nj - pas, 0) ; + + liste_pixel_move[ind++] = min(ni + pas, idim_m1) ; + liste_pixel_move[ind++] = max(nj - pas, 0) ; + + return ind ; +} + + + +/** + * \fn inline int sign_diff_ou_egal_zero(int val1, int val2) + * + * \brief fonction qui test si les arguments sont de signes differents ou nuls + * \author NB - PhyTI + * + * \param[in] val1 + * \param[in] val2 + * + * \return le test 0/1 + * + */ +inline int sign_diff_ou_egal_zero(int val1, int val2) +{ + if (val1 > 0) + { + if (val2 > 0) return 0 ; + else return 1 ; + } + else + if (val1 < 0) + { + if (val2 < 0) return 0 ; + else return 1 ; + } + else + return 1 ;/* val1 == 0 */ +} + +/** + * \fn inline int sign_diff_strict(int val1, int val2) + * + * \brief fonction qui test si les arguments sont de signes differents strictement + * \author NB - PhyTI + * + * \param[in] val1 + * \param[in] val2 + * + * \return le test 0/1 + * + */ +inline int sign_diff_strict(int val1, int val2) +{ + if (val1 > 0) + { + if (val2 >= 0) return 0 ; + else return 1 ; + } + else + if (val1 < 0) + { + if (val2 <= 0) return 0 ; + else return 1 ; + } + else + return 0 ;/* val1 == 0 */ +} + + + +/** + * \fn inline int sinus_triangle(int Ai, int Aj, int Bi, int Bj, int Ci, int Cj) + * + * \brief calcul le "sinus" de l'angle du triangle ABC + * \author NB - PhyTI + * + * \param[in] Ai les coordonnees + * \param[in] Aj + * \param[in] Bi + * \param[in] Bj + * \param[in] Ci + * \param[in] Cj + * + * \return le sinus non normalise + * + * Cette fonction est utile pour determiner si un triangle ABC + * est donne dans l'ordre trigo. + * Signe > 0: sens trigo, + * signe < 0: sens antitrigo + * = 0: plat + */ +inline int sinus_triangle(int Ai, int Aj, int Bi, int Bj, int Ci, int Cj) +{ + return (((Bi-Ai)*(Cj-Aj)) - ((Ci-Ai)*(Bj-Aj))) ; +} + + +/** + * \fn void recopie_vecteur(int *in, int *out, int dim) + * + * \brief recopie le vecteur out vers in + * \author NB - PhyTI + * + * \param[in] in vecteur d'entree + * \param[out] out vecteur recopier + * \param[in] dim longueur du vecteur + */ +void recopie_vecteur(int *in, int *out, int dim) +{ + int n ; + for (n=0; n +#include "structures.h" + +void tic(struct timeval* temps, char* texte) ; +double toc(struct timeval start, char* texte) ; + +void min_max_int1d(int *val_min, int *val_max, int *vect, int dim) ; +void min_max_ushort1d(int *val_min, int *val_max, unsigned short *vect, int dim) ; + +#define min(a,b) ((a)<(b)) ? (a) : (b) +#define max(a,b) ((a)>(b)) ? (a) : (b) + +uint32 calcul_px_autour_noeud(uint32 ni, uint32 nj, int pas, + uint32 idim_m1, uint32 jdim_m1, + uint32 *liste_pixel_move); + +inline int test_inf(double arg1, double arg2); + +inline int sign_diff_ou_egal_zero(int val1, int val2); +inline int sign_diff_strict(int val1, int val2); + +inline int sinus_triangle(int Ai, int Aj, int Bi, int Bj, int Ci, int Cj); + +void recopie_vecteur(int *in, int *out, int dim) ; + + +#endif diff --git a/src/lib_math.h~ b/src/lib_math.h~ new file mode 100644 index 0000000..ba4bead --- /dev/null +++ b/src/lib_math.h~ @@ -0,0 +1,30 @@ +#ifndef _LIB_MATH_H +#define _LIB_MATH_H + + +#include +#include "structures.h" + +void tic(struct timeval* temps, char* texte) ; +double toc(struct timeval start, char* texte) ; + +void min_max_int1d(int *val_min, int *val_max, int *vect, int dim) ; + +#define min(a,b) ((a)<(b)) ? (a) : (b) +#define max(a,b) ((a)>(b)) ? (a) : (b) + +uint32 calcul_px_autour_noeud(uint32 ni, uint32 nj, int pas, + uint32 idim_m1, uint32 jdim_m1, + uint32 *liste_pixel_move); + +inline int test_inf(double arg1, double arg2); + +inline int sign_diff_ou_egal_zero(int val1, int val2); +inline int sign_diff_strict(int val1, int val2); + +inline int sinus_triangle(int Ai, int Aj, int Bi, int Bj, int Ci, int Cj); + +void recopie_vecteur(int *in, int *out, int dim) ; + + +#endif diff --git a/src/lib_snake_2_gpu.cu b/src/lib_snake_2_gpu.cu new file mode 100644 index 0000000..63db755 --- /dev/null +++ b/src/lib_snake_2_gpu.cu @@ -0,0 +1,141 @@ + +extern "C"{ +#include "structures.h" +#include "lib_snake_2_gpu.h" +} +#include "stdio.h" + +void snake2gpu( snake_node_gpu * d_snake, snake_node* h_snake, int n ){ + + snake_node * snake = h_snake; + snake_node_gpu snake_nodes[n]; + + for (int i=0; icontrib.sum_1 ; + snake_nodes[i].sum_x = snake->contrib.sum_x ; + snake_nodes[i].sum_x2 = snake->contrib.sum_x2 ; + + snake_nodes[i].code_segment = snake->code_segment ; + snake_nodes[i].freeman_in = snake->freeman_in ; + snake_nodes[i].freeman_out = snake->freeman_out ; + snake_nodes[i].nb_pixels = snake->nb_pixels ; + + snake_nodes[i].posi = snake->posi ; + snake_nodes[i].posj = snake->posj ; + snake_nodes[i].centre_i = snake->centre_i ; + snake_nodes[i].centre_j = snake->centre_j ; + + snake_nodes[i].last_move = snake->last_move ; + + snake = snake->noeud_suiv ; + } + + cudaMemcpy( d_snake, snake_nodes, n*sizeof(snake_node_gpu), cudaMemcpyHostToDevice ); + + /*verif integrite data*/ + /* + snake_node_gpu new_snake_nodes[n]; + cudaMemcpy( new_snake_nodes , d_snake, n*sizeof(snake_node_gpu), cudaMemcpyDeviceToHost ); + + for (int i=0; inoeud_suiv = snake; + snake->noeud_prec = snake_prec; + } + if (i == n-1) { + snake->noeud_suiv = *h_snake; + (*h_snake)->noeud_prec = snake; + } + /* + snake->contrib.sum_1 = snake_nodes[i].sum_1 ; + snake->contrib.sum_x = snake_nodes[i].sum_x ; + snake->contrib.sum_x2 = snake_nodes[i].sum_x2 ; + + snake->code_segment = snake_nodes[i].code_segment ; + snake->freeman_in = snake_nodes[i].freeman_in ; + snake->freeman_out = snake_nodes[i].freeman_out ; + */ + snake->nb_pixels = snake_nodes[i].nb_pixels ; + snake->posi = snake_nodes[i].posi ; + snake->posj = snake_nodes[i].posj ; + /* + snake->centre_i = snake_nodes[i].centre_i ; + snake->centre_j = snake_nodes[i].centre_j ; + snake->last_move = snake_nodes[i].last_move ; + */ + } + + /*verif integrite donnees*/ + + snake = *h_snake ; + for (int i=0; icode_segment ) ; + printf("\tfreeman_in : %d\n", snake->freeman_in ); + printf("\tfreeman_out : %d\n", snake->freeman_out ) ; + */ + printf("\tnb_pixels : %d", snake->nb_pixels ) ; + + printf("\ti = %d ", snake->posi ) ; + printf("\tj = %d \n", snake->posj ) ; + /* + printf("\tcentre i = %d\n", snake->centre_i ) ; + printf("\tcentre j = %d\n", snake->centre_j ) ; + + printf("\tcontribs = %lu - %lu - %lu\n", snake->contrib.sum_1, snake->contrib.sum_x, snake->contrib.sum_x2 ) ; + + printf("\tlast_move : %d\n", snake->last_move ) ; + */ + snake = snake->noeud_suiv; + } + delete snake_nodes; +} + +void dessine_snake(snake_node_gpu * snake, int nnodes, unsigned short ** img, int r){ + int couleur = 255; + int i,j; + for (int node=0;node < nnodes; node++){ + i = snake[node].posi ; + j = snake[node].posj ; + for (int xi=0; xi<=r; xi++){ + img[i-xi][j] = couleur ; + img[i+xi][j] = couleur ; + img[i][j-xi] = couleur ; + img[i][j+xi] = couleur ; + } + } +} \ No newline at end of file diff --git a/src/lib_snake_2_gpu.cu~ b/src/lib_snake_2_gpu.cu~ new file mode 100644 index 0000000..3f44329 --- /dev/null +++ b/src/lib_snake_2_gpu.cu~ @@ -0,0 +1,126 @@ + +extern "C"{ +#include "structures.h" +#include "lib_snake_2_gpu.h" +} +#include "stdio.h" + +void snake2gpu( snake_node_gpu * d_snake, snake_node* h_snake, int n ){ + + snake_node * snake = h_snake; + snake_node_gpu snake_nodes[n]; + + for (int i=0; icontrib.sum_1 ; + snake_nodes[i].sum_x = snake->contrib.sum_x ; + snake_nodes[i].sum_x2 = snake->contrib.sum_x2 ; + + snake_nodes[i].code_segment = snake->code_segment ; + snake_nodes[i].freeman_in = snake->freeman_in ; + snake_nodes[i].freeman_out = snake->freeman_out ; + snake_nodes[i].nb_pixels = snake->nb_pixels ; + + snake_nodes[i].posi = snake->posi ; + snake_nodes[i].posj = snake->posj ; + snake_nodes[i].centre_i = snake->centre_i ; + snake_nodes[i].centre_j = snake->centre_j ; + + snake_nodes[i].last_move = snake->last_move ; + + snake = snake->noeud_suiv ; + } + + cudaMemcpy( d_snake, snake_nodes, n*sizeof(snake_node_gpu), cudaMemcpyHostToDevice ); + + /*verif integrite data*/ + /* + snake_node_gpu new_snake_nodes[n]; + cudaMemcpy( new_snake_nodes , d_snake, n*sizeof(snake_node_gpu), cudaMemcpyDeviceToHost ); + + for (int i=0; inoeud_suiv = snake; + snake->noeud_prec = snake_prec; + } + if (i == n-1) { + snake->noeud_suiv = *h_snake; + (*h_snake)->noeud_prec = snake; + } + /* + snake->contrib.sum_1 = snake_nodes[i].sum_1 ; + snake->contrib.sum_x = snake_nodes[i].sum_x ; + snake->contrib.sum_x2 = snake_nodes[i].sum_x2 ; + + snake->code_segment = snake_nodes[i].code_segment ; + snake->freeman_in = snake_nodes[i].freeman_in ; + snake->freeman_out = snake_nodes[i].freeman_out ; + */ + snake->nb_pixels = snake_nodes[i].nb_pixels ; + snake->posi = snake_nodes[i].posi ; + snake->posj = snake_nodes[i].posj ; + /* + snake->centre_i = snake_nodes[i].centre_i ; + snake->centre_j = snake_nodes[i].centre_j ; + snake->last_move = snake_nodes[i].last_move ; + */ + } + + /*verif integrite donnees*/ + + snake = *h_snake ; + for (int i=0; icode_segment ) ; + printf("\tfreeman_in : %d\n", snake->freeman_in ); + printf("\tfreeman_out : %d\n", snake->freeman_out ) ; + */ + printf("\tnb_pixels : %d", snake->nb_pixels ) ; + + printf("\ti = %d ", snake->posi ) ; + printf("\tj = %d \n", snake->posj ) ; + /* + printf("\tcentre i = %d\n", snake->centre_i ) ; + printf("\tcentre j = %d\n", snake->centre_j ) ; + + printf("\tcontribs = %lu - %lu - %lu\n", snake->contrib.sum_1, snake->contrib.sum_x, snake->contrib.sum_x2 ) ; + + printf("\tlast_move : %d\n", snake->last_move ) ; + */ + snake = snake->noeud_suiv; + } + delete snake_nodes; +} diff --git a/src/lib_snake_2_gpu.h b/src/lib_snake_2_gpu.h new file mode 100644 index 0000000..b4fa104 --- /dev/null +++ b/src/lib_snake_2_gpu.h @@ -0,0 +1,9 @@ +#ifndef __LIB_SNAKE_2_GPU_H__ +#define __LIB_SNAKE_2_GPU_H__ + +void snake2gpu( struct snake_node_gpu * d_snake, struct snake_node * h_snake, int n ); + +void gpu2snake( struct snake_node_gpu * d_snake, struct snake_node ** h_snake, int n ); + +void dessine_snake(snake_node_gpu * snake, int nnodes, unsigned short ** img, int r); +#endif diff --git a/src/lib_snake_2_gpu.h~ b/src/lib_snake_2_gpu.h~ new file mode 100644 index 0000000..506c387 --- /dev/null +++ b/src/lib_snake_2_gpu.h~ @@ -0,0 +1,8 @@ +#ifndef __LIB_SNAKE_2_GPU_H__ +#define __LIB_SNAKE_2_GPU_H__ + +void snake2gpu( struct snake_node_gpu * d_snake, struct snake_node * h_snake, int n ); + +void gpu2snake( struct snake_node_gpu * d_snake, struct snake_node ** h_snake, int n ); + +#endif diff --git a/src/lib_snake_common.c b/src/lib_snake_common.c new file mode 100644 index 0000000..dc2b49e --- /dev/null +++ b/src/lib_snake_common.c @@ -0,0 +1,723 @@ +/** + * \file lib_snake_common.c + * \brief routines de bases pour la gestion du snake + * \author NB - PhyTI + * \version x.x + * \date 25 decembre 2009 + * + */ + +#include + +#include "lib_snake_common.h" +#include "lib_contour.h" +#include "lib_math.h" + + +/* debug */ +#include +#include "lib_images.h" +#include "lib_alloc.h" + + +/** + * \fn struct snake_node* genere_snake_rectangle_bords(int *nb_noeud, int distance_bords, + * int i_dim, int j_dim) + * + * \brief Initialise un snake rectangulaire par rapport au bords de l'image + * \author NB - PhyTI + * + * \param[out] nb_noeud renvoie le nombre de node du snake + * \param[in] distance_bords distance du rectangle par rapport au bords + * \param[in] i_dim dimension verticale de l'image + * \param[in] j_dim dimension horizontale de l'image + * + * \return le pointeur du premier noeud du snake + * + * note : les calculs sse necessite l'alignement memoire des snake_node + */ +struct snake_node* genere_snake_rectangle_bords(uint32 *nb_noeud, uint32 distance_bords, + uint32 i_dim, uint32 j_dim) +{ + struct snake_node *n1, *n2, *n3 ; /* noeuds courant */ + struct snake_node *n0 = malloc(sizeof(struct snake_node)) ; + + /* n0 */ + *nb_noeud = 1 ; + n0->posi = distance_bords ; + n0->posj = distance_bords ; + n0->noeud_suiv = malloc(sizeof(struct snake_node)) ; + (*nb_noeud)++ ; + /* n1 */ + n1 = n0->noeud_suiv ; + n1->posi = i_dim - distance_bords ; + n1->posj = distance_bords ; + n1->noeud_prec = n0 ; + n1->noeud_suiv = malloc(sizeof(struct snake_node)) ; + (*nb_noeud)++ ; + /* n2 */ + n2 = n1->noeud_suiv ; + n2->posi = i_dim - distance_bords ; + n2->posj = j_dim - distance_bords ; + n2->noeud_prec = n1 ; + n2->noeud_suiv = malloc(sizeof(struct snake_node)) ; + (*nb_noeud)++ ; + /* n3 */ + n3 = n2->noeud_suiv ; + n3->posi = distance_bords ; + n3->posj = j_dim - distance_bords ; + n3->noeud_prec = n2 ; + n3->noeud_suiv = n0 ; /* on ferme le snake */ + /* n0, on ferme le snake */ + n0->noeud_prec = n3 ; + + return n0 ; +} + +/** + * \fn struct snake_node* genere_snake_rectangle(int *nb_noeud,int i1,int j1,int i2,int j2, + * int i_dim, int j_dim) + * + * \brief Initialise un snake rectangulaire definie par deux noeuds + * \author NB - PhyTI + * + * \param[out] nb_noeud renvoie le nombre de node du snake + * \param[in] i1 coordonnee du coin haut-gauche + * \param[in] j1 coordonnee du coin haut-gauche + * \param[in] i2 coordonnee du coin bas-droit + * \param[in] j2 coordonnee du coin bas-droit + * \param[in] i_dim dimension verticale de l'image + * \param[in] j_dim dimension horizontale de l'image + * + * \return le pointeur du premier noeud du snake + * + * note : les calculs sse necessite l'alignement memoire des snake_node + */ +struct snake_node* genere_snake_rectangle(int *nb_noeud, uint32 i1, uint32 j1, uint32 i2, uint32 j2, + uint32 i_dim, uint32 j_dim) +{ + struct snake_node *n1, *n2, *n3 ; /* noeuds courant */ + struct snake_node *n0 = malloc(sizeof(struct snake_node)) ; + + /* n0 */ + *nb_noeud = 1 ; + n0->posi = i1 ; + n0->posj = j1 ; + n0->noeud_suiv = malloc(sizeof(struct snake_node)) ; + (*nb_noeud)++ ; + /* n1 */ + n1 = n0->noeud_suiv ; + n1->posi = i2 ; + n1->posj = j1 ; + n1->noeud_prec = n0 ; + n1->noeud_suiv = malloc(sizeof(struct snake_node)) ; + (*nb_noeud)++ ; + /* n2 */ + n2 = n1->noeud_suiv ; + n2->posi = i2 ; + n2->posj = j2 ; + n2->noeud_prec = n1 ; + n2->noeud_suiv = malloc(sizeof(struct snake_node)) ; + (*nb_noeud)++ ; + /* n3 */ + n3 = n2->noeud_suiv ; + n3->posi = i1 ; + n3->posj = j2 ; + n3->noeud_prec = n2 ; + n3->noeud_suiv = n0 ; /* on ferme le snake */ + /* n0, on ferme le snake */ + n0->noeud_prec = n3 ; + + return n0 ; +} + + + + + + +/** + * \fn int affiche_snake(int **image, struct snake_node *snake, + * int valseg, int valnoeud, int *liste_pixel_segment) + * + * \brief Affiche le snake sur une image N&B + * \author NB - PhyTI + * + * \param[out] image image ou le snake doit etre affiche + * \param[in] snake premier noeud du snake + * \param[in] valseg valeur de niveau de gris des segments + * \param[in] valnoeud valeur de niveau de gris des noeuds + * \param[out] liste_pixel_segment pour caluls temporaire + * + * \return nb_noeud le nombre de noeuds du snake + * + */ +void affiche_snake(int **image, struct snake_node *snake, int valseg, int valnoeud, + uint32 *liste_pixel_segment) +{ + uint32 i1, j1, i2, j2 ; + uint32 n, nb_pixel_segment ; + struct snake_node *np, *npp1 ; + + /* les segments */ + np = snake ; + npp1 = snake->noeud_suiv ; + do + { + i1 = np->posi ; j1 = np->posj ; + i2 = npp1->posi ; j2 = npp1->posj ; + + nb_pixel_segment = calcul_liste_pixel_segment(i1,j1,i2,j2, liste_pixel_segment, 0) ; + for (n=1; nnoeud_suiv ; + } + while (np != snake) ; + + /* les deux noeuds */ + np = snake ; + npp1 = snake->noeud_suiv ; + do + { + i1 = np->posi ; j1 = np->posj ; + i2 = npp1->posi ; j2 = npp1->posj ; + + image[i1][j1] = valnoeud ; + image[i2][j2] = valnoeud ; + + np = npp1 ; + npp1 = np->noeud_suiv ; + } + while (np != snake) ; +} + +void affiche_snake_ushort(unsigned short **image, struct snake_node *snake, int valseg, unsigned short valnoeud, + uint32 *liste_pixel_segment) +{ + uint32 i1, j1, i2, j2 ; + uint32 n, nb_pixel_segment ; + struct snake_node *np, *npp1 ; + + /* les segments */ + np = snake ; + npp1 = snake->noeud_suiv ; + + do + { + i1 = np->posi ; j1 = np->posj ; + i2 = npp1->posi ; j2 = npp1->posj ; + + nb_pixel_segment = calcul_liste_pixel_segment(i1,j1,i2,j2, liste_pixel_segment, 0) ; + + for (n=1; nnoeud_suiv ; + + } + while (np != snake) ; + + /* les deux noeuds */ + np = snake ; + npp1 = snake->noeud_suiv ; + /* + do + { + i1 = np->posi ; j1 = np->posj ; + i2 = npp1->posi ; j2 = npp1->posj ; + + image[i1][j1] = valnoeud ; + image[i2][j2] = valnoeud ; + + np = npp1 ; + npp1 = np->noeud_suiv ; + } + while (np != snake) ; + */ +} + +/* ========== DEBUG ================= */ +/* fonction d'affichage pour le debug */ +/* affiche les deux premiers segments avec valseg-1 */ +/* et incremenent les valeurs des noeuds */ +void affiche_snake_couleur(int **image, struct snake_node *snake, int valseg, int valnoeud, + uint32 *liste_pixel_segment) +{ + uint32 i1, j1, i2, j2 ; + uint32 n, nb_pixel_segment ; + struct snake_node *np, *npp1 ; + int cpt = 0; + snake = snake->noeud_prec ; + + /* les segments */ + np = snake ; + npp1 = np->noeud_suiv ; + do + { + i1 = np->posi ; j1 = np->posj ; + i2 = npp1->posi ; j2 = npp1->posj ; + + nb_pixel_segment = calcul_liste_pixel_segment(i1,j1,i2,j2, liste_pixel_segment, 0) ; + if (cpt < 2 ) + for (n=1; nnoeud_suiv ; + } + while (np != snake) ; + + /* les deux noeuds */ + cpt = 0 ; + np = snake ; + npp1 = snake->noeud_suiv ; + do + { + i1 = np->posi ; j1 = np->posj ; + i2 = npp1->posi ; j2 = npp1->posj ; + + image[i1][j1] = valnoeud+cpt ; + image[i2][j2] = valnoeud+cpt++ ; + + np = npp1 ; + npp1 = np->noeud_suiv ; + } + while (np != snake) ; +} + +/* ========= DEBUG ===================== */ +/* copie l'image du snake avec un numero */ +/* de fichier incremente a chaque appel */ +void debug_aff_snake(struct snake_node *snake) +{ + const int idim=256, jdim=256 ; + + int **image = new_matrix_int(idim, jdim) ; + uint32 *liste_pixel = malloc(sizeof(int)*2*(idim+jdim)) ; + static int iter=0 ; + char output[256] ; + int n; + + for (n=0; nposi = node->centre_i ; + najout->posj = node->centre_j ; + + najout->noeud_prec = node ; + najout->noeud_suiv = node->noeud_suiv ; + node->noeud_suiv->noeud_prec = najout ; + node->noeud_suiv = najout ; + + (*nb_noeud)++ ; + return najout ; +} + + + + +/** + * \fn inline int test_croisement_segment_large(int Ai, int Aj, int Bi, int Bj, + * int Ui, int Uj, int Vi, int Vj) + * + * \brief Test qui determine si deux segments croisent + * \author NB - PhyTI + * + * \param[in] Ai + * \param[in] Aj + * \param[in] Bi + * \param[in] Bj + * \param[in] Ui + * \param[in] Uj + * \param[in] Vi + * \param[in] Vj + * + * \return 1 si croisement, 0 sinon + * + * Version large : autorise les triangles nulles + * Test de croisement des segments : + * si deux segments AB et UV croisent, alors + * les triangles ABU et ABV change d'ordre de rotation + * ET + * les triangles UVA et UVB change aussi d'ordre de rotation + * + */ +inline int test_croisement_segment_large(uint32 Ai, uint32 Aj, uint32 Bi, uint32 Bj, + uint32 Ui, uint32 Uj, uint32 Vi, uint32 Vj) +{ + if (sign_diff_strict(sinus_triangle(Ai, Aj, Bi, Bj, Ui, Uj), // Bi-Ai, Uj-Aj, Ui-Ai, Bj-Aj + sinus_triangle(Ai, Aj, Bi, Bj, Vi, Vj))) // Bi-Ai, Vj-Aj, Vi-Ai, Bj-Aj + if (sign_diff_strict(sinus_triangle(Ui, Uj, Vi, Vj, Ai, Aj), // Vi-Ui, Aj-Uj, Ai-Ui, Vj-Uj + sinus_triangle(Ui, Uj, Vi, Vj, Bi, Bj))) // Vi-Ui, Bj-Uj, Bi-Ui, Vj-Uj + return 1 ; + return 0 ; +} + + +/** + * \fn inline int test_croisement_segment_strict(int Ai, int Aj, int Bi, int Bj, + * int Ui, int Uj, int Vi, int Vj) + * + * \brief Test qui determine si deux segments croisent + * \author NB - PhyTI + * + * \param[in] Ai + * \param[in] Aj + * \param[in] Bi + * \param[in] Bj + * \param[in] Ui + * \param[in] Uj + * \param[in] Vi + * \param[in] Vj + * + * \return 1 si croisement, 0 sinon + * + * Version strict : n'autorise pas les triangles nulles + * Test de croisement des segments : + * si deux segments AB et UV croisent, alors + * les triangles ABU et ABV change d'ordre de rotation + * ET + * les triangles UVA et UVB change aussi d'ordre de rotation + * + */ +inline int test_croisement_segment_strict(uint32 Ai, uint32 Aj, uint32 Bi, uint32 Bj, + uint32 Ui, uint32 Uj, uint32 Vi, uint32 Vj) +{ + if (sign_diff_ou_egal_zero(sinus_triangle(Ai, Aj, Bi, Bj, Ui, Uj), + sinus_triangle(Ai, Aj, Bi, Bj, Vi, Vj))) + if (sign_diff_ou_egal_zero(sinus_triangle(Ui, Uj, Vi, Vj, Ai, Aj), + sinus_triangle(Ui, Uj, Vi, Vj, Bi, Bj))) + return 1 ; + return 0 ; +} + + + + + +/** + * \fn int test_croisement_add_noeud_large(struct snake_node *N, int Nxi, int Nxj, int seg) + * + * \brief test si il y a croisement de segments lors de l'ajout d'un noeud + * \author NB - PhyTI + * + * \param[in] N le noeud ou l'on veut ajouter un noeud Nx au milieu de son segment + * \param[in] Nxi coordonnee i du nouveau node Nx + * \param[in] Nxj coordonnee j du nouveau node Nx + * \param[in] seg booleen pour definir quel segment on test: + * Nx --> Nsuiv si seg==1 + * N --> Nx si seg==0 + * + * \return 1 si croisement, 0 sinon + * + * Version large : autorise les triangles nulles + * + */ +int test_croisement_add_noeud_large(struct snake_node *N, uint32 Nxi, uint32 Nxj, int seg) +{ + uint32 Nrefi=0, Nrefj=0 ; /* reference avec Nxi et Nxj */ + uint32 posi, posj, suivi, suivj ; /* segment courant de test */ + struct snake_node *node, *end ; + + if (seg==1) + { + Nrefi = N->noeud_suiv->posi ; + Nrefj = N->noeud_suiv->posj ; + } + else + { + Nrefi = N->posi ; + Nrefj = N->posj ; + } + + node = N->noeud_suiv ; + suivi = node->posi ; + suivj = node->posj ; + + end = N ; + while (node != end) + { + posi = suivi ; + posj = suivj ; + node = node->noeud_suiv ; + suivi = node->posi ; + suivj = node->posj ; + if (test_croisement_segment_large(Nxi,Nxj,Nrefi,Nrefj, posi,posj,suivi,suivj)) + return 1 ; + } + return 0 ; +} + + + +/** + * \fn int test_croisement_add_noeud_strict(struct snake_node *N, int Nxi, int Nxj, int seg) + * + * \brief test si il y a croisement de segments lors de l'ajout d'un noeud + * \author NB - PhyTI + * + * \param[in] N le noeud ou l'on veut ajouter un noeud Nx au milieu de son segment + * \param[in] Nxi coordonnee i du nouveau node Nx + * \param[in] Nxj coordonnee j du nouveau node Nx + * \param[in] seg booleen pour definir quel segment on test: + * Nx --> Nsuiv si seg==1 + * N --> Nx si seg==0 + * + * \return 1 si croisement, 0 sinon + * + * Version strict : n'autorise pas les triangles nulles + * + */ +int test_croisement_add_noeud_strict(struct snake_node *N, uint32 Nxi, uint32 Nxj, int seg) +{ + uint32 Nrefi=0, Nrefj=0 ; /* reference avec Nxi et Nxj */ + uint32 posi, posj, suivi, suivj ; /* segment courant de test */ + struct snake_node *node, *end ; + + /* segment de reference */ + if (seg) + { + Nrefi = N->noeud_suiv->posi ; + Nrefj = N->noeud_suiv->posj ; + } + else + { + Nrefi = N->posi ; + Nrefj = N->posj ; + } + + /* segments de tests */ + node = N->noeud_suiv ; + suivi = node->posi ; + suivj = node->posj ; + + if (seg) + { + /* premier segment : il touche le segment de ref */ + /* pour Nx --> Nsuiv(ref) */ + posi = suivi ; + posj = suivj ; + node = node->noeud_suiv ; + suivi = node->posi ; + suivj = node->posj ; + if (test_croisement_segment_large(Nxi,Nxj,Nrefi,Nrefj,posi,posj,suivi,suivj)) + return 1 ; + end = N ; /* pour boucle while */ + } + else + /* pour N --> Nx */ + end = N->noeud_prec ; /* pour boucle while */ + + /* boucle sur les segments */ + while (node != end) + { + posi = suivi ; + posj = suivj ; + node = node->noeud_suiv ; + suivi = node->posi ; + suivj = node->posj ; + if (test_croisement_segment_strict(posi,posj,suivi,suivj,Nxi,Nxj,Nrefi,Nrefj)) + return 1 ; + } + + /* dernier segment */ + /* cas N --> Nx */ + if (!seg) + { + posi = suivi ; + posj = suivj ; + node = node->noeud_suiv ; + suivi = node->posi ; + suivj = node->posj ; + if (test_croisement_segment_large(Nxi,Nxj,Nrefi,Nrefj, posi,posj,suivi,suivj)) + return 1 ; + } + return 0 ; +} + + + + + + +/** + * \fn int test_croisement_move_seg_large(struct snake_node *Nx, int Nxi, int Nxj, int seg) + * + * \brief test si il y a croisement de segments lors du deplacement d'un noeud + * \author NB - PhyTI + * + * \param[in] Nx le noeud que l'on veut deplacer + * \param[in] Nxi coordonnee i du nouveau node Nx + * \param[in] Nxj coordonnee j du nouveau node Nx + * \param[in] seg booleen pour definir quel segment on test: + * Nx --> Nsuiv si seg==1 + * Nprec --> Nx si seg==0 + * + * \return 1 si croisement, 0 sinon + * + * Version large : autorise les triangles nulles + * Version non optimisee car elle test systematiquement tout les segments du snake ! + * + */ +int test_croisement_move_seg_large(struct snake_node *Nx, uint32 Nxi, uint32 Nxj, int seg) +{ + uint32 Nrefi=0, Nrefj=0 ; /* reference avec Nxi et Nxj */ + uint32 posi, posj, suivi, suivj ; /* segment courant de test */ + struct snake_node *node, *end ; + + if (seg==1) + { + Nrefi = Nx->noeud_suiv->posi ; + Nrefj = Nx->noeud_suiv->posj ; + } + else + { + Nrefi = Nx->noeud_prec->posi ; + Nrefj = Nx->noeud_prec->posj ; + } + + node = Nx->noeud_suiv ; + suivi = node->posi ; + suivj = node->posj ; + + end = Nx->noeud_prec ; + while (node != end) + { + posi = suivi ; + posj = suivj ; + node = node->noeud_suiv ; + suivi = node->posi ; + suivj = node->posj ; + if (test_croisement_segment_large(Nxi,Nxj,Nrefi,Nrefj, posi,posj,suivi,suivj)) + return 1 ; + } + return 0 ; +} + + + +/** + * \fn int test_croisement_move_seg_strict(struct snake_node *Nx, int Nxi, int Nxj, int seg) + * + * \brief test si il y a croisement de segments lors du deplacement d'un noeud + * \author NB - PhyTI + * + * \param[in] Nx le noeud que l'on veut deplacer + * \param[in] Nxi coordonnee i du nouveau node Nx + * \param[in] Nxj coordonnee j du nouveau node Nx + * \param[in] seg booleen pour definir quel segment on test: + * Nx --> Nsuiv si seg==1 + * Nprec --> Nx si seg==0 + * + * \return 1 si croisement, 0 sinon + * + * Version strict : n'autorise pas les triangles nulles + * Version non optimisee car elle test systematiquement tout les segments du snake ! + */ +int test_croisement_move_seg_strict(struct snake_node *Nx, uint32 Nxi, uint32 Nxj, int seg) +{ + uint32 Nrefi=0, Nrefj=0 ; /* reference avec Nxi et Nxj */ + uint32 posi, posj, suivi, suivj ; /* segment courant de test */ + struct snake_node *node, *end ; + + /* segment de reference */ + if (seg) + { + Nrefi = Nx->noeud_suiv->posi ; + Nrefj = Nx->noeud_suiv->posj ; + } + else + { + Nrefi = Nx->noeud_prec->posi ; + Nrefj = Nx->noeud_prec->posj ; + } + + /* segments de tests */ + node = Nx->noeud_suiv ; + suivi = node->posi ; + suivj = node->posj ; + + if (seg) + { + /* premier segment : il touche le segment de ref */ + /* pour Nx --> Nsuiv(ref) */ + posi = suivi ; + posj = suivj ; + node = node->noeud_suiv ; + suivi = node->posi ; + suivj = node->posj ; + if (test_croisement_segment_large(Nxi,Nxj,Nrefi,Nrefj,posi,posj,suivi,suivj)) + return 1 ; + + end = Nx->noeud_prec ; /* pour boucle while */ + } + else + /* pour Nprec --> Nx */ + end = Nx->noeud_prec->noeud_prec ; /* pour boucle while */ + + /* boucle sur les segments */ + while (node != end) + { + posi = suivi ; + posj = suivj ; + node = node->noeud_suiv ; + suivi = node->posi ; + suivj = node->posj ; + if (test_croisement_segment_strict(posi,posj,suivi,suivj,Nxi,Nxj,Nrefi,Nrefj)) + return 1 ; + } + + /* dernier segment */ + /* cas Nprec --> Nx */ + if (!seg) + { + posi = suivi ; + posj = suivj ; + node = node->noeud_suiv ; + suivi = node->posi ; + suivj = node->posj ; + if (test_croisement_segment_large(Nxi,Nxj,Nrefi,Nrefj, posi,posj,suivi,suivj)) + return 1 ; + } + return 0 ; +} + diff --git a/src/lib_snake_common.c~ b/src/lib_snake_common.c~ new file mode 100644 index 0000000..ed1bfc8 --- /dev/null +++ b/src/lib_snake_common.c~ @@ -0,0 +1,718 @@ +/** + * \file lib_snake_common.c + * \brief routines de bases pour la gestion du snake + * \author NB - PhyTI + * \version x.x + * \date 25 decembre 2009 + * + */ + +#include + +#include "lib_snake_common.h" +#include "lib_contour.h" +#include "lib_math.h" + + +/* debug */ +#include +#include "lib_images.h" +#include "lib_alloc.h" + + +/** + * \fn struct snake_node* genere_snake_rectangle_bords(int *nb_noeud, int distance_bords, + * int i_dim, int j_dim) + * + * \brief Initialise un snake rectangulaire par rapport au bords de l'image + * \author NB - PhyTI + * + * \param[out] nb_noeud renvoie le nombre de node du snake + * \param[in] distance_bords distance du rectangle par rapport au bords + * \param[in] i_dim dimension verticale de l'image + * \param[in] j_dim dimension horizontale de l'image + * + * \return le pointeur du premier noeud du snake + * + * note : les calculs sse necessite l'alignement memoire des snake_node + */ +struct snake_node* genere_snake_rectangle_bords(uint32 *nb_noeud, uint32 distance_bords, + uint32 i_dim, uint32 j_dim) +{ + struct snake_node *n1, *n2, *n3 ; /* noeuds courant */ + struct snake_node *n0 = malloc(sizeof(struct snake_node)) ; + + /* n0 */ + *nb_noeud = 1 ; + n0->posi = distance_bords ; + n0->posj = distance_bords ; + n0->noeud_suiv = malloc(sizeof(struct snake_node)) ; + (*nb_noeud)++ ; + /* n1 */ + n1 = n0->noeud_suiv ; + n1->posi = i_dim - distance_bords ; + n1->posj = distance_bords ; + n1->noeud_prec = n0 ; + n1->noeud_suiv = malloc(sizeof(struct snake_node)) ; + (*nb_noeud)++ ; + /* n2 */ + n2 = n1->noeud_suiv ; + n2->posi = i_dim - distance_bords ; + n2->posj = j_dim - distance_bords ; + n2->noeud_prec = n1 ; + n2->noeud_suiv = malloc(sizeof(struct snake_node)) ; + (*nb_noeud)++ ; + /* n3 */ + n3 = n2->noeud_suiv ; + n3->posi = distance_bords ; + n3->posj = j_dim - distance_bords ; + n3->noeud_prec = n2 ; + n3->noeud_suiv = n0 ; /* on ferme le snake */ + /* n0, on ferme le snake */ + n0->noeud_prec = n3 ; + + return n0 ; +} + +/** + * \fn struct snake_node* genere_snake_rectangle(int *nb_noeud,int i1,int j1,int i2,int j2, + * int i_dim, int j_dim) + * + * \brief Initialise un snake rectangulaire definie par deux noeuds + * \author NB - PhyTI + * + * \param[out] nb_noeud renvoie le nombre de node du snake + * \param[in] i1 coordonnee du coin haut-gauche + * \param[in] j1 coordonnee du coin haut-gauche + * \param[in] i2 coordonnee du coin bas-droit + * \param[in] j2 coordonnee du coin bas-droit + * \param[in] i_dim dimension verticale de l'image + * \param[in] j_dim dimension horizontale de l'image + * + * \return le pointeur du premier noeud du snake + * + * note : les calculs sse necessite l'alignement memoire des snake_node + */ +struct snake_node* genere_snake_rectangle(int *nb_noeud, uint32 i1, uint32 j1, uint32 i2, uint32 j2, + uint32 i_dim, uint32 j_dim) +{ + struct snake_node *n1, *n2, *n3 ; /* noeuds courant */ + struct snake_node *n0 = malloc(sizeof(struct snake_node)) ; + + /* n0 */ + *nb_noeud = 1 ; + n0->posi = i1 ; + n0->posj = j1 ; + n0->noeud_suiv = malloc(sizeof(struct snake_node)) ; + (*nb_noeud)++ ; + /* n1 */ + n1 = n0->noeud_suiv ; + n1->posi = i2 ; + n1->posj = j1 ; + n1->noeud_prec = n0 ; + n1->noeud_suiv = malloc(sizeof(struct snake_node)) ; + (*nb_noeud)++ ; + /* n2 */ + n2 = n1->noeud_suiv ; + n2->posi = i2 ; + n2->posj = j2 ; + n2->noeud_prec = n1 ; + n2->noeud_suiv = malloc(sizeof(struct snake_node)) ; + (*nb_noeud)++ ; + /* n3 */ + n3 = n2->noeud_suiv ; + n3->posi = i1 ; + n3->posj = j2 ; + n3->noeud_prec = n2 ; + n3->noeud_suiv = n0 ; /* on ferme le snake */ + /* n0, on ferme le snake */ + n0->noeud_prec = n3 ; + + return n0 ; +} + + + + + + +/** + * \fn int affiche_snake(int **image, struct snake_node *snake, + * int valseg, int valnoeud, int *liste_pixel_segment) + * + * \brief Affiche le snake sur une image N&B + * \author NB - PhyTI + * + * \param[out] image image ou le snake doit etre affiche + * \param[in] snake premier noeud du snake + * \param[in] valseg valeur de niveau de gris des segments + * \param[in] valnoeud valeur de niveau de gris des noeuds + * \param[out] liste_pixel_segment pour caluls temporaire + * + * \return nb_noeud le nombre de noeuds du snake + * + */ +void affiche_snake(int **image, struct snake_node *snake, int valseg, int valnoeud, + uint32 *liste_pixel_segment) +{ + uint32 i1, j1, i2, j2 ; + uint32 n, nb_pixel_segment ; + struct snake_node *np, *npp1 ; + + /* les segments */ + np = snake ; + npp1 = snake->noeud_suiv ; + do + { + i1 = np->posi ; j1 = np->posj ; + i2 = npp1->posi ; j2 = npp1->posj ; + + nb_pixel_segment = calcul_liste_pixel_segment(i1,j1,i2,j2, liste_pixel_segment, 0) ; + for (n=1; nnoeud_suiv ; + } + while (np != snake) ; + + /* les deux noeuds */ + np = snake ; + npp1 = snake->noeud_suiv ; + do + { + i1 = np->posi ; j1 = np->posj ; + i2 = npp1->posi ; j2 = npp1->posj ; + + image[i1][j1] = valnoeud ; + image[i2][j2] = valnoeud ; + + np = npp1 ; + npp1 = np->noeud_suiv ; + } + while (np != snake) ; +} + +void affiche_snake_ushort(unsigned short **image, struct snake_node *snake, int valseg, unsigned short valnoeud, + uint32 *liste_pixel_segment) +{ + uint32 i1, j1, i2, j2 ; + uint32 n, nb_pixel_segment ; + struct snake_node *np, *npp1 ; + + /* les segments */ + np = snake ; + npp1 = snake->noeud_suiv ; + do + { + i1 = np->posi ; j1 = np->posj ; + i2 = npp1->posi ; j2 = npp1->posj ; + + nb_pixel_segment = calcul_liste_pixel_segment(i1,j1,i2,j2, liste_pixel_segment, 0) ; + for (n=1; nnoeud_suiv ; + } + while (np != snake) ; + + /* les deux noeuds */ + np = snake ; + npp1 = snake->noeud_suiv ; + do + { + i1 = np->posi ; j1 = np->posj ; + i2 = npp1->posi ; j2 = npp1->posj ; + + image[i1][j1] = valnoeud ; + image[i2][j2] = valnoeud ; + + np = npp1 ; + npp1 = np->noeud_suiv ; + } + while (np != snake) ; +} + +/* ========== DEBUG ================= */ +/* fonction d'affichage pour le debug */ +/* affiche les deux premiers segments avec valseg-1 */ +/* et incremenent les valeurs des noeuds */ +void affiche_snake_couleur(int **image, struct snake_node *snake, int valseg, int valnoeud, + uint32 *liste_pixel_segment) +{ + uint32 i1, j1, i2, j2 ; + uint32 n, nb_pixel_segment ; + struct snake_node *np, *npp1 ; + int cpt = 0; + snake = snake->noeud_prec ; + + /* les segments */ + np = snake ; + npp1 = np->noeud_suiv ; + do + { + i1 = np->posi ; j1 = np->posj ; + i2 = npp1->posi ; j2 = npp1->posj ; + + nb_pixel_segment = calcul_liste_pixel_segment(i1,j1,i2,j2, liste_pixel_segment, 0) ; + if (cpt < 2 ) + for (n=1; nnoeud_suiv ; + } + while (np != snake) ; + + /* les deux noeuds */ + cpt = 0 ; + np = snake ; + npp1 = snake->noeud_suiv ; + do + { + i1 = np->posi ; j1 = np->posj ; + i2 = npp1->posi ; j2 = npp1->posj ; + + image[i1][j1] = valnoeud+cpt ; + image[i2][j2] = valnoeud+cpt++ ; + + np = npp1 ; + npp1 = np->noeud_suiv ; + } + while (np != snake) ; +} + +/* ========= DEBUG ===================== */ +/* copie l'image du snake avec un numero */ +/* de fichier incremente a chaque appel */ +void debug_aff_snake(struct snake_node *snake) +{ + const int idim=256, jdim=256 ; + + int **image = new_matrix_int(idim, jdim) ; + uint32 *liste_pixel = malloc(sizeof(int)*2*(idim+jdim)) ; + static int iter=0 ; + char output[256] ; + int n; + + for (n=0; nposi = node->centre_i ; + najout->posj = node->centre_j ; + + najout->noeud_prec = node ; + najout->noeud_suiv = node->noeud_suiv ; + node->noeud_suiv->noeud_prec = najout ; + node->noeud_suiv = najout ; + + (*nb_noeud)++ ; + return najout ; +} + + + + +/** + * \fn inline int test_croisement_segment_large(int Ai, int Aj, int Bi, int Bj, + * int Ui, int Uj, int Vi, int Vj) + * + * \brief Test qui determine si deux segments croisent + * \author NB - PhyTI + * + * \param[in] Ai + * \param[in] Aj + * \param[in] Bi + * \param[in] Bj + * \param[in] Ui + * \param[in] Uj + * \param[in] Vi + * \param[in] Vj + * + * \return 1 si croisement, 0 sinon + * + * Version large : autorise les triangles nulles + * Test de croisement des segments : + * si deux segments AB et UV croisent, alors + * les triangles ABU et ABV change d'ordre de rotation + * ET + * les triangles UVA et UVB change aussi d'ordre de rotation + * + */ +inline int test_croisement_segment_large(uint32 Ai, uint32 Aj, uint32 Bi, uint32 Bj, + uint32 Ui, uint32 Uj, uint32 Vi, uint32 Vj) +{ + if (sign_diff_strict(sinus_triangle(Ai, Aj, Bi, Bj, Ui, Uj), // Bi-Ai, Uj-Aj, Ui-Ai, Bj-Aj + sinus_triangle(Ai, Aj, Bi, Bj, Vi, Vj))) // Bi-Ai, Vj-Aj, Vi-Ai, Bj-Aj + if (sign_diff_strict(sinus_triangle(Ui, Uj, Vi, Vj, Ai, Aj), // Vi-Ui, Aj-Uj, Ai-Ui, Vj-Uj + sinus_triangle(Ui, Uj, Vi, Vj, Bi, Bj))) // Vi-Ui, Bj-Uj, Bi-Ui, Vj-Uj + return 1 ; + return 0 ; +} + + +/** + * \fn inline int test_croisement_segment_strict(int Ai, int Aj, int Bi, int Bj, + * int Ui, int Uj, int Vi, int Vj) + * + * \brief Test qui determine si deux segments croisent + * \author NB - PhyTI + * + * \param[in] Ai + * \param[in] Aj + * \param[in] Bi + * \param[in] Bj + * \param[in] Ui + * \param[in] Uj + * \param[in] Vi + * \param[in] Vj + * + * \return 1 si croisement, 0 sinon + * + * Version strict : n'autorise pas les triangles nulles + * Test de croisement des segments : + * si deux segments AB et UV croisent, alors + * les triangles ABU et ABV change d'ordre de rotation + * ET + * les triangles UVA et UVB change aussi d'ordre de rotation + * + */ +inline int test_croisement_segment_strict(uint32 Ai, uint32 Aj, uint32 Bi, uint32 Bj, + uint32 Ui, uint32 Uj, uint32 Vi, uint32 Vj) +{ + if (sign_diff_ou_egal_zero(sinus_triangle(Ai, Aj, Bi, Bj, Ui, Uj), + sinus_triangle(Ai, Aj, Bi, Bj, Vi, Vj))) + if (sign_diff_ou_egal_zero(sinus_triangle(Ui, Uj, Vi, Vj, Ai, Aj), + sinus_triangle(Ui, Uj, Vi, Vj, Bi, Bj))) + return 1 ; + return 0 ; +} + + + + + +/** + * \fn int test_croisement_add_noeud_large(struct snake_node *N, int Nxi, int Nxj, int seg) + * + * \brief test si il y a croisement de segments lors de l'ajout d'un noeud + * \author NB - PhyTI + * + * \param[in] N le noeud ou l'on veut ajouter un noeud Nx au milieu de son segment + * \param[in] Nxi coordonnee i du nouveau node Nx + * \param[in] Nxj coordonnee j du nouveau node Nx + * \param[in] seg booleen pour definir quel segment on test: + * Nx --> Nsuiv si seg==1 + * N --> Nx si seg==0 + * + * \return 1 si croisement, 0 sinon + * + * Version large : autorise les triangles nulles + * + */ +int test_croisement_add_noeud_large(struct snake_node *N, uint32 Nxi, uint32 Nxj, int seg) +{ + uint32 Nrefi=0, Nrefj=0 ; /* reference avec Nxi et Nxj */ + uint32 posi, posj, suivi, suivj ; /* segment courant de test */ + struct snake_node *node, *end ; + + if (seg==1) + { + Nrefi = N->noeud_suiv->posi ; + Nrefj = N->noeud_suiv->posj ; + } + else + { + Nrefi = N->posi ; + Nrefj = N->posj ; + } + + node = N->noeud_suiv ; + suivi = node->posi ; + suivj = node->posj ; + + end = N ; + while (node != end) + { + posi = suivi ; + posj = suivj ; + node = node->noeud_suiv ; + suivi = node->posi ; + suivj = node->posj ; + if (test_croisement_segment_large(Nxi,Nxj,Nrefi,Nrefj, posi,posj,suivi,suivj)) + return 1 ; + } + return 0 ; +} + + + +/** + * \fn int test_croisement_add_noeud_strict(struct snake_node *N, int Nxi, int Nxj, int seg) + * + * \brief test si il y a croisement de segments lors de l'ajout d'un noeud + * \author NB - PhyTI + * + * \param[in] N le noeud ou l'on veut ajouter un noeud Nx au milieu de son segment + * \param[in] Nxi coordonnee i du nouveau node Nx + * \param[in] Nxj coordonnee j du nouveau node Nx + * \param[in] seg booleen pour definir quel segment on test: + * Nx --> Nsuiv si seg==1 + * N --> Nx si seg==0 + * + * \return 1 si croisement, 0 sinon + * + * Version strict : n'autorise pas les triangles nulles + * + */ +int test_croisement_add_noeud_strict(struct snake_node *N, uint32 Nxi, uint32 Nxj, int seg) +{ + uint32 Nrefi=0, Nrefj=0 ; /* reference avec Nxi et Nxj */ + uint32 posi, posj, suivi, suivj ; /* segment courant de test */ + struct snake_node *node, *end ; + + /* segment de reference */ + if (seg) + { + Nrefi = N->noeud_suiv->posi ; + Nrefj = N->noeud_suiv->posj ; + } + else + { + Nrefi = N->posi ; + Nrefj = N->posj ; + } + + /* segments de tests */ + node = N->noeud_suiv ; + suivi = node->posi ; + suivj = node->posj ; + + if (seg) + { + /* premier segment : il touche le segment de ref */ + /* pour Nx --> Nsuiv(ref) */ + posi = suivi ; + posj = suivj ; + node = node->noeud_suiv ; + suivi = node->posi ; + suivj = node->posj ; + if (test_croisement_segment_large(Nxi,Nxj,Nrefi,Nrefj,posi,posj,suivi,suivj)) + return 1 ; + end = N ; /* pour boucle while */ + } + else + /* pour N --> Nx */ + end = N->noeud_prec ; /* pour boucle while */ + + /* boucle sur les segments */ + while (node != end) + { + posi = suivi ; + posj = suivj ; + node = node->noeud_suiv ; + suivi = node->posi ; + suivj = node->posj ; + if (test_croisement_segment_strict(posi,posj,suivi,suivj,Nxi,Nxj,Nrefi,Nrefj)) + return 1 ; + } + + /* dernier segment */ + /* cas N --> Nx */ + if (!seg) + { + posi = suivi ; + posj = suivj ; + node = node->noeud_suiv ; + suivi = node->posi ; + suivj = node->posj ; + if (test_croisement_segment_large(Nxi,Nxj,Nrefi,Nrefj, posi,posj,suivi,suivj)) + return 1 ; + } + return 0 ; +} + + + + + + +/** + * \fn int test_croisement_move_seg_large(struct snake_node *Nx, int Nxi, int Nxj, int seg) + * + * \brief test si il y a croisement de segments lors du deplacement d'un noeud + * \author NB - PhyTI + * + * \param[in] Nx le noeud que l'on veut deplacer + * \param[in] Nxi coordonnee i du nouveau node Nx + * \param[in] Nxj coordonnee j du nouveau node Nx + * \param[in] seg booleen pour definir quel segment on test: + * Nx --> Nsuiv si seg==1 + * Nprec --> Nx si seg==0 + * + * \return 1 si croisement, 0 sinon + * + * Version large : autorise les triangles nulles + * Version non optimisee car elle test systematiquement tout les segments du snake ! + * + */ +int test_croisement_move_seg_large(struct snake_node *Nx, uint32 Nxi, uint32 Nxj, int seg) +{ + uint32 Nrefi=0, Nrefj=0 ; /* reference avec Nxi et Nxj */ + uint32 posi, posj, suivi, suivj ; /* segment courant de test */ + struct snake_node *node, *end ; + + if (seg==1) + { + Nrefi = Nx->noeud_suiv->posi ; + Nrefj = Nx->noeud_suiv->posj ; + } + else + { + Nrefi = Nx->noeud_prec->posi ; + Nrefj = Nx->noeud_prec->posj ; + } + + node = Nx->noeud_suiv ; + suivi = node->posi ; + suivj = node->posj ; + + end = Nx->noeud_prec ; + while (node != end) + { + posi = suivi ; + posj = suivj ; + node = node->noeud_suiv ; + suivi = node->posi ; + suivj = node->posj ; + if (test_croisement_segment_large(Nxi,Nxj,Nrefi,Nrefj, posi,posj,suivi,suivj)) + return 1 ; + } + return 0 ; +} + + + +/** + * \fn int test_croisement_move_seg_strict(struct snake_node *Nx, int Nxi, int Nxj, int seg) + * + * \brief test si il y a croisement de segments lors du deplacement d'un noeud + * \author NB - PhyTI + * + * \param[in] Nx le noeud que l'on veut deplacer + * \param[in] Nxi coordonnee i du nouveau node Nx + * \param[in] Nxj coordonnee j du nouveau node Nx + * \param[in] seg booleen pour definir quel segment on test: + * Nx --> Nsuiv si seg==1 + * Nprec --> Nx si seg==0 + * + * \return 1 si croisement, 0 sinon + * + * Version strict : n'autorise pas les triangles nulles + * Version non optimisee car elle test systematiquement tout les segments du snake ! + */ +int test_croisement_move_seg_strict(struct snake_node *Nx, uint32 Nxi, uint32 Nxj, int seg) +{ + uint32 Nrefi=0, Nrefj=0 ; /* reference avec Nxi et Nxj */ + uint32 posi, posj, suivi, suivj ; /* segment courant de test */ + struct snake_node *node, *end ; + + /* segment de reference */ + if (seg) + { + Nrefi = Nx->noeud_suiv->posi ; + Nrefj = Nx->noeud_suiv->posj ; + } + else + { + Nrefi = Nx->noeud_prec->posi ; + Nrefj = Nx->noeud_prec->posj ; + } + + /* segments de tests */ + node = Nx->noeud_suiv ; + suivi = node->posi ; + suivj = node->posj ; + + if (seg) + { + /* premier segment : il touche le segment de ref */ + /* pour Nx --> Nsuiv(ref) */ + posi = suivi ; + posj = suivj ; + node = node->noeud_suiv ; + suivi = node->posi ; + suivj = node->posj ; + if (test_croisement_segment_large(Nxi,Nxj,Nrefi,Nrefj,posi,posj,suivi,suivj)) + return 1 ; + + end = Nx->noeud_prec ; /* pour boucle while */ + } + else + /* pour Nprec --> Nx */ + end = Nx->noeud_prec->noeud_prec ; /* pour boucle while */ + + /* boucle sur les segments */ + while (node != end) + { + posi = suivi ; + posj = suivj ; + node = node->noeud_suiv ; + suivi = node->posi ; + suivj = node->posj ; + if (test_croisement_segment_strict(posi,posj,suivi,suivj,Nxi,Nxj,Nrefi,Nrefj)) + return 1 ; + } + + /* dernier segment */ + /* cas Nprec --> Nx */ + if (!seg) + { + posi = suivi ; + posj = suivj ; + node = node->noeud_suiv ; + suivi = node->posi ; + suivj = node->posj ; + if (test_croisement_segment_large(Nxi,Nxj,Nrefi,Nrefj, posi,posj,suivi,suivj)) + return 1 ; + } + return 0 ; +} + diff --git a/src/lib_snake_common.h b/src/lib_snake_common.h new file mode 100644 index 0000000..7fa395a --- /dev/null +++ b/src/lib_snake_common.h @@ -0,0 +1,21 @@ +#include "structures.h" + + +struct snake_node* genere_snake_rectangle_bords(uint32 *nb_noeud, uint32 distance_bords, uint32 i_dim, uint32 j_dim) ; +struct snake_node* genere_snake_rectangle(int *nb_noeud, uint32 i1, uint32 j1, uint32 i2, uint32 j2, + uint32 i_dim, uint32 j_dim) ; + + +void affiche_snake(int **image, struct snake_node *snake, int valseg, int valnoeud, uint32 *liste_pixel_segment) ; +void affiche_snake_ushort(unsigned short **image, struct snake_node *snake, int valseg, unsigned short valnoeud, uint32 *liste_pixel_segment) ; + + +struct snake_node* ajout_noeud_snake(struct snake_node *node, uint32 *nb_noeud) ; +int test_croisement_add_noeud_large(struct snake_node *N, uint32 Nxi, uint32 Nxj, int seg); +int test_croisement_add_noeud_strict(struct snake_node *N, uint32 Nxi, uint32 Nxj, int seg); + +int test_croisement_move_seg_strict(struct snake_node *Nx, uint32 Nxi, uint32 Nxj, int seg); +int test_croisement_move_seg_large(struct snake_node *Nx, uint32 Nxi, uint32 Nxj, int seg); + + +void debug_aff_snake(struct snake_node *snake); diff --git a/src/lib_snake_common.h~ b/src/lib_snake_common.h~ new file mode 100644 index 0000000..ba8e011 --- /dev/null +++ b/src/lib_snake_common.h~ @@ -0,0 +1,21 @@ +#include "structures.h" + + +struct snake_node* genere_snake_rectangle_bords(uint32 *nb_noeud, uint32 distance_bords, uint32 i_dim, uint32 j_dim) ; +struct snake_node* genere_snake_rectangle(int *nb_noeud, uint32 i1, uint32 j1, uint32 i2, uint32 j2, + uint32 i_dim, uint32 j_dim) ; + + +void affiche_snake(int **image, struct snake_node *snake, int valseg, int valnoeud, uint32 *liste_pixel_segment) ; +void affiche_snake_ushort(unsigned short **image, struct snake_node *snake, int valseg, int valnoeud, uint32 *liste_pixel_segment) ; + + +struct snake_node* ajout_noeud_snake(struct snake_node *node, uint32 *nb_noeud) ; +int test_croisement_add_noeud_large(struct snake_node *N, uint32 Nxi, uint32 Nxj, int seg); +int test_croisement_add_noeud_strict(struct snake_node *N, uint32 Nxi, uint32 Nxj, int seg); + +int test_croisement_move_seg_strict(struct snake_node *Nx, uint32 Nxi, uint32 Nxj, int seg); +int test_croisement_move_seg_large(struct snake_node *Nx, uint32 Nxi, uint32 Nxj, int seg); + + +void debug_aff_snake(struct snake_node *snake); diff --git a/src/lib_test_gpu.cu b/src/lib_test_gpu.cu new file mode 100644 index 0000000..c65ea16 --- /dev/null +++ b/src/lib_test_gpu.cu @@ -0,0 +1,78 @@ +#include "stdio.h" +#include "structures.h" +#include "lib_test_gpu.h" + +void verif_cumuls(int ** img_in, int H, int L, uint64 ** d_img_1, uint64 ** d_img_x, uint64 ** d_img_x2, int BS, bool DISPLAY){ + int taille = H*L ; + //allocation memoire CPU + uint64 img_1[H][L]; + uint64 img_x[H][L]; + uint64 img_x2[H][L]; + + /*pour test comparaison*/ + uint64 img_1b[H][L]; + uint64 img_xb[H][L]; + uint64 img_x2b[H][L]; + cudaMemcpy( img_1b, *d_img_1, taille*sizeof(uint64), cudaMemcpyDeviceToHost ); + cudaMemcpy( img_xb, *d_img_x, taille*sizeof(uint64), cudaMemcpyDeviceToHost); + cudaMemcpy( img_x2b, *d_img_x2, taille*sizeof(uint64), cudaMemcpyDeviceToHost); + + for (int i=0; i +#include +#include "structures.h" +extern "C"{ +#include "lib_alloc.h" +#include "lib_images.h" +#include "lib_snake_common.h" +#include "lib_math.h" +#include "lib_gpu.h" +#include "defines.h" +#include "lib_snake_2_gpu.h" +} +#include "lib_kernels_maths.cu" +#include "lib_kernels_contribs.cu" + + + + +int main(int argc, char **argv) +{ + /* declaration des variables */ + int ret ; + int Prof ; /* profondeur en octets */ + uint32 I_dim ; /* hauteur de l'image */ + uint32 J_dim ; /* largeur de l'image */ + int Nb_level ; /* dynamique de l'image */ + char *File_name ; + + + /* images */ + unsigned short **Image_in; + struct timeval chrono, chrono_all ; + + /* lecture argument entree (basique!) */ + File_name = argv[3] ; + + /* verif type image (pgm 8/16) */ + ret = type_image_ppm(&Prof, &I_dim, &J_dim, &Nb_level, File_name) ; + + if ((ret == 0) | (Prof == 3)) + { + printf("format non pris en charge ... exit\n") ; + return(0) ; + } + + /* infos */ + printf("Image : %s\n", File_name) ; + printf("lecture OK : %d\n", ret) ; + printf("Image (%d x %d) pixels\n", I_dim, J_dim) ; + printf("Dynamique : %d\n", Nb_level) ; + + /* Allocation */ + Image_in = new_matrix_ushort(I_dim, J_dim) ; + + /* chargement image d'entree */ + load_pgm2ushort(Image_in, I_dim, J_dim, Nb_level, File_name) ; + + //POINTEURS VARIABLES MEMOIRE GLOBALE GPU + unsigned short * d_img ; // image + t_cumul_x * d_img_x ; // images cumulees + t_cumul_x2 * d_img_x2; // + + snake_node_gpu * d_snake ; //image du snake CPU dans un tableau en gmem GPUe + + int * d_freemanDiDj ; // table de correspondance [Di][Dj]->Freemans + int * d_codeNoeud ; // table de correspondance [F_in][F_out]->codeNoeud + + uint4 * d_positions ; // positions de test autour des noeuds + + uint2 * d_listes_pixels ; // coordonnees des pixels des segments correspondants aux 8 posiionstest + uint2 * d_liste_temp ; + uint32 * d_nb_pix_max ; // taille max des segments a tester + + uint64 * d_contribs_segments_blocs ;// sommes des contribs pixels par blocs de calcul + uint64 * d_contribs_segments ; // contribs segments 1, x et x2 + uint64 * d_sompart ; // vecteur de resultats intermediaires (sommes partielles = sommes par blocs) + + int64 * d_stats, * d_stats_ref ; // stats des positions de test, du snake sans les segments en test + int64 * d_stats_snake; // stats du snake + stats de l'image complete + double * d_vrais, * d_vrais_snake ; // valeurs de la log-vraisemblance des positions de test, du snake + + uint4 * d_freemans_centres ; // valeurs des F_in, F_out et coord. + // centres des 16 segments associes aux 8 positions de test + + int * d_codes_segments ; // valeurs de codes des 16 segments + bool * d_move ; // nb de deplacement effectues lors d'une iteration + int * d_nb_nodes ; // nb de noeuds du snake + + snake_node_gpu * d_snake_tmp ; // snake tampon pour l'etape d'ajout de noeuds + + /* pointeurs sur mem CPU */ + int *h_nb_nodes = new int; // image CPU du nb de noeud du snake + snake_node_gpu h_snake[MAX_NODES]; + double h_vrais_snake, h_vrais_mem ; // image CPU de la log-vraisemblance + bool * h_move = new bool[MAX_NODES];// image CPU du vecteur identifiant les noeuds qui ont bouge + uint32 h_nb_pix_max, npixmax ; // taille max des segments a tester : utile pour determiner les params d'execution + int nnodes = 4 ; // 4 ou 40 pour l'instant + + + /*allocation memoire GPU */ + cudaMalloc((void**) &d_nb_nodes, sizeof(int)); + cudaMalloc((void**) &d_sompart, MAX_NODES*256*16*sizeof(uint64)); + cudaMalloc((void**) &d_liste_temp, MAX_NODES*5*16*sizeof(uint2)); + cudaMalloc((void**) &d_snake_tmp, MAX_NODES*sizeof(snake_node_gpu) ); + + /*init snake (positions/contribs/stats/freemans/centres/codes)*/ + cuda_init_img_cumul(Image_in, I_dim, J_dim, nnodes, + &d_img, &d_img_x, &d_img_x2, + &d_freemanDiDj, &d_codeNoeud, + &d_snake, &d_nb_pix_max, + &d_positions, &d_contribs_segments, &d_freemans_centres, + &d_codes_segments, &d_stats_snake, + &d_stats, &d_stats_ref, &d_vrais, &d_vrais_snake, + &d_listes_pixels, &d_contribs_segments_blocs, + &d_move + ); + + /* debug : affichage snake */ + int Verbose = 1 ; + int VERBOSE = 1 ; + int Display = 1 ; + + //snake_node * h_snake_ll; + uint64 h_stats_snake[6]; + //gpu2snake(d_snake, &h_snake_ll, nnodes); + + + // variables de debug + int nb_move, iter, i ; + int nb_move_total=0, nb_test_total=0 ; + int NB_iter_max = atoi(argv[1]); + int Pas = atoi(argv[2]) ; // distance entre la position actuelle et les positions de test + int Dist_min_entre_noeud = 4*Pas ; + int bs, nblocs_seg, tpb, bps ; // nb de threads par blocs pour l'execution des kernels, nb de blocs de threads par segment a tester + dim3 threads, grid ; // params d'execution des kernels + int n_interval ; // nombre d'intervalles Na--Nx--Nb concernes + int taille_smem ; // quantite de shared memory allouee pour le calcul des contribs des segments de test + bool pairs = true ; // mouvement des noeuds pairs/impairs + + if (Verbose) { + printf("nb noeuds : %d\n", nnodes) ; + tic(&chrono_all, NULL) ; + } + + for (iter=1; (iter<=NB_iter_max)&&(Pas>0); iter++, Pas>>=1) + { + + if (VERBOSE) + { + cudaMemcpy( &h_vrais_snake, d_vrais_snake, sizeof(double), cudaMemcpyDeviceToHost); + printf("\n#%d : pas %d pixels, LV = %lf \n", iter, Pas, h_vrais_snake) ; + tic(&chrono, NULL) ; + } + // DEBUT MOVE SNAKE + do { + + //memorisation precedente LV + h_vrais_mem = h_vrais_snake ; + // calcul stats sans les paires de segments a bouger + soustrait_aux_stats_2N_segments_noeud<<< nnodes , 1 >>>(d_snake, d_stats_snake, d_stats_ref, + d_img_x, d_img_x2, + d_codeNoeud, J_dim + ); + + // calcul des coordonnées de toutes les positions possibles des noeud a l'etape N+1 + liste_positions_a_tester<<>>(d_snake, d_positions, d_nb_pix_max, Pas, nnodes, I_dim, J_dim) ; + + // recupere la taille maxi des segments + cudaMemcpy( &h_nb_pix_max, d_nb_pix_max, sizeof(uint32), cudaMemcpyDeviceToHost) ; + + // determination des parametres des kernels + bs = nextPow2(h_nb_pix_max) ; + if (bs>=BSMAX) bs = BSMAX ; // /!\ le kernel <<< calcul_contrib...>>> ne supporte pas un bs>256 a cause de la shared-mem nécessaire + if (bs<32) bs = 32 ; + nblocs_seg = (h_nb_pix_max+bs-1)/bs ; + + pairs = false ; + n_interval = nnodes/2 + pairs*(nnodes%2) ; + taille_smem = CFI(bs)*sizeof(tcontribs) ; + threads = dim3(bs,1,1) ; + grid = dim3( n_interval*16*nblocs_seg ,1,1) ; + + //calcul listes pix + contrib partielles + freemans + centres + calcul_contribs_segments_blocs_full<<< grid , threads, taille_smem >>>( d_snake, nnodes, d_positions, h_nb_pix_max, + d_img_x, d_img_x2, d_codes_segments, + J_dim, d_listes_pixels, d_contribs_segments_blocs, + pairs); + + calcul_freemans_centre<<>>( d_listes_pixels, d_freemanDiDj, d_freemans_centres); + //printf("EXEC impairs : %d max pix - %d intervalles => %d blocs de %d threads - %d octets de smem\n", h_nb_pix_max, n_interval, grid.x, threads.x, taille_smem); + //sommes des contribs partielles -> contribs segments + somsom_full<<< 16*n_interval , 1>>>(d_contribs_segments_blocs, nnodes, nblocs_seg, d_contribs_segments) ; + + //calcul des stats associees a chaque position de test + calcul_stats_full<<< n_interval, 8 >>>(d_snake, nnodes, pairs, d_stats_snake, d_stats_ref, d_stats, d_contribs_segments, + d_positions, d_codes_segments, d_freemans_centres, d_codeNoeud, + d_img_x, d_img_x2, I_dim, J_dim, d_vrais, d_vrais_snake, d_move); + + pairs = true ; + n_interval = nnodes/2 + pairs*(nnodes%2) ; + grid = dim3( n_interval*16*nblocs_seg ,1,1) ; + + //calcul listes pix + contrib partielles + freemans + centres + calcul_contribs_segments_blocs_full<<< grid , threads, taille_smem >>>( d_snake, nnodes, d_positions, h_nb_pix_max, + d_img_x, d_img_x2, d_codes_segments, + J_dim, d_listes_pixels, d_contribs_segments_blocs, + pairs); + calcul_freemans_centre<<>>( d_listes_pixels, d_freemanDiDj, d_freemans_centres); + //printf("EXEC pairs : %d max pix - %d intervalles => %d blocs de %d threads - %d octets de smem\n", h_nb_pix_max, n_interval, grid.x, threads.x, taille_smem); + //sommes des contribs partielles -> contribs segments + somsom_full<<< 16*n_interval , 1>>>(d_contribs_segments_blocs, nnodes, nblocs_seg, d_contribs_segments) ; + + //calcul des stats associees a chaque position de test + calcul_stats_full<<< n_interval, 8 >>>(d_snake, nnodes, pairs, d_stats_snake, d_stats_ref, d_stats, d_contribs_segments, + d_positions, d_codes_segments, d_freemans_centres, d_codeNoeud, + d_img_x, d_img_x2, I_dim, J_dim, d_vrais, d_vrais_snake, d_move); + + + //il faut recalculer les stats du snake apres modif + recalcul_stats_snake<<< 1 , 1 >>>(d_snake, nnodes, d_stats_snake, d_vrais_snake, + d_img_x, d_img_x2, + d_codeNoeud, J_dim + ); + + cudaMemcpy( &h_vrais_snake, d_vrais_snake, sizeof(double), cudaMemcpyDeviceToHost); + //printf("iter %d apres recalcul du move LV = %lf - ", iter, h_vrais_snake) ; + + nb_move = 0; + //recup move + cudaMemcpy( h_move, d_move, nnodes*sizeof(bool), cudaMemcpyDeviceToHost); + i = 0; + while (i>>(d_snake, d_snake_tmp, nnodes, Dist_min_entre_noeud, d_nb_nodes ); + //recup nb de nouveaux noeuds + cudaMemcpy( h_nb_nodes, d_nb_nodes, sizeof(int), cudaMemcpyDeviceToHost); + //mise a jour nb de noeuds + nnodes += (*h_nb_nodes) ; + + //parametres d'execution des kernels pour le recalcul des contribs et stats du snake + npixmax = h_nb_pix_max ; + tpb = nextPow2(npixmax) ; + if (tpb >= BSMAX) tpb = BSMAX ;// /!\ le kernel <<< calcul_contrib...>>> ne supporte pas un bs>BSMAX a cause de la shared-mem nécessaire + if (tpb < 32 ) tpb = 32 ; + bps = (npixmax+tpb-1)/tpb ; + + //calcul sommes partielles des contribs + codes segments + recalcul_contribs_segments_snake<<< nnodes*bps, tpb, CFI(tpb)*sizeof(tcontribs)>>>(d_snake, nnodes, + d_img_x, d_img_x2, + J_dim, d_liste_temp, d_sompart ); + //calcul des freemans et des centres a partir des 5 points stockes par segment dans 'd_liste_temp' + recalcul_freemans_centre<<>>(d_snake, d_liste_temp, d_freemanDiDj); + //somme des sommes partielles + resomsom_snake<<< nnodes , 1 >>>(d_sompart, nnodes, bps, d_snake); + //calcul des stats + recalcul_stats_snake<<< 1 , 1 >>>(d_snake, nnodes, d_stats_snake, d_vrais_snake, + d_img_x, d_img_x2, + d_codeNoeud, J_dim + ); + //tant que l'on peut ajouter des noeuds + if (*h_nb_nodes == 0) break ; + //recup LogVraisemblance + cudaMemcpy( &h_vrais_snake, d_vrais_snake, sizeof(double), cudaMemcpyDeviceToHost); + + } + + if (VERBOSE) + { + toc(chrono, "temps sequence move"); + + printf("nb deplacements : %d\n", nb_move) ; + printf("nb deplacements total/test : %d/%d\n", nb_move_total, nb_test_total) ; + printf("nb nouveaux noeuds : %d (total: %d)\n", *h_nb_nodes, nnodes) ; + printf("\nlongueur de codage de gl : %lf \n", h_vrais_snake) ; + } + } + + if (Verbose) { + toc(chrono_all, "temps move mv") ; + cudaMemcpy( h_stats_snake, d_stats_snake, 6*sizeof(uint64), cudaMemcpyDeviceToHost); + cudaMemcpy( &h_vrais_snake, d_vrais_snake, sizeof(double), cudaMemcpyDeviceToHost); + printf("\nFIN : longueur de codage de gl : %lf (%d)\n", h_vrais_snake, h_stats_snake[0]) ; + printf("nb noeuds : %d, nb_iter : %d\n", nnodes, iter-1) ; + printf("nb deplacements total/test : %d/%d\n", nb_move_total, nb_test_total) ; + } + + + if (Display) { + /* old fashion way to draw the snake + gpu2snake(d_snake, &h_snake_ll, nnodes); + uint32 * Liste_pixel_segment = new uint32[I_dim+J_dim]; + affiche_snake_ushort(Image_in, h_snake_ll, 255, 0, Liste_pixel_segment) ; + delete Liste_pixel_segment ; + delete h_snake_ll; + */ + cudaMemcpy( h_snake, d_snake, nnodes*sizeof(snake_node_gpu), cudaMemcpyDeviceToHost); + //affiche coordonnees + for (int node=0; node +#include +#include "structures.h" +extern "C"{ +#include "lib_alloc.h" +#include "lib_images.h" +#include "lib_snake_common.h" +#include "lib_math.h" +#include "lib_gpu.h" +#include "defines.h" +#include "lib_snake_2_gpu.h" +} +#include "lib_kernels_maths.cu" +#include "lib_kernels_contribs.cu" + + + + +int main(int argc, char **argv) +{ + /* declaration des variables */ + int ret ; + int Prof ; /* profondeur en octets */ + uint32 I_dim ; /* hauteur de l'image */ + uint32 J_dim ; /* largeur de l'image */ + int Nb_level ; /* dynamique de l'image */ + char *File_name ; + + + /* images */ + unsigned short **Image_in; + struct timeval chrono, chrono2, chrono_all ; + + /* lecture argument entree (basique!) */ + File_name = argv[3] ; + + /* verif type image (pgm 8/16) */ + ret = type_image_ppm(&Prof, &I_dim, &J_dim, &Nb_level, File_name) ; + + if ((ret == 0) | (Prof == 3)) + { + printf("format non pris en charge ... exit\n") ; + return(0) ; + } + + /* infos */ + printf("Image : %s\n", File_name) ; + printf("lecture OK : %d\n", ret) ; + printf("Image (%d x %d) pixels\n", I_dim, J_dim) ; + printf("Dynamique : %d\n", Nb_level) ; + + /* Allocation */ + Image_in = new_matrix_ushort(I_dim, J_dim) ; + + /* chargement image d'entree */ + load_pgm2ushort(Image_in, I_dim, J_dim, Nb_level, File_name) ; + + //POINTEURS VARIABLES MEMOIRE GLOBALE GPU + unsigned short * d_img ; // image + t_cumul_x * d_img_x ; // images cumulees + t_cumul_x2 * d_img_x2; // + + snake_node_gpu * d_snake ; //image du snake CPU dans un tableau en gmem GPUe + + int * d_freemanDiDj ; // table de correspondance [Di][Dj]->Freemans + int * d_codeNoeud ; // table de correspondance [F_in][F_out]->codeNoeud + + uint4 * d_positions ; // positions de test autour des noeuds + + uint2 * d_listes_pixels ; // coordonnees des pixels des segments correspondants aux 8 posiionstest + uint2 * d_liste_temp ; + uint32 * d_nb_pix_max ; // taille max des segments a tester + + uint64 * d_contribs_segments_blocs ;// sommes des contribs pixels par blocs de calcul + uint64 * d_contribs_segments ; // contribs segments 1, x et x2 + uint64 * d_sompart ; // vecteur de resultats intermediaires (sommes partielles = sommes par blocs) + + int64 * d_stats, * d_stats_ref ; // stats des positions de test, du snake sans les segments en test + int64 * d_stats_snake; // stats du snake + stats de l'image complete + double * d_vrais, * d_vrais_snake ; // valeurs de la log-vraisemblance des positions de test, du snake + + uint4 * d_freemans_centres ; // valeurs des F_in, F_out et coord. + // centres des 16 segments associes aux 8 positions de test + + int * d_codes_segments ; // valeurs de codes des 16 segments + bool * d_move ; // nb de deplacement effectues lors d'une iteration + int * d_nb_nodes ; // nb de noeuds du snake + + snake_node_gpu * d_snake_tmp ; // snake tampon pour l'etape d'ajout de noeuds + + /* pointeurs sur mem CPU */ + int *h_nb_nodes = new int; // image CPU du nb de noeud du snake + snake_node_gpu h_snake[MAX_NODES] ; + uint2 h_listes_pixels[MAX_NODES*16*5] ; + double h_vrais_snake, h_vrais_mem, h_vrais[8*MAX_NODES] ; // image CPU de la log-vraisemblance + bool * h_move = new bool[MAX_NODES];// image CPU du vecteur identifiant les noeuds qui ont bouge + uint32 h_nb_pix_max, npixmax ; // taille max des segments a tester : utile pour determiner les params d'execution + int nnodes = 4 ; // 4 ou 40 pour l'instant + + + /*allocation memoire GPU */ + int retour_cmd = 0; + cudaMalloc((void**) &d_nb_nodes, sizeof(int)); + cudaMalloc((void**) &d_sompart, MAX_NODES*256*16*sizeof(uint64)); + cudaMalloc((void**) &d_liste_temp, MAX_NODES*5*16*sizeof(uint2)); + retour_cmd = cudaMalloc((void**) &d_snake_tmp, MAX_NODES*sizeof(snake_node_gpu) ); + printf("RESULTAT ALLOC snake_tmp = %d\n", retour_cmd); + + + /*init snake (positions/contribs/stats/freemans/centres/codes)*/ + cuda_init_img_cumul(Image_in, I_dim, J_dim, nnodes, + &d_img, &d_img_x, &d_img_x2, + &d_freemanDiDj, &d_codeNoeud, + &d_snake, &d_nb_pix_max, + &d_positions, &d_contribs_segments, &d_freemans_centres, + &d_codes_segments, &d_stats_snake, + &d_stats, &d_stats_ref, &d_vrais, &d_vrais_snake, + &d_listes_pixels, &d_contribs_segments_blocs, + &d_move + ); + + /* debug : affichage snake */ + int Verbose = 1 ; + int VERBOSE = 1 ; + int Display = 1 ; + + /* debug : tests fonctions*/ + int H= I_dim, L= J_dim ; + + //snake_node * h_snake_ll; + uint64 h_stats_snake[6]; + //gpu2snake(d_snake, &h_snake_ll, nnodes); + + + // variables de debug + int nb_move, nb_newnode, iter, i ; + int nb_move_total=0, nb_test_total=0 ; + int NB_iter_max = atoi(argv[1]); + int dist = (I_dim+J_dim)/5; + int Pas = atoi(argv[2]) ; // dist entre la position actuelle et les positions de test + int Dist_min_entre_noeud = 4*Pas ; + int bs, nblocs_seg, tpb, bps ; // nb de threads par blocs pour l'execution des kernels, nb de blocs de threads par segment a tester + dim3 threads, grid ; // params d'execution des kernels + int n_interval ; // nombre d'intervalles Na--Nx--Nb concernes + int taille_smem ; // quantite de shared memory allouee pour le calcul des contribs des segments de test + bool pairs = true ; // mouvement des noeuds pairs/impairs + + if (Verbose) { + printf("nb noeuds : %d\n", nnodes) ; + tic(&chrono_all, NULL) ; + } + + for (iter=1; (iter<=NB_iter_max)&&(Pas>0); iter++, Pas>>=1) + { + + if (VERBOSE) + { + cudaMemcpy( &h_vrais_snake, d_vrais_snake, sizeof(double), cudaMemcpyDeviceToHost); + printf("\n#%d : pas %d pixels, LV = %lf \n", iter, Pas, h_vrais_snake) ; + tic(&chrono, NULL) ; + } + // DEBUT MOVE SNAKE + do { + + //memorisation precedente LV + h_vrais_mem = h_vrais_snake ; + // calcul stats sans les paires de segments a bouger + soustrait_aux_stats_2N_segments_noeud<<< nnodes , 1 >>>(d_snake, d_stats_snake, d_stats_ref, + d_img_x, d_img_x2, + d_codeNoeud, J_dim + ); + + // calcul des coordonnées de toutes les positions possibles des noeud a l'etape N+1 + liste_positions_a_tester<<>>(d_snake, d_positions, d_nb_pix_max, Pas, nnodes, I_dim, J_dim) ; + + // recupere la taille maxi des segments + cudaMemcpy( &h_nb_pix_max, d_nb_pix_max, sizeof(uint32), cudaMemcpyDeviceToHost) ; + + // determination des parametres des kernels + bs = nextPow2(h_nb_pix_max) ; + if (bs>=BSMAX) bs = BSMAX ; // /!\ le kernel <<< calcul_contrib...>>> ne supporte pas un bs>256 a cause de la shared-mem nécessaire + if (bs<32) bs = 32 ; + nblocs_seg = (h_nb_pix_max+bs-1)/bs ; + + pairs = false ; + n_interval = nnodes/2 + pairs*(nnodes%2) ; + taille_smem = CFI(bs)*sizeof(tcontribs) ; + threads = dim3(bs,1,1) ; + grid = dim3( n_interval*16*nblocs_seg ,1,1) ; + + //calcul listes pix + contrib partielles + freemans + centres + calcul_contribs_segments_blocs_full<<< grid , threads, taille_smem >>>( d_snake, nnodes, d_positions, h_nb_pix_max, + d_img_x, d_img_x2, d_codes_segments, + J_dim, d_listes_pixels, d_contribs_segments_blocs, + pairs); + /*debug*/ + cudaMemcpy( h_listes_pixels, d_listes_pixels, 16*5*n_interval*sizeof(uint2), cudaMemcpyDeviceToHost); + for(int inter=0; inter < 3; inter++){ + for(int seg=0; seg<16; seg++){ + printf(" intervalle %d segment %d : (%d,%d)-(%d,%d)-(%d,%d)-(%d,%d)-(%d,%d)\n", + inter, seg, h_listes_pixels[5*(16*inter+seg)].x,h_listes_pixels[5*(16*inter+seg)].y,h_listes_pixels[5*(16*inter+seg)+1].x, + h_listes_pixels[5*(16*inter+seg)+1].y,h_listes_pixels[5*(16*inter+seg)+2].x,h_listes_pixels[5*(16*inter+seg)+2].y, + h_listes_pixels[5*(16*inter+seg)+3].x,h_listes_pixels[5*(16*inter+seg)+3].y,h_listes_pixels[5*(16*inter+seg)+4].x, + h_listes_pixels[5*(16*inter+seg)+4].y); + } + } + /*fin*/ + calcul_freemans_centre<<>>( d_listes_pixels, d_freemanDiDj, d_freemans_centres); + //printf("EXEC impairs : %d max pix - %d intervalles => %d blocs de %d threads - %d octets de smem\n", h_nb_pix_max, n_interval, grid.x, threads.x, taille_smem); + //sommes des contribs partielles -> contribs segments + somsom_full<<< 16*n_interval , 1>>>(d_contribs_segments_blocs, nnodes, nblocs_seg, d_contribs_segments) ; + + //calcul des stats associees a chaque position de test + calcul_stats_full<<< n_interval, 8 >>>(d_snake, nnodes, pairs, d_stats_snake, d_stats_ref, d_stats, d_contribs_segments, + d_positions, d_codes_segments, d_freemans_centres, d_codeNoeud, + d_img_x, d_img_x2, I_dim, J_dim, d_vrais, d_vrais_snake, d_move); + + pairs = true ; + n_interval = nnodes/2 + pairs*(nnodes%2) ; + grid = dim3( n_interval*16*nblocs_seg ,1,1) ; + + //calcul listes pix + contrib partielles + freemans + centres + calcul_contribs_segments_blocs_full<<< grid , threads, taille_smem >>>( d_snake, nnodes, d_positions, h_nb_pix_max, + d_img_x, d_img_x2, d_codes_segments, + J_dim, d_listes_pixels, d_contribs_segments_blocs, + pairs); + calcul_freemans_centre<<>>( d_listes_pixels, d_freemanDiDj, d_freemans_centres); + //printf("EXEC pairs : %d max pix - %d intervalles => %d blocs de %d threads - %d octets de smem\n", h_nb_pix_max, n_interval, grid.x, threads.x, taille_smem); + //sommes des contribs partielles -> contribs segments + somsom_full<<< 16*n_interval , 1>>>(d_contribs_segments_blocs, nnodes, nblocs_seg, d_contribs_segments) ; + + //calcul des stats associees a chaque position de test + calcul_stats_full<<< n_interval, 8 >>>(d_snake, nnodes, pairs, d_stats_snake, d_stats_ref, d_stats, d_contribs_segments, + d_positions, d_codes_segments, d_freemans_centres, d_codeNoeud, + d_img_x, d_img_x2, I_dim, J_dim, d_vrais, d_vrais_snake, d_move); + + + //il faut recalculer les stats du snake apres modif + recalcul_stats_snake<<< 1 , 1 >>>(d_snake, nnodes, d_stats_snake, d_vrais_snake, + d_img_x, d_img_x2, + d_codeNoeud, J_dim + ); + + cudaMemcpy( &h_vrais_snake, d_vrais_snake, sizeof(double), cudaMemcpyDeviceToHost); + //printf("iter %d apres recalcul du move LV = %lf - ", iter, h_vrais_snake) ; + + nb_move = 0; + //recup move + cudaMemcpy( h_move, d_move, nnodes*sizeof(bool), cudaMemcpyDeviceToHost); + i = 0; + while (i>>(d_snake, d_snake_tmp, nnodes, Dist_min_entre_noeud, d_nb_nodes ); + cudaMemcpy( h_nb_nodes, d_nb_nodes, sizeof(int), cudaMemcpyDeviceToHost); + nnodes += (*h_nb_nodes) ; + + cudaMemcpy( d_snake, d_snake_tmp, nnodes*sizeof(snake_node_gpu), cudaMemcpyDeviceToDevice); + + npixmax = h_nb_pix_max ; + tpb = nextPow2(npixmax) ; + if (tpb >= BSMAX) tpb = BSMAX ;// /!\ le kernel <<< calcul_contrib...>>> ne supporte pas un bs>256 a cause de la shared-mem nécessaire + if (tpb < 32 ) tpb = 32 ; + bps = (npixmax+tpb-1)/tpb ; + + recalcul_contribs_segments_snake<<< nnodes*bps, tpb, CFI(tpb)*sizeof(tcontribs)>>>(d_snake, nnodes, + d_img_x, d_img_x2, + J_dim, d_liste_temp, d_sompart ); + + recalcul_freemans_centre<<>>(d_snake, d_liste_temp, d_freemanDiDj); + resomsom_snake<<< nnodes , 1 >>>(d_sompart, nnodes, bps, d_snake); + + recalcul_stats_snake<<< 1 , 1 >>>(d_snake, nnodes, d_stats_snake, d_vrais_snake, + d_img_x, d_img_x2, + d_codeNoeud, J_dim + ); + + if (*h_nb_nodes == 0) break ; + cudaMemcpy( &h_vrais_snake, d_vrais_snake, sizeof(double), cudaMemcpyDeviceToHost); + printf("\niter %d LV apres ajout noeuds = %lf \n ", iter, h_vrais_snake) ; + } + + if (VERBOSE) + { + toc(chrono, "temps sequence move"); + + printf("nb deplacements : %d\n", nb_move) ; + printf("nb deplacements total/test : %d/%d\n", nb_move_total, nb_test_total) ; + printf("nb nouveaux noeuds : %d (total: %d)\n", *h_nb_nodes, nnodes) ; + printf("\nlongueur de codage de gl : %lf \n", h_vrais_snake) ; + } + } + + if (Verbose) { + toc(chrono_all, "temps move mv") ; + cudaMemcpy( h_stats_snake, d_stats_snake, 6*sizeof(uint64), cudaMemcpyDeviceToHost); + cudaMemcpy( &h_vrais_snake, d_vrais_snake, sizeof(double), cudaMemcpyDeviceToHost); + printf("\nFIN : longueur de codage de gl : %lf (%d)\n", h_vrais_snake, h_stats_snake[0]) ; + printf("nb noeuds : %d, nb_iter : %d\n", nnodes, iter-1) ; + printf("nb deplacements total/test : %d/%d\n", nb_move_total, nb_test_total) ; + } + + + if (Display) { + /* old fashion way to draw the snake + gpu2snake(d_snake, &h_snake_ll, nnodes); + uint32 * Liste_pixel_segment = new uint32[I_dim+J_dim]; + affiche_snake_ushort(Image_in, h_snake_ll, 255, 0, Liste_pixel_segment) ; + delete Liste_pixel_segment ; + delete h_snake_ll; + */ + cudaMemcpy( h_snake, d_snake, nnodes*sizeof(snake_node_gpu), cudaMemcpyDeviceToHost); + //affiche coordonnees + for (int node=0; node taill image sur 14 bits soit 16000 x 16000 \n + * longueur des segments sur 15 bits + * \verbatim + sum_1 : 0/1 + 14b + 15b = 29b => unsigned int + sum_x : 16b + 14b + 15b = 45b => unsigned long int + sum_x2 : 32b + 14b + 15b = 61b => unsigned long int + \endverbatim + * + * Avec la definition suivante, les contributions des segments + * ne pourrons etre que positives. Le signe associe au segment devra + * etre memorise par une autre variable (code_segment) + */ +struct pixel_cumul +{ + uint32 sum_1; + uint64 sum_x; + uint64 sum_x2; + } ; + +/* + * structure pour la smem des kernels cumuls + * + */ +struct tcumuls { + t_cumul_x x; + t_cumul_x2 x2; +}; + + +/* + * structure pour le smem des kernels contribs + * + */ + +struct tcontribs { + t_sum_1 c1; + t_sum_x cx; + t_sum_x2 cx2; +}; + +/** + * \struct snake_node structures.h + * + * \brief noeud du snake defini par liste chainee + * + * Cette structure memorise la position du noeud + * ET les informations concernant le segment qui + * part de ce noeud et va au noeud suivant dans + * l'ordre trigo + */ + +struct snake_node +{ + /* contribution du segment allant de ce noeud */ + /* au noeud suivant, defini dans l'ordre trigo */ + /* l'union pixel_cumul est definie en premier */ + /* pour permettre l'alignement memoire */ + struct pixel_cumul contrib ; + + int code_segment ; /* signe de la contrib */ + int freeman_in ; /* du noeud d'arrivee du segment (ordre trigo) */ + int freeman_out ; /* du noeud de depart du segment (ordre trigo) */ + uint32 nb_pixels ; + + uint32 posi ; /* position i du noeud */ + uint32 posj ; /* position j du noeud */ + uint32 centre_i ; /* centre i du segment */ + uint32 centre_j ; /* centre j du segment */ + + int last_move ; /* dernier test deplacement accepte */ + + /* lien liste chainee snake2D */ + struct snake_node *noeud_suiv ; /* dans le sens trigo */ + struct snake_node *noeud_prec ; + + + /* lien liste chainee snake3D */ +}; + + +struct snake_node_gpu +{ + t_sum_1 sum_1 ; + t_sum_x sum_x ; + t_sum_x2 sum_x2; + + int code_segment ; /* signe de la contrib */ + int freeman_in ; /* du noeud d'arrivee du segment (ordre trigo) */ + int freeman_out ; /* du noeud de depart du segment (ordre trigo) */ + uint32 nb_pixels ; + + uint32 posi ; /* position i du noeud */ + uint32 posj ; /* position j du noeud */ + uint32 centre_i ; /* centre i du segment */ + uint32 centre_j ; /* centre j du segment */ + + int last_move ; /* dernier test deplacement accepte */ +}; + + +#endif diff --git a/src/structures.h~ b/src/structures.h~ new file mode 100644 index 0000000..40b16dd --- /dev/null +++ b/src/structures.h~ @@ -0,0 +1,129 @@ +/** + * \file structures.h + * \author NB - PhyTI + * \version x.x + * \date 20 decembre 2009 + * + * + */ + +#ifndef _STRUCTURES_H +#define _STRUCTURES_H + + +/** + * \def uint64 entier non signe 64 bits + * \def int64 entier signe 64 bits + * \def uint32 entier non signe 32 bits + */ +typedef unsigned long long int uint64 ; +typedef unsigned int uint32; +typedef long long int int64 ; +typedef long int int32 ; + +typedef unsigned short t_cumul_1 ; +typedef unsigned int t_cumul_x ; +typedef unsigned long long t_cumul_x2 ; + +typedef unsigned int t_sum_1 ; +typedef unsigned long long t_sum_x ; +typedef unsigned long long t_sum_x2 ; + + +/** + * \struct pixel_cumul structures.h + * + * \brief structure des images cumulees + * + * Version d'algorithme sans SSE2 \n + * les images cumulees correspondent a \n + * [sum 1, sum x, sum x^2] \n + * + * Trois donnees a stocker de dynamique differente \n + * + * cas d'images en profondeur 16 bits \n + * avec xum_x2 sur 64 bits => taill image sur 14 bits soit 16000 x 16000 \n + * longueur des segments sur 15 bits + * \verbatim + sum_1 : 0/1 + 14b + 15b = 29b => unsigned int + sum_x : 16b + 14b + 15b = 45b => unsigned long int + sum_x2 : 32b + 14b + 15b = 61b => unsigned long int + \endverbatim + * + * Avec la definition suivante, les contributions des segments + * ne pourrons etre que positives. Le signe associe au segment devra + * etre memorise par une autre variable (code_segment) + */ +struct pixel_cumul +{ + uint32 sum_1; + uint64 sum_x; + uint64 sum_x2; + } ; + +struct tcumuls { + t_cumul_x x; + t_cumul_x2 x2; +}; + +/** + * \struct snake_node structures.h + * + * \brief noeud du snake defini par liste chainee + * + * Cette structure memorise la position du noeud + * ET les informations concernant le segment qui + * part de ce noeud et va au noeud suivant dans + * l'ordre trigo + */ + +struct snake_node +{ + /* contribution du segment allant de ce noeud */ + /* au noeud suivant, defini dans l'ordre trigo */ + /* l'union pixel_cumul est definie en premier */ + /* pour permettre l'alignement memoire */ + struct pixel_cumul contrib ; + + int code_segment ; /* signe de la contrib */ + int freeman_in ; /* du noeud d'arrivee du segment (ordre trigo) */ + int freeman_out ; /* du noeud de depart du segment (ordre trigo) */ + uint32 nb_pixels ; + + uint32 posi ; /* position i du noeud */ + uint32 posj ; /* position j du noeud */ + uint32 centre_i ; /* centre i du segment */ + uint32 centre_j ; /* centre j du segment */ + + int last_move ; /* dernier test deplacement accepte */ + + /* lien liste chainee snake2D */ + struct snake_node *noeud_suiv ; /* dans le sens trigo */ + struct snake_node *noeud_prec ; + + + /* lien liste chainee snake3D */ +}; + + +struct snake_node_gpu +{ + t_sum_1 sum_1 ; + t_sum_x sum_x ; + t_sum_x2 sum_x2; + + int code_segment ; /* signe de la contrib */ + int freeman_in ; /* du noeud d'arrivee du segment (ordre trigo) */ + int freeman_out ; /* du noeud de depart du segment (ordre trigo) */ + uint32 nb_pixels ; + + uint32 posi ; /* position i du noeud */ + uint32 posj ; /* position j du noeud */ + uint32 centre_i ; /* centre i du segment */ + uint32 centre_j ; /* centre j du segment */ + + int last_move ; /* dernier test deplacement accepte */ +}; + + +#endif -- 2.39.5