# Raytracing contributed by <`zhanyangch`>, <`hunng`>, <`yanang`> ## 預期目標 * Task: 比照 [B02: raytracing](https://hackmd.io/s/HyuBWDwYl) 要求,以 SIMD 改善 math-toolkit.h 定義的若干數學操作函式,進而推廣到整個光線追蹤的演算法 (可在不影響正確性的前提下,將原本的實作替換)。 * Task: 參考 Joshua Barczak 的 [SIMD Raytracing 實作](http://www.joshbarczak.com/blog/?p=787),改寫原有的程式碼,但是 model 應該和 [B02: raytracing](https://hackmd.io/s/HyuBWDwYl) 一致 ## math-toolkit.h with SSE Streaming SIMD Extensions:8個 128 位元暫存器,每個暫存器可以容納 2 個 64位元雙精度浮點數 Intrinsics:是一種外表類似一般的函數,但是實際上會被 compiler 直接編譯成組合語言的東西。 * 常用指令 ```clike __m128d _mm_loadu_pd (double const* mem_addr) __m128d _mm_load_sd (double const* mem_addr)//高位為0 __m128d _mm_add_pd (__m128d a, __m128d b) __m128d _mm_div_pd (__m128d a, __m128d b) __m128d _mm_mul_pd (__m128d a, __m128d b) void _mm_storeu_pd (double* mem_addr, __m128d a) void _mm_store_pd1 (double* mem_addr, __m128d a)//皆填入 a[63:0] __m128d _mm_sqrt_pd(__m128d a) _mm_shuffle_pd (__m128d a, __m128d b, int imm8) __m128d _mm_unpackhi_pd (__m128d a, __m128d b) double _mm_cvtsd_f64(__m128d a) ``` * math-toolkit-sse.h 實做 ```clike __attribute__((always_inline)) static inline void normalize(double *v) { __m128d I0 = _mm_loadu_pd(v);//v0 v1 __m128d I1 = _mm_loadu_pd(v+1);//v1 v2 __m128d I2 = _mm_mul_pd (I0,I0);//v0^2 v1^2 __m128d I3 = _mm_mul_pd (I1,I1);//v1^2 v2^2 __m128d I4 = _mm_add_pd (I2,I3);//v0^2+v1^2 v1^2+v2^2 __m128d T0 = _mm_unpackhi_pd(I3,I3); // v2^2 v2^2 __m128d I5 = _mm_add_pd(I4,T0);//v0^2+v1^2+v2^2 __m128d I6 = _mm_unpacklo_pd(I5,I5); __m128d I7 =_mm_sqrt_pd(I6); I0=_mm_div_pd(I0,I7); I1=_mm_div_pd(I1,I7); _mm_storeu_pd(v,I0); _mm_storeu_pd(v+1,I1); } __attribute__((always_inline)) static inline double length(const double *v) { __m128d I0 = _mm_loadu_pd(v);//v0 v1 __m128d I1 = _mm_loadu_pd(v+1);//v1 v2 __m128d I2 = _mm_mul_pd (I0,I0);//v0^2 v1^2 __m128d I3 = _mm_mul_pd (I1,I1);//v1^2 v2^2 __m128d I4 = _mm_add_pd (I2,I3);//v0^2+v1^2 v1^2+v2^2 __m128d T0 = _mm_unpackhi_pd(I3,I3); // v2^2 v2^2 __m128d I5 = _mm_add_pd(I4,T0);//v0^2+v1^2+v2^2 __m128d I6 = _mm_sqrt_pd(I5); return _mm_cvtsd_f64(I6); } __attribute__((always_inline)) static inline void add_vector(const double *a, const double *b, double *out) { __m128d I0 = _mm_loadu_pd(a);//a0 a1 __m128d I1 = _mm_loadu_pd(a+1);//a1 a2 __m128d I2 = _mm_loadu_pd(b);//b0 b1 __m128d I3 = _mm_loadu_pd(b+1);//b1 b2 __m128d T0 = _mm_add_pd(I0,I2); __m128d T1 = _mm_add_pd(I1,I3); _mm_storeu_pd(out,T0); _mm_storeu_pd(out+1,T1); } __attribute__((always_inline)) static inline void subtract_vector(const double *a, const double *b, double *out) { __m128d I0 = _mm_loadu_pd(a);//a0 a1 __m128d I1 = _mm_loadu_pd(a+1);//a1 a2 __m128d I2 = _mm_loadu_pd(b);//b0 b1 __m128d I3 = _mm_loadu_pd(b+1);//b1 b2 __m128d T0 = _mm_sub_pd(I0,I2); __m128d T1 = _mm_sub_pd(I1,I3); _mm_storeu_pd(out,T0); _mm_storeu_pd(out+1,T1); } __attribute__((always_inline)) static inline void multiply_vectors(const double *a, const double *b, double *out) { __m128d I0 = _mm_loadu_pd(a);//a0 a1 __m128d I1 = _mm_loadu_pd(a+1);//a1 a2 __m128d I2 = _mm_loadu_pd(b);//b0 b1 __m128d I3 = _mm_loadu_pd(b+1);//b1 b2 __m128d T0 = _mm_mul_pd(I0,I2); __m128d T1 = _mm_mul_pd(I1,I3); _mm_storeu_pd(out,T0); _mm_storeu_pd(out+1,T1); } static inline void multiply_vector(const double *a, double b, double *out) { __m128d I0 = _mm_loadu_pd(a);//a0 a1 __m128d I1 = _mm_loadu_pd(a+1);//a1 a2 __m128d I2 = _mm_load_pd1(&b);//b0 b1 __m128d T0 = _mm_mul_pd(I0,I2); __m128d T1 = _mm_mul_pd(I1,I2); _mm_storeu_pd(out,T0); _mm_storeu_pd(out+1,T1); } __attribute__((always_inline)) static inline void cross_product(const double *v1, const double *v2, double *out) { __m128d I0 = _mm_loadu_pd(v1);//v1[0] v1[1] __m128d I1 = _mm_loadu_pd(v1+1);//v1[1] v1[2] __m128d I2 = _mm_loadu_pd(v2);//v2[0] v2[1] __m128d I3 = _mm_loadu_pd(v2+1);//v2[1] v2[2] __m128d I4 = _mm_shuffle_pd(I1,I1,1);//v1[2] __m128d I5 = _mm_shuffle_pd(I3,I3,1);//v2[2] __m128d T1 = _mm_mul_pd(I0,I3); __m128d T2 = _mm_mul_pd(I1,I2); __m128d T3 = _mm_sub_pd(T1,T2);// out[2] out[0] __m128d T4 = _mm_mul_pd(I4,I2); __m128d T5 = _mm_mul_pd(I5,I0); __m128d T6 = _mm_sub_pd(T4,T5);// out[1] __m128d T7 =_mm_shuffle_pd(T3,T6,1);//out[0] out[1] _mm_storeu_pd(out,T7); *(out+2)=_mm_cvtsd_f64(T3); } __attribute__((always_inline)) static inline double dot_product(const double *v1, const double *v2) { __m128d I0 = _mm_loadu_pd(v1);//v1[0] v1[1] __m128d I1 = _mm_loadu_pd(v1+1);//v1[1] v1[2] __m128d I2 = _mm_loadu_pd(v2);//v2[0] v2[1] __m128d I3 = _mm_loadu_pd(v2+1);//v2[1] v2[2] __m128d T1 = _mm_mul_pd(I0,I2); __m128d T2 = _mm_mul_pd(I1,I3); __m128d T3 = _mm_add_pd(T1,T2); __m128d T4 = _mm_unpackhi_pd(T2,T2); __m128d T5 = _mm_add_pd(T3,T4); return _mm_cvtsd_f64(T5); } __attribute__((always_inline)) static inline void scalar_triple_product(const double *u, const double *v, const double *w, double *out) { cross_product(v, w, out); multiply_vectors(u, out, out); } __attribute__((always_inline)) static inline double scalar_triple(const double *u, const double *v, const double *w) { double tmp[3]; cross_product(w, u, tmp); return dot_product(v, tmp); } ``` ## 執行測試 * 正確性 ```shell $ ./raytracing-sse # Rendering scene Done! Execution time of raytracing() : 2.419169 sec $ diff baseline.ppm out.ppm ``` * 時間比較 在此的 orig 為 loop unrolling 的版本 1. 不使用 openmp ``` orig:2.247861 sec sse:5.866375 sec ``` 2. 使用 openmp num_threads (2) ``` orig:1.289200 sec sse:2.836198 sec ``` * 分析 使用 SSE 的版本反而比原版慢,推測可能的原因有 1. SSE 的之資料長度為 128bit 運算 3個 double 需要切個成兩部份計算,造成指令數目增加 2. 載入跟儲存的部份佔全部指令的比例太高,使用 _mm_load _mm_store ,取代 _mm_loadu _mm_storeu 應可以加快載入跟儲存的速度,但記憶體需對齊 16byte ,目前試著加入 __attribute__((aligned(16))但未能成功 * 修改 raytracing.c 為了解決載入跟儲存的部份佔全部指令的比例太高的問題,我們決定採用幾個合併多個功能的函數 ``` clike void multiply_vector_add(const double *a, double b,const double *c,double *out) //b*a+c void subtract_vector_normalize(const double *a, const double *b, double *out) void multiply_vector_normalize(const double *a, double b, double *out) void add_vectors4(const double *a, const double *b, const double *c,const double *d,double *out)//a+b+c+d void cross_product_normalize(const double *v1, const double *v2, double *out) ``` 1. 不使用 openmp ``` sse with additional functions: 4.545110 sec ``` 2. 使用 openmp num_threads (2) ``` sse with additional functions: 2.658897 sec ``` 不使用 openmp 的相對改善幅度較大,但皆無法超過原始版本。 ## math-toolkit.h with AVX **針對 math-toolkit.h 進行 avx 的改寫** 起先因為是計算的向量皆為三項的 依照叫淺顯的寫法就是直接使用 `_mm256_set_pd` 以下方的 subtrack_vector 為例 ```c __attribute__((always_inline)) static inline void subtract_vector(const double *a, const double *b, double *out) { register __m256d ymm0, ymm1; double temp[4] = {0, 0, 0, 0}; ymm0 = _mm256_set_pd (*temp, *(a+2), *(a+1), *a); // ymm0 = {a0, a1, a2, 0} ymm1 = _mm256_set_pd (*temp, *(b+2), *(b+1), *b); // ymm1 = {b0, b1, b2, 0} ymm0 = _mm256_sub_pd (ymm0, ymm1); // ymm0 = {a0-b0, a1-b1, a2-b2, 0} _mm256_storeu_pd (temp, ymm0); *out = *temp; *(out+1) = *(temp+1); *(out+2) = *(temp+2); } ``` 但因為 `_mm256_set_pd` 的 latency 較高 所以我們修改 `primitives.h` 中 point3, point4 的存儲數 3 -> 4 ```c typedef double point3[4]; typedef double point4[4]; ``` 改用 `_mm256_loadu_pd` 作為載入資料手段 ```c __attribute__((always_inline)) static inline void subtract_vector_avx(const double *a, const double *b, double *out) { register __m256d ymm0, ymm1; ymm0 = _mm256_loadu_pd (a); // ymm0 = {a0, a1, a2, X} ymm1 = _mm256_loadu_pd (b); // ymm1 = {b0, b1, b2, X} ymm0 = _mm256_sub_pd (ymm0, ymm1); // ymm0 = {a0+b0, a1+b1, a2+b2, X} _mm256_storeu_pd (out, ymm0); // temp = {a0+b0, a1+b1, a2+b2, X} } ``` 則可以讓整體程式碼較為簡潔 但缺點就是需要注意其最後一項的值是否會影響其結果 (像是後面用到的 `hadd`) 為了解決這個問題,我們改用了 `_mm256_maskload_pd` ```c __attribute__((always_inline)) static inline void subtract_vector(const double *a, const double *b, double *out) { register __m256d ymm0, ymm1; ymm0 = _mm256_maskload_pd (a, mask); // ymm0 = {a0, a1, a2, 0} ymm1 = _mm256_maskload_pd (b, mask); // ymm0 = {b0, b1, b2, 0} ymm0 = _mm256_sub_pd (ymm0, ymm1); // ymm0 = {a0-b0, a1-b1, a2-b2, 0} _mm256_maskstore_pd (out, mask, ymm0); } ``` 若要使用 mask 則需要指定一個全域變數 `mask` 並在使用raytracing時叫用 `init_mask()` ```c void init_mask() { long int value = 0xAAAAAAAAAAAAAAAA; mask = _mm256_set_epi64x (0, value, value, value); } ``` 但這又衍生出了 latency 的問題 若每個 load, store 都使用 maskload, maskstore 會降低整體的速度 要衡量每個 load, store 是否需要使用 mask 而在 dot_product 中會使用到 `_mm256_hadd_pd` ```c __attribute__((always_inline)) static inline double dot_product(const double *a, const double *b) { double temp[4] = {0, 0, 0, 0}; register __m256d ymm0, ymm1, ymm2; ymm0 = _mm256_maskload_pd (a, mask); // ymm0 = {a0, a1, a2, 0} ymm1 = _mm256_maskload_pd (b, mask); // ymm0 = {b0, b1, b2, 0} ymm2 = _mm256_setzero_pd(); // ymm2 = {0, 0, 0, 0} ymm0 = _mm256_mul_pd (ymm0, ymm1); // ymm0 = {a0*b0, a1*b1, a2*b2, 0} ymm0 = _mm256_hadd_pd (ymm0, ymm2); // ymm0 = {a0*b0+a1*b1, 0, a2*b2, 0} ymm1 = _mm256_permute2f128_pd (ymm0, ymm0, 131); // ymm0 = { a2*b2, 0, 0, 0} ymm0 = _mm256_add_pd (ymm0, ymm1); // ymm0 = {a0*b0+a1*b1+a2*b2, 0, 0, 0} _mm256_storeu_pd (temp, ymm0); return *temp; } ``` 原本是使用以下寫法,利用一個 128 bit 的暫存,然後在衍生至 256 bit ```c const __m128d valupper = _mm256_extractf128_pd(ymm0, 1); // valupper = {a2*b2, 0} ymm1 = _mm256_castpd128_pd256 (valupper); // ymm2 = {a2*b2, 0, X, X} X means undefined ``` 但考量到整體可讀性及正確性,改用了 `_mm256_permute2f128_pd` ```c ymm2 = _mm256_permute2f128_pd (ymm3, ymm3, 131); // ymm0 = { v2^2, 0, 0, 0} ``` 而 cross_product 因為使用了叫複雜的內容 所以所有值都是利用設定的 並為了讓整體運算結果會剛好符合順序 載入的順序就沒有那麼直觀 ```c __attribute__((always_inline)) static inline void cross_product(const double *a, const double *b, double *out) { register __m256d ymm0, ymm1, ymm2, ymm3; double temp = 0; ymm0 = _mm256_set_pd (*(a+1), *a, *(a+2), *(a+1)); // ymm0 = {a1, a2, a2, a0} ymm1 = _mm256_set_pd (*b, *(b+1), *(b+1), *(b+2)); // ymm1 = {b2, b1, b0, b2} ymm2 = _mm256_set_pd (temp, temp, *a, *(a+2)); // ymm2 = {a0, a1, 0, 0} ymm3 = _mm256_set_pd (temp, temp, *(b+2), *b); // ymm3 = {b1, b0, 0, 0} ymm0 = _mm256_mul_pd (ymm0, ymm1); // ymm0 = {a1*b2, a2*b1, a2*b0, a0*b2} ymm2 = _mm256_mul_pd (ymm2, ymm3); // ymm2 = {a0*b1, a1*b0, 0, 0} ymm3 = _mm256_hsub_pd (ymm0, ymm2); _mm256_maskstore_pd (out, mask, ymm3); } ``` 在整體完成後 因為在實際在 raytracing 的程式中看到了速度大幅的降低 所以我們對舊版 與 avx 版本的 math-toolkit.h 進行測試 ![](https://i.imgur.com/bzfzvPp.png) 發現只有 normalize 與 length 有較佳的表現 ## Raytracing.c * all function - [x] raySphereIntersection - [x] rayRectangularIntersection - [ ] localColor - [x] compute_specular_diffuse - [x] reflection - [x] refraction - [x] fresnel - [ ] ray_hit_object - [x] rayConstruction - [x] calculateBasisVectors - [ ] protect_color_overflow - [ ] ray_color ## 修改內容 [Joshua Barczak的文章筆記](https://hackmd.io/s/rJhfg1IlZ) 為了使一次能運算 4 個 double 我們首先修改了 point3 的使用 改為使用 point3v 其內容為 ```c typedef struct { __m256d x; __m256d y; __m256d z; }point3v; ``` 一個 point3v 將會對應儲存四個需要運算的點 x 第 0 個值, y 第 0 個值, z 第 0 個值為第 0 個點 x 第 1 個值, y 第 1 個值, z 第 1 個值為第 1 個點 x 第 2 個值, y 第 2 個值, z 第 2 個值為第 2 個點 x 第 3 個值, y 第 3 個值, z 第 3 個值為第 3 個點 接下來修改了 math-toolkit.h 使其的運算參數從 point3 -> point3v 一次處理四個點的運算 舉 cross_product 為例 ```c __attribute__((always_inline)) static inline void cross_product(const point3v *a, const point3v *b, point3v *out) { __m256d x1 = __mm256_mul_pd(a->y, b->z); __m256d x2 = __mm256_mul_pd(a->z, b->y); __m256d y1 = __mm256_mul_pd(a->z, b->x); __m256d y2 = __mm256_mul_pd(a->x, b->z); __m256d z1 = __mm256_mul_pd(a->x, b->y); __m256d z2 = __mm256_mul_pd(a->y, b->x); out->x = __mm256_sub_pd(x1, x2); out->y = __mm256_sub_pd(y1, y2); out->z = __mm256_sub_pd(z1, z2); } ``` 再來就是 Raytracing.c 的改寫 Raytracing.c 內許多函式的寫法為利用 if 判斷後 return 但因為現在為計算四個點,所以當判斷後並不能直接 return 需要考量其他三個點的情況 而我們目前的作法是先全部運算後 在利用 mask 去還原舊的值(該 return 的點) 以 refraction 為例,以下為原檔案 ```c= static void refraction(point3 t, const point3 I, const point3 N, double n1, double n2) { double eta = n1 / n2; double dot_NI = dot_product(N,I); double k = 1.0 - eta * eta * (1.0 - dot_NI * dot_NI); if (k < 0.0 || n2 <= 0.0) t[0] = t[1] = t[2] = 0.0; else { point3 tmp; multiply_vector(I, eta, t); multiply_vector(N, eta * dot_NI + sqrt(k), tmp); subtract_vector(t, tmp, t); } } ``` 修改過後 ```c= static void refraction (point3v *t, const point3v *I, const point3v *N, double n1, double n2) { __m256d n2v = _mm256_set1_pd(n2); __m256d eta = _mm256_set1_pd(n1/n2); __m256d dot_NI = dot_product(N, I); __m256d k = _mm256_set_pd(1); __m256d eta2 = _mm256_mul_pd(eta, eta); // eta2 = eta * eta __m256d dot_NI2 = _mm256_mul_pd(dot_NI, dot_NI); // dot_NI2 = dot_NI * dot_NI dot_NI2 = _mm256_sub_pd(k, dot_NI2); // dot_NI2 = 1 - dot_NI * dot_NI eta2 = _mm256_mul_pd(eta2, dot_NI2); // eta2 = eta * eta * ( 1 - dot_NI * dot_NI) k = _mm256_sub_pd(k, eta2); // k = 1 - eta * eta * ( 1 - dot_NI * dot_NI) #define _CMP_LT_OS 0x01 /* Less-than (ordered, signaling) */ #define _CMP_LE_OS 0x02 /* Less-than-or-equal (ordered, signaling) */ __m256d mzero = _mm256_setzero_pd(); __m256i iall1 = _mm256_set1_epi64x(-1); // iall1 = 0xFF...F __m256d dall1 = _mm256_castsi256_pd(iall1); __m256d if1 = _mm256_cmp_pd(k, mzero, _CMP_LT_OS); // k < 0.0 __m256d if2 = _mm256_cmp_pd(n2v, mzero, _CMP_LE_OS); // n2 <= 0.0 __m256d ifa = _mm256_or_pd(k, n2v); // if (k < 0.0 || n2 <= 0.0) __m256d notifa = _mm256_xor_pd(ifa, dall1); point3v tmp; multiply_vector(I, eta, t); __m256d midpar = _mm256_mul_pd(eta, dot_NI); k = _mm256_sqrt_pd(k); // k = sqrt(k) midpar = _mm256_add_pd(midpar, k); multiply_vector(N, midpar, tmp); subtract_vector(t, tmp, t); t->x = _mm256_and_pd(t->x, notifa); t->y = _mm256_and_pd(t->y, notifa); t->z = _mm256_and_pd(t->z, notifa); } ``` * 處理 if 的方法是利用 `_mm256_cmp_pd` 並適時利用每個 bit 皆為 1 的 `__m256d` 和 `_mm256_xor_pd` 將 0->1 1->0 而每個 bit 皆為 1 的 `__m256d` 則透過整數 `__m256i` 設定為 -1 後轉換 (16, 17 行) * 19~22 為處理 `if (k < 0.0 || n2 <= 0.0)` (原程式 7 行) 而因為這裡為 if 成立就設定為0,所以利用 xor 轉換 bit 最後將求出來的值 and 這個產生出來的 mask (notifa) `t->x = _mm256_and_pd(t->x, notifa);` (32 行) * 但這個方法因為每次使用這個函式就必定會運算 假如四個點皆不需運算,則會造成嚴重的資源浪費 這也是 [Joshua Barczak](http://www.joshbarczak.com/blog/?p=787) 後面提到他使用到 Ray Reordering 的原因 * 而目前遇到的問題是 * 如何處理遞迴呼叫的問題 e.g., ray_color 會呼叫自己 * 且因為整體 struct 尚未設計完全,寫出的函式沒辦法測試,無法確認其正確性 ## 其他 raytrace 實做 - [ ] 探討 [jaybird19/simd-raytracer](https://github.com/jaybird19/simd-raytracer) simd 切割及multi-threaded方式 ### LinearAllocator ```c++ allocator.init(main_arena_size + ALIGNMENT_PADDING, true); ``` 先一次宣告大量的記憶體 ```c++ pixmap = init_itr<real>((real*)allocator.allocate(pixels_heap_size, SSE_ALIGNMENT), pixels_heap_size); ``` 為每一個資料分配位置 ```c++ //Align address if necessary if (alignment != 0 && (reinterpret_cast<size_t>(aligned_address) & (alignment - 1))) { aligned_address = reinterpret_cast<void*>((reinterpret_cast<size_t>(aligned_address) + (alignment - 1)) & ~(alignment - 1)); alignment_padding = static_cast<uchar>((size_t)aligned_address - (size_t)m_top); } m_allocated_memory += size + alignment_padding; ``` * 先看 if 在哪些情況下成立,alignment 為要對齊的大小 ,通常不為 0,(aligned_address) & (alignment - 1)可以視為取 alignment 的餘數, 注意 alignment 為2^n,才能這樣使用,我們帶數字來看,假設 alignment 為 16 ```clike alignment=0x100;//alignment-1=0xFF aligned_address=0xA00; (aligned_address) & (alignment - 1)//=0 不作 align aligned_address=0xA0A; (aligned_address) & (alignment - 1)//=0x0A 需要 align ``` * 如果需要 align 則找到下一個對齊的位置,並將設置 padding 使下一次 m_allocated_memory 數值正確 ### SIMD trace_section 為 single thread時主要的函式,其針對每4個 pixel(x,y) 產生對應的 rdir 結構,並且呼叫trace_ray 進行計算。 ```clike static void VECTOR_CC trace_section(sse_ray rorigin, uint32 width, uint32 height, int ystart) { sse_ray rdir; sse_pixel pixel; uint32 disp = width*ystart*3; for (uint32 y = ystart; y < height; ++y) { for (uint32 x = 0; x < width - 3; x += 4) { rdir.x = _mm_load_ps(&rtm.cam_ray.x[SsePos(y*width + x)]); rdir.y = _mm_load_ps(&rtm.cam_ray.y[SsePos(y*width + x)]); rdir.z = _mm_load_ps(&rtm.cam_ray.z[SsePos(y*width + x)]); pixel = trace_ray(rorigin, rdir, 0); _mm_store_ps(&rtm.pixmap[SsePos(disp)], pixel.r); disp += 4; _mm_store_ps(&rtm.pixmap[SsePos(disp)], pixel.g); disp += 4; _mm_store_ps(&rtm.pixmap[SsePos(disp)], pixel.b); disp += 4; } } } ``` * trace_ray 中利用 intersect_ray_spheres(rorigin, rdir); 先確定有哪些 spheres 需要被計算(此函式可得到4個點分別需要被計算的 spheres 之 index)在進行計算 ```C++ static ray_sphere_interesection VECTOR_CC intersect_ray_spheres(sse_ray rorigin, sse_ray rdir) { ray_sphere_interesection rs; rs.tpoint = _mm_set_ps1(FLT_MAX); rs.index = _mm_set_ps1(-1.0f); sse_ray rlength; __m128 no_intersection; for (uint32 i = 0; i < rtm.num_of_spheres; ++i) { sse_ray sphere_pos; sphere_pos.x = _mm_set_ps1(rtm.spheres.x[i]); sphere_pos.y = _mm_set_ps1(rtm.spheres.y[i]); sphere_pos.z = _mm_set_ps1(rtm.spheres.z[i]); __m128 radius2 = _mm_set_ps1(rtm.spheres.radius[i]); radius2 = _mm_mul_ps(radius2, radius2); rlength.x = _mm_sub_ps(sphere_pos.x, rorigin.x); rlength.y = _mm_sub_ps(sphere_pos.y, rorigin.y); rlength.z = _mm_sub_ps(sphere_pos.z, rorigin.z); __m128 lendotdir = dot(rlength, rdir); no_intersection = _mm_cmplt_ps(lendotdir, _mm_set_ps1(0.0f)); __m128 d2 = _mm_sub_ps(dot(rlength, rlength), _mm_mul_ps(lendotdir, lendotdir)); no_intersection = _mm_or_ps(no_intersection, _mm_cmpgt_ps(d2, radius2)); if (_mm_test_all_ones(_mm_castps_si128(no_intersection))) continue; __m128 dprojrdir = _mm_sub_ps(radius2, d2); dprojrdir = _mm_sqrt_ps(dprojrdir); __m128 tnear = _mm_sub_ps(lendotdir, dprojrdir); __m128 tfar = _mm_add_ps(lendotdir, dprojrdir); __m128 tpoint = _mm_cmpgt_ps(tnear, _mm_set_ps1(0.0f)); tnear = _mm_blendv_ps(tfar, tnear, tpoint); tpoint = _mm_cmplt_ps(tnear, rs.tpoint); __m128 add_intersection = _mm_andnot_ps(no_intersection, tpoint); rs.tpoint = _mm_blendv_ps(rs.tpoint, tnear, add_intersection); rs.index = _mm_blendv_ps(rs.index, _mm_set_ps1(static_cast<float>(i)), add_intersection); } return rs; } ``` * fresnel effect 計算出新的原點及方向後遞迴呼叫trace_ray ```clike if (depth < TRACER_DEPTH) { __m128 facingratio = dot(rdir, nhit); facingratio = _mm_mul_ps(facingratio, _mm_set_ps1(-1.0f)); __m128 fresneleffect = _mm_sub_ps(_mm_set_ps1(1.0f), facingratio); fresneleffect = _mm_mul_ps(fresneleffect, fresneleffect); fresneleffect = _mm_mul_ps(fresneleffect, fresneleffect); const real mixing_value = 0.2f; fresneleffect = _mm_mul_ps(fresneleffect, _mm_set_ps1(1.0f - 0.2f)); fresneleffect = _mm_add_ps(fresneleffect, _mm_set_ps1(mixing_value)); sse_ray refldir; __m128 rdirdotnhit = dot(rdir, nhit); refldir.x = _mm_sub_ps(rdir.x, _mm_mul_ps(_mm_mul_ps(nhit.x, rdirdotnhit), _mm_set_ps1(2.0f))); refldir.y = _mm_sub_ps(rdir.y, _mm_mul_ps(_mm_mul_ps(nhit.y, rdirdotnhit), _mm_set_ps1(2.0f))); refldir.z = _mm_sub_ps(rdir.z, _mm_mul_ps(_mm_mul_ps(nhit.z, rdirdotnhit), _mm_set_ps1(2.0f))); normalize(refldir); sse_pixel reflpixel = trace_ray(phit, refldir, depth + 1); __m128 fresnel_r = _mm_mul_ps(reflpixel.r, fresneleffect); __m128 fresnel_g = _mm_mul_ps(reflpixel.g, fresneleffect); __m128 fresnel_b = _mm_mul_ps(reflpixel.b, fresneleffect); fresnel_r = _mm_mul_ps(sphere_color.r, _mm_andnot_ps(_mm_castsi128_ps(test_intersection), fresnel_r)); fresnel_g = _mm_mul_ps(sphere_color.g, _mm_andnot_ps(_mm_castsi128_ps(test_intersection), fresnel_g)); fresnel_b = _mm_mul_ps(sphere_color.b, _mm_andnot_ps(_mm_castsi128_ps(test_intersection), fresnel_b)); pixel.r = _mm_add_ps(pixel.r, fresnel_r); pixel.g = _mm_add_ps(pixel.g, fresnel_g); pixel.b = _mm_add_ps(pixel.b, fresnel_b); } } ``` ### multi-threaded * 利用 threadpool 將任務以 height_section = height / num_of_tasks 切割,測試在不同 thread 及 tasks 數量下的差異 ``` RayTracing Started Width: 1920 rays Height: 1020 rays Tracing 9 spheres Ray Tracer(0)[ Generated Camera Rays]: 0.0249563 s Ray Tracer(1)[ Raytracing Complete]: 0.190369 s Ray Tracer(2)[ BMP and PPM Images Saved to Disk]: 0.353309 s Have a nice day! ``` * 使用內建的模型來測試時間,有平行化的部份為Ray Tracer(1)[ Raytracing Complete] 我們探討這部份的時間 * 在 Task 為 32 下改變 thread 的數目,可以發現 2 thread 以後時間差異不大,這是受限於硬體的核心。![](https://i.imgur.com/cWbH7Y6.png) * 在 thread 為 2 下改變 task 的數目,在 32 個 task 下時間最小,但在 8 個task 以上的差異不大。 ![](https://i.imgur.com/2JOyWtL.png)