main.cpp 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. #include <cstdio>
  2. #include <cstdlib>
  3. #include <cstdint>
  4. #include <ctime>
  5. #include <algorithm>
  6. #include <tmmintrin.h>
  7. #include <smmintrin.h>
  8. #define STB_IMAGE_IMPLEMENTATION
  9. #define STBI_ONLY_JPEG
  10. #define STBI_ONLY_PNG
  11. #include "./stb_image.h"
  12. #define STB_IMAGE_WRITE_IMPLEMENTATION
  13. #include "./stb_image_write.h"
  14. struct Pixel32
  15. {
  16. uint8_t r, g, b, a;
  17. };
  18. struct Image32
  19. {
  20. Pixel32 *pixels;
  21. size_t width;
  22. size_t height;
  23. };
  24. const size_t SIMD_PIXEL_PACK_SIZE = sizeof(__m128i) / sizeof(Pixel32);
  25. Pixel32 mix_pixels(Pixel32 a32, Pixel32 b32)
  26. {
  27. const float a32_alpha = a32.a / 255.0;
  28. const float b32_alpha = b32.a / 255.0;
  29. const float r_alpha = b32_alpha + a32_alpha * (1.0f - b32_alpha);
  30. Pixel32 r = {};
  31. r.r = (uint8_t) ((b32.r * b32_alpha + a32.r * a32_alpha * (1.0f - b32_alpha)) / r_alpha);
  32. r.g = (uint8_t) ((b32.g * b32_alpha + a32.g * a32_alpha * (1.0f - b32_alpha)) / r_alpha);
  33. r.b = (uint8_t) ((b32.b * b32_alpha + a32.b * a32_alpha * (1.0f - b32_alpha)) / r_alpha);
  34. r.a = (uint8_t) (r_alpha * 255.0);
  35. return r;
  36. }
  37. static inline Pixel32 mix_pixels_no_float(Pixel32 src, Pixel32 dst)
  38. {
  39. uint8_t rev_src_a = 255 - src.a;
  40. Pixel32 result;
  41. result.r = ((uint16_t) src.r * (uint16_t) src.a + (uint16_t) dst.r * rev_src_a) >> 8;
  42. result.g = ((uint16_t) src.g * (uint16_t) src.a + (uint16_t) dst.g * rev_src_a) >> 8;
  43. result.b = ((uint16_t) src.b * (uint16_t) src.a + (uint16_t) dst.b * rev_src_a) >> 8;
  44. result.a = dst.a;
  45. return result;
  46. }
  47. // NOTE: Stolen from https://stackoverflow.com/a/53707227
  48. void mix_pixels_sse(Pixel32 *src, Pixel32 *dst, Pixel32 *c)
  49. {
  50. const __m128i _swap_mask =
  51. _mm_set_epi8(7, 6, 5, 4,
  52. 3, 2, 1, 0,
  53. 15, 14, 13, 12,
  54. 11, 10, 9, 8
  55. );
  56. const __m128i _aa =
  57. _mm_set_epi8( 15,15,15,15,
  58. 11,11,11,11,
  59. 7,7,7,7,
  60. 3,3,3,3 );
  61. const __m128i _mask1 = _mm_set_epi16(-1,0,0,0, -1,0,0,0);
  62. const __m128i _mask2 = _mm_set_epi16(0,-1,-1,-1, 0,-1,-1,-1);
  63. const __m128i _v1 = _mm_set1_epi16( 1 );
  64. __m128i _src = _mm_loadu_si128((__m128i*)src);
  65. __m128i _src_a = _mm_shuffle_epi8(_src, _aa);
  66. __m128i _dst = _mm_loadu_si128((__m128i*)dst);
  67. __m128i _dst_a = _mm_shuffle_epi8(_dst, _aa);
  68. __m128i _one_minus_src_a = _mm_subs_epu8(
  69. _mm_set1_epi8(-1), _src_a);
  70. __m128i _out = {};
  71. {
  72. __m128i _s_a = _mm_cvtepu8_epi16( _src_a );
  73. __m128i _s = _mm_cvtepu8_epi16( _src );
  74. __m128i _d = _mm_cvtepu8_epi16( _dst );
  75. __m128i _d_a = _mm_cvtepu8_epi16( _one_minus_src_a );
  76. _out = _mm_adds_epu16(
  77. _mm_mullo_epi16(_s, _s_a),
  78. _mm_mullo_epi16(_d, _d_a));
  79. _out = _mm_srli_epi16(
  80. _mm_adds_epu16(
  81. _mm_adds_epu16( _v1, _out ),
  82. _mm_srli_epi16( _out, 8 ) ), 8 );
  83. _out = _mm_or_si128(
  84. _mm_and_si128(_out,_mask2),
  85. _mm_and_si128(
  86. _mm_adds_epu16(
  87. _s_a,
  88. _mm_cvtepu8_epi16(_dst_a)), _mask1));
  89. }
  90. // compute _out2 using high quadword of of the _src and _dst
  91. //...
  92. __m128i _out2 = {};
  93. {
  94. __m128i _s_a = _mm_cvtepu8_epi16(_mm_shuffle_epi8(_src_a, _swap_mask));
  95. __m128i _s = _mm_cvtepu8_epi16(_mm_shuffle_epi8(_src, _swap_mask));
  96. __m128i _d = _mm_cvtepu8_epi16(_mm_shuffle_epi8(_dst, _swap_mask));
  97. __m128i _d_a = _mm_cvtepu8_epi16(_mm_shuffle_epi8(_one_minus_src_a, _swap_mask));
  98. _out2 = _mm_adds_epu16(
  99. _mm_mullo_epi16(_s, _s_a),
  100. _mm_mullo_epi16(_d, _d_a));
  101. _out2 = _mm_srli_epi16(
  102. _mm_adds_epu16(
  103. _mm_adds_epu16( _v1, _out2 ),
  104. _mm_srli_epi16( _out2, 8 ) ), 8 );
  105. _out2 = _mm_or_si128(
  106. _mm_and_si128(_out2,_mask2),
  107. _mm_and_si128(
  108. _mm_adds_epu16(
  109. _s_a,
  110. _mm_cvtepu8_epi16(_dst_a)), _mask1));
  111. }
  112. __m128i _ret = _mm_packus_epi16( _out, _out2 );
  113. _mm_storeu_si128( (__m128i_u*) c, _ret );
  114. }
  115. void slap_image32_onto_image32_no_float(Image32 src, Image32 dst,
  116. size_t x0, size_t y0)
  117. {
  118. size_t x1 = std::min(x0 + src.width, dst.width);
  119. size_t y1 = std::min(y0 + src.height, dst.height);
  120. for (size_t y = y0; y < y1; ++y) {
  121. for (size_t x = x0; x < x1; ++x) {
  122. dst.pixels[y * dst.width + x] =
  123. mix_pixels_no_float(
  124. src.pixels[(y - y0) * src.width + (x - x0)],
  125. dst.pixels[y * dst.width + x]);
  126. }
  127. }
  128. }
  129. void slap_image32_onto_image32(Image32 src, Image32 dst,
  130. size_t x0, size_t y0)
  131. {
  132. size_t x1 = std::min(x0 + src.width, dst.width);
  133. size_t y1 = std::min(y0 + src.height, dst.height);
  134. for (size_t y = y0; y < y1; ++y) {
  135. for (size_t x = x0; x < x1; ++x) {
  136. dst.pixels[y * dst.width + x] =
  137. mix_pixels(
  138. dst.pixels[y * dst.width + x],
  139. src.pixels[(y - y0) * src.width + (x - x0)]);
  140. }
  141. }
  142. }
  143. void slap_image32_onto_image32_simd(Image32 src, Image32 dst,
  144. size_t x0, size_t y0)
  145. {
  146. Pixel32 out[SIMD_PIXEL_PACK_SIZE] = {};
  147. size_t x1 = std::min(x0 + src.width, dst.width);
  148. size_t y1 = std::min(y0 + src.height, dst.height);
  149. for (size_t y = y0; y < y1; ++y) {
  150. for (size_t x = x0; x < x1; x += SIMD_PIXEL_PACK_SIZE) {
  151. mix_pixels_sse(
  152. &src.pixels[(y - y0) * src.width + (x - x0)],
  153. &dst.pixels[y * dst.width + x],
  154. &dst.pixels[y * dst.width + x]);
  155. // TODO: tail of the row is not taken into account
  156. }
  157. }
  158. }
  159. Image32 load_image32(const char *filepath)
  160. {
  161. Image32 result = {};
  162. int x, y, n;
  163. result.pixels = (Pixel32*) stbi_load(filepath, &x, &y, &n, 4);
  164. result.width = x;
  165. result.height = y;
  166. return result;
  167. }
  168. int main_(int argc, char *argv[])
  169. {
  170. Pixel32 a[] = {
  171. {1, 2, 3, 4},
  172. {5, 6, 7, 8},
  173. {9, 10, 11, 12},
  174. {13, 14, 15, 16},
  175. };
  176. Pixel32 b[] = {
  177. {17, 18, 19, 20},
  178. {21, 22, 23, 24},
  179. {25, 26, 27, 28},
  180. {29, 30, 31, 32},
  181. };
  182. Pixel32 c[4] = {};
  183. mix_pixels_sse(a, b, c);
  184. return 0;
  185. }
  186. template <typename Slap>
  187. void benchmark(Slap slap,
  188. Image32 src, Image32 dst,
  189. size_t pos_x, size_t pos_y,
  190. size_t N, const char *message)
  191. {
  192. printf("%s\n", message);
  193. clock_t begin = clock();
  194. for (size_t i = 0; i < N; ++i) {
  195. slap(src, dst, pos_x, pos_y);
  196. }
  197. printf(" %fs\n", (float)(clock() - begin) / (float) CLOCKS_PER_SEC);
  198. }
  199. int main(int argc, char *argv[])
  200. {
  201. static_assert(sizeof(Pixel32) == sizeof(uint32_t),
  202. "Size of Pixel32 is scuffed on your platform lol");
  203. const char * const DST_FILENAME = "maxresdefault.jpg";
  204. Image32 dst = load_image32(DST_FILENAME);
  205. const char * const SRC_FILENAME = "tsodinFeels.png";
  206. Image32 src = load_image32(SRC_FILENAME);
  207. for (size_t i = 0; i < src.width * src.height; ++i) {
  208. src.pixels[i].a = src.pixels[i].a >> 1;
  209. }
  210. size_t pos_x = (dst.width >> 1) - (src.width >> 1);
  211. size_t pos_y = (dst.height >> 1) - (src.height >> 1);
  212. const size_t N = 100'000;
  213. benchmark(slap_image32_onto_image32, src, dst, pos_x, pos_y, N, "Original NO SIMD");
  214. benchmark(slap_image32_onto_image32_no_float, src, dst, pos_x, pos_y, N, "Faster NO SIMD");
  215. benchmark(slap_image32_onto_image32_simd, src, dst, pos_x, pos_y, N, "SIMD");
  216. const char * const OUT_FILENAME = "output.png";
  217. int ret = stbi_write_png(OUT_FILENAME, dst.width, dst.height, 4, dst.pixels, dst.width * 4);
  218. printf(" ret = %d\n", ret);
  219. return 0;
  220. }